GuardianUnivalle-Benito-Yucra 1.1.18__py3-none-any.whl

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.
@@ -0,0 +1,352 @@
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
+ # 1. FireHOL (Agregador General de Nivel 1)
30
+ # Resultado: Éxito al obtener 4438 IPs/CIDR
31
+ "https://iplists.firehol.org/files/firehol_level1.netset",
32
+
33
+ # 2. Abuse.ch Feodo Tracker (Botnets C&C)
34
+ # Resultado: Éxito al obtener 2 IPs/CIDR (puede ser bajo, pero es funcional)
35
+ "https://feodotracker.abuse.ch/downloads/ipblocklist.txt",
36
+
37
+ # 3. Tor Project (Nodos de Salida)
38
+ # Resultado: Éxito al obtener 1166 IPs/CIDR
39
+ "https://check.torproject.org/torbulkexitlist?ip=1.1.1.1"
40
+ ]
41
+
42
+ # Cabeceras para simular un navegador
43
+ SCRAPING_HEADERS = {
44
+ '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'
45
+ }
46
+
47
+ # =====================================================
48
+ # === FUNCIONES DE INTELIGENCIA DE AMENAZAS ===
49
+ # =====================================================
50
+
51
+ def fetch_and_parse_blacklists() -> Set[str]:
52
+ """
53
+ Intenta obtener y parsear IPs/CIDR de varias fuentes externas.
54
+ """
55
+ global_blacklist: Set[str] = set()
56
+ # Patrón Regex para IPs (admite también rangos CIDR)
57
+
58
+ ip_pattern = re.compile(r'\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(?:/\d{1,2})?\b')
59
+
60
+ for url in IP_BLACKLIST_SOURCES:
61
+ try:
62
+ response = requests.get(url, headers=SCRAPING_HEADERS, timeout=15)
63
+ response.raise_for_status()
64
+
65
+ found_ips = ip_pattern.findall(response.text)
66
+
67
+ # Limpieza
68
+ #cleaned_ips = {ip[0] for ip in found_ips if ip[0] not in ('0.0.0.0', '255.255.255.255')}
69
+ cleaned_ips = {ip for ip in found_ips if ip not in ('0.0.0.0', '255.255.255.255')}
70
+
71
+
72
+ global_blacklist.update(cleaned_ips)
73
+ logger.info(f"[Threat Intel] Éxito al obtener {len(cleaned_ips)} IPs/CIDR de {url}")
74
+
75
+ except requests.exceptions.RequestException as e:
76
+ logger.error(f"[Threat Intel] Error de conexión con {url}: {e}")
77
+ except Exception as e:
78
+ logger.error(f"[Threat Intel] Error inesperado al parsear {url}: {e}")
79
+
80
+ if '127.0.0.1' in global_blacklist:
81
+ global_blacklist.remove('127.0.0.1')
82
+
83
+ return global_blacklist
84
+
85
+ def check_ip_in_advanced_blacklist(client_ip: str, global_blacklist_cidrs: Set[str]) -> bool:
86
+ """
87
+ Escaneo avanzado: Chequea si una IP está en la lista negra, incluyendo rangos CIDR.
88
+ """
89
+ if not global_blacklist_cidrs:
90
+ return False
91
+
92
+ try:
93
+ ip_a_chequear = IPv4Address(client_ip)
94
+
95
+ # 1. Chequeo rápido de IPs individuales
96
+ if client_ip in global_blacklist_cidrs:
97
+ return True
98
+
99
+ # 2. Chequeo de rangos CIDR (más lento)
100
+ for cidr_entry in global_blacklist_cidrs:
101
+ if '/' in cidr_entry:
102
+ try:
103
+ if ip_a_chequear in IPv4Network(cidr_entry, strict=False):
104
+ return True
105
+ except ValueError:
106
+ continue # No es una red CIDR válida, continuar
107
+ return False
108
+
109
+ except ValueError:
110
+ logger.error(f"IP del cliente inválida o no IPv4: {client_ip}")
111
+ return False
112
+
113
+ # =====================================================
114
+ # === PARÁMETROS DE CONFIGURACIÓN BASE Y SCORE ===
115
+ # =====================================================
116
+ LIMITE_PETICIONES = getattr(settings, "DOS_LIMITE_PETICIONES", 100)
117
+ VENTANA_SEGUNDOS = getattr(settings, "DOS_VENTANA_SEGUNDOS", 60)
118
+ PESO_DOS = getattr(settings, "DOS_PESO", 0.6)
119
+ LIMITE_ENDPOINTS_DISTINTOS = getattr(settings, "DOS_LIMITE_ENDPOINTS", 50)
120
+ TRUSTED_IPS = getattr(settings, "DOS_TRUSTED_IPS", [])
121
+ TIEMPO_BLOQUEO_SEGUNDOS = getattr(settings, "DOS_TIEMPO_BLOQUEO", 300)
122
+
123
+ # Parámetros del Score Avanzado
124
+ PESO_BLACKLIST = getattr(settings, "DOS_PESO_BLACKLIST", 0.3)
125
+ PESO_HEURISTICA = getattr(settings, "DOS_PESO_HEURISTICA", 0.1)
126
+ UMBRAL_BLOQUEO = getattr(settings, "DOS_UMBRAL_BLOQUEO", 0.8)
127
+
128
+ # === CARGA INICIAL DE LA LISTA NEGRA ===
129
+ try:
130
+ IP_BLACKLIST: Set[str] = fetch_and_parse_blacklists()
131
+ output_filename = "blacklist_cargada.txt"
132
+ with open(output_filename, 'w') as f:
133
+ # Escribe cada IP/CIDR en una nueva línea
134
+ for ip in sorted(list(IP_BLACKLIST)): # Usamos sorted() para orden alfabético/numérico
135
+ f.write(f"{ip}\n")
136
+ logger.info(f"Lista Negra Externa GUARDADA en {output_filename} para inspección.")
137
+ logger.info(f"Lista Negra Externa cargada con {len(IP_BLACKLIST)} IPs/CIDR.")
138
+ except Exception as e:
139
+ logger.error(f"Error al cargar la IP Blacklist: {e}. Usando lista vacía.")
140
+ IP_BLACKLIST = set()
141
+
142
+ # =====================================================
143
+ # === REGISTRO TEMPORAL EN MEMORIA ===
144
+ # =====================================================
145
+ REGISTRO_SOLICITUDES: Dict[str, deque] = {}
146
+ REGISTRO_ENDPOINTS: Dict[str, set] = {}
147
+ BLOQUEOS_TEMPORALES: Dict[str, float] = {}
148
+
149
+ # =====================================================
150
+ # === FUNCIONES AUXILIARES ===
151
+ # =====================================================
152
+ def get_client_ip(request) -> str:
153
+ """Obtiene la IP real del cliente (considera proxies)."""
154
+ x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR")
155
+ if x_forwarded_for:
156
+ return x_forwarded_for.split(",")[0].strip()
157
+ return request.META.get("REMOTE_ADDR", "") or "0.0.0.0"
158
+
159
+ def limpiar_registro_global():
160
+ """Elimina IPs sin actividad reciente y desbloquea IPs temporales."""
161
+ # ... (La implementación de limpiar_registro_global permanece igual)
162
+ ahora = time.time()
163
+ expiracion = VENTANA_SEGUNDOS * 2
164
+ inactivas = []
165
+
166
+ for ip, tiempos in REGISTRO_SOLICITUDES.items():
167
+ if tiempos and ahora - tiempos[-1] > expiracion:
168
+ inactivas.append(ip)
169
+
170
+ for ip in inactivas:
171
+ REGISTRO_SOLICITUDES.pop(ip, None)
172
+ REGISTRO_ENDPOINTS.pop(ip, None)
173
+
174
+ ips_a_desbloquear = [ip for ip, tiempo_desbloqueo in BLOQUEOS_TEMPORALES.items() if ahora > tiempo_desbloqueo]
175
+ for ip in ips_a_desbloquear:
176
+ BLOQUEOS_TEMPORALES.pop(ip, None)
177
+ logger.info(f"[Desbloqueo] IP {ip} desbloqueada automáticamente.")
178
+
179
+ def limpiar_registro(ip: str):
180
+ """Limpia peticiones antiguas fuera de la ventana de tiempo."""
181
+ # ... (La implementación de limpiar_registro permanece igual)
182
+ ahora = time.time()
183
+ if ip not in REGISTRO_SOLICITUDES:
184
+ REGISTRO_SOLICITUDES[ip] = deque()
185
+ tiempos = REGISTRO_SOLICITUDES[ip]
186
+ while tiempos and ahora - tiempos[0] > VENTANA_SEGUNDOS:
187
+ tiempos.popleft()
188
+
189
+ def calcular_nivel_amenaza_dos(tasa_peticion: int, limite: int = LIMITE_PETICIONES) -> float:
190
+ """Calcula la puntuación de amenaza DoS (Rate Limiting)."""
191
+ # ... (La implementación de calcular_nivel_amenaza_dos permanece igual)
192
+ proporcion = tasa_peticion / max(limite, 1)
193
+ s_dos = PESO_DOS * min(proporcion, 2.0)
194
+ return round(min(s_dos, 1.0), 3)
195
+
196
+
197
+ # =====================================================
198
+ # === FUNCIONES INTERNAS DE SEGURIDAD Y AUDITORÍA ===
199
+ # =====================================================
200
+ def limitar_peticion(usuario_id: str):
201
+ """Implementa la mitigación: Bloquea temporalmente la IP."""
202
+ ahora = time.time()
203
+ tiempo_desbloqueo = ahora + TIEMPO_BLOQUEO_SEGUNDOS
204
+ BLOQUEOS_TEMPORALES[usuario_id] = tiempo_desbloqueo
205
+ logger.warning(
206
+ f"[Bloqueo Activo] IP {usuario_id} bloqueada temporalmente hasta {time.ctime(tiempo_desbloqueo)}"
207
+ )
208
+
209
+ def registrar_evento(tipo: str, descripcion: str, severidad: str = "MEDIA"):
210
+ """Simula el registro de auditoría de un evento de seguridad."""
211
+ evento = {
212
+ "tipo": tipo,
213
+ "descripcion": descripcion,
214
+ "severidad": severidad,
215
+ "timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
216
+ }
217
+ logger.info(f"[AUDITORÍA] {json.dumps(evento, ensure_ascii=False)}")
218
+
219
+ def detectar_dos(ip: str, tasa_peticion: int, limite: int = LIMITE_PETICIONES) -> bool:
220
+ """Evalúa si la tasa de peticiones excede el umbral permitido y aplica mitigación."""
221
+ # ... (La implementación de detectar_dos permanece igual)
222
+ if tasa_peticion > limite:
223
+ registrar_evento(
224
+ tipo="DoS",
225
+ descripcion=f"Alta tasa de peticiones desde {ip}: {tasa_peticion} req/min (límite {limite})",
226
+ severidad="ALTA",
227
+ )
228
+ limitar_peticion(usuario_id=ip)
229
+ return True
230
+ elif tasa_peticion > limite * 0.75:
231
+ registrar_evento(
232
+ tipo="DoS",
233
+ descripcion=f"Posible saturación desde {ip}: {tasa_peticion} req/min",
234
+ severidad="MEDIA",
235
+ )
236
+ return False
237
+
238
+ def analizar_headers_avanzado(user_agent: str, referer: str) -> List[str]:
239
+ """Detecta patrones sospechosos, penalizando User-Agents automatizados."""
240
+ # ... (La implementación de analizar_headers_avanzado permanece igual)
241
+ sospechas = []
242
+
243
+ if not user_agent or len(user_agent) < 10 or user_agent.lower() == "python-requests/2.25.1":
244
+ sospechas.append("User-Agent vacío/Defecto")
245
+
246
+ automation_keywords = ["curl", "python", "wget", "bot", "spider", "scraper", "headless", "phantom"]
247
+ if any(patron in user_agent.lower() for patron in automation_keywords):
248
+ sospechas.append("Herramienta de automatización detectada")
249
+
250
+ if referer and any(palabra in referer.lower() for palabra in ["attack", "scan"]):
251
+ sospechas.append("Referer indicando abuso")
252
+
253
+ return sospechas
254
+
255
+
256
+ # =====================================================
257
+ # === MIDDLEWARE PRINCIPAL DE DEFENSA DoS ===
258
+ # =====================================================
259
+ class DOSDefenseMiddleware(MiddlewareMixin):
260
+ """
261
+ Middleware de detección, registro y mitigación de ataques DoS/Scraping avanzado.
262
+ """
263
+
264
+ def process_request(self, request):
265
+ limpiar_registro_global()
266
+
267
+ client_ip = get_client_ip(request)
268
+
269
+ # 1. BLOQUEOS Y EXCEPCIONES PREVIAS
270
+ if client_ip in TRUSTED_IPS:
271
+ return None
272
+
273
+ # BLOQUEO TEMPORAL: IPs previamente bloqueadas por alto Score o DoS
274
+ if client_ip in BLOQUEOS_TEMPORALES and time.time() < BLOQUEOS_TEMPORALES[client_ip]:
275
+ registrar_evento(
276
+ tipo="Temporary Block",
277
+ descripcion=f"Bloqueo temporal por abuso previo: IP {client_ip}.",
278
+ severidad="ALTA",
279
+ )
280
+ return HttpResponseForbidden("Acceso denegado temporalmente por comportamiento sospechoso.")
281
+
282
+ # 2. ANÁLISIS DE LA PETICIÓN Y CÁLCULO DE MÉTRICAS BASE
283
+ user_agent = request.META.get("HTTP_USER_AGENT", "Desconocido")
284
+ referer = request.META.get("HTTP_REFERER", "")
285
+ path = request.path
286
+
287
+ # Mantener ventana deslizante y tasa
288
+ REGISTRO_ENDPOINTS.setdefault(client_ip, set()).add(path)
289
+ limpiar_registro(client_ip)
290
+ REGISTRO_SOLICITUDES[client_ip].append(time.time())
291
+
292
+ tasa = len(REGISTRO_SOLICITUDES[client_ip])
293
+
294
+ # 3. CÁLCULO DE LOS COMPONENTES DEL SCORE DE AMENAZA
295
+
296
+ # S_dos: Tasa de Petición (Rate Limiting)
297
+ nivel_dos = calcular_nivel_amenaza_dos(tasa)
298
+
299
+ # S_blacklist: Escaneo Avanzado (CIDR)
300
+ nivel_blacklist = PESO_BLACKLIST if check_ip_in_advanced_blacklist(client_ip, IP_BLACKLIST) else 0
301
+
302
+ # S_heuristica: Análisis de Comportamiento (Scraping/Escaneo)
303
+ sospechas_headers = analizar_headers_avanzado(user_agent, referer)
304
+
305
+ score_headers = 0.5 if sospechas_headers else 0
306
+ score_endpoints = 0.5 if len(REGISTRO_ENDPOINTS[client_ip]) > LIMITE_ENDPOINTS_DISTINTOS else 0
307
+
308
+ nivel_heuristica = PESO_HEURISTICA * (score_headers + score_endpoints)
309
+
310
+ # 4. CÁLCULO DEL SCORE TOTAL Y DECISIÓN DE MITIGACIÓN
311
+
312
+ S_total = nivel_dos + nivel_blacklist + nivel_heuristica
313
+
314
+ if S_total >= UMBRAL_BLOQUEO:
315
+ descripcion_log = [
316
+ f"Score Total: {S_total:.3f} > Umbral {UMBRAL_BLOQUEO}",
317
+ f"DoS: {nivel_dos:.3f}, Blacklist: {nivel_blacklist:.3f}, Heurística: {nivel_heuristica:.3f}"
318
+ ]
319
+ registrar_evento(
320
+ tipo="Bloqueo por Score Total",
321
+ descripcion=" ; ".join(descripcion_log),
322
+ severidad="CRÍTICA",
323
+ )
324
+ limitar_peticion(usuario_id=client_ip)
325
+ return HttpResponseForbidden("Acceso denegado por alto Score de Amenaza.")
326
+
327
+ # 5. REGISTRO DE ADVERTENCIA (Si no se bloquea, pero es sospechoso)
328
+
329
+ if S_total > UMBRAL_BLOQUEO * 0.75 or (nivel_dos > 0) or len(sospechas_headers) > 0:
330
+
331
+ descripcion = sospechas_headers
332
+ if score_endpoints > 0:
333
+ descripcion.append("Número anormal de endpoints distintos accedidos (posible escaneo/scraping)")
334
+
335
+ descripcion.insert(0, f"Score Total: {S_total:.3f} (Tasa: {tasa} req/min)")
336
+ descripcion.append(f"Ruta: {path}")
337
+
338
+ logger.warning(
339
+ "Tráfico Sospechoso desde IP %s: %s",
340
+ client_ip,
341
+ " ; ".join(descripcion),
342
+ )
343
+
344
+ request.dos_attack_info = {
345
+ "ip": client_ip,
346
+ "tipos": ["DoS", "Scraping/Escaneo"],
347
+ "descripcion": descripcion,
348
+ "payload": json.dumps({"user_agent": user_agent, "referer": referer, "path": path}),
349
+ "score": S_total,
350
+ }
351
+
352
+ return None