GuardianUnivalle-Benito-Yucra 0.1.50__tar.gz → 0.1.52__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of GuardianUnivalle-Benito-Yucra might be problematic. Click here for more details.
- guardianunivalle_benito_yucra-0.1.52/GuardianUnivalle_Benito_Yucra/detectores/detector_dos.py +335 -0
- {guardianunivalle_benito_yucra-0.1.50 → guardianunivalle_benito_yucra-0.1.52}/GuardianUnivalle_Benito_Yucra.egg-info/PKG-INFO +1 -1
- {guardianunivalle_benito_yucra-0.1.50 → guardianunivalle_benito_yucra-0.1.52}/GuardianUnivalle_Benito_Yucra.egg-info/SOURCES.txt +0 -8
- {guardianunivalle_benito_yucra-0.1.50 → guardianunivalle_benito_yucra-0.1.52}/PKG-INFO +1 -1
- {guardianunivalle_benito_yucra-0.1.50 → guardianunivalle_benito_yucra-0.1.52}/pyproject.toml +1 -1
- guardianunivalle_benito_yucra-0.1.50/GuardianUnivalle_Benito_Yucra/auditoria/auditoria_servidor.py +0 -119
- guardianunivalle_benito_yucra-0.1.50/GuardianUnivalle_Benito_Yucra/criptografia/cifrado_aead.py +0 -25
- guardianunivalle_benito_yucra-0.1.50/GuardianUnivalle_Benito_Yucra/criptografia/intercambio_claves.py +0 -23
- guardianunivalle_benito_yucra-0.1.50/GuardianUnivalle_Benito_Yucra/criptografia/kdf.py +0 -23
- guardianunivalle_benito_yucra-0.1.50/GuardianUnivalle_Benito_Yucra/detectores/detector_dos.py +0 -226
- guardianunivalle_benito_yucra-0.1.50/GuardianUnivalle_Benito_Yucra/middleware_web/middleware_web.py +0 -13
- guardianunivalle_benito_yucra-0.1.50/GuardianUnivalle_Benito_Yucra/mitigacion/limitador_peticion.py +0 -7
- guardianunivalle_benito_yucra-0.1.50/GuardianUnivalle_Benito_Yucra/mitigacion/lista_bloqueo.py +0 -10
- guardianunivalle_benito_yucra-0.1.50/GuardianUnivalle_Benito_Yucra/puntuacion/puntuacion_amenaza.py +0 -15
- {guardianunivalle_benito_yucra-0.1.50 → guardianunivalle_benito_yucra-0.1.52}/GuardianUnivalle_Benito_Yucra/__init__.py +0 -0
- {guardianunivalle_benito_yucra-0.1.50 → guardianunivalle_benito_yucra-0.1.52}/GuardianUnivalle_Benito_Yucra/auditoria/registro_auditoria.py +0 -0
- {guardianunivalle_benito_yucra-0.1.50 → guardianunivalle_benito_yucra-0.1.52}/GuardianUnivalle_Benito_Yucra/detectores/detector_csrf.py +0 -0
- {guardianunivalle_benito_yucra-0.1.50 → guardianunivalle_benito_yucra-0.1.52}/GuardianUnivalle_Benito_Yucra/detectores/detector_keylogger.py +0 -0
- {guardianunivalle_benito_yucra-0.1.50 → guardianunivalle_benito_yucra-0.1.52}/GuardianUnivalle_Benito_Yucra/detectores/detector_sql.py +0 -0
- {guardianunivalle_benito_yucra-0.1.50 → guardianunivalle_benito_yucra-0.1.52}/GuardianUnivalle_Benito_Yucra/detectores/detector_xss.py +0 -0
- {guardianunivalle_benito_yucra-0.1.50 → guardianunivalle_benito_yucra-0.1.52}/GuardianUnivalle_Benito_Yucra/utilidades.py +0 -0
- {guardianunivalle_benito_yucra-0.1.50 → guardianunivalle_benito_yucra-0.1.52}/GuardianUnivalle_Benito_Yucra.egg-info/dependency_links.txt +0 -0
- {guardianunivalle_benito_yucra-0.1.50 → guardianunivalle_benito_yucra-0.1.52}/GuardianUnivalle_Benito_Yucra.egg-info/requires.txt +0 -0
- {guardianunivalle_benito_yucra-0.1.50 → guardianunivalle_benito_yucra-0.1.52}/GuardianUnivalle_Benito_Yucra.egg-info/top_level.txt +0 -0
- {guardianunivalle_benito_yucra-0.1.50 → guardianunivalle_benito_yucra-0.1.52}/LICENSE +0 -0
- {guardianunivalle_benito_yucra-0.1.50 → guardianunivalle_benito_yucra-0.1.52}/README.md +0 -0
- {guardianunivalle_benito_yucra-0.1.50 → guardianunivalle_benito_yucra-0.1.52}/setup.cfg +0 -0
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import time
|
|
3
|
+
import logging
|
|
4
|
+
import json
|
|
5
|
+
from collections import deque
|
|
6
|
+
from typing import Dict, List, Set
|
|
7
|
+
from django.conf import settings
|
|
8
|
+
from django.utils.deprecation import MiddlewareMixin
|
|
9
|
+
from django.http import HttpResponseForbidden
|
|
10
|
+
import requests # ⬅️ Necesario para la función de scraping
|
|
11
|
+
import re # ⬅️ Necesario para el parseo de IPs/CIDR
|
|
12
|
+
from ipaddress import ip_address, IPv4Address, IPv4Network # Necesario para el Escaneo Avanzado (CIDR)
|
|
13
|
+
|
|
14
|
+
# =====================================================
|
|
15
|
+
# === CONFIGURACIÓN GLOBAL Y LOGGER ===
|
|
16
|
+
# =====================================================
|
|
17
|
+
logger = logging.getLogger("dosdefense")
|
|
18
|
+
logger.setLevel(logging.INFO)
|
|
19
|
+
if not logger.handlers:
|
|
20
|
+
handler = logging.StreamHandler()
|
|
21
|
+
handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s"))
|
|
22
|
+
logger.addHandler(handler)
|
|
23
|
+
|
|
24
|
+
# =====================================================
|
|
25
|
+
# === CONFIGURACIÓN DE INTELIGENCIA DE AMENAZAS (THREAT INTEL) ===
|
|
26
|
+
# =====================================================
|
|
27
|
+
# URLs CONCEPTUALES de donde EXTRAERÍAS IPs/CIDR
|
|
28
|
+
IP_BLACKLIST_SOURCES = [
|
|
29
|
+
"http://api.sitio_de_inteligencia.com/blacklist/ip_list",
|
|
30
|
+
"http://otro_sitio.com/export_ips.txt",
|
|
31
|
+
"https://iplists.firehol.org/files/firehol_level1.netset",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
# Cabeceras para simular un navegador
|
|
35
|
+
SCRAPING_HEADERS = {
|
|
36
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
# =====================================================
|
|
40
|
+
# === FUNCIONES DE INTELIGENCIA DE AMENAZAS ===
|
|
41
|
+
# =====================================================
|
|
42
|
+
|
|
43
|
+
def fetch_and_parse_blacklists() -> Set[str]:
|
|
44
|
+
"""
|
|
45
|
+
Intenta obtener y parsear IPs/CIDR de varias fuentes externas.
|
|
46
|
+
"""
|
|
47
|
+
global_blacklist: Set[str] = set()
|
|
48
|
+
# Patrón Regex para IPs (admite también rangos CIDR)
|
|
49
|
+
ip_pattern = re.compile(r'\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(/\d{1,2})?\b')
|
|
50
|
+
|
|
51
|
+
for url in IP_BLACKLIST_SOURCES:
|
|
52
|
+
try:
|
|
53
|
+
response = requests.get(url, headers=SCRAPING_HEADERS, timeout=15)
|
|
54
|
+
response.raise_for_status()
|
|
55
|
+
|
|
56
|
+
found_ips = set(ip_pattern.findall(response.text))
|
|
57
|
+
|
|
58
|
+
# Limpieza
|
|
59
|
+
cleaned_ips = {ip[0] for ip in found_ips if ip[0] not in ('0.0.0.0', '255.255.255.255')}
|
|
60
|
+
|
|
61
|
+
global_blacklist.update(cleaned_ips)
|
|
62
|
+
logger.info(f"[Threat Intel] Éxito al obtener {len(cleaned_ips)} IPs/CIDR de {url}")
|
|
63
|
+
|
|
64
|
+
except requests.exceptions.RequestException as e:
|
|
65
|
+
logger.error(f"[Threat Intel] Error de conexión con {url}: {e}")
|
|
66
|
+
except Exception as e:
|
|
67
|
+
logger.error(f"[Threat Intel] Error inesperado al parsear {url}: {e}")
|
|
68
|
+
|
|
69
|
+
if '127.0.0.1' in global_blacklist:
|
|
70
|
+
global_blacklist.remove('127.0.0.1')
|
|
71
|
+
|
|
72
|
+
return global_blacklist
|
|
73
|
+
|
|
74
|
+
def check_ip_in_advanced_blacklist(client_ip: str, global_blacklist_cidrs: Set[str]) -> bool:
|
|
75
|
+
"""
|
|
76
|
+
Escaneo avanzado: Chequea si una IP está en la lista negra, incluyendo rangos CIDR.
|
|
77
|
+
"""
|
|
78
|
+
if not global_blacklist_cidrs:
|
|
79
|
+
return False
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
ip_a_chequear = IPv4Address(client_ip)
|
|
83
|
+
|
|
84
|
+
# 1. Chequeo rápido de IPs individuales
|
|
85
|
+
if client_ip in global_blacklist_cidrs:
|
|
86
|
+
return True
|
|
87
|
+
|
|
88
|
+
# 2. Chequeo de rangos CIDR (más lento)
|
|
89
|
+
for cidr_entry in global_blacklist_cidrs:
|
|
90
|
+
if '/' in cidr_entry:
|
|
91
|
+
try:
|
|
92
|
+
if ip_a_chequear in IPv4Network(cidr_entry, strict=False):
|
|
93
|
+
return True
|
|
94
|
+
except ValueError:
|
|
95
|
+
continue # No es una red CIDR válida, continuar
|
|
96
|
+
return False
|
|
97
|
+
|
|
98
|
+
except ValueError:
|
|
99
|
+
logger.error(f"IP del cliente inválida o no IPv4: {client_ip}")
|
|
100
|
+
return False
|
|
101
|
+
|
|
102
|
+
# =====================================================
|
|
103
|
+
# === PARÁMETROS DE CONFIGURACIÓN BASE Y SCORE ===
|
|
104
|
+
# =====================================================
|
|
105
|
+
LIMITE_PETICIONES = getattr(settings, "DOS_LIMITE_PETICIONES", 100)
|
|
106
|
+
VENTANA_SEGUNDOS = getattr(settings, "DOS_VENTANA_SEGUNDOS", 60)
|
|
107
|
+
PESO_DOS = getattr(settings, "DOS_PESO", 0.6)
|
|
108
|
+
LIMITE_ENDPOINTS_DISTINTOS = getattr(settings, "DOS_LIMITE_ENDPOINTS", 50)
|
|
109
|
+
TRUSTED_IPS = getattr(settings, "DOS_TRUSTED_IPS", [])
|
|
110
|
+
TIEMPO_BLOQUEO_SEGUNDOS = getattr(settings, "DOS_TIEMPO_BLOQUEO", 300)
|
|
111
|
+
|
|
112
|
+
# Parámetros del Score Avanzado
|
|
113
|
+
PESO_BLACKLIST = getattr(settings, "DOS_PESO_BLACKLIST", 0.3)
|
|
114
|
+
PESO_HEURISTICA = getattr(settings, "DOS_PESO_HEURISTICA", 0.1)
|
|
115
|
+
UMBRAL_BLOQUEO = getattr(settings, "DOS_UMBRAL_BLOQUEO", 0.8)
|
|
116
|
+
|
|
117
|
+
# === CARGA INICIAL DE LA LISTA NEGRA ===
|
|
118
|
+
try:
|
|
119
|
+
IP_BLACKLIST: Set[str] = fetch_and_parse_blacklists()
|
|
120
|
+
logger.info(f"Lista Negra Externa cargada con {len(IP_BLACKLIST)} IPs/CIDR.")
|
|
121
|
+
except Exception as e:
|
|
122
|
+
logger.error(f"Error al cargar la IP Blacklist: {e}. Usando lista vacía.")
|
|
123
|
+
IP_BLACKLIST = set()
|
|
124
|
+
|
|
125
|
+
# =====================================================
|
|
126
|
+
# === REGISTRO TEMPORAL EN MEMORIA ===
|
|
127
|
+
# =====================================================
|
|
128
|
+
REGISTRO_SOLICITUDES: Dict[str, deque] = {}
|
|
129
|
+
REGISTRO_ENDPOINTS: Dict[str, set] = {}
|
|
130
|
+
BLOQUEOS_TEMPORALES: Dict[str, float] = {}
|
|
131
|
+
|
|
132
|
+
# =====================================================
|
|
133
|
+
# === FUNCIONES AUXILIARES ===
|
|
134
|
+
# =====================================================
|
|
135
|
+
def get_client_ip(request) -> str:
|
|
136
|
+
"""Obtiene la IP real del cliente (considera proxies)."""
|
|
137
|
+
x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR")
|
|
138
|
+
if x_forwarded_for:
|
|
139
|
+
return x_forwarded_for.split(",")[0].strip()
|
|
140
|
+
return request.META.get("REMOTE_ADDR", "") or "0.0.0.0"
|
|
141
|
+
|
|
142
|
+
def limpiar_registro_global():
|
|
143
|
+
"""Elimina IPs sin actividad reciente y desbloquea IPs temporales."""
|
|
144
|
+
# ... (La implementación de limpiar_registro_global permanece igual)
|
|
145
|
+
ahora = time.time()
|
|
146
|
+
expiracion = VENTANA_SEGUNDOS * 2
|
|
147
|
+
inactivas = []
|
|
148
|
+
|
|
149
|
+
for ip, tiempos in REGISTRO_SOLICITUDES.items():
|
|
150
|
+
if tiempos and ahora - tiempos[-1] > expiracion:
|
|
151
|
+
inactivas.append(ip)
|
|
152
|
+
|
|
153
|
+
for ip in inactivas:
|
|
154
|
+
REGISTRO_SOLICITUDES.pop(ip, None)
|
|
155
|
+
REGISTRO_ENDPOINTS.pop(ip, None)
|
|
156
|
+
|
|
157
|
+
ips_a_desbloquear = [ip for ip, tiempo_desbloqueo in BLOQUEOS_TEMPORALES.items() if ahora > tiempo_desbloqueo]
|
|
158
|
+
for ip in ips_a_desbloquear:
|
|
159
|
+
BLOQUEOS_TEMPORALES.pop(ip, None)
|
|
160
|
+
logger.info(f"[Desbloqueo] IP {ip} desbloqueada automáticamente.")
|
|
161
|
+
|
|
162
|
+
def limpiar_registro(ip: str):
|
|
163
|
+
"""Limpia peticiones antiguas fuera de la ventana de tiempo."""
|
|
164
|
+
# ... (La implementación de limpiar_registro permanece igual)
|
|
165
|
+
ahora = time.time()
|
|
166
|
+
if ip not in REGISTRO_SOLICITUDES:
|
|
167
|
+
REGISTRO_SOLICITUDES[ip] = deque()
|
|
168
|
+
tiempos = REGISTRO_SOLICITUDES[ip]
|
|
169
|
+
while tiempos and ahora - tiempos[0] > VENTANA_SEGUNDOS:
|
|
170
|
+
tiempos.popleft()
|
|
171
|
+
|
|
172
|
+
def calcular_nivel_amenaza_dos(tasa_peticion: int, limite: int = LIMITE_PETICIONES) -> float:
|
|
173
|
+
"""Calcula la puntuación de amenaza DoS (Rate Limiting)."""
|
|
174
|
+
# ... (La implementación de calcular_nivel_amenaza_dos permanece igual)
|
|
175
|
+
proporcion = tasa_peticion / max(limite, 1)
|
|
176
|
+
s_dos = PESO_DOS * min(proporcion, 2.0)
|
|
177
|
+
return round(min(s_dos, 1.0), 3)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
# =====================================================
|
|
181
|
+
# === FUNCIONES INTERNAS DE SEGURIDAD Y AUDITORÍA ===
|
|
182
|
+
# =====================================================
|
|
183
|
+
def limitar_peticion(usuario_id: str):
|
|
184
|
+
"""Implementa la mitigación: Bloquea temporalmente la IP."""
|
|
185
|
+
ahora = time.time()
|
|
186
|
+
tiempo_desbloqueo = ahora + TIEMPO_BLOQUEO_SEGUNDOS
|
|
187
|
+
BLOQUEOS_TEMPORALES[usuario_id] = tiempo_desbloqueo
|
|
188
|
+
logger.warning(
|
|
189
|
+
f"[Bloqueo Activo] IP {usuario_id} bloqueada temporalmente hasta {time.ctime(tiempo_desbloqueo)}"
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
def registrar_evento(tipo: str, descripcion: str, severidad: str = "MEDIA"):
|
|
193
|
+
"""Simula el registro de auditoría de un evento de seguridad."""
|
|
194
|
+
evento = {
|
|
195
|
+
"tipo": tipo,
|
|
196
|
+
"descripcion": descripcion,
|
|
197
|
+
"severidad": severidad,
|
|
198
|
+
"timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
|
|
199
|
+
}
|
|
200
|
+
logger.info(f"[AUDITORÍA] {json.dumps(evento, ensure_ascii=False)}")
|
|
201
|
+
|
|
202
|
+
def detectar_dos(ip: str, tasa_peticion: int, limite: int = LIMITE_PETICIONES) -> bool:
|
|
203
|
+
"""Evalúa si la tasa de peticiones excede el umbral permitido y aplica mitigación."""
|
|
204
|
+
# ... (La implementación de detectar_dos permanece igual)
|
|
205
|
+
if tasa_peticion > limite:
|
|
206
|
+
registrar_evento(
|
|
207
|
+
tipo="DoS",
|
|
208
|
+
descripcion=f"Alta tasa de peticiones desde {ip}: {tasa_peticion} req/min (límite {limite})",
|
|
209
|
+
severidad="ALTA",
|
|
210
|
+
)
|
|
211
|
+
limitar_peticion(usuario_id=ip)
|
|
212
|
+
return True
|
|
213
|
+
elif tasa_peticion > limite * 0.75:
|
|
214
|
+
registrar_evento(
|
|
215
|
+
tipo="DoS",
|
|
216
|
+
descripcion=f"Posible saturación desde {ip}: {tasa_peticion} req/min",
|
|
217
|
+
severidad="MEDIA",
|
|
218
|
+
)
|
|
219
|
+
return False
|
|
220
|
+
|
|
221
|
+
def analizar_headers_avanzado(user_agent: str, referer: str) -> List[str]:
|
|
222
|
+
"""Detecta patrones sospechosos, penalizando User-Agents automatizados."""
|
|
223
|
+
# ... (La implementación de analizar_headers_avanzado permanece igual)
|
|
224
|
+
sospechas = []
|
|
225
|
+
|
|
226
|
+
if not user_agent or len(user_agent) < 10 or user_agent.lower() == "python-requests/2.25.1":
|
|
227
|
+
sospechas.append("User-Agent vacío/Defecto")
|
|
228
|
+
|
|
229
|
+
automation_keywords = ["curl", "python", "wget", "bot", "spider", "scraper", "headless", "phantom"]
|
|
230
|
+
if any(patron in user_agent.lower() for patron in automation_keywords):
|
|
231
|
+
sospechas.append("Herramienta de automatización detectada")
|
|
232
|
+
|
|
233
|
+
if referer and any(palabra in referer.lower() for palabra in ["attack", "scan"]):
|
|
234
|
+
sospechas.append("Referer indicando abuso")
|
|
235
|
+
|
|
236
|
+
return sospechas
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
# =====================================================
|
|
240
|
+
# === MIDDLEWARE PRINCIPAL DE DEFENSA DoS ===
|
|
241
|
+
# =====================================================
|
|
242
|
+
class DOSDefenseMiddleware(MiddlewareMixin):
|
|
243
|
+
"""
|
|
244
|
+
Middleware de detección, registro y mitigación de ataques DoS/Scraping avanzado.
|
|
245
|
+
"""
|
|
246
|
+
|
|
247
|
+
def process_request(self, request):
|
|
248
|
+
limpiar_registro_global()
|
|
249
|
+
|
|
250
|
+
client_ip = get_client_ip(request)
|
|
251
|
+
|
|
252
|
+
# 1. BLOQUEOS Y EXCEPCIONES PREVIAS
|
|
253
|
+
if client_ip in TRUSTED_IPS:
|
|
254
|
+
return None
|
|
255
|
+
|
|
256
|
+
# BLOQUEO TEMPORAL: IPs previamente bloqueadas por alto Score o DoS
|
|
257
|
+
if client_ip in BLOQUEOS_TEMPORALES and time.time() < BLOQUEOS_TEMPORALES[client_ip]:
|
|
258
|
+
registrar_evento(
|
|
259
|
+
tipo="Temporary Block",
|
|
260
|
+
descripcion=f"Bloqueo temporal por abuso previo: IP {client_ip}.",
|
|
261
|
+
severidad="ALTA",
|
|
262
|
+
)
|
|
263
|
+
return HttpResponseForbidden("Acceso denegado temporalmente por comportamiento sospechoso.")
|
|
264
|
+
|
|
265
|
+
# 2. ANÁLISIS DE LA PETICIÓN Y CÁLCULO DE MÉTRICAS BASE
|
|
266
|
+
user_agent = request.META.get("HTTP_USER_AGENT", "Desconocido")
|
|
267
|
+
referer = request.META.get("HTTP_REFERER", "")
|
|
268
|
+
path = request.path
|
|
269
|
+
|
|
270
|
+
# Mantener ventana deslizante y tasa
|
|
271
|
+
REGISTRO_ENDPOINTS.setdefault(client_ip, set()).add(path)
|
|
272
|
+
limpiar_registro(client_ip)
|
|
273
|
+
REGISTRO_SOLICITUDES[client_ip].append(time.time())
|
|
274
|
+
|
|
275
|
+
tasa = len(REGISTRO_SOLICITUDES[client_ip])
|
|
276
|
+
|
|
277
|
+
# 3. CÁLCULO DE LOS COMPONENTES DEL SCORE DE AMENAZA
|
|
278
|
+
|
|
279
|
+
# S_dos: Tasa de Petición (Rate Limiting)
|
|
280
|
+
nivel_dos = calcular_nivel_amenaza_dos(tasa)
|
|
281
|
+
|
|
282
|
+
# S_blacklist: Escaneo Avanzado (CIDR)
|
|
283
|
+
nivel_blacklist = PESO_BLACKLIST if check_ip_in_advanced_blacklist(client_ip, IP_BLACKLIST) else 0
|
|
284
|
+
|
|
285
|
+
# S_heuristica: Análisis de Comportamiento (Scraping/Escaneo)
|
|
286
|
+
sospechas_headers = analizar_headers_avanzado(user_agent, referer)
|
|
287
|
+
|
|
288
|
+
score_headers = 0.5 if sospechas_headers else 0
|
|
289
|
+
score_endpoints = 0.5 if len(REGISTRO_ENDPOINTS[client_ip]) > LIMITE_ENDPOINTS_DISTINTOS else 0
|
|
290
|
+
|
|
291
|
+
nivel_heuristica = PESO_HEURISTICA * (score_headers + score_endpoints)
|
|
292
|
+
|
|
293
|
+
# 4. CÁLCULO DEL SCORE TOTAL Y DECISIÓN DE MITIGACIÓN
|
|
294
|
+
|
|
295
|
+
S_total = nivel_dos + nivel_blacklist + nivel_heuristica
|
|
296
|
+
|
|
297
|
+
if S_total >= UMBRAL_BLOQUEO:
|
|
298
|
+
descripcion_log = [
|
|
299
|
+
f"Score Total: {S_total:.3f} > Umbral {UMBRAL_BLOQUEO}",
|
|
300
|
+
f"DoS: {nivel_dos:.3f}, Blacklist: {nivel_blacklist:.3f}, Heurística: {nivel_heuristica:.3f}"
|
|
301
|
+
]
|
|
302
|
+
registrar_evento(
|
|
303
|
+
tipo="Bloqueo por Score Total",
|
|
304
|
+
descripcion=" ; ".join(descripcion_log),
|
|
305
|
+
severidad="CRÍTICA",
|
|
306
|
+
)
|
|
307
|
+
limitar_peticion(usuario_id=client_ip)
|
|
308
|
+
return HttpResponseForbidden("Acceso denegado por alto Score de Amenaza.")
|
|
309
|
+
|
|
310
|
+
# 5. REGISTRO DE ADVERTENCIA (Si no se bloquea, pero es sospechoso)
|
|
311
|
+
|
|
312
|
+
if S_total > UMBRAL_BLOQUEO * 0.75 or (nivel_dos > 0) or len(sospechas_headers) > 0:
|
|
313
|
+
|
|
314
|
+
descripcion = sospechas_headers
|
|
315
|
+
if score_endpoints > 0:
|
|
316
|
+
descripcion.append("Número anormal de endpoints distintos accedidos (posible escaneo/scraping)")
|
|
317
|
+
|
|
318
|
+
descripcion.insert(0, f"Score Total: {S_total:.3f} (Tasa: {tasa} req/min)")
|
|
319
|
+
descripcion.append(f"Ruta: {path}")
|
|
320
|
+
|
|
321
|
+
logger.warning(
|
|
322
|
+
"Tráfico Sospechoso desde IP %s: %s",
|
|
323
|
+
client_ip,
|
|
324
|
+
" ; ".join(descripcion),
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
request.dos_attack_info = {
|
|
328
|
+
"ip": client_ip,
|
|
329
|
+
"tipos": ["DoS", "Scraping/Escaneo"],
|
|
330
|
+
"descripcion": descripcion,
|
|
331
|
+
"payload": json.dumps({"user_agent": user_agent, "referer": referer, "path": path}),
|
|
332
|
+
"score": S_total,
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return None
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: GuardianUnivalle-Benito-Yucra
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.52
|
|
4
4
|
Summary: Middleware y detectores de seguridad (SQLi, XSS, CSRF, DoS, Keylogger) para Django/Flask
|
|
5
5
|
Author-email: Andres Benito Calle Yucra <benitoandrescalle035@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -9,20 +9,12 @@ GuardianUnivalle_Benito_Yucra.egg-info/SOURCES.txt
|
|
|
9
9
|
GuardianUnivalle_Benito_Yucra.egg-info/dependency_links.txt
|
|
10
10
|
GuardianUnivalle_Benito_Yucra.egg-info/requires.txt
|
|
11
11
|
GuardianUnivalle_Benito_Yucra.egg-info/top_level.txt
|
|
12
|
-
GuardianUnivalle_Benito_Yucra/auditoria/auditoria_servidor.py
|
|
13
12
|
GuardianUnivalle_Benito_Yucra/auditoria/registro_auditoria.py
|
|
14
|
-
GuardianUnivalle_Benito_Yucra/criptografia/cifrado_aead.py
|
|
15
|
-
GuardianUnivalle_Benito_Yucra/criptografia/intercambio_claves.py
|
|
16
|
-
GuardianUnivalle_Benito_Yucra/criptografia/kdf.py
|
|
17
13
|
GuardianUnivalle_Benito_Yucra/detectores/detector_csrf.py
|
|
18
14
|
GuardianUnivalle_Benito_Yucra/detectores/detector_dos.py
|
|
19
15
|
GuardianUnivalle_Benito_Yucra/detectores/detector_keylogger.py
|
|
20
16
|
GuardianUnivalle_Benito_Yucra/detectores/detector_sql.py
|
|
21
17
|
GuardianUnivalle_Benito_Yucra/detectores/detector_xss.py
|
|
22
|
-
GuardianUnivalle_Benito_Yucra/middleware_web/middleware_web.py
|
|
23
|
-
GuardianUnivalle_Benito_Yucra/mitigacion/limitador_peticion.py
|
|
24
|
-
GuardianUnivalle_Benito_Yucra/mitigacion/lista_bloqueo.py
|
|
25
|
-
GuardianUnivalle_Benito_Yucra/puntuacion/puntuacion_amenaza.py
|
|
26
18
|
guardianunivalle_benito_yucra.egg-info/PKG-INFO
|
|
27
19
|
guardianunivalle_benito_yucra.egg-info/SOURCES.txt
|
|
28
20
|
guardianunivalle_benito_yucra.egg-info/dependency_links.txt
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: GuardianUnivalle-Benito-Yucra
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.52
|
|
4
4
|
Summary: Middleware y detectores de seguridad (SQLi, XSS, CSRF, DoS, Keylogger) para Django/Flask
|
|
5
5
|
Author-email: Andres Benito Calle Yucra <benitoandrescalle035@gmail.com>
|
|
6
6
|
License: MIT
|
{guardianunivalle_benito_yucra-0.1.50 → guardianunivalle_benito_yucra-0.1.52}/pyproject.toml
RENAMED
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "GuardianUnivalle-Benito-Yucra" # usar mayúsculas consistente
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.52"
|
|
8
8
|
description = "Middleware y detectores de seguridad (SQLi, XSS, CSRF, DoS, Keylogger) para Django/Flask"
|
|
9
9
|
authors = [
|
|
10
10
|
{ name = "Andres Benito Calle Yucra", email = "benitoandrescalle035@gmail.com" }
|
guardianunivalle_benito_yucra-0.1.50/GuardianUnivalle_Benito_Yucra/auditoria/auditoria_servidor.py
DELETED
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
import json
|
|
3
|
-
import platform
|
|
4
|
-
import socket
|
|
5
|
-
import uuid
|
|
6
|
-
from datetime import datetime
|
|
7
|
-
from django.utils.deprecation import MiddlewareMixin
|
|
8
|
-
|
|
9
|
-
logger = logging.getLogger("auditoria_servidor")
|
|
10
|
-
logger.setLevel(logging.INFO)
|
|
11
|
-
if not logger.handlers:
|
|
12
|
-
handler = logging.StreamHandler()
|
|
13
|
-
handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s"))
|
|
14
|
-
logger.addHandler(handler)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def obtener_datos_maquina(request):
|
|
18
|
-
"""
|
|
19
|
-
Retorna un diccionario con información del cliente y del servidor.
|
|
20
|
-
No accede a ningún modelo Django.
|
|
21
|
-
"""
|
|
22
|
-
datos = {}
|
|
23
|
-
try:
|
|
24
|
-
# Datos del cliente
|
|
25
|
-
datos["ip"] = request.META.get("HTTP_X_FORWARDED_FOR", "").split(",")[0] or request.META.get("REMOTE_ADDR", "")
|
|
26
|
-
datos["user_agent"] = request.META.get("HTTP_USER_AGENT", "")
|
|
27
|
-
datos["navegador"] = datos["user_agent"].split("/")[0] if "/" in datos["user_agent"] else datos["user_agent"]
|
|
28
|
-
datos["sistema_operativo"] = platform.system()
|
|
29
|
-
datos["url"] = request.build_absolute_uri()
|
|
30
|
-
datos["fecha"] = datetime.now().isoformat()
|
|
31
|
-
|
|
32
|
-
# Datos del servidor
|
|
33
|
-
datos["servidor_nombre"] = socket.gethostname()
|
|
34
|
-
datos["servidor_ip"] = socket.gethostbyname(socket.gethostname())
|
|
35
|
-
datos["servidor_uuid"] = str(uuid.uuid4())
|
|
36
|
-
except Exception as e:
|
|
37
|
-
logger.error(f"Error obteniendo datos de máquina: {e}")
|
|
38
|
-
return datos
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def analizar_comportamiento_cliente(datos):
|
|
42
|
-
"""
|
|
43
|
-
Analiza los datos del cliente y retorna una evaluación general:
|
|
44
|
-
(nivel_severidad, descripcion)
|
|
45
|
-
"""
|
|
46
|
-
if not datos:
|
|
47
|
-
return "BAJA", "Sin datos del cliente"
|
|
48
|
-
|
|
49
|
-
ip = datos.get("ip", "")
|
|
50
|
-
navegador = datos.get("navegador", "")
|
|
51
|
-
descripcion = f"Actividad detectada desde IP {ip} con navegador {navegador}"
|
|
52
|
-
|
|
53
|
-
if "bot" in navegador.lower():
|
|
54
|
-
return "MEDIA", descripcion + " (posible bot)"
|
|
55
|
-
return "BAJA", descripcion
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def registrar_evento(tipo, descripcion, severidad="BAJA", usuario_id=None, extra=None):
|
|
59
|
-
"""
|
|
60
|
-
Prepara un registro de auditoría (para luego guardarlo en BD desde Django).
|
|
61
|
-
"""
|
|
62
|
-
try:
|
|
63
|
-
registro = {
|
|
64
|
-
"tipo": tipo,
|
|
65
|
-
"descripcion": descripcion,
|
|
66
|
-
"severidad": severidad,
|
|
67
|
-
"usuario_id": usuario_id, # <-- guardamos ID en lugar de FK
|
|
68
|
-
"fecha": datetime.now().isoformat(),
|
|
69
|
-
"extra": extra or {},
|
|
70
|
-
}
|
|
71
|
-
logger.info("[AUDITORIA] %s", json.dumps(registro, ensure_ascii=False))
|
|
72
|
-
return registro
|
|
73
|
-
except Exception as e:
|
|
74
|
-
logger.error(f"Error registrando evento: {e}")
|
|
75
|
-
return None
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
class AuditoriaServidorMiddleware(MiddlewareMixin):
|
|
79
|
-
"""
|
|
80
|
-
Middleware que integra toda la información generada por los detectores (SQLi, XSS, etc.)
|
|
81
|
-
y la deja lista en request.guardian_auditoria para que el backend (Django)
|
|
82
|
-
la guarde en la base de datos.
|
|
83
|
-
"""
|
|
84
|
-
|
|
85
|
-
def process_request(self, request):
|
|
86
|
-
try:
|
|
87
|
-
datos_cliente = obtener_datos_maquina(request)
|
|
88
|
-
severidad, descripcion = analizar_comportamiento_cliente(datos_cliente)
|
|
89
|
-
|
|
90
|
-
# Base del registro
|
|
91
|
-
registro_base = {
|
|
92
|
-
"datos_cliente": datos_cliente,
|
|
93
|
-
"descripcion": descripcion,
|
|
94
|
-
"severidad": severidad,
|
|
95
|
-
"eventos_detectados": [],
|
|
96
|
-
"usuario_id": getattr(request.user, "id", None) # <-- si está autenticado
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
# Integrar información de ataques detectada por otros middlewares
|
|
100
|
-
for attr in ["sql_attack_info", "xss_attack_info", "csrf_attack_info", "dos_attack_info"]:
|
|
101
|
-
if hasattr(request, attr):
|
|
102
|
-
registro_base["eventos_detectados"].append(getattr(request, attr))
|
|
103
|
-
|
|
104
|
-
# Si hubo algún evento sospechoso
|
|
105
|
-
if registro_base["eventos_detectados"]:
|
|
106
|
-
registrar_evento(
|
|
107
|
-
tipo="ATAQUE_DETECTADO",
|
|
108
|
-
descripcion="Se detectó comportamiento sospechoso en la solicitud",
|
|
109
|
-
severidad="ALTA",
|
|
110
|
-
usuario_id=registro_base["usuario_id"],
|
|
111
|
-
extra=registro_base,
|
|
112
|
-
)
|
|
113
|
-
|
|
114
|
-
# Guardar la info para el backend (sin registrar aún en BD)
|
|
115
|
-
request.guardian_auditoria = registro_base
|
|
116
|
-
|
|
117
|
-
except Exception as e:
|
|
118
|
-
logger.error(f"Error en AuditoriaServidorMiddleware: {e}")
|
|
119
|
-
return None
|
guardianunivalle_benito_yucra-0.1.50/GuardianUnivalle_Benito_Yucra/criptografia/cifrado_aead.py
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Cifrado simétrico autenticado: AES-GCM y ChaCha20-Poly1305
|
|
3
|
-
"""
|
|
4
|
-
from cryptography.hazmat.primitives.ciphers.aead import AESGCM, ChaCha20Poly1305
|
|
5
|
-
import os
|
|
6
|
-
|
|
7
|
-
def cifrar_aes_gcm(mensaje: bytes, clave: bytes) -> dict:
|
|
8
|
-
aes = AESGCM(clave)
|
|
9
|
-
nonce = os.urandom(12)
|
|
10
|
-
ciphertext = aes.encrypt(nonce, mensaje, None)
|
|
11
|
-
return {"nonce": nonce, "ciphertext": ciphertext}
|
|
12
|
-
|
|
13
|
-
def descifrar_aes_gcm(cipher: dict, clave: bytes) -> bytes:
|
|
14
|
-
aes = AESGCM(clave)
|
|
15
|
-
return aes.decrypt(cipher["nonce"], cipher["ciphertext"], None)
|
|
16
|
-
|
|
17
|
-
def cifrar_chacha20(mensaje: bytes, clave: bytes) -> dict:
|
|
18
|
-
cipher = ChaCha20Poly1305(clave)
|
|
19
|
-
nonce = os.urandom(12)
|
|
20
|
-
ciphertext = cipher.encrypt(nonce, mensaje, None)
|
|
21
|
-
return {"nonce": nonce, "ciphertext": ciphertext}
|
|
22
|
-
|
|
23
|
-
def descifrar_chacha20(cipher: dict, clave: bytes) -> bytes:
|
|
24
|
-
cipher_obj = ChaCha20Poly1305(clave)
|
|
25
|
-
return cipher_obj.decrypt(cipher["nonce"], cipher["ciphertext"], None)
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Gestión de intercambio de claves con ECDH y derivación HKDF.
|
|
3
|
-
"""
|
|
4
|
-
from cryptography.hazmat.primitives.asymmetric import ec
|
|
5
|
-
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
|
|
6
|
-
from cryptography.hazmat.primitives import hashes
|
|
7
|
-
|
|
8
|
-
def generar_claves_ecdh():
|
|
9
|
-
"""Genera clave privada y pública ECDH"""
|
|
10
|
-
clave_privada = ec.generate_private_key(ec.SECP384R1())
|
|
11
|
-
clave_publica = clave_privada.public_key()
|
|
12
|
-
return clave_privada, clave_publica
|
|
13
|
-
|
|
14
|
-
def derivar_clave_secreta(clave_privada, clave_publica):
|
|
15
|
-
"""Deriva una clave compartida usando HKDF"""
|
|
16
|
-
shared_key = clave_privada.exchange(ec.ECDH(), clave_publica)
|
|
17
|
-
derived_key = HKDF(
|
|
18
|
-
algorithm=hashes.SHA256(),
|
|
19
|
-
length=32,
|
|
20
|
-
salt=None,
|
|
21
|
-
info=b'guardianclave'
|
|
22
|
-
).derive(shared_key)
|
|
23
|
-
return derived_key
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Wrappers para derivación de claves segura: PBKDF2 y Argon2
|
|
3
|
-
"""
|
|
4
|
-
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
|
5
|
-
from argon2 import PasswordHasher
|
|
6
|
-
from cryptography.hazmat.primitives import hashes
|
|
7
|
-
import os
|
|
8
|
-
|
|
9
|
-
def pbkdf2_derivar_clave(password: str, salt: bytes = None) -> bytes:
|
|
10
|
-
"""Deriva clave usando PBKDF2"""
|
|
11
|
-
salt = salt or os.urandom(16)
|
|
12
|
-
kdf = PBKDF2HMAC(
|
|
13
|
-
algorithm=hashes.SHA256(),
|
|
14
|
-
length=32,
|
|
15
|
-
salt=salt,
|
|
16
|
-
iterations=100_000,
|
|
17
|
-
)
|
|
18
|
-
return kdf.derive(password.encode()), salt
|
|
19
|
-
|
|
20
|
-
def argon2_derivar_clave(password: str) -> str:
|
|
21
|
-
"""Deriva clave usando Argon2"""
|
|
22
|
-
ph = PasswordHasher()
|
|
23
|
-
return ph.hash(password)
|
guardianunivalle_benito_yucra-0.1.50/GuardianUnivalle_Benito_Yucra/detectores/detector_dos.py
DELETED
|
@@ -1,226 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
import time
|
|
3
|
-
import logging
|
|
4
|
-
import json
|
|
5
|
-
from collections import deque
|
|
6
|
-
from typing import Dict, List
|
|
7
|
-
from django.conf import settings
|
|
8
|
-
from django.utils.deprecation import MiddlewareMixin
|
|
9
|
-
from ..mitigacion.limitador_peticion import limitar_peticion
|
|
10
|
-
from ..auditoria.registro_auditoria import registrar_evento
|
|
11
|
-
|
|
12
|
-
# =====================================================
|
|
13
|
-
# === CONFIGURACIÓN DEL LOGGER ===
|
|
14
|
-
# =====================================================
|
|
15
|
-
logger = logging.getLogger("dosdefense")
|
|
16
|
-
logger.setLevel(logging.INFO)
|
|
17
|
-
if not logger.handlers:
|
|
18
|
-
handler = logging.StreamHandler()
|
|
19
|
-
handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s"))
|
|
20
|
-
logger.addHandler(handler)
|
|
21
|
-
|
|
22
|
-
# =====================================================
|
|
23
|
-
# === PARÁMETROS DE CONFIGURACIÓN BASE ===
|
|
24
|
-
# =====================================================
|
|
25
|
-
LIMITE_PETICIONES = getattr(settings, "DOS_LIMITE_PETICIONES", 100) # por minuto
|
|
26
|
-
VENTANA_SEGUNDOS = getattr(settings, "DOS_VENTANA_SEGUNDOS", 60)
|
|
27
|
-
PESO_DOS = getattr(settings, "DOS_PESO", 0.6)
|
|
28
|
-
LIMITE_ENDPOINTS_DISTINTOS = getattr(settings, "DOS_LIMITE_ENDPOINTS", 50)
|
|
29
|
-
TRUSTED_IPS = getattr(settings, "DOS_TRUSTED_IPS", [])
|
|
30
|
-
|
|
31
|
-
# =====================================================
|
|
32
|
-
# === REGISTRO TEMPORAL EN MEMORIA ===
|
|
33
|
-
# =====================================================
|
|
34
|
-
# Estructura: { ip: deque([timestamps]), ... }
|
|
35
|
-
# deque es eficiente para ventanas deslizantes
|
|
36
|
-
REGISTRO_SOLICITUDES: Dict[str, deque] = {}
|
|
37
|
-
REGISTRO_ENDPOINTS: Dict[str, set] = {}
|
|
38
|
-
|
|
39
|
-
# =====================================================
|
|
40
|
-
# === FUNCIONES AUXILIARES ===
|
|
41
|
-
# =====================================================
|
|
42
|
-
def get_client_ip(request) -> str:
|
|
43
|
-
"""Obtiene la IP real del cliente (considera proxies)."""
|
|
44
|
-
x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR")
|
|
45
|
-
if x_forwarded_for:
|
|
46
|
-
return x_forwarded_for.split(",")[0].strip()
|
|
47
|
-
return request.META.get("REMOTE_ADDR", "") or "0.0.0.0"
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def limpiar_registro_global():
|
|
51
|
-
"""Elimina IPs sin actividad reciente para evitar uso excesivo de memoria."""
|
|
52
|
-
ahora = time.time()
|
|
53
|
-
expiracion = VENTANA_SEGUNDOS * 2 # doble ventana
|
|
54
|
-
inactivas = [
|
|
55
|
-
ip for ip, tiempos in REGISTRO_SOLICITUDES.items()
|
|
56
|
-
if tiempos and ahora - tiempos[-1] > expiracion
|
|
57
|
-
]
|
|
58
|
-
for ip in inactivas:
|
|
59
|
-
REGISTRO_SOLICITUDES.pop(ip, None)
|
|
60
|
-
REGISTRO_ENDPOINTS.pop(ip, None)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
def limpiar_registro(ip: str):
|
|
64
|
-
"""Limpia peticiones antiguas fuera de la ventana de tiempo."""
|
|
65
|
-
ahora = time.time()
|
|
66
|
-
if ip not in REGISTRO_SOLICITUDES:
|
|
67
|
-
REGISTRO_SOLICITUDES[ip] = deque()
|
|
68
|
-
tiempos = REGISTRO_SOLICITUDES[ip]
|
|
69
|
-
while tiempos and ahora - tiempos[0] > VENTANA_SEGUNDOS:
|
|
70
|
-
tiempos.popleft()
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
def calcular_nivel_amenaza_dos(tasa_peticion: int, limite: int = LIMITE_PETICIONES) -> float:
|
|
74
|
-
"""
|
|
75
|
-
Calcula la puntuación de amenaza DoS.
|
|
76
|
-
Fórmula: S_dos = w_dos * (tasa_peticion / limite)
|
|
77
|
-
"""
|
|
78
|
-
proporcion = tasa_peticion / max(limite, 1)
|
|
79
|
-
s_dos = PESO_DOS * min(proporcion, 2.0)
|
|
80
|
-
return round(min(s_dos, 1.0), 3)
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
def detectar_dos(ip: str, tasa_peticion: int, limite: int = LIMITE_PETICIONES) -> bool:
|
|
84
|
-
"""Evalúa si la tasa de peticiones excede el umbral permitido."""
|
|
85
|
-
if tasa_peticion > limite:
|
|
86
|
-
registrar_evento(
|
|
87
|
-
tipo="DoS",
|
|
88
|
-
descripcion=f"Alta tasa de peticiones desde {ip}: {tasa_peticion} req/min (límite {limite})",
|
|
89
|
-
severidad="ALTA",
|
|
90
|
-
)
|
|
91
|
-
limitar_peticion(usuario_id="anonimo")
|
|
92
|
-
return True
|
|
93
|
-
elif tasa_peticion > limite * 0.75:
|
|
94
|
-
# Umbral de advertencia
|
|
95
|
-
registrar_evento(
|
|
96
|
-
tipo="DoS",
|
|
97
|
-
descripcion=f"Posible saturación desde {ip}: {tasa_peticion} req/min",
|
|
98
|
-
severidad="MEDIA",
|
|
99
|
-
)
|
|
100
|
-
return False
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
def analizar_headers(user_agent: str, referer: str) -> List[str]:
|
|
104
|
-
"""Detecta patrones de agentes o cabeceras sospechosas."""
|
|
105
|
-
sospechas = []
|
|
106
|
-
if not user_agent or len(user_agent) < 10:
|
|
107
|
-
sospechas.append("User-Agent vacío o anómalo")
|
|
108
|
-
if "curl" in user_agent.lower() or "python" in user_agent.lower():
|
|
109
|
-
sospechas.append("User-Agent indica script o bot")
|
|
110
|
-
if referer and any(palabra in referer.lower() for palabra in ["attack", "scan", "bot"]):
|
|
111
|
-
sospechas.append("Referer sospechoso")
|
|
112
|
-
return sospechas
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
# =====================================================
|
|
116
|
-
# === MIDDLEWARE PRINCIPAL DE DEFENSA DoS ===
|
|
117
|
-
# =====================================================
|
|
118
|
-
class DOSDefenseMiddleware(MiddlewareMixin):
|
|
119
|
-
"""
|
|
120
|
-
Middleware de detección y registro de ataques DoS.
|
|
121
|
-
"""
|
|
122
|
-
|
|
123
|
-
def process_request(self, request):
|
|
124
|
-
limpiar_registro_global()
|
|
125
|
-
|
|
126
|
-
client_ip = get_client_ip(request)
|
|
127
|
-
if client_ip in TRUSTED_IPS:
|
|
128
|
-
return None
|
|
129
|
-
|
|
130
|
-
user_agent = request.META.get("HTTP_USER_AGENT", "Desconocido")
|
|
131
|
-
referer = request.META.get("HTTP_REFERER", "")
|
|
132
|
-
path = request.path
|
|
133
|
-
|
|
134
|
-
# Registrar endpoint accedido
|
|
135
|
-
REGISTRO_ENDPOINTS.setdefault(client_ip, set()).add(path)
|
|
136
|
-
|
|
137
|
-
# Mantener ventana deslizante
|
|
138
|
-
limpiar_registro(client_ip)
|
|
139
|
-
REGISTRO_SOLICITUDES[client_ip].append(time.time())
|
|
140
|
-
|
|
141
|
-
tasa = len(REGISTRO_SOLICITUDES[client_ip])
|
|
142
|
-
nivel = calcular_nivel_amenaza_dos(tasa)
|
|
143
|
-
es_dos = detectar_dos(client_ip, tasa)
|
|
144
|
-
|
|
145
|
-
descripcion = []
|
|
146
|
-
sospechas_headers = analizar_headers(user_agent, referer)
|
|
147
|
-
if sospechas_headers:
|
|
148
|
-
descripcion.extend(sospechas_headers)
|
|
149
|
-
|
|
150
|
-
# Verificar exceso de endpoints distintos (indicativo de escaneo)
|
|
151
|
-
if len(REGISTRO_ENDPOINTS[client_ip]) > LIMITE_ENDPOINTS_DISTINTOS:
|
|
152
|
-
descripcion.append("Número anormal de endpoints distintos accedidos")
|
|
153
|
-
|
|
154
|
-
if es_dos or descripcion:
|
|
155
|
-
descripcion.insert(0, f"Tasa actual: {tasa} req/min (nivel {nivel:.2f})")
|
|
156
|
-
descripcion.append(f"Ruta: {path}")
|
|
157
|
-
|
|
158
|
-
logger.warning(
|
|
159
|
-
"DoS detectado o sospechoso desde IP %s: %s ; nivel: %.2f",
|
|
160
|
-
client_ip,
|
|
161
|
-
descripcion,
|
|
162
|
-
nivel,
|
|
163
|
-
)
|
|
164
|
-
|
|
165
|
-
request.dos_attack_info = {
|
|
166
|
-
"ip": client_ip,
|
|
167
|
-
"tipos": ["DoS"],
|
|
168
|
-
"descripcion": descripcion,
|
|
169
|
-
"payload": json.dumps(
|
|
170
|
-
{"user_agent": user_agent, "referer": referer, "path": path}
|
|
171
|
-
),
|
|
172
|
-
"score": nivel,
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
return None
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
"""
|
|
179
|
-
Detector de ataques de tipo DoS (Denial of Service)
|
|
180
|
-
====================================================
|
|
181
|
-
|
|
182
|
-
Este módulo forma parte del sistema de detección de amenazas.
|
|
183
|
-
Detecta tasas de petición anómalas en base a límites configurables,
|
|
184
|
-
captura datos del atacante (IP, agente, cabeceras)
|
|
185
|
-
y registra los incidentes para su auditoría.
|
|
186
|
-
|
|
187
|
-
Componentes:
|
|
188
|
-
- DOSDefenseMiddleware: Middleware principal de detección.
|
|
189
|
-
- detectar_dos(): Evalúa si la tasa supera el umbral permitido.
|
|
190
|
-
- calcular_nivel_amenaza_dos(): Calcula la severidad proporcional.
|
|
191
|
-
- registrar_evento(): Registra los incidentes en auditoría.
|
|
192
|
-
|
|
193
|
-
Algoritmos relacionados:
|
|
194
|
-
* Rate Limiting basado en ventana deslizante.
|
|
195
|
-
* Cálculo de score: S_dos = w_dos * (tasa_peticion / limite)
|
|
196
|
-
"""
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
"""
|
|
200
|
-
Algoritmos relacionados:
|
|
201
|
-
*Rate Limiting, listas de bloqueo.
|
|
202
|
-
*Opcional: cifrado de logs con ChaCha20-Poly1305.
|
|
203
|
-
Contribución a fórmula de amenaza S:
|
|
204
|
-
S_dos = w_dos * (tasa_peticion / limite)
|
|
205
|
-
S_dos = 0.6 * (150 / 100)
|
|
206
|
-
donde w_dos es peso asignado a DoS y tasa_peticion / limite es la proporción de la tasa actual sobre el límite.
|
|
207
|
-
"""
|
|
208
|
-
"""
|
|
209
|
-
Detector de ataques de tipo DoS (Denial of Service)
|
|
210
|
-
====================================================
|
|
211
|
-
|
|
212
|
-
Este módulo forma parte del sistema de detección de amenazas.
|
|
213
|
-
Detecta tasas de petición anómalas en base a límites configurables,
|
|
214
|
-
captura datos del atacante (IP, agente, cabeceras)
|
|
215
|
-
y registra los incidentes para su auditoría.
|
|
216
|
-
|
|
217
|
-
Componentes:
|
|
218
|
-
- DOSDefenseMiddleware: Middleware principal de detección.
|
|
219
|
-
- detectar_dos(): Evalúa si la tasa supera el umbral permitido.
|
|
220
|
-
- calcular_nivel_amenaza_dos(): Calcula la severidad proporcional.
|
|
221
|
-
- registrar_evento(): Registra los incidentes en auditoría.
|
|
222
|
-
|
|
223
|
-
Algoritmos relacionados:
|
|
224
|
-
* Rate Limiting basado en ventana deslizante.
|
|
225
|
-
* Cálculo de score: S_dos = w_dos * (tasa_peticion / limite)
|
|
226
|
-
"""
|
guardianunivalle_benito_yucra-0.1.50/GuardianUnivalle_Benito_Yucra/middleware_web/middleware_web.py
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Middleware base para frameworks web (Django/Flask/FastAPI)
|
|
3
|
-
"""
|
|
4
|
-
from ..detectores.detector_sql import detectar_inyeccion_sql
|
|
5
|
-
from ..detectores.detector_xss import detectar_xss
|
|
6
|
-
|
|
7
|
-
def middleware_proteccion(request):
|
|
8
|
-
# Simulación de protección de entrada
|
|
9
|
-
if detectar_inyeccion_sql(request.get("query", "")):
|
|
10
|
-
return {"error": "SQL Injection detectado"}
|
|
11
|
-
if detectar_xss(request.get("input", "")):
|
|
12
|
-
return {"error": "XSS detectado"}
|
|
13
|
-
return {"ok": True}
|
guardianunivalle_benito_yucra-0.1.50/GuardianUnivalle_Benito_Yucra/puntuacion/puntuacion_amenaza.py
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Fórmula compuesta S para puntuar la amenaza global
|
|
3
|
-
"""
|
|
4
|
-
def calcular_puntuacion(detecciones_sql=0, detecciones_xss=0, intentos_csrf=0,
|
|
5
|
-
procesos_keylogger=0, tasa_dos=0,
|
|
6
|
-
w_sql=1.5, w_xss=1.2, w_csrf=1.0, w_keylogger=2.0, w_dos=2.5,
|
|
7
|
-
limite_dos=100) -> float:
|
|
8
|
-
S = (
|
|
9
|
-
w_sql * detecciones_sql +
|
|
10
|
-
w_xss * detecciones_xss +
|
|
11
|
-
w_csrf * intentos_csrf +
|
|
12
|
-
w_keylogger * procesos_keylogger +
|
|
13
|
-
w_dos * (tasa_dos / limite_dos)
|
|
14
|
-
)
|
|
15
|
-
return S
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|