GuardianUnivalle-Benito-Yucra 0.1.68__tar.gz → 0.1.70__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 (24) hide show
  1. {guardianunivalle_benito_yucra-0.1.68 → guardianunivalle_benito_yucra-0.1.70}/GuardianUnivalle_Benito_Yucra/detectores/detector_csrf.py +111 -20
  2. {guardianunivalle_benito_yucra-0.1.68 → guardianunivalle_benito_yucra-0.1.70}/GuardianUnivalle_Benito_Yucra/detectores/detector_sql.py +94 -36
  3. {guardianunivalle_benito_yucra-0.1.68 → guardianunivalle_benito_yucra-0.1.70}/GuardianUnivalle_Benito_Yucra/detectores/detector_xss.py +37 -35
  4. {guardianunivalle_benito_yucra-0.1.68 → guardianunivalle_benito_yucra-0.1.70}/GuardianUnivalle_Benito_Yucra.egg-info/PKG-INFO +3 -5
  5. {guardianunivalle_benito_yucra-0.1.68 → guardianunivalle_benito_yucra-0.1.70}/PKG-INFO +3 -5
  6. {guardianunivalle_benito_yucra-0.1.68 → guardianunivalle_benito_yucra-0.1.70}/README.md +2 -4
  7. {guardianunivalle_benito_yucra-0.1.68 → guardianunivalle_benito_yucra-0.1.70}/pyproject.toml +1 -1
  8. {guardianunivalle_benito_yucra-0.1.68 → guardianunivalle_benito_yucra-0.1.70}/GuardianUnivalle_Benito_Yucra/__init__.py +0 -0
  9. {guardianunivalle_benito_yucra-0.1.68 → guardianunivalle_benito_yucra-0.1.70}/GuardianUnivalle_Benito_Yucra/auditoria/registro_auditoria.py +0 -0
  10. {guardianunivalle_benito_yucra-0.1.68 → guardianunivalle_benito_yucra-0.1.70}/GuardianUnivalle_Benito_Yucra/criptografia/cifrado_aead.py +0 -0
  11. {guardianunivalle_benito_yucra-0.1.68 → guardianunivalle_benito_yucra-0.1.70}/GuardianUnivalle_Benito_Yucra/criptografia/intercambio_claves.py +0 -0
  12. {guardianunivalle_benito_yucra-0.1.68 → guardianunivalle_benito_yucra-0.1.70}/GuardianUnivalle_Benito_Yucra/criptografia/kdf.py +0 -0
  13. {guardianunivalle_benito_yucra-0.1.68 → guardianunivalle_benito_yucra-0.1.70}/GuardianUnivalle_Benito_Yucra/detectores/detector_dos.py +0 -0
  14. {guardianunivalle_benito_yucra-0.1.68 → guardianunivalle_benito_yucra-0.1.70}/GuardianUnivalle_Benito_Yucra/middleware_web/middleware_web.py +0 -0
  15. {guardianunivalle_benito_yucra-0.1.68 → guardianunivalle_benito_yucra-0.1.70}/GuardianUnivalle_Benito_Yucra/mitigacion/limitador_peticion.py +0 -0
  16. {guardianunivalle_benito_yucra-0.1.68 → guardianunivalle_benito_yucra-0.1.70}/GuardianUnivalle_Benito_Yucra/mitigacion/lista_bloqueo.py +0 -0
  17. {guardianunivalle_benito_yucra-0.1.68 → guardianunivalle_benito_yucra-0.1.70}/GuardianUnivalle_Benito_Yucra/puntuacion/puntuacion_amenaza.py +0 -0
  18. {guardianunivalle_benito_yucra-0.1.68 → guardianunivalle_benito_yucra-0.1.70}/GuardianUnivalle_Benito_Yucra/utilidades.py +0 -0
  19. {guardianunivalle_benito_yucra-0.1.68 → guardianunivalle_benito_yucra-0.1.70}/GuardianUnivalle_Benito_Yucra.egg-info/SOURCES.txt +0 -0
  20. {guardianunivalle_benito_yucra-0.1.68 → guardianunivalle_benito_yucra-0.1.70}/GuardianUnivalle_Benito_Yucra.egg-info/dependency_links.txt +0 -0
  21. {guardianunivalle_benito_yucra-0.1.68 → guardianunivalle_benito_yucra-0.1.70}/GuardianUnivalle_Benito_Yucra.egg-info/requires.txt +0 -0
  22. {guardianunivalle_benito_yucra-0.1.68 → guardianunivalle_benito_yucra-0.1.70}/GuardianUnivalle_Benito_Yucra.egg-info/top_level.txt +0 -0
  23. {guardianunivalle_benito_yucra-0.1.68 → guardianunivalle_benito_yucra-0.1.70}/LICENSE +0 -0
  24. {guardianunivalle_benito_yucra-0.1.68 → guardianunivalle_benito_yucra-0.1.70}/setup.cfg +0 -0
@@ -1,10 +1,12 @@
1
- # CSRF defense (versión reforzada)
1
+ # csrf_defense.py
2
+ # GuardianUnivalle_Benito_Yucra/detectores/csrf_defense.py
3
+
2
4
  from __future__ import annotations
3
5
  import secrets
4
6
  import logging
5
7
  import re
6
8
  import json
7
- from typing import List
9
+ from typing import List, Dict, Any
8
10
  from urllib.parse import urlparse
9
11
  from django.conf import settings
10
12
  from django.utils.deprecation import MiddlewareMixin
@@ -21,21 +23,42 @@ CSRF_HEADER_NAMES = ("HTTP_X_CSRFTOKEN", "HTTP_X_CSRF_TOKEN")
21
23
  CSRF_COOKIE_NAME = getattr(settings, "CSRF_COOKIE_NAME", "csrftoken")
22
24
  POST_FIELD_NAME = "csrfmiddlewaretoken"
23
25
 
24
- # Patrón de Content-Type sospechoso
26
+ # Patrón de Content-Type sospechoso - EXPANDIDO
25
27
  SUSPICIOUS_CT_PATTERNS = [
26
28
  re.compile(r"text/plain", re.I),
27
29
  re.compile(r"application/x-www-form-urlencoded", re.I),
28
30
  re.compile(r"multipart/form-data", re.I),
31
+ re.compile(r"application/json", re.I),
32
+ re.compile(r"text/html", re.I), # Agregado para HTML CSRF
29
33
  ]
30
34
 
31
- # Parámetros sensibles típicos de CSRF
35
+ # Parámetros sensibles típicos de CSRF - EXPANDIDO
32
36
  SENSITIVE_PARAMS = [
33
- "password", "csrfmiddlewaretoken", "token", "amount", "transfer", "delete", "update"
37
+ "password", "csrfmiddlewaretoken", "token", "amount", "transfer", "delete", "update", "action", "email", "username"
34
38
  ]
35
39
 
40
+ # Campos sensibles: ANALIZAMOS COMPLETAMENTE SIN DESCUENTO PARA ROBUSTEZ MÁXIMA
41
+ SENSITIVE_FIELDS = ["password", "csrfmiddlewaretoken", "token", "auth", "email", "username"]
42
+
36
43
  CSRF_DEFENSE_MIN_SIGNALS = getattr(settings, "CSRF_DEFENSE_MIN_SIGNALS", 1)
37
44
  CSRF_DEFENSE_EXCLUDED_API_PREFIXES = getattr(settings, "CSRF_DEFENSE_EXCLUDED_API_PREFIXES", ["/api/"])
38
45
 
46
+ # PATRONES EXPANDIDOS PARA ANÁLISIS DE PAYLOAD EN TODOS LOS CAMPOS (SIN DESCUENTO)
47
+ CSRF_PAYLOAD_PATTERNS = [
48
+ (re.compile(r"<script[^>]*>.*?</script>", re.I | re.S), "Script tag en payload", 0.9),
49
+ (re.compile(r"javascript\s*:", re.I), "URI javascript: en payload", 0.8),
50
+ (re.compile(r"http[s]?://[^\s]+", re.I), "URL externa en payload", 0.7),
51
+ (re.compile(r"eval\s*\(", re.I), "eval() en payload", 1.0),
52
+ (re.compile(r"document\.cookie", re.I), "Acceso a cookie en payload", 0.9),
53
+ (re.compile(r"innerHTML\s*=", re.I), "Manipulación DOM innerHTML", 0.8),
54
+ (re.compile(r"XMLHttpRequest", re.I), "XHR en payload", 0.7),
55
+ (re.compile(r"fetch\s*\(", re.I), "fetch() en payload", 0.7),
56
+ (re.compile(r"&#x[0-9a-fA-F]+;", re.I), "Entidades HTML en payload", 0.6),
57
+ (re.compile(r"%3Cscript", re.I), "Script URL-encoded en payload", 0.8),
58
+ (re.compile(r"on\w+\s*=", re.I), "Eventos on* en payload", 0.7),
59
+ (re.compile(r"alert\s*\(", re.I), "alert() en payload (prueba)", 0.5),
60
+ ]
61
+
39
62
  def get_client_ip(request):
40
63
  x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR")
41
64
  if x_forwarded_for:
@@ -62,15 +85,15 @@ def origin_matches_host(request) -> bool:
62
85
  host = host_header.split(":")[0]
63
86
  origin = request.META.get("HTTP_ORIGIN", "")
64
87
  referer = request.META.get("HTTP_REFERER", "")
65
- origin_host = host_from_header(origin)
66
- referer_host = host_from_header(referer)
67
88
  # Bloquear obvious javascript: referers
68
89
  if any(re.search(r"(javascript:|<script|data:text/html)", h or "", re.I) for h in [origin, referer]):
69
90
  return False
70
- if origin_host and origin_host == host:
71
- return True
72
- if referer_host and referer_host == host:
73
- return True
91
+ if origin_host := host_from_header(origin):
92
+ if origin_host == host:
93
+ return True
94
+ if referer_host := host_from_header(referer):
95
+ if referer_host == host:
96
+ return True
74
97
  if not origin and not referer:
75
98
  return True
76
99
  return False
@@ -118,6 +141,34 @@ def extract_parameters(request) -> List[str]:
118
141
  pass
119
142
  return params
120
143
 
144
+ # FUNCIÓN ROBUSTA: Analizar payload en TODOS los campos (incluyendo sensibles sin descuento)
145
+ def analyze_payload(value: str) -> float:
146
+ score = 0.0
147
+ for patt, desc, weight in CSRF_PAYLOAD_PATTERNS:
148
+ if patt.search(value):
149
+ score += weight # Score full, sin descuento
150
+ return round(score, 3)
151
+
152
+ # NUEVA FUNCIÓN: Extraer y analizar query string
153
+ def analyze_query_string(request) -> float:
154
+ qs = request.META.get("QUERY_STRING", "")
155
+ if qs:
156
+ return analyze_payload(qs)
157
+ return 0.0
158
+
159
+ # NUEVA FUNCIÓN: Analizar headers adicionales
160
+ def analyze_headers(request) -> List[str]:
161
+ issues = []
162
+ ua = request.META.get("HTTP_USER_AGENT", "")
163
+ if re.search(r"(script|<|eval|bot|crawler)", ua, re.I):
164
+ issues.append("User-Agent sospechoso (posible automatización/bot)")
165
+
166
+ accept_lang = request.META.get("HTTP_ACCEPT_LANGUAGE", "")
167
+ if not accept_lang or len(accept_lang) < 2:
168
+ issues.append("Accept-Language ausente o muy corto (posible bot)")
169
+
170
+ return issues
171
+
121
172
  class CSRFDefenseMiddleware(MiddlewareMixin):
122
173
  def process_request(self, request):
123
174
  # Excluir APIs JSON si se configuró así
@@ -136,7 +187,7 @@ class CSRFDefenseMiddleware(MiddlewareMixin):
136
187
  return None
137
188
 
138
189
  method = (request.method or "").upper()
139
- if method not in STATE_CHANGING_METHODS:
190
+ if method not in STATE_CHANGING_METHODS: # CORREGIDO: Agregado "in"
140
191
  return None
141
192
 
142
193
  descripcion: List[str] = []
@@ -174,33 +225,73 @@ class CSRFDefenseMiddleware(MiddlewareMixin):
174
225
  if origin and host_from_header(origin) != (request.META.get("HTTP_HOST") or "").split(":")[0]:
175
226
  descripcion.append("JSON POST desde origen externo (posible CSRF)")
176
227
 
228
+ # 7) Análisis ROBUSTO de payload en TODOS los campos (sin descuento)
229
+ payload_score = 0.0
230
+ payload_summary: List[Dict[str, Any]] = []
231
+ try:
232
+ # Analizar POST
233
+ if hasattr(request, "POST"):
234
+ for key, value in request.POST.items():
235
+ if isinstance(value, str):
236
+ score = analyze_payload(value)
237
+ payload_score += score
238
+ if score > 0:
239
+ payload_summary.append({"field": key, "snippet": value[:300], "score": score})
240
+ # Analizar JSON
241
+ if "application/json" in content_type:
242
+ data = json.loads(request.body.decode("utf-8") or "{}")
243
+ for key, value in data.items():
244
+ if isinstance(value, str):
245
+ score = analyze_payload(value)
246
+ payload_score += score
247
+ if score > 0:
248
+ payload_summary.append({"field": key, "snippet": value[:300], "score": score})
249
+ except Exception as e:
250
+ logger.debug(f"Error analizando payload: {e}")
251
+
252
+ if payload_score > 0:
253
+ descripcion.append(f"Payload sospechoso detectado (score total: {payload_score})")
254
+
255
+ # 8) Análisis de query string
256
+ qs_score = analyze_query_string(request)
257
+ if qs_score > 0:
258
+ descripcion.append(f"Query string sospechosa (score: {qs_score})")
259
+ payload_score += qs_score
260
+
261
+ # 9) Análisis de headers adicionales
262
+ header_issues = analyze_headers(request)
263
+ descripcion.extend(header_issues)
264
+
177
265
  # Señales >= umbral => marcar
178
- if descripcion and len(descripcion) >= CSRF_DEFENSE_MIN_SIGNALS:
266
+ total_signals = len(descripcion)
267
+ if descripcion and total_signals >= CSRF_DEFENSE_MIN_SIGNALS:
179
268
  w_csrf = getattr(settings, "CSRF_DEFENSE_WEIGHT", 0.2)
180
- s_csrf = w_csrf * len(descripcion)
269
+ s_csrf = w_csrf * total_signals + payload_score # Score full sin descuento
181
270
  request.csrf_attack_info = {
182
271
  "ip": client_ip,
183
272
  "tipos": ["CSRF"],
184
273
  "descripcion": descripcion,
185
- "payload": payload,
274
+ "payload": json.dumps(payload_summary, ensure_ascii=False)[:1000],
186
275
  "score": s_csrf,
187
276
  }
188
277
  logger.warning(
189
- "CSRF detectado desde IP %s: %s ; path=%s ; Content-Type=%s ; score=%.2f",
278
+ "CSRF detectado desde IP %s: %s ; path=%s ; Content-Type=%s ; score=%.2f (Ultra-Robust: nada ignorado)",
190
279
  client_ip, descripcion, request.path, content_type, s_csrf
191
280
  )
192
281
  else:
193
282
  if descripcion:
194
- logger.debug(f"[CSRFDefense] low-signals ({len(descripcion)}) not marking: {descripcion}")
283
+ logger.debug(f"[CSRFDefense] low-signals ({total_signals}) not marking: {descripcion}")
195
284
 
196
285
  return None
197
286
 
198
287
  """
199
- CSRF Defense Middleware - Reforzado
200
- ===================================
288
+ CSRF Defense Middleware - Ultra-Robusto (Nada Ignorado)
289
+ =======================================================
201
290
  - Detecta múltiples categorías de CSRF: clásico, login, logout, password change, file/action, JSON API.
202
- - Escanea payloads POST, GET y JSON.
291
+ - Escanea payloads POST, GET, JSON, query string y headers, incluyendo TODOS los campos (sensibles y no) con score full (sin descuento).
203
292
  - Detecta parámetros sensibles enviados en GET o JSON desde origen externo.
293
+ - Análisis adicional de User-Agent, Accept-Language, y payloads con patrones expandidos.
204
294
  - Scoring configurable y logging detallado.
205
295
  - Fácil integración con auditoría XSS/SQLi.
296
+ - Máxima robustez: no ignora nada para detección óptima.
206
297
  """
@@ -8,7 +8,7 @@ from django.utils.deprecation import MiddlewareMixin
8
8
  from django.conf import settings
9
9
  import urllib.parse
10
10
  import html
11
- from typing import List, Tuple, Dict
11
+ from typing import List, Tuple, Dict, Any
12
12
 
13
13
  # ----------------------------
14
14
  # Configuración del logger
@@ -108,9 +108,23 @@ SQL_PATTERNS: List[Tuple[re.Pattern, str, float]] = [
108
108
  # ------------------ Catch‑all aggressive patterns (usar con cuidado) ------------------
109
109
  (re.compile(r"(['\"]).*?;\s*(drop|truncate|delete|update|insert)\b", re.I | re.S), "Cadena con terminador y DDL/DML (potencial ataque)", 0.9),
110
110
  (re.compile(r"\b(or)\b\s+1\s*=\s*1\b", re.I), "OR 1=1 tautology", 0.85),
111
+
112
+ # ---------- NUEVOS PATRONES PARA ROBUSTEZ ----------
113
+ (re.compile(r"\b(select\s+.*\s+from\s+.*\s+where\s+.*\s+in\s*\()", re.I | re.S), "Subquery anidada (IN subquery)", 0.75),
114
+ (re.compile(r"\bcase\s+when\s+.*\s+then\s+.*\s+else\b", re.I), "CASE WHEN (blind boolean)", 0.78),
115
+ (re.compile(r"/\*\!.+\*\//", re.I), "Comentarios condicionales MySQL (/*!...*/)", 0.7),
116
+ (re.compile(r"\bif\s*\(\s*.*\s*,\s*.*\s*,\s*.*\s*\)", re.I), "IF() MySQL (conditional)", 0.72),
117
+ (re.compile(r"\bgroup_concat\s*\(", re.I), "GROUP_CONCAT() (exfiltración en error)", 0.8),
111
118
  ]
112
119
 
113
- IGNORED_FIELDS = ["password", "csrfmiddlewaretoken", "token", "auth"]
120
+ # Campos sensibles: ANALIZAMOS COMPLETAMENTE SIN DESCUENTO PARA ROBUSTEZ MÁXIMA
121
+ SENSITIVE_FIELDS = ["password", "csrfmiddlewaretoken", "token", "auth", "email", "username"]
122
+
123
+ DEFAULT_THRESHOLDS = {
124
+ "HIGH": 1.8,
125
+ "MEDIUM": 1.0,
126
+ "LOW": 0.5,
127
+ }
114
128
 
115
129
  # ----------------------------
116
130
  # Obtener IP real del cliente
@@ -124,26 +138,40 @@ def get_client_ip(request):
124
138
  return request.META.get("REMOTE_ADDR", "")
125
139
 
126
140
  # ----------------------------
127
- # Extraer payload de la solicitud
141
+ # Extraer payload de la solicitud - MEJORADO PARA ANÁLISIS POR CAMPO
128
142
  # ----------------------------
129
- def extract_payload(request):
130
- parts = []
143
+ def extract_payload_as_map(request) -> Dict[str, Any]:
144
+ """
145
+ Extrae un diccionario con los datos a analizar:
146
+ - Si JSON: devuelve el dict JSON.
147
+ - Si form-data: devuelve request.POST.dict()
148
+ - Si otro: devuelve {'raw': <texto>}
149
+ """
131
150
  try:
132
- if "application/json" in request.META.get("CONTENT_TYPE", ""):
133
- data = json.loads(request.body.decode("utf-8") or "{}")
134
- parts.append(json.dumps(data))
151
+ ct = request.META.get("CONTENT_TYPE", "")
152
+ if "application/json" in ct:
153
+ raw = request.body.decode("utf-8") or "{}"
154
+ try:
155
+ data = json.loads(raw)
156
+ if isinstance(data, dict):
157
+ return data
158
+ else:
159
+ return {"raw": raw}
160
+ except Exception:
161
+ return {"raw": raw}
135
162
  else:
136
- body = request.body.decode("utf-8", errors="ignore")
137
- if body:
138
- parts.append(body)
163
+ try:
164
+ post = request.POST.dict()
165
+ if post:
166
+ return post
167
+ except Exception:
168
+ pass
169
+ raw = request.body.decode("utf-8", errors="ignore")
170
+ if raw:
171
+ return {"raw": raw}
139
172
  except Exception:
140
173
  pass
141
-
142
- qs = request.META.get("QUERY_STRING", "")
143
- if qs:
144
- parts.append(qs)
145
-
146
- return " ".join(parts)
174
+ return {}
147
175
 
148
176
  # ----------------------------
149
177
  # Normalización / preprocesamiento
@@ -159,13 +187,12 @@ def normalize_input(s: str) -> str:
159
187
  s_dec = html.unescape(s_dec)
160
188
  except Exception:
161
189
  pass
162
- # Reemplazo seguro de secuencias \xNN
163
190
  s_dec = re.sub(r"\\x([0-9a-fA-F]{2})", r"\\x\g<1>", s_dec)
164
191
  s_dec = re.sub(r"\s+", " ", s_dec)
165
192
  return s_dec.strip()
166
193
 
167
194
  # ----------------------------
168
- # Detector SQLi
195
+ # Detector SQLi - ROBUSTO SIN DESCUENTO
169
196
  # ----------------------------
170
197
  def detect_sql_injection(text: str) -> Dict:
171
198
  norm = normalize_input(text or "")
@@ -174,7 +201,7 @@ def detect_sql_injection(text: str) -> Dict:
174
201
  descriptions = []
175
202
  for pattern, desc, weight in SQL_PATTERNS:
176
203
  if pattern.search(norm):
177
- score += weight
204
+ score += weight # Score full, sin descuento
178
205
  matches.append((desc, pattern.pattern, weight))
179
206
  descriptions.append(desc)
180
207
 
@@ -185,14 +212,8 @@ def detect_sql_injection(text: str) -> Dict:
185
212
  "sample": norm[:1200],
186
213
  }
187
214
 
188
- DEFAULT_THRESHOLDS = {
189
- "HIGH": 1.8,
190
- "MEDIUM": 1.0,
191
- "LOW": 0.5,
192
- }
193
-
194
215
  # ----------------------------
195
- # Middleware SQLi
216
+ # Middleware SQLi - ULTRA-ROBUSTO
196
217
  # ----------------------------
197
218
  class SQLIDefenseMiddleware(MiddlewareMixin):
198
219
  def process_request(self, request):
@@ -208,27 +229,64 @@ class SQLIDefenseMiddleware(MiddlewareMixin):
208
229
  if any(url in referer for url in trusted_urls) or any(url in host for url in trusted_urls):
209
230
  return None
210
231
 
211
- payload = extract_payload(request)
212
- result = detect_sql_injection(payload)
213
- score = result["score"]
214
- descripciones = result["descriptions"]
232
+ # Extraer datos como mapa para análisis por campo
233
+ data = extract_payload_as_map(request)
234
+ qs = request.META.get("QUERY_STRING", "")
235
+ if qs:
236
+ data["_query_string"] = qs
237
+
238
+ if not data:
239
+ return None
215
240
 
216
- if score == 0:
241
+ total_score = 0.0
242
+ all_descriptions = []
243
+ all_matches = []
244
+ payload_summary = []
245
+
246
+ # Analizar campo por campo - AHORA SIN DESCUENTO PARA ROBUSTEZ
247
+ if isinstance(data, dict):
248
+ for key, value in data.items():
249
+ if isinstance(value, (dict, list)):
250
+ try:
251
+ vtext = json.dumps(value, ensure_ascii=False)
252
+ except Exception:
253
+ vtext = str(value)
254
+ else:
255
+ vtext = str(value or "")
256
+
257
+ result = detect_sql_injection(vtext)
258
+ total_score += result["score"]
259
+ all_descriptions.extend(result["descriptions"])
260
+ all_matches.extend(result["matches"])
261
+
262
+ if result["score"] > 0:
263
+ is_sensitive = isinstance(key, str) and key.lower() in SENSITIVE_FIELDS
264
+ payload_summary.append({"field": key, "snippet": vtext[:300], "sensitive": is_sensitive})
265
+ else:
266
+ raw = str(data)
267
+ result = detect_sql_injection(raw)
268
+ total_score += result["score"]
269
+ all_descriptions.extend(result["descriptions"])
270
+ all_matches.extend(result["matches"])
271
+ if result["score"] > 0:
272
+ payload_summary.append({"field": "raw", "snippet": raw[:500], "sensitive": False})
273
+
274
+ if total_score == 0:
217
275
  return None
218
276
 
219
277
  # Registrar ataque
220
278
  logger.warning(
221
279
  f"[SQLiDetect] IP={client_ip} Host={host} Referer={referer} "
222
- f"Score={score:.2f} Desc={descripciones} Payload={payload[:500]}"
280
+ f"Score={total_score:.2f} Desc={all_descriptions} Payload={json.dumps(payload_summary, ensure_ascii=False)[:500]}"
223
281
  )
224
282
 
225
283
  # Guardar info en request
226
284
  request.sql_attack_info = {
227
285
  "ip": client_ip,
228
286
  "tipos": ["SQLi"],
229
- "descripcion": descripciones,
230
- "payload": payload[:1000],
231
- "score": round(score, 2),
287
+ "descripcion": all_descriptions,
288
+ "payload": json.dumps(payload_summary, ensure_ascii=False)[:1000],
289
+ "score": round(total_score, 2),
232
290
  "url": request.build_absolute_uri(),
233
291
  }
234
292
 
@@ -29,11 +29,10 @@ except Exception:
29
29
  _BLEACH_AVAILABLE = False
30
30
 
31
31
  # -------------------------------------------------
32
- # Patrones XSS con peso (descripcion, peso)
32
+ # Patrones XSS con peso (descripcion, peso) - EXPANDIDOS PARA ROBUSTEZ
33
33
  # - pesos mayores = más severo (por ejemplo <script> o javascript:)
34
- # - esto permite un scoring acumulativo y menos falsos positivos
34
+ # - Agregados patrones para DOM-based, polyglots y evasiones avanzadas
35
35
  # -------------------------------------------------
36
-
37
36
  XSS_PATTERNS: List[Tuple[re.Pattern, str, float]] = [
38
37
  # ---------- Máxima severidad / ejecución directa ----------
39
38
  (re.compile(r"<\s*script\b", re.I), "Etiqueta <script> (directa)", 0.95),
@@ -94,17 +93,26 @@ XSS_PATTERNS: List[Tuple[re.Pattern, str, float]] = [
94
93
  # ---------- Heurísticos de baja severidad (informativo) ----------
95
94
  (re.compile(r"<\s*form\b", re.I), "Form (posible vector de ataque relacionado)", 0.25),
96
95
  (re.compile(r"(onmouseover|onfocus|onmouseenter|onmouseleave)\b", re.I), "Eventos UI (mouseover/focus)", 0.45),
96
+
97
+ # ---------- NUEVOS PATRONES PARA ROBUSTEZ ----------
98
+ (re.compile(r"\binnerHTML\s*=\s*.*[<>\"']", re.I), "Asignación a innerHTML con tags", 0.85), # DOM-based
99
+ (re.compile(r"\bdocument\.getElementById\s*\(\s*.*\)\.innerHTML", re.I), "Manipulación DOM innerHTML", 0.80),
100
+ (re.compile(r"<script[^>]*src\s*=\s*['\"][^'\"]*['\"][^>]*>", re.I), "Script externo (posible carga remota)", 0.75),
101
+ (re.compile(r"\bXMLHttpRequest\s*\(\s*\)\.open\s*\(\s*['\"](GET|POST)['\"]", re.I), "XHR manipulado (posible exfiltración)", 0.70),
102
+ (re.compile(r"<\s*link\b[^>]*\bhref\s*=\s*['\"][^'\"]*javascript\s*:", re.I), "Link con href javascript:", 0.78),
103
+ (re.compile(r"\bwindow\.open\s*\(\s*['\"]*javascript\s*:", re.I), "window.open con javascript:", 0.82),
97
104
  ]
98
105
 
99
106
  # -------------------------------------------------
100
- # Campos que NO queremos analizar (contraseñas, tokens, etc.)
107
+ # Campos sensibles: NO LOS IGNORAMOS COMPLETAMENTE, PERO LES DAMOS DESCUENTO EN SCORE
108
+ # Para robustez, los analizamos pero reducimos el peso para evitar falsos positivos.
101
109
  # -------------------------------------------------
102
- IGNORED_FIELDS = getattr(settings, "XSS_DEFENSE_IGNORED_FIELDS", ["password", "csrfmiddlewaretoken", "token", "auth"])
110
+ SENSITIVE_FIELDS = ["password", "csrfmiddlewaretoken", "token", "auth"]
111
+ SENSITIVE_DISCOUNT = 0.5 # Multiplicador para campos sensibles
103
112
 
104
113
  # Umbral por defecto para considerar "alto riesgo" (Auditoria puede bloquear según su lógica)
105
114
  XSS_DEFENSE_THRESHOLD = getattr(settings, "XSS_DEFENSE_THRESHOLD", 0.6)
106
115
 
107
-
108
116
  # -------------------------------------------------
109
117
  # Util: validación / extracción de IP (robusta)
110
118
  # -------------------------------------------------
@@ -117,7 +125,6 @@ def _is_valid_ip(ip: str) -> bool:
117
125
  except Exception:
118
126
  return False
119
127
 
120
-
121
128
  def get_client_ip(request) -> str:
122
129
  """
123
130
  Obtiene la mejor estimación de la IP del cliente:
@@ -143,7 +150,6 @@ def get_client_ip(request) -> str:
143
150
  remote = request.META.get("REMOTE_ADDR")
144
151
  return remote or ""
145
152
 
146
-
147
153
  # -------------------------------------------------
148
154
  # Extraer payload pero evitando cabeceras (para reducir falsos positivos)
149
155
  # - Devuelve dict si es JSON, o dict con 'raw' para otros cuerpos
@@ -185,15 +191,14 @@ def extract_body_as_map(request) -> Dict[str, Any]:
185
191
  pass
186
192
  return {}
187
193
 
188
-
189
194
  # -------------------------------------------------
190
195
  # Analizar un solo valor (string) en busca de XSS usando patrones
191
196
  # Devuelve (score, descripciones, matches_patterns)
192
197
  # -------------------------------------------------
193
- def detect_xss_in_value(value: str) -> Tuple[float, List[str], List[str]]:
198
+ def detect_xss_in_value(value: str, is_sensitive: bool = False) -> Tuple[float, List[str], List[str]]:
194
199
  """
195
200
  Analiza una cadena y devuelve:
196
- - score acumulado (sum pesos)
201
+ - score acumulado (sum pesos, con descuento si es campo sensible)
197
202
  - lista de descripciones activadas
198
203
  - lista de patrones (regex.pattern) que matchearon
199
204
  """
@@ -204,25 +209,31 @@ def detect_xss_in_value(value: str) -> Tuple[float, List[str], List[str]]:
204
209
  descripcion = []
205
210
  matches = []
206
211
 
207
- # Si bleach está disponible, podemos "limpiar" y comparar; pero aquí solo detectamos
212
+ # Si bleach está disponible, sanitizar y comparar para detección adicional
213
+ if _BLEACH_AVAILABLE:
214
+ cleaned = bleach.clean(value, strip=True)
215
+ if cleaned != value:
216
+ score_total += 0.5 # Penalización por cambios en sanitización
217
+ descripcion.append("Contenido alterado por sanitización (bleach)")
218
+
208
219
  for patt, msg, weight in XSS_PATTERNS:
209
220
  if patt.search(value):
210
- score_total += weight
221
+ adjusted_weight = weight * SENSITIVE_DISCOUNT if is_sensitive else weight
222
+ score_total += adjusted_weight
211
223
  descripcion.append(msg)
212
224
  matches.append(patt.pattern)
213
225
 
214
226
  return round(score_total, 3), descripcion, matches
215
227
 
216
-
217
228
  # -------------------------------------------------
218
- # Middleware principal XSS
229
+ # Middleware principal XSS - MEJORADO PARA ROBUSTEZ
219
230
  # -------------------------------------------------
220
231
  class XSSDefenseMiddleware(MiddlewareMixin):
221
232
  """
222
- Middleware para detección XSS.
223
- - Analiza el body (campo por campo) y querystring si aplica.
224
- - Ignora campos sensibles (password, token).
225
- - No incluye User-Agent/Referer en el texto analizado (evita falsos positivos).
233
+ Middleware para detección XSS - Versión Robusta.
234
+ - Analiza TODO el body (campo por campo) y querystring.
235
+ - Campos sensibles se analizan con descuento en score para evitar falsos positivos.
236
+ - Usa bleach si disponible para sanitización.
226
237
  - Añade request.xss_attack_info con: ip, tipos, descripcion, payload, score, url.
227
238
  """
228
239
 
@@ -251,15 +262,12 @@ class XSSDefenseMiddleware(MiddlewareMixin):
251
262
  total_score = 0.0
252
263
  all_descriptions: List[str] = []
253
264
  all_matches: List[str] = []
254
- # payload_for_storage: guardamos un resumen/truncado para auditoría
255
265
  payload_summary = []
256
266
 
257
- # 3) Analizar campo por campo (si es dict) o el raw
267
+ # 3) Analizar campo por campo (si es dict) o el raw - AHORA ANALIZA TODO, CON DESCUENTO PARA SENSIBLES
258
268
  if isinstance(data, dict):
259
269
  for key, value in data.items():
260
- # evitar analizar campos sensibles
261
- if isinstance(key, str) and key.lower() in [f.lower() for f in IGNORED_FIELDS]:
262
- continue
270
+ is_sensitive = isinstance(key, str) and key.lower() in SENSITIVE_FIELDS
263
271
 
264
272
  # convertir a string si es otro tipo (list, int...)
265
273
  if isinstance(value, (dict, list)):
@@ -270,20 +278,13 @@ class XSSDefenseMiddleware(MiddlewareMixin):
270
278
  else:
271
279
  vtext = str(value or "")
272
280
 
273
- # salto rápido: si el valor parece ser un email o password muy corto y sin signos,
274
- # las probabilidades de XSS son muy bajas; continúa (reduce falsos positivos).
275
- if key.lower() in ("email", "username") and len(vtext) < 256:
276
- # aún así pasar por patrones (no lo ignoramos completamente), pero podemos bajar sensibilidad
277
- pass
278
-
279
- s, descs, matches = detect_xss_in_value(vtext)
281
+ s, descs, matches = detect_xss_in_value(vtext, is_sensitive)
280
282
  total_score += s
281
283
  all_descriptions.extend(descs)
282
284
  all_matches.extend(matches)
283
285
 
284
286
  if s > 0:
285
- # almacenar fragmento del campo para auditoría (truncado)
286
- payload_summary.append({ "field": key, "snippet": vtext[:300] })
287
+ payload_summary.append({"field": key, "snippet": vtext[:300], "sensitive": is_sensitive})
287
288
 
288
289
  else:
289
290
  # si no es dict, analizar el raw como texto
@@ -293,7 +294,7 @@ class XSSDefenseMiddleware(MiddlewareMixin):
293
294
  all_descriptions.extend(descs)
294
295
  all_matches.extend(matches)
295
296
  if s > 0:
296
- payload_summary.append({"field":"raw","snippet": raw[:500]})
297
+ payload_summary.append({"field": "raw", "snippet": raw[:500], "sensitive": False})
297
298
 
298
299
  # 4) si no detectó nada, continuar
299
300
  if total_score == 0:
@@ -305,7 +306,7 @@ class XSSDefenseMiddleware(MiddlewareMixin):
305
306
  payload_for_request = json.dumps(payload_summary, ensure_ascii=False)[:2000]
306
307
 
307
308
  logger.warning(
308
- "XSS detectado desde IP %s URL=%s Score=%.3f Desc=%s",
309
+ "XSS detectado desde IP %s URL=%s Score=%.3f Desc=%s (Robust: incluye sensibles con descuento)",
309
310
  client_ip,
310
311
  url,
311
312
  score_rounded,
@@ -322,6 +323,7 @@ class XSSDefenseMiddleware(MiddlewareMixin):
322
323
  "url": url,
323
324
  }
324
325
 
326
+
325
327
  # 7) NO bloquear aquí — lo hace AuditoriaMiddleware según su política
326
328
  return None
327
329
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GuardianUnivalle-Benito-Yucra
3
- Version: 0.1.68
3
+ Version: 0.1.70
4
4
  Summary: Middleware y detectores de seguridad (SQLi, XSS, CSRF, DoS) para Django/Flask
5
5
  Author-email: Andres Benito Calle Yucra <benitoandrescalle035@gmail.com>
6
6
  License: MIT
@@ -23,11 +23,9 @@ Dynamic: license-file
23
23
 
24
24
  <!-- Información de la librería -->
25
25
 
26
- ![Guardian Univalle – Benito & Junkrat](https://raw.githubusercontent.com/Andyyupy/guardianunivalle-benito-yucra/main/docs/logo_guardian.png)
26
+ # 🛡️ Guardian Univalle – Benito & Junkrat
27
27
 
28
- 🛡️ Guardian Univalle Benito & Junkrat
29
-
30
- Framework de detección y defensa de amenazas web para Django
28
+ Framework de detección y defensa de amenazas web para Django y Flask
31
29
 
32
30
  Guardian Univalle es un sistema de seguridad modular desarrollado para fortalecer aplicaciones Django frente a ataques web comunes como XSS, CSRF, inyección SQL, ataques DoS y scraping automatizado.
33
31
  Cada módulo opera mediante middleware independientes que analizan el tráfico HTTP en tiempo real, aplican heurísticas inteligentes y registran eventos sospechosos para auditoría y bloqueo adaptativo.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GuardianUnivalle-Benito-Yucra
3
- Version: 0.1.68
3
+ Version: 0.1.70
4
4
  Summary: Middleware y detectores de seguridad (SQLi, XSS, CSRF, DoS) para Django/Flask
5
5
  Author-email: Andres Benito Calle Yucra <benitoandrescalle035@gmail.com>
6
6
  License: MIT
@@ -23,11 +23,9 @@ Dynamic: license-file
23
23
 
24
24
  <!-- Información de la librería -->
25
25
 
26
- ![Guardian Univalle – Benito & Junkrat](https://raw.githubusercontent.com/Andyyupy/guardianunivalle-benito-yucra/main/docs/logo_guardian.png)
26
+ # 🛡️ Guardian Univalle – Benito & Junkrat
27
27
 
28
- 🛡️ Guardian Univalle Benito & Junkrat
29
-
30
- Framework de detección y defensa de amenazas web para Django
28
+ Framework de detección y defensa de amenazas web para Django y Flask
31
29
 
32
30
  Guardian Univalle es un sistema de seguridad modular desarrollado para fortalecer aplicaciones Django frente a ataques web comunes como XSS, CSRF, inyección SQL, ataques DoS y scraping automatizado.
33
31
  Cada módulo opera mediante middleware independientes que analizan el tráfico HTTP en tiempo real, aplican heurísticas inteligentes y registran eventos sospechosos para auditoría y bloqueo adaptativo.
@@ -1,10 +1,8 @@
1
1
  <!-- Información de la librería -->
2
2
 
3
- ![Guardian Univalle – Benito & Junkrat](https://raw.githubusercontent.com/Andyyupy/guardianunivalle-benito-yucra/main/docs/logo_guardian.png)
3
+ # 🛡️ Guardian Univalle – Benito & Junkrat
4
4
 
5
- 🛡️ Guardian Univalle Benito & Junkrat
6
-
7
- Framework de detección y defensa de amenazas web para Django
5
+ Framework de detección y defensa de amenazas web para Django y Flask
8
6
 
9
7
  Guardian Univalle es un sistema de seguridad modular desarrollado para fortalecer aplicaciones Django frente a ataques web comunes como XSS, CSRF, inyección SQL, ataques DoS y scraping automatizado.
10
8
  Cada módulo opera mediante middleware independientes que analizan el tráfico HTTP en tiempo real, aplican heurísticas inteligentes y registran eventos sospechosos para auditoría y bloqueo adaptativo.
@@ -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.68"
7
+ version = "0.1.70"
8
8
  description = "Middleware y detectores de seguridad (SQLi, XSS, CSRF, DoS) para Django/Flask"
9
9
  authors = [
10
10
  { name = "Andres Benito Calle Yucra", email = "benitoandrescalle035@gmail.com" }