GuardianUnivalle-Benito-Yucra 0.1.44__py3-none-any.whl → 0.1.45__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.

Potentially problematic release.


This version of GuardianUnivalle-Benito-Yucra might be problematic. Click here for more details.

@@ -1,4 +1,4 @@
1
-
1
+ # CSRF defense (parche recomendado)
2
2
  from __future__ import annotations
3
3
  import secrets
4
4
  import logging
@@ -9,9 +9,6 @@ from urllib.parse import urlparse
9
9
  from django.conf import settings
10
10
  from django.utils.deprecation import MiddlewareMixin
11
11
 
12
- # ======================================================
13
- # === CONFIGURACIÓN DE LOGGER ===
14
- # ======================================================
15
12
  logger = logging.getLogger("csrfdefense")
16
13
  logger.setLevel(logging.INFO)
17
14
  if not logger.handlers:
@@ -19,52 +16,34 @@ if not logger.handlers:
19
16
  handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s"))
20
17
  logger.addHandler(handler)
21
18
 
22
- # ======================================================
23
- # === FUNCIONES AUXILIARES DE TOKEN CSRF ===
24
- # ======================================================
25
- def registrar_evento(tipo: str, mensaje: str):
26
- """Registra eventos importantes en los logs."""
27
- logger.warning(f"[{tipo}] {mensaje}")
28
-
29
- def generar_token_csrf() -> str:
30
- """Genera un token CSRF seguro."""
31
- token = secrets.token_hex(32)
32
- registrar_evento("CSRF", "Token CSRF generado")
33
- return token
34
-
35
- def validar_token_csrf(token: str, token_sesion: str) -> bool:
36
- """Valida que el token recibido coincida con el token en sesión."""
37
- valido = token == token_sesion
38
- if not valido:
39
- registrar_evento("CSRF", "Intento de CSRF detectado (token no coincide)")
40
- return valido
41
-
42
- # ======================================================
43
- # === CONSTANTES Y CONFIGURACIONES ===
44
- # ======================================================
45
19
  STATE_CHANGING_METHODS = {"POST", "PUT", "PATCH", "DELETE"}
46
20
  CSRF_HEADER_NAMES = ("HTTP_X_CSRFTOKEN", "HTTP_X_CSRF_TOKEN")
47
21
  CSRF_COOKIE_NAME = getattr(settings, "CSRF_COOKIE_NAME", "csrftoken")
48
22
  POST_FIELD_NAME = "csrfmiddlewaretoken"
49
23
 
50
- # Content-Type realmente sospechosos
24
+ # Nota: NO consideramos 'application/json' sospechoso aquí por defecto,
25
+ # porque muchas APIs legítimas usan JSON.
51
26
  SUSPICIOUS_CT_PATTERNS = [
52
27
  re.compile(r"text/plain", re.I),
53
- re.compile(r"application/json", re.I),
28
+ re.compile(r"application/x-www-form-urlencoded", re.I),
29
+ re.compile(r"multipart/form-data", re.I),
54
30
  ]
55
31
 
56
- # ======================================================
57
- # === FUNCIONES DE APOYO ===
58
- # ======================================================
32
+ # Umbral minimo de "señales" para marcar como ataque (configurable)
33
+ CSRF_DEFENSE_MIN_SIGNALS = getattr(settings, "CSRF_DEFENSE_MIN_SIGNALS", 1)
34
+ # Opción para excluir rutas de API que manejan JSON (cambia según tu proyecto)
35
+ CSRF_DEFENSE_EXCLUDED_API_PREFIXES = getattr(settings, "CSRF_DEFENSE_EXCLUDED_API_PREFIXES", ["/api/"])
36
+
59
37
  def get_client_ip(request):
60
- """Obtiene la IP real del cliente."""
61
38
  x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR")
62
39
  if x_forwarded_for:
63
- return x_forwarded_for.split(",")[0].strip()
40
+ # toma la primera IP real
41
+ ips = [ip.strip() for ip in x_forwarded_for.split(",") if ip.strip()]
42
+ if ips:
43
+ return ips[0]
64
44
  return request.META.get("REMOTE_ADDR", "")
65
45
 
66
46
  def host_from_header(header_value: str) -> str | None:
67
- """Extrae el host limpio desde una cabecera."""
68
47
  if not header_value:
69
48
  return None
70
49
  try:
@@ -76,53 +55,43 @@ def host_from_header(header_value: str) -> str | None:
76
55
  return None
77
56
 
78
57
  def origin_matches_host(request) -> bool:
79
- """Verifica si Origin/Referer coinciden con Host."""
80
58
  host_header = request.META.get("HTTP_HOST") or request.META.get("SERVER_NAME")
81
59
  if not host_header:
82
60
  return True
83
-
84
61
  host = host_header.split(":")[0]
85
62
  origin = request.META.get("HTTP_ORIGIN", "")
86
63
  referer = request.META.get("HTTP_REFERER", "")
87
-
88
64
  origin_host = host_from_header(origin)
89
65
  referer_host = host_from_header(referer)
90
-
91
- # Detectar valores con scripts o URLs maliciosas
92
- if any(re.search(r"(javascript:|<script|data:text/html)", h or "", re.I)
93
- for h in [origin, referer]):
66
+ # bloquear obvious javascript: referers
67
+ if any(re.search(r"(javascript:|<script|data:text/html)", h or "", re.I) for h in [origin, referer]):
94
68
  return False
95
-
96
69
  if origin_host and origin_host == host:
97
70
  return True
98
71
  if referer_host and referer_host == host:
99
72
  return True
73
+ # si no hay origin ni referer, lo consideramos neutral (no marcar)
100
74
  if not origin and not referer:
101
75
  return True
102
-
103
76
  return False
104
77
 
105
78
  def has_csrf_token(request) -> bool:
106
- """Comprueba si hay un token CSRF presente."""
79
+ # busca header, cookie o campo form
107
80
  for h in CSRF_HEADER_NAMES:
108
81
  if request.META.get(h):
109
82
  return True
110
-
111
83
  cookie_val = request.COOKIES.get(CSRF_COOKIE_NAME)
112
84
  if cookie_val:
113
85
  return True
114
-
115
86
  try:
116
87
  if request.method == "POST" and hasattr(request, "POST"):
117
88
  if request.POST.get(POST_FIELD_NAME):
118
89
  return True
119
90
  except Exception:
120
91
  pass
121
-
122
92
  return False
123
93
 
124
94
  def extract_payload_text(request) -> str:
125
- """Extrae el cuerpo útil de la solicitud."""
126
95
  parts: List[str] = []
127
96
  try:
128
97
  body = request.body.decode("utf-8", errors="ignore")
@@ -137,17 +106,15 @@ def extract_payload_text(request) -> str:
137
106
  parts.append(request.META.get("HTTP_REFERER", ""))
138
107
  return " ".join([p for p in parts if p])
139
108
 
140
- # ======================================================
141
- # === MIDDLEWARE DE DEFENSA CSRF ===
142
- # ======================================================
143
109
  class CSRFDefenseMiddleware(MiddlewareMixin):
144
- """
145
- Middleware para DETECTAR intentos de CSRF:
146
- - Registra request.csrf_attack_info con 'tipos': ['CSRF'] y 'descripcion' con las razones.
147
- - No bloquea directamente: deja que AuditoriaMiddleware lo maneje.
148
- """
149
-
150
110
  def process_request(self, request):
111
+ # 1) Excluir APIs JSON si se configuró así
112
+ for prefix in CSRF_DEFENSE_EXCLUDED_API_PREFIXES:
113
+ if request.path.startswith(prefix):
114
+ # debug log opcional
115
+ logger.debug(f"[CSRFDefense] Skip analysis for API prefix {prefix} path {request.path}")
116
+ return None
117
+
151
118
  client_ip = get_client_ip(request)
152
119
  trusted_ips = getattr(settings, "CSRF_DEFENSE_TRUSTED_IPS", [])
153
120
  if client_ip in trusted_ips:
@@ -172,20 +139,20 @@ class CSRFDefenseMiddleware(MiddlewareMixin):
172
139
  if not origin_matches_host(request):
173
140
  descripcion.append("Origin/Referer no coinciden con Host (posible cross-site)")
174
141
 
175
- # 3) Content-Type sospechoso
176
- content_type = request.META.get("CONTENT_TYPE", "") or ""
142
+ # 3) Content-Type sospechoso (solo marcaremos si coincide uno de los patterns)
143
+ content_type = (request.META.get("CONTENT_TYPE") or "")
177
144
  for patt in SUSPICIOUS_CT_PATTERNS:
178
145
  if patt.search(content_type):
179
146
  descripcion.append(f"Content-Type sospechoso: {content_type}")
180
147
  break
181
148
 
182
- # 4) Referer ausente y sin token
149
+ # 4) Referer ausente y sin header CSRF
183
150
  referer = request.META.get("HTTP_REFERER", "")
184
151
  if not referer and not any(request.META.get(h) for h in CSRF_HEADER_NAMES):
185
152
  descripcion.append("Referer ausente y sin X-CSRFToken")
186
153
 
187
- # === Registro del ataque detectado ===
188
- if descripcion:
154
+ # Si señales >= umbral entonces marcamos para auditoría
155
+ if descripcion and len(descripcion) >= CSRF_DEFENSE_MIN_SIGNALS:
189
156
  w_csrf = getattr(settings, "CSRF_DEFENSE_WEIGHT", 0.2)
190
157
  intentos_csrf = len(descripcion)
191
158
  s_csrf = w_csrf * intentos_csrf
@@ -199,9 +166,13 @@ class CSRFDefenseMiddleware(MiddlewareMixin):
199
166
  }
200
167
 
201
168
  logger.warning(
202
- "CSRF detectado desde IP %s: %s ; payload: %.200s ; score: %.2f",
203
- client_ip, descripcion, payload, s_csrf,
169
+ "CSRF detectado desde IP %s: %s ; path=%s ; Content-Type=%s ; score=%.2f",
170
+ client_ip, descripcion, request.path, content_type, s_csrf
204
171
  )
172
+ else:
173
+ # debug útil: saber por qué NO se marcó
174
+ if descripcion:
175
+ logger.debug(f"[CSRFDefense] low-signals ({len(descripcion)}) not marking: {descripcion}")
205
176
 
206
177
  return None
207
178
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GuardianUnivalle-Benito-Yucra
3
- Version: 0.1.44
3
+ Version: 0.1.45
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 @@ GuardianUnivalle_Benito_Yucra/auditoria/registro_auditoria.py,sha256=NnKBOeRWkXV
4
4
  GuardianUnivalle_Benito_Yucra/criptografia/cifrado_aead.py,sha256=wfoRpaKvOqPbollNQsDNUNWClYJlXYTKTYvv0qcR6aI,962
5
5
  GuardianUnivalle_Benito_Yucra/criptografia/intercambio_claves.py,sha256=9djnlzb022hUhrDbQyWz7lWLbkn_vQZ4K7qar1FXYmo,829
6
6
  GuardianUnivalle_Benito_Yucra/criptografia/kdf.py,sha256=_sbepEY1qHEKga0ExrX2WRg1HeCPY5MC5CfXZWYyl-A,709
7
- GuardianUnivalle_Benito_Yucra/detectores/detector_csrf.py,sha256=tEFJTydl9gKcYQPxeQNSQ1WwoY_4oHvtDrhJROeH4E0,8109
7
+ GuardianUnivalle_Benito_Yucra/detectores/detector_csrf.py,sha256=q7-UsVseTtIYZz4bbpx2X0kzpDmu2Cetm7eYPJtsruA,7608
8
8
  GuardianUnivalle_Benito_Yucra/detectores/detector_dos.py,sha256=l_JYCmRYpsXt1ZauNPF_wy5uGJhmunRbtJ_WKpC3Otc,6953
9
9
  GuardianUnivalle_Benito_Yucra/detectores/detector_keylogger.py,sha256=L5RQ0Sdgg7hTU1qkZYwt7AcDqtAzT6u-jwBGo7YWfsw,8078
10
10
  GuardianUnivalle_Benito_Yucra/detectores/detector_sql.py,sha256=EEbnn5J7sZxnsA2a0cT1VAB4ZS7BMhQiHSeqrR2SU3A,4820
@@ -13,8 +13,8 @@ GuardianUnivalle_Benito_Yucra/middleware_web/middleware_web.py,sha256=23pLLYqliU
13
13
  GuardianUnivalle_Benito_Yucra/mitigacion/limitador_peticion.py,sha256=ipMOebYhql-6mSyHs0ddYXOcXq9w8P_IXLlpiIqGncw,246
14
14
  GuardianUnivalle_Benito_Yucra/mitigacion/lista_bloqueo.py,sha256=6AYWII4mrmwCLHCvGTyoBxR4Oasr4raSHpFbVjqn7d8,193
15
15
  GuardianUnivalle_Benito_Yucra/puntuacion/puntuacion_amenaza.py,sha256=Wx5XfcII4oweLvZsTBEJ7kUc9pMpP5-36RfI5C5KJXo,561
16
- guardianunivalle_benito_yucra-0.1.44.dist-info/licenses/LICENSE,sha256=5e4IdL542v1E8Ft0A24GZjrxZeTsVK7XrS3mZEUhPtM,37
17
- guardianunivalle_benito_yucra-0.1.44.dist-info/METADATA,sha256=8h01QKIbaxCdYkJ9wPEkmiIsRRDlIptoO_O_aWJkoaQ,1893
18
- guardianunivalle_benito_yucra-0.1.44.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
19
- guardianunivalle_benito_yucra-0.1.44.dist-info/top_level.txt,sha256=HTWfZM64WAV_QYr5cnXnLuabQt92dvlxqlR3pCwpbDQ,30
20
- guardianunivalle_benito_yucra-0.1.44.dist-info/RECORD,,
16
+ guardianunivalle_benito_yucra-0.1.45.dist-info/licenses/LICENSE,sha256=5e4IdL542v1E8Ft0A24GZjrxZeTsVK7XrS3mZEUhPtM,37
17
+ guardianunivalle_benito_yucra-0.1.45.dist-info/METADATA,sha256=XUUVE_QmQaisJ7mmP4kV42K68pCcTtNjyEiip4DzCdw,1893
18
+ guardianunivalle_benito_yucra-0.1.45.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
19
+ guardianunivalle_benito_yucra-0.1.45.dist-info/top_level.txt,sha256=HTWfZM64WAV_QYr5cnXnLuabQt92dvlxqlR3pCwpbDQ,30
20
+ guardianunivalle_benito_yucra-0.1.45.dist-info/RECORD,,