GuardianUnivalle-Benito-Yucra 0.1.50__tar.gz → 0.1.51__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.

Files changed (27) hide show
  1. guardianunivalle_benito_yucra-0.1.51/GuardianUnivalle_Benito_Yucra/detectores/detector_dos.py +335 -0
  2. {guardianunivalle_benito_yucra-0.1.50 → guardianunivalle_benito_yucra-0.1.51}/GuardianUnivalle_Benito_Yucra.egg-info/PKG-INFO +1 -1
  3. {guardianunivalle_benito_yucra-0.1.50 → guardianunivalle_benito_yucra-0.1.51}/GuardianUnivalle_Benito_Yucra.egg-info/SOURCES.txt +0 -8
  4. {guardianunivalle_benito_yucra-0.1.50 → guardianunivalle_benito_yucra-0.1.51}/PKG-INFO +1 -1
  5. {guardianunivalle_benito_yucra-0.1.50 → guardianunivalle_benito_yucra-0.1.51}/pyproject.toml +1 -1
  6. guardianunivalle_benito_yucra-0.1.50/GuardianUnivalle_Benito_Yucra/auditoria/auditoria_servidor.py +0 -119
  7. guardianunivalle_benito_yucra-0.1.50/GuardianUnivalle_Benito_Yucra/criptografia/cifrado_aead.py +0 -25
  8. guardianunivalle_benito_yucra-0.1.50/GuardianUnivalle_Benito_Yucra/criptografia/intercambio_claves.py +0 -23
  9. guardianunivalle_benito_yucra-0.1.50/GuardianUnivalle_Benito_Yucra/criptografia/kdf.py +0 -23
  10. guardianunivalle_benito_yucra-0.1.50/GuardianUnivalle_Benito_Yucra/detectores/detector_dos.py +0 -226
  11. guardianunivalle_benito_yucra-0.1.50/GuardianUnivalle_Benito_Yucra/middleware_web/middleware_web.py +0 -13
  12. guardianunivalle_benito_yucra-0.1.50/GuardianUnivalle_Benito_Yucra/mitigacion/limitador_peticion.py +0 -7
  13. guardianunivalle_benito_yucra-0.1.50/GuardianUnivalle_Benito_Yucra/mitigacion/lista_bloqueo.py +0 -10
  14. guardianunivalle_benito_yucra-0.1.50/GuardianUnivalle_Benito_Yucra/puntuacion/puntuacion_amenaza.py +0 -15
  15. {guardianunivalle_benito_yucra-0.1.50 → guardianunivalle_benito_yucra-0.1.51}/GuardianUnivalle_Benito_Yucra/__init__.py +0 -0
  16. {guardianunivalle_benito_yucra-0.1.50 → guardianunivalle_benito_yucra-0.1.51}/GuardianUnivalle_Benito_Yucra/auditoria/registro_auditoria.py +0 -0
  17. {guardianunivalle_benito_yucra-0.1.50 → guardianunivalle_benito_yucra-0.1.51}/GuardianUnivalle_Benito_Yucra/detectores/detector_csrf.py +0 -0
  18. {guardianunivalle_benito_yucra-0.1.50 → guardianunivalle_benito_yucra-0.1.51}/GuardianUnivalle_Benito_Yucra/detectores/detector_keylogger.py +0 -0
  19. {guardianunivalle_benito_yucra-0.1.50 → guardianunivalle_benito_yucra-0.1.51}/GuardianUnivalle_Benito_Yucra/detectores/detector_sql.py +0 -0
  20. {guardianunivalle_benito_yucra-0.1.50 → guardianunivalle_benito_yucra-0.1.51}/GuardianUnivalle_Benito_Yucra/detectores/detector_xss.py +0 -0
  21. {guardianunivalle_benito_yucra-0.1.50 → guardianunivalle_benito_yucra-0.1.51}/GuardianUnivalle_Benito_Yucra/utilidades.py +0 -0
  22. {guardianunivalle_benito_yucra-0.1.50 → guardianunivalle_benito_yucra-0.1.51}/GuardianUnivalle_Benito_Yucra.egg-info/dependency_links.txt +0 -0
  23. {guardianunivalle_benito_yucra-0.1.50 → guardianunivalle_benito_yucra-0.1.51}/GuardianUnivalle_Benito_Yucra.egg-info/requires.txt +0 -0
  24. {guardianunivalle_benito_yucra-0.1.50 → guardianunivalle_benito_yucra-0.1.51}/GuardianUnivalle_Benito_Yucra.egg-info/top_level.txt +0 -0
  25. {guardianunivalle_benito_yucra-0.1.50 → guardianunivalle_benito_yucra-0.1.51}/LICENSE +0 -0
  26. {guardianunivalle_benito_yucra-0.1.50 → guardianunivalle_benito_yucra-0.1.51}/README.md +0 -0
  27. {guardianunivalle_benito_yucra-0.1.50 → guardianunivalle_benito_yucra-0.1.51}/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.50
3
+ Version: 0.1.51
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.50
3
+ Version: 0.1.51
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
@@ -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.50"
7
+ version = "0.1.51"
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" }
@@ -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
@@ -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)
@@ -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
- """
@@ -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}
@@ -1,7 +0,0 @@
1
- """
2
- Limitador de peticiones para prevenir DoS
3
- """
4
- def limitar_peticion(usuario_id: str, max_peticion: int = 100) -> bool:
5
- # Simulación de limitador: True si excede
6
- print(f"✅ Limitador activo para {usuario_id}")
7
- return False
@@ -1,10 +0,0 @@
1
- """
2
- Manejo de lista de bloqueo
3
- """
4
- lista_bloqueo = set()
5
-
6
- def agregar_bloqueo(ip: str):
7
- lista_bloqueo.add(ip)
8
-
9
- def esta_bloqueado(ip: str) -> bool:
10
- return ip in lista_bloqueo
@@ -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