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,626 @@
1
+ # xss_defense_crypto.py
2
+ # GuardianUnivalle_Benito_Yucra/detectores/xss_defense_crypto.py
3
+ # Middleware robusto para detección y mitigación de XSS con componentes criptográficos integrados
4
+ # - Detección por patrones con pesos y saturación
5
+ # - Sanitización con bleach (si disponible)
6
+ # - Integración de HMAC-SHA256 para firmar tokens/cookies
7
+ # - SHA-256/SHA-3 para hashes de contenido
8
+ # - AES-GCM/ChaCha20-Poly1305 para cifrar cookies sensibles
9
+ # - HKDF para derivar claves
10
+ # - Argon2id para seguridad de claves derivadas
11
+ # - TLS 1.3 recomendado en configuración del servidor (no implementado aquí, pero documentado)
12
+ # - Registro cifrado de eventos y payloads
13
+ # xss_defense_crypto.py
14
+ # GuardianUnivalle_Benito_Yucra/detectores/xss_defense_crypto.py
15
+ # Middleware robusto para detección y mitigación de XSS con componentes criptográficos integrados
16
+ # - Detección por patrones con pesos y saturación
17
+ # - Sanitización con bleach (si disponible)
18
+ # - Integración de HMAC-SHA256 para firmar tokens/cookies
19
+ # - SHA-256/SHA-3 para hashes de contenido
20
+ # - AES-GCM/ChaCha20-Poly1305 para cifrar cookies sensibles
21
+ # - HKDF para derivar claves
22
+ # - Argon2id para seguridad de claves derivadas
23
+ # - TLS 1.3 recomendado en configuración del servidor (no implementado aquí, pero documentado)
24
+ # - Registro cifrado de eventos y payloads
25
+ # xss_defense_crypto.py
26
+ # GuardianUnivalle_Benito_Yucra/detectores/xss_defense_crypto.py
27
+ # Middleware robusto para detección y mitigación de XSS con componentes criptográficos integrados
28
+ # - Detección por patrones con pesos y saturación
29
+ # - Sanitización con bleach (si disponible)
30
+ # - Integración de HMAC-SHA256 para firmar tokens/cookies
31
+ # - SHA-256/SHA-3 para hashes de contenido
32
+ # - AES-GCM/ChaCha20-Poly1305 para cifrar cookies sensibles
33
+ # - HKDF para derivar claves
34
+ # - Argon2id para seguridad de claves derivadas
35
+ # - TLS 1.3 recomendado en configuración del servidor (no implementado aquí, pero documentado)
36
+ # - Registro cifrado de eventos y payloads
37
+
38
+ from __future__ import annotations
39
+ import json
40
+ import logging
41
+ import re
42
+ import math
43
+ import base64
44
+ import os
45
+ import time
46
+ from typing import List, Tuple, Dict, Any
47
+ from django.utils.deprecation import MiddlewareMixin
48
+ from django.conf import settings
49
+ from django.http import HttpResponseForbidden, HttpResponse
50
+ from django.core.cache import cache
51
+
52
+ try:
53
+ import bleach
54
+ _BLEACH_AVAILABLE = True
55
+ except Exception:
56
+ _BLEACH_AVAILABLE = False
57
+
58
+ # cryptography & argon2
59
+ from cryptography.hazmat.primitives.ciphers.aead import AESGCM, ChaCha20Poly1305
60
+ from cryptography.hazmat.primitives import hashes, hmac as crypto_hmac
61
+ from cryptography.hazmat.primitives.kdf.hkdf import HKDF
62
+ from cryptography.exceptions import InvalidSignature
63
+ from argon2.low_level import hash_secret_raw, Type as Argon2Type
64
+
65
+ # ----------------------------
66
+ # Logger
67
+ # ----------------------------
68
+ logger = logging.getLogger("xssdefense_crypto")
69
+ logger.setLevel(logging.INFO)
70
+ if not logger.handlers:
71
+ handler = logging.StreamHandler()
72
+ handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s"))
73
+ logger.addHandler(handler)
74
+
75
+ # ----------------------------
76
+ # Configuraciones criptográficas (similar a SQLi)
77
+ # ----------------------------
78
+ MASTER_KEY_B64 = getattr(settings, "XSS_DEFENSE_MASTER_KEY", None)
79
+ if not MASTER_KEY_B64:
80
+ MASTER_KEY = os.urandom(32)
81
+ else:
82
+ try:
83
+ MASTER_KEY = base64.b64decode(MASTER_KEY_B64)
84
+ except Exception:
85
+ MASTER_KEY = MASTER_KEY_B64.encode() if isinstance(MASTER_KEY_B64, str) else MASTER_KEY_B64
86
+
87
+ AEAD_CHOICE = getattr(settings, "XSS_DEFENSE_AEAD", "AESGCM").upper() # AESGCM o CHACHA20
88
+ ARGON2_CONFIG = getattr(settings, "XSS_DEFENSE_ARGON2", {
89
+ "time_cost": 2,
90
+ "memory_cost": 65536,
91
+ "parallelism": 1,
92
+ "hash_len": 32,
93
+ "type": Argon2Type.ID,
94
+ })
95
+ HMAC_LABEL = b"xssdefense-hmac"
96
+ AEAD_LABEL = b"xssdefense-aead"
97
+ HASH_CHOICE = getattr(settings, "XSS_DEFENSE_HASH", "SHA256").upper() # SHA256 o SHA3
98
+
99
+ # ----------------------------
100
+ # Configuraciones de bloqueo y cache (similar a SQLi, pero con prefijo XSS_)
101
+ # ----------------------------
102
+ XSS_BLOCK_TIMEOUT = getattr(settings, "XSS_DEFENSE_BLOCK_SECONDS", 60 * 60)
103
+ XSS_COUNTER_WINDOW = getattr(settings, "XSS_DEFENSE_COUNTER_WINDOW", 60 * 5)
104
+ XSS_COUNTER_THRESHOLD = getattr(settings, "XSS_DEFENSE_COUNTER_THRESHOLD", 5)
105
+ XSS_CACHE_BLOCK_KEY_PREFIX = "xss_block:"
106
+ XSS_CACHE_COUNTER_KEY_PREFIX = "xss_count:"
107
+ XSS_DEFAULT_BACKOFF_LEVELS = getattr(settings, "XSS_DEFENSE_BACKOFF_LEVELS", [0, 60 * 15, 60 * 60, 60 * 60 * 6, 60 * 60 * 24, 60 * 60 * 24 * 7])
108
+ XSS_NORM_THRESHOLDS = {
109
+ "HIGH": getattr(settings, "XSS_DEFENSE_NORM_HIGH", 0.2),
110
+ "MEDIUM": getattr(settings, "XSS_DEFENSE_NORM_MED", 0.1),
111
+ "LOW": getattr(settings, "XSS_DEFENSE_NORM_LOW", 0.05),
112
+ }
113
+
114
+ # ----------------------------
115
+ # Patrones XSS robustos (igual que antes)
116
+ # ----------------------------
117
+ XSS_PATTERNS: List[Tuple[re.Pattern, str, float]] = [
118
+ (re.compile(r"<\s*script\b", re.I), "<script> directo", 0.95),
119
+ (re.compile(r"<\s*s\s*c\s*r\s*i\s*p\s*t\b", re.I), "<script> ofuscado", 0.90),
120
+ (re.compile(r"\b(eval|Function|setTimeout|setInterval|document\.write)\s*\(", re.I),
121
+ "Ejecución JS dinámica", 0.88),
122
+ (re.compile(r"\bjavascript\s*:", re.I), "URI javascript:", 0.85),
123
+ (re.compile(r"\bdata\s*:\s*text\/html\b", re.I), "URI data:text/html", 0.82),
124
+ (re.compile(r"\bvbscript\s*:", re.I), "URI vbscript:", 0.7),
125
+ (re.compile(r"<\s*(iframe|embed|object|svg|math|meta)\b", re.I), "Iframe/Embed/Object/SVG/Meta", 0.88),
126
+ (re.compile(r"<\s*img\b[^>]*\bonerror\b", re.I), "<img onerror>", 0.86),
127
+ (re.compile(r"<\s*svg\b[^>]*\bonload\b", re.I), "SVG onload/on*", 0.84),
128
+ (re.compile(r"\s+on[a-zA-Z]+\s*=", re.I), "Atributo evento on*", 0.80),
129
+ (re.compile(r"<\s*(a|img|body|div|span|form|input|button)\b[^>]*on[a-zA-Z]+\s*=", re.I),
130
+ "Elemento con evento on*", 0.82),
131
+ (re.compile(r"\binnerHTML\s*=\s*.*[<>\"']", re.I), "Asignación innerHTML", 0.85),
132
+ (re.compile(r"\bdocument\.getElementById\s*\(\s*.*\)\.innerHTML", re.I), "Manipulación DOM innerHTML", 0.80),
133
+ (re.compile(r"\bJSON\.parse\(|\beval\(\s*JSON", re.I), "JSON parse/eval inseguro", 0.75),
134
+ (re.compile(r"\bstyle\s*=\s*[\"'][^\"']*(expression\s*\(|url\s*\(\s*javascript:)", re.I), "CSS expression/url()", 0.66),
135
+ (re.compile(r"@import\s+url\s*\(", re.I), "CSS @import vector", 0.45),
136
+ (re.compile(r"<!\[CDATA\[|\/\/\s*<\s*!\s*\[CDATA\[", re.I), "CDATA/comentarios para evasión", 0.48),
137
+ (re.compile(r"&#x[0-9a-fA-F]+;|&#\d+;", re.I), "Entidades HTML/encoding", 0.70),
138
+ (re.compile(r"%3C\s*script|%3Cscript%3E", re.I), "Tags URL-encoded", 0.68),
139
+ ]
140
+
141
+ SENSITIVE_FIELDS = ["password", "csrfmiddlewaretoken", "token", "auth"]
142
+ SENSITIVE_DISCOUNT = 0.5
143
+
144
+ # ----------------------------
145
+ # Funciones criptográficas (derivación, AEAD, HMAC, hash)
146
+ # ----------------------------
147
+ def derive_key(label: bytes, context: bytes = b"") -> bytes:
148
+ salt = (label + context)[:16].ljust(16, b"\0")
149
+ try:
150
+ raw = hash_secret_raw(secret=MASTER_KEY if isinstance(MASTER_KEY, (bytes, bytearray)) else MASTER_KEY.encode(),
151
+ salt=salt,
152
+ time_cost=ARGON2_CONFIG["time_cost"],
153
+ memory_cost=ARGON2_CONFIG["memory_cost"],
154
+ parallelism=ARGON2_CONFIG["parallelism"],
155
+ hash_len=ARGON2_CONFIG["hash_len"],
156
+ type=ARGON2_CONFIG["type"])
157
+ hk = HKDF(algorithm=hashes.SHA256(), length=32, salt=salt, info=label + context)
158
+ return hk.derive(raw)
159
+ except Exception:
160
+ hk = HKDF(algorithm=hashes.SHA256(), length=32, salt=salt, info=label + context)
161
+ return hk.derive(MASTER_KEY if isinstance(MASTER_KEY, bytes) else MASTER_KEY.encode())
162
+
163
+ def aead_encrypt(plaintext: bytes, aad: bytes = b"", context: bytes = b"") -> Dict[str, bytes]:
164
+ key = derive_key(AEAD_LABEL, context)
165
+ if AEAD_CHOICE == "CHACHA20":
166
+ aead = ChaCha20Poly1305(key)
167
+ nonce = os.urandom(12)
168
+ ct = aead.encrypt(nonce, plaintext, aad)
169
+ return {"alg": "CHACHA20-POLY1305", "nonce": nonce, "ciphertext": ct}
170
+ else:
171
+ aead = AESGCM(key)
172
+ nonce = os.urandom(12)
173
+ ct = aead.encrypt(nonce, plaintext, aad)
174
+ return {"alg": "AES-GCM", "nonce": nonce, "ciphertext": ct}
175
+
176
+ def aead_decrypt(payload: Dict[str, bytes], aad: bytes = b"", context: bytes = b"") -> bytes:
177
+ key = derive_key(AEAD_LABEL, context)
178
+ alg = payload.get("alg", "AES-GCM")
179
+ nonce = payload.get("nonce")
180
+ ct = payload.get("ciphertext")
181
+ if not nonce or not ct:
182
+ raise ValueError("invalid payload for AEAD decrypt")
183
+ if alg.startswith("CHACHA20"):
184
+ aead = ChaCha20Poly1305(key)
185
+ return aead.decrypt(nonce, ct, aad)
186
+ else:
187
+ aead = AESGCM(key)
188
+ return aead.decrypt(nonce, ct, aad)
189
+
190
+ def compute_hmac(data: bytes, context: bytes = b"") -> bytes:
191
+ key = derive_key(HMAC_LABEL, context)
192
+ h = crypto_hmac.HMAC(key, hashes.SHA256())
193
+ h.update(data)
194
+ return h.finalize()
195
+
196
+ def verify_hmac(data: bytes, tag: bytes, context: bytes = b"") -> bool:
197
+ key = derive_key(HMAC_LABEL, context)
198
+ h = crypto_hmac.HMAC(key, hashes.SHA256())
199
+ h.update(data)
200
+ try:
201
+ h.verify(tag)
202
+ return True
203
+ except InvalidSignature:
204
+ return False
205
+
206
+ def compute_hash(data: bytes) -> str:
207
+ if HASH_CHOICE == "SHA3":
208
+ h = hashes.Hash(hashes.SHA3_256())
209
+ else:
210
+ h = hashes.Hash(hashes.SHA256())
211
+ h.update(data)
212
+ return base64.b64encode(h.finalize()).decode()
213
+
214
+ # ----------------------------
215
+ # Función de saturación
216
+ # ----------------------------
217
+ SATURATION_C = getattr(settings, "XSS_DEFENSE_SATURATION_C", 1.5)
218
+ SATURATION_ALPHA = getattr(settings, "XSS_DEFENSE_SATURATION_ALPHA", 2.0)
219
+
220
+ def saturate_score(raw_score: float) -> float:
221
+ try:
222
+ x = float(raw_score)
223
+ alpha = float(SATURATION_ALPHA)
224
+ c = float(SATURATION_C)
225
+ return 1.0 / (1.0 + math.exp(-alpha * (x - c)))
226
+ except Exception:
227
+ return 0.0
228
+
229
+ # ----------------------------
230
+ # IP robusta
231
+ # ----------------------------
232
+ def _is_valid_ip(ip: str) -> bool:
233
+ try:
234
+ import ipaddress
235
+ ipaddress.ip_address(ip)
236
+ return True
237
+ except Exception:
238
+ return False
239
+
240
+ def get_client_ip(request) -> str:
241
+ xff = request.META.get("HTTP_X_FORWARDED_FOR")
242
+ if xff:
243
+ parts = [p.strip() for p in xff.split(",") if p.strip()]
244
+ if parts:
245
+ return parts[0]
246
+ for h in ("HTTP_X_REAL_IP", "HTTP_CF_CONNECTING_IP", "HTTP_CLIENT_IP"):
247
+ v = request.META.get(h)
248
+ if v and _is_valid_ip(v):
249
+ return v
250
+ return request.META.get("REMOTE_ADDR") or ""
251
+
252
+ # ----------------------------
253
+ # Extraer payload
254
+ # ----------------------------
255
+ def extract_body_as_map(request) -> Dict[str, Any]:
256
+ try:
257
+ ct = request.META.get("CONTENT_TYPE", "")
258
+ if "application/json" in ct:
259
+ raw = request.body.decode("utf-8") or "{}"
260
+ try:
261
+ data = json.loads(raw)
262
+ if isinstance(data, dict):
263
+ return data
264
+ return {"raw": raw}
265
+ except Exception:
266
+ return {"raw": raw}
267
+ try:
268
+ post = request.POST.dict()
269
+ if post:
270
+ return post
271
+ except Exception:
272
+ pass
273
+ raw = request.body.decode("utf-8", errors="ignore")
274
+ if raw:
275
+ return {"raw": raw}
276
+ except Exception:
277
+ pass
278
+ return {}
279
+
280
+ # ----------------------------
281
+ # Detect XSS en valor
282
+ # ----------------------------
283
+ def detect_xss_in_value(value: str, is_sensitive: bool = False) -> Tuple[float, List[str], List[str]]:
284
+ if not value:
285
+ return 0.0, [], []
286
+ score_total = 0.0
287
+ descripcion = []
288
+ matches = []
289
+ value = value.lower().strip()
290
+ if _BLEACH_AVAILABLE:
291
+ cleaned = bleach.clean(value, strip=True)
292
+ if cleaned != value:
293
+ score_total += 0.5
294
+ descripcion.append("Contenido alterado por sanitización (bleach)")
295
+ for patt, msg, weight in XSS_PATTERNS:
296
+ occ = len(patt.findall(value))
297
+ if occ > 0:
298
+ added = sum(weight * (0.5 ** i) for i in range(occ))
299
+ if is_sensitive:
300
+ added *= SENSITIVE_DISCOUNT
301
+ score_total += added
302
+ descripcion.append(msg)
303
+ matches.append(patt.pattern)
304
+ return round(score_total, 3), descripcion, matches
305
+
306
+ # ----------------------------
307
+ # Conversión a probabilidad
308
+ # ----------------------------
309
+ def weight_to_prob(w: float) -> float:
310
+ try:
311
+ q = 1.0 - math.exp(-max(w, 0.0))
312
+ return min(max(q, 0.0), 0.999999)
313
+ except Exception:
314
+ return min(max(w, 0.0), 0.999999)
315
+
316
+ def combine_probs(qs: List[float]) -> float:
317
+ prod = 1.0
318
+ for q in qs:
319
+ prod *= (1.0 - q)
320
+ return 1.0 - prod
321
+
322
+ # ----------------------------
323
+ # Firmar/cifrar cookies (integración cripto)
324
+ # ----------------------------
325
+ def sign_cookie_value(value: str, context: bytes = b"") -> str:
326
+ """Firma un valor de cookie con HMAC-SHA256 para evitar alteraciones por XSS."""
327
+ data = value.encode("utf-8")
328
+ tag = compute_hmac(data, context)
329
+ return f"{value}.{base64.b64encode(tag).decode()}"
330
+
331
+ def verify_cookie_signature(signed_value: str, context: bytes = b"") -> str:
332
+ """Verifica la firma de una cookie y retorna el valor original si es válido."""
333
+ try:
334
+ value, tag_b64 = signed_value.rsplit(".", 1)
335
+ tag = base64.b64decode(tag_b64)
336
+ if verify_hmac(value.encode("utf-8"), tag, context):
337
+ return value
338
+ else:
339
+ raise ValueError("Invalid signature")
340
+ except Exception:
341
+ raise ValueError("Invalid signed cookie")
342
+
343
+ def encrypt_cookie_value(value: str, context: bytes = b"") -> str:
344
+ """Cifra un valor de cookie sensible con AEAD."""
345
+ enc = aead_encrypt(value.encode("utf-8"), context=context)
346
+ return base64.b64encode(json.dumps(enc).encode()).decode()
347
+
348
+ def decrypt_cookie_value(encrypted_value: str, context: bytes = b"") -> str:
349
+ """Descifra un valor de cookie."""
350
+ try:
351
+ enc = json.loads(base64.b64decode(encrypted_value))
352
+ plaintext = aead_decrypt(enc, context=context)
353
+ return plaintext.decode("utf-8")
354
+ except Exception:
355
+ raise ValueError("Invalid encrypted cookie")
356
+
357
+ # ----------------------------
358
+ # Funciones de cache para bloqueo (similar a SQLi, pero con prefijo XSS_)
359
+ # ----------------------------
360
+ def cache_block_ip_with_backoff(ip: str):
361
+ if not ip:
362
+ return 0, 0
363
+ level_key = f"{XSS_CACHE_BLOCK_KEY_PREFIX}{ip}:level"
364
+ level = cache.get(level_key, 0) or 0
365
+ level = int(level) + 1
366
+ cache.set(level_key, level, timeout=60 * 60 * 24 * 7)
367
+ durations = XSS_DEFAULT_BACKOFF_LEVELS
368
+ idx = min(level, len(durations) - 1)
369
+ timeout = durations[idx]
370
+ cache.set(f"{XSS_CACHE_BLOCK_KEY_PREFIX}{ip}", True, timeout=timeout)
371
+ return level, timeout
372
+
373
+ def is_ip_blocked(ip: str) -> bool:
374
+ if not ip:
375
+ return False
376
+ return bool(cache.get(f"{XSS_CACHE_BLOCK_KEY_PREFIX}{ip}"))
377
+
378
+ def incr_ip_counter(ip: str) -> int:
379
+ if not ip:
380
+ return 0
381
+ key = f"{XSS_CACHE_COUNTER_KEY_PREFIX}{ip}"
382
+ current = cache.get(key, 0)
383
+ try:
384
+ current = int(current)
385
+ except Exception:
386
+ current = 0
387
+ current += 1
388
+ cache.set(key, current, timeout=XSS_COUNTER_WINDOW)
389
+ return current
390
+
391
+ # ----------------------------
392
+ # Registro cifrado de eventos (similar a SQLi para asegurar registro)
393
+ # ----------------------------
394
+ def record_xss_event(event: dict) -> None:
395
+ try:
396
+ ts = int(time.time())
397
+ # cifrar payload si existe
398
+ if "payload" in event and event["payload"]:
399
+ try:
400
+ ctx = f"{event.get('ip','')}-{ts}".encode()
401
+ enc = aead_encrypt(json.dumps(event["payload"], ensure_ascii=False).encode("utf-8"), context=ctx)
402
+ htag = compute_hmac(enc["ciphertext"], context=ctx)
403
+ event["_payload_encrypted"] = {
404
+ "alg": enc["alg"],
405
+ "nonce": base64.b64encode(enc["nonce"]).decode(),
406
+ "ciphertext": base64.b64encode(enc["ciphertext"]).decode(),
407
+ "hmac": base64.b64encode(htag).decode(),
408
+ }
409
+ del event["payload"] # no almacenar plaintext
410
+ except Exception:
411
+ # si falla, simplemente no incluimos payload
412
+ event.pop("payload", None)
413
+ key = f"xss_event:{ts}:{event.get('ip', '')}"
414
+ cache.set(key, json.dumps(event, ensure_ascii=False), timeout=60 * 60 * 24)
415
+ except Exception:
416
+ logger.exception("record_xss_event failed")
417
+
418
+ # ----------------------------
419
+ # Middleware XSS con cripto integrado (ajustado para registro similar a SQLi y chequeo de bloqueo inicial)
420
+ # ----------------------------
421
+ class XSSDefenseCryptoMiddleware(MiddlewareMixin):
422
+ def process_request(self, request):
423
+ client_ip = get_client_ip(request)
424
+
425
+ # Chequear bloqueo inicial (similar a SQLi)
426
+ if is_ip_blocked(client_ip):
427
+ warning_message = (
428
+ "Acceso denegado. Su dirección IP y actividades han sido registradas y monitoreadas. "
429
+ "Continuar con estos intentos podría resultar en exposición pública, bloqueos permanentes o acciones legales. "
430
+ "Recomendamos detenerse inmediatamente para evitar riesgos mayores."
431
+ )
432
+ logger.warning(f"[XSSBlock:Persistent] IP={client_ip} - Intento persistente de acceso bloqueado. Mensaje enviado.")
433
+ return HttpResponseForbidden(warning_message)
434
+
435
+ trusted_ips: List[str] = getattr(settings, "XSS_DEFENSE_TRUSTED_IPS", [])
436
+ if client_ip in trusted_ips:
437
+ return None
438
+ excluded_paths: List[str] = getattr(settings, "XSS_DEFENSE_EXCLUDED_PATHS", [])
439
+ if any(request.path.startswith(p) for p in excluded_paths):
440
+ return None
441
+
442
+ data = extract_body_as_map(request)
443
+ qs = request.META.get("QUERY_STRING", "")
444
+ if qs:
445
+ data["_query_string"] = qs
446
+ if not data:
447
+ return None
448
+
449
+ total_score = 0.0
450
+ all_descriptions: List[str] = []
451
+ global_prob_list: List[float] = []
452
+ payload_summary = []
453
+
454
+ if isinstance(data, dict):
455
+ for key, value in data.items():
456
+ is_sensitive = key.lower() in SENSITIVE_FIELDS
457
+ vtext = value
458
+ if isinstance(value, (dict, list)):
459
+ try:
460
+ vtext = json.dumps(value, ensure_ascii=False)
461
+ except Exception:
462
+ vtext = str(value)
463
+ else:
464
+ vtext = str(value or "")
465
+ s, descs, matches = detect_xss_in_value(vtext, is_sensitive)
466
+ total_score += s
467
+ all_descriptions.extend(descs)
468
+ for m in matches:
469
+ q = weight_to_prob(s)
470
+ global_prob_list.append(q)
471
+ if s > 0:
472
+ payload_summary.append({"field": key, "snippet": vtext[:300], "sensitive": is_sensitive})
473
+ else:
474
+ raw = str(data)
475
+ s, descs, matches = detect_xss_in_value(raw)
476
+ total_score += s
477
+ all_descriptions.extend(descs)
478
+ for m in matches:
479
+ q = weight_to_prob(s)
480
+ global_prob_list.append(q)
481
+ if s > 0:
482
+ payload_summary.append({"field": "raw", "snippet": raw[:500], "sensitive": False})
483
+
484
+ if total_score == 0:
485
+ return None
486
+
487
+ p_attack = combine_probs(global_prob_list) if global_prob_list else 0.0
488
+ s_norm = saturate_score(total_score)
489
+ url = request.build_absolute_uri()
490
+ payload_for_request = json.dumps(payload_summary, ensure_ascii=False)[:2000]
491
+
492
+ logger.warning(
493
+ "[XSSDetect] IP=%s URL=%s ScoreRaw=%.3f ScoreNorm=%.3f Prob=%.3f Desc=%s",
494
+ client_ip, url, total_score, s_norm, p_attack, all_descriptions
495
+ )
496
+
497
+ # Registrar el evento de manera cifrada para auditoría (similar a SQLi)
498
+ try:
499
+ record_xss_event({
500
+ "ts": int(time.time()),
501
+ "ip": client_ip,
502
+ "score_raw": total_score,
503
+ "score_norm": s_norm,
504
+ "prob": p_attack,
505
+ "desc": all_descriptions,
506
+ "url": url,
507
+ "payload": payload_summary, # se cifrará en record_xss_event
508
+ })
509
+ except Exception:
510
+ logger.exception("failed to record XSS event")
511
+
512
+ # Asignar información de ataque al request para uso posterior (e.g., en vistas o middleware de respuesta)
513
+ request.xss_attack_info = {
514
+ "ip": client_ip,
515
+ "tipos": ["XSS"],
516
+ "descripcion": all_descriptions,
517
+ "payload": payload_for_request,
518
+ "score_raw": total_score,
519
+ "score_norm": s_norm,
520
+ "prob": p_attack,
521
+ "url": url,
522
+ }
523
+
524
+ # Políticas de bloqueo (similar a SQLi, pero ajustadas para XSS)
525
+ if s_norm >= XSS_NORM_THRESHOLDS["HIGH"]:
526
+ level, timeout = cache_block_ip_with_backoff(client_ip)
527
+ logger.error(f"[XSSBlock] IP={client_ip} ScoreRaw={total_score:.3f} ScoreNorm={s_norm:.3f} URL={url}")
528
+ request.xss_attack_info.update({"blocked": True, "action": "block", "block_timeout": timeout, "block_level": level})
529
+ # Setear flag para bloqueo
530
+ request.xss_block = True
531
+ request.xss_block_response = HttpResponseForbidden("Request blocked by XSS defense")
532
+ return None
533
+ elif s_norm >= XSS_NORM_THRESHOLDS["MEDIUM"]:
534
+ logger.warning(f"[XSSAlert] IP={client_ip} ScoreRaw={total_score:.3f} ScoreNorm={s_norm:.3f} - applying counter/challenge")
535
+ count = incr_ip_counter(client_ip)
536
+ request.xss_attack_info.update({"blocked": False, "action": "alert", "counter": count})
537
+ if count >= XSS_COUNTER_THRESHOLD:
538
+ level, timeout = cache_block_ip_with_backoff(client_ip)
539
+ cache.set(f"{XSS_CACHE_COUNTER_KEY_PREFIX}{client_ip}", 0, timeout=XSS_COUNTER_WINDOW)
540
+ logger.error(f"[XSSAutoBlock] IP={client_ip} reached counter={count} -> blocking for {timeout}s")
541
+ request.xss_attack_info.update({"blocked": True, "action": "auto_block", "block_timeout": timeout, "block_level": level})
542
+ # Setear flag para bloqueo
543
+ request.xss_block = True
544
+ request.xss_block_response = HttpResponseForbidden("Request blocked by XSS defense (auto block)")
545
+ return None
546
+ if getattr(settings, "XSS_DEFENSE_USE_CHALLENGE", False):
547
+ # Setear flag para challenge
548
+ request.xss_challenge = True
549
+ request.xss_challenge_response = HttpResponse("Challenge required", status=403)
550
+ request.xss_challenge_response["X-XSS-Challenge"] = "captcha"
551
+ return None
552
+ return None
553
+ elif s_norm >= XSS_NORM_THRESHOLDS["LOW"]:
554
+ logger.info(f"[XSSMonitor] IP={client_ip} ScoreRaw={total_score:.3f} ScoreNorm={s_norm:.3f} - monitored")
555
+ request.xss_attack_info.update({"blocked": False, "action": "monitor"})
556
+ return None
557
+ return None
558
+
559
+
560
+ # =====================================================
561
+ # === INFORMACIÓN EXTRA ===
562
+ # =====================================================
563
+ """
564
+ Algoritmos relacionados:
565
+ - Se recomienda almacenar los payloads XSS cifrados con AES-GCM
566
+ para confidencialidad e integridad.
567
+
568
+ Contribución a fórmula de amenaza S:
569
+ S_xss = w_xss * detecciones_xss
570
+ Ejemplo: S_xss = 0.3 * 2 = 0.6
571
+
572
+ Notas sobre implementación de algoritmos de seguridad:
573
+ - HMAC-SHA256: Usado para firmar tokens/cookies (sign_cookie_value, verify_cookie_signature).
574
+ - SHA-256/SHA-3: Usado para hashes de contenido (compute_hash).
575
+ - AES-GCM/ChaCha20-Poly1305: Usado para cifrar cookies sensibles (encrypt_cookie_value, decrypt_cookie_value).
576
+ - HKDF: Usado para derivar claves seguras (derive_key).
577
+ - Argon2id: Usado para derivar claves con resistencia a ataques de fuerza bruta (derive_key).
578
+ - TLS 1.3: Recomendado para configurar en el servidor web (e.g., Nginx/Apache) para proteger datos en tránsito.
579
+ Ejemplo configuración Nginx:
580
+ server {
581
+ listen 443 ssl http2;
582
+ ssl_protocols TLSv1.3;
583
+ ssl_ciphers ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-CHACHA20-POLY1305;
584
+ # ... otras configuraciones SSL
585
+ }
586
+ Esto asegura que las comunicaciones sean seguras y eviten que XSS robe datos en tránsito.
587
+
588
+ Para usar en producción:
589
+ - Configura XSS_DEFENSE_MASTER_KEY en settings.py como base64 de una clave segura de 32 bytes.
590
+ - Ajusta umbrales y configuraciones según necesidades.
591
+ - Integra con CSP (Content Security Policy) en headers de respuesta para mayor protección.
592
+ """
593
+
594
+ # =====================================================
595
+ # === INFORMACIÓN EXTRA ===
596
+ # =====================================================
597
+ """
598
+ Algoritmos relacionados:
599
+ - Se recomienda almacenar los payloads XSS cifrados con AES-GCM
600
+ para confidencialidad e integridad.
601
+
602
+ Contribución a fórmula de amenaza S:
603
+ S_xss = w_xss * detecciones_xss
604
+ Ejemplo: S_xss = 0.3 * 2 = 0.6
605
+
606
+ Notas sobre implementación de algoritmos de seguridad:
607
+ - HMAC-SHA256: Usado para firmar tokens/cookies (sign_cookie_value, verify_cookie_signature).
608
+ - SHA-256/SHA-3: Usado para hashes de contenido (compute_hash).
609
+ - AES-GCM/ChaCha20-Poly1305: Usado para cifrar cookies sensibles (encrypt_cookie_value, decrypt_cookie_value).
610
+ - HKDF: Usado para derivar claves seguras (derive_key).
611
+ - Argon2id: Usado para derivar claves con resistencia a ataques de fuerza bruta (derive_key).
612
+ - TLS 1.3: Recomendado para configurar en el servidor web (e.g., Nginx/Apache) para proteger datos en tránsito.
613
+ Ejemplo configuración Nginx:
614
+ server {
615
+ listen 443 ssl http2;
616
+ ssl_protocols TLSv1.3;
617
+ ssl_ciphers ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-CHACHA20-POLY1305;
618
+ # ... otras configuraciones SSL
619
+ }
620
+ Esto asegura que las comunicaciones sean seguras y eviten que XSS robe datos en tránsito.
621
+
622
+ Para usar en producción:
623
+ - Configura XSS_DEFENSE_MASTER_KEY en settings.py como base64 de una clave segura de 32 bytes.
624
+ - Ajusta umbrales y configuraciones según necesidades.
625
+ - Integra con CSP (Content Security Policy) en headers de respuesta para mayor protección.
626
+ """
@@ -0,0 +1,13 @@
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}
@@ -0,0 +1,7 @@
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
@@ -0,0 +1,10 @@
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
@@ -0,0 +1,15 @@
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
@@ -0,0 +1,7 @@
1
+ """
2
+ Funciones auxiliares generales
3
+ """
4
+ import os
5
+
6
+ def generar_id_unico() -> str:
7
+ return os.urandom(16).hex()