GuardianUnivalle-Benito-Yucra 0.1.9__tar.gz → 0.1.10__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.
- {guardianunivalle_benito_yucra-0.1.9 → guardianunivalle_benito_yucra-0.1.10}/GuardianUnivalle_Benito_Yucra/detectores/detector_sql.py +38 -37
- {guardianunivalle_benito_yucra-0.1.9 → guardianunivalle_benito_yucra-0.1.10}/GuardianUnivalle_Benito_Yucra.egg-info/PKG-INFO +1 -1
- {guardianunivalle_benito_yucra-0.1.9 → guardianunivalle_benito_yucra-0.1.10}/PKG-INFO +1 -1
- {guardianunivalle_benito_yucra-0.1.9 → guardianunivalle_benito_yucra-0.1.10}/pyproject.toml +1 -1
- {guardianunivalle_benito_yucra-0.1.9 → guardianunivalle_benito_yucra-0.1.10}/GuardianUnivalle_Benito_Yucra/__init__.py +0 -0
- {guardianunivalle_benito_yucra-0.1.9 → guardianunivalle_benito_yucra-0.1.10}/GuardianUnivalle_Benito_Yucra/auditoria/registro_auditoria.py +0 -0
- {guardianunivalle_benito_yucra-0.1.9 → guardianunivalle_benito_yucra-0.1.10}/GuardianUnivalle_Benito_Yucra/criptografia/cifrado_aead.py +0 -0
- {guardianunivalle_benito_yucra-0.1.9 → guardianunivalle_benito_yucra-0.1.10}/GuardianUnivalle_Benito_Yucra/criptografia/intercambio_claves.py +0 -0
- {guardianunivalle_benito_yucra-0.1.9 → guardianunivalle_benito_yucra-0.1.10}/GuardianUnivalle_Benito_Yucra/criptografia/kdf.py +0 -0
- {guardianunivalle_benito_yucra-0.1.9 → guardianunivalle_benito_yucra-0.1.10}/GuardianUnivalle_Benito_Yucra/detectores/detector_csrf.py +0 -0
- {guardianunivalle_benito_yucra-0.1.9 → guardianunivalle_benito_yucra-0.1.10}/GuardianUnivalle_Benito_Yucra/detectores/detector_dos.py +0 -0
- {guardianunivalle_benito_yucra-0.1.9 → guardianunivalle_benito_yucra-0.1.10}/GuardianUnivalle_Benito_Yucra/detectores/detector_keylogger.py +0 -0
- {guardianunivalle_benito_yucra-0.1.9 → guardianunivalle_benito_yucra-0.1.10}/GuardianUnivalle_Benito_Yucra/detectores/detector_xss.py +0 -0
- {guardianunivalle_benito_yucra-0.1.9 → guardianunivalle_benito_yucra-0.1.10}/GuardianUnivalle_Benito_Yucra/middleware_web/middleware_web.py +0 -0
- {guardianunivalle_benito_yucra-0.1.9 → guardianunivalle_benito_yucra-0.1.10}/GuardianUnivalle_Benito_Yucra/mitigacion/limitador_peticion.py +0 -0
- {guardianunivalle_benito_yucra-0.1.9 → guardianunivalle_benito_yucra-0.1.10}/GuardianUnivalle_Benito_Yucra/mitigacion/lista_bloqueo.py +0 -0
- {guardianunivalle_benito_yucra-0.1.9 → guardianunivalle_benito_yucra-0.1.10}/GuardianUnivalle_Benito_Yucra/puntuacion/puntuacion_amenaza.py +0 -0
- {guardianunivalle_benito_yucra-0.1.9 → guardianunivalle_benito_yucra-0.1.10}/GuardianUnivalle_Benito_Yucra/utilidades.py +0 -0
- {guardianunivalle_benito_yucra-0.1.9 → guardianunivalle_benito_yucra-0.1.10}/GuardianUnivalle_Benito_Yucra.egg-info/SOURCES.txt +0 -0
- {guardianunivalle_benito_yucra-0.1.9 → guardianunivalle_benito_yucra-0.1.10}/GuardianUnivalle_Benito_Yucra.egg-info/dependency_links.txt +0 -0
- {guardianunivalle_benito_yucra-0.1.9 → guardianunivalle_benito_yucra-0.1.10}/GuardianUnivalle_Benito_Yucra.egg-info/requires.txt +0 -0
- {guardianunivalle_benito_yucra-0.1.9 → guardianunivalle_benito_yucra-0.1.10}/GuardianUnivalle_Benito_Yucra.egg-info/top_level.txt +0 -0
- {guardianunivalle_benito_yucra-0.1.9 → guardianunivalle_benito_yucra-0.1.10}/LICENSE +0 -0
- {guardianunivalle_benito_yucra-0.1.9 → guardianunivalle_benito_yucra-0.1.10}/README.md +0 -0
- {guardianunivalle_benito_yucra-0.1.9 → guardianunivalle_benito_yucra-0.1.10}/setup.cfg +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# detector_sql.py
|
|
1
2
|
import re
|
|
2
3
|
import json
|
|
3
4
|
import time
|
|
@@ -11,10 +12,11 @@ from django.core.signing import TimestampSigner, BadSignature, SignatureExpired
|
|
|
11
12
|
# ---------- Configuración de logging ----------
|
|
12
13
|
logger = logging.getLogger("sqlidefense")
|
|
13
14
|
logger.setLevel(logging.INFO)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
if not logger.handlers:
|
|
16
|
+
handler = logging.StreamHandler() # en prod usa FileHandler/RotatingFileHandler
|
|
17
|
+
formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
|
|
18
|
+
handler.setFormatter(formatter)
|
|
19
|
+
logger.addHandler(handler)
|
|
18
20
|
|
|
19
21
|
# ---------- Patrones de ataques SQL ----------
|
|
20
22
|
PATTERNS = [
|
|
@@ -43,13 +45,14 @@ PATTERNS = [
|
|
|
43
45
|
|
|
44
46
|
# ---------- Bloqueo temporal por IP ----------
|
|
45
47
|
TEMP_BLOCK = {} # {ip: timestamp}
|
|
46
|
-
BLOCK_DURATION = 30 # segundos
|
|
48
|
+
BLOCK_DURATION = getattr(settings, "SQL_DEFENSE_BLOCK_TTL", 30) # segundos
|
|
47
49
|
|
|
48
|
-
# ----------
|
|
49
|
-
|
|
50
|
+
# ---------- Excepciones y bypass ----------
|
|
51
|
+
EXEMPT_PATHS = getattr(settings, "SQLI_EXEMPT_PATHS", ["/api/login/"])
|
|
50
52
|
BYPASS_HEADER = "HTTP_X_SQLI_BYPASS"
|
|
53
|
+
BYPASS_MAX_AGE = getattr(settings, "SQLI_BYPASS_MAX_AGE", 30) # segundos (muy corto)
|
|
51
54
|
|
|
52
|
-
#
|
|
55
|
+
# JWT support (optional)
|
|
53
56
|
try:
|
|
54
57
|
from rest_framework_simplejwt.backends import TokenBackend
|
|
55
58
|
|
|
@@ -60,25 +63,24 @@ except Exception:
|
|
|
60
63
|
|
|
61
64
|
# ---------- Helpers ----------
|
|
62
65
|
def extract_payload_text(request) -> str:
|
|
63
|
-
"""Extrae todo el contenido que podría contener inyecciones"""
|
|
64
66
|
parts = []
|
|
65
|
-
|
|
66
|
-
# Query params
|
|
67
|
-
if request.META.get("QUERY_STRING"):
|
|
68
|
-
parts.append(request.META.get("QUERY_STRING"))
|
|
69
|
-
|
|
70
|
-
# Body
|
|
67
|
+
content_type = request.META.get("CONTENT_TYPE", "")
|
|
71
68
|
try:
|
|
72
|
-
content_type = request.META.get("CONTENT_TYPE", "")
|
|
73
69
|
if "application/json" in content_type:
|
|
74
70
|
body_json = json.loads(request.body.decode("utf-8") or "{}")
|
|
71
|
+
# 🔒 quitar campos sensibles antes de loguear/analizar
|
|
72
|
+
for key in getattr(settings, "SQL_DEFENSE_SENSITIVE_KEYS", []):
|
|
73
|
+
if key in body_json:
|
|
74
|
+
body_json[key] = "***"
|
|
75
75
|
parts.append(json.dumps(body_json))
|
|
76
76
|
else:
|
|
77
77
|
parts.append(request.body.decode("utf-8", errors="ignore"))
|
|
78
78
|
except Exception:
|
|
79
79
|
pass
|
|
80
80
|
|
|
81
|
-
|
|
81
|
+
if request.META.get("QUERY_STRING"):
|
|
82
|
+
parts.append(request.META.get("QUERY_STRING"))
|
|
83
|
+
|
|
82
84
|
parts.append(request.META.get("HTTP_USER_AGENT", ""))
|
|
83
85
|
parts.append(request.META.get("HTTP_REFERER", ""))
|
|
84
86
|
|
|
@@ -86,7 +88,6 @@ def extract_payload_text(request) -> str:
|
|
|
86
88
|
|
|
87
89
|
|
|
88
90
|
def detect_sqli_text(text: str) -> Tuple[bool, list]:
|
|
89
|
-
"""Detecta ataques SQL en el texto"""
|
|
90
91
|
matches = []
|
|
91
92
|
for patt, message in PATTERNS:
|
|
92
93
|
if patt.search(text):
|
|
@@ -95,17 +96,13 @@ def detect_sqli_text(text: str) -> Tuple[bool, list]:
|
|
|
95
96
|
|
|
96
97
|
|
|
97
98
|
def get_client_ip(request):
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
else:
|
|
103
|
-
ip = request.META.get("REMOTE_ADDR", "0.0.0.0")
|
|
104
|
-
return ip
|
|
99
|
+
xff = request.META.get("HTTP_X_FORWARDED_FOR")
|
|
100
|
+
if xff:
|
|
101
|
+
return xff.split(",")[0].strip()
|
|
102
|
+
return request.META.get("REMOTE_ADDR", "0.0.0.0")
|
|
105
103
|
|
|
106
104
|
|
|
107
105
|
def is_valid_jwt(request) -> bool:
|
|
108
|
-
"""Valida si la petición trae un JWT válido en Authorization: Bearer <token>"""
|
|
109
106
|
if not SIMPLEJWT_AVAILABLE:
|
|
110
107
|
return False
|
|
111
108
|
auth = request.META.get("HTTP_AUTHORIZATION", "")
|
|
@@ -126,7 +123,6 @@ def is_valid_jwt(request) -> bool:
|
|
|
126
123
|
|
|
127
124
|
|
|
128
125
|
def is_valid_signed_bypass(request) -> bool:
|
|
129
|
-
"""Valida si la petición trae un token firmado válido y no expirado"""
|
|
130
126
|
signed = request.META.get(BYPASS_HEADER, "")
|
|
131
127
|
if not signed:
|
|
132
128
|
return False
|
|
@@ -145,43 +141,48 @@ def is_valid_signed_bypass(request) -> bool:
|
|
|
145
141
|
# ---------- Middleware ----------
|
|
146
142
|
class SQLIDefenseStrongMiddleware(MiddlewareMixin):
|
|
147
143
|
def process_request(self, request):
|
|
144
|
+
path = request.path or ""
|
|
148
145
|
client_ip = get_client_ip(request)
|
|
146
|
+
if any(
|
|
147
|
+
request.path.startswith(p)
|
|
148
|
+
for p in getattr(settings, "SQL_DEFENSE_EXEMPT_PATHS", [])
|
|
149
|
+
):
|
|
150
|
+
return None
|
|
151
|
+
# 1) Si la ruta está exenta -> no inspeccionar
|
|
152
|
+
for p in EXEMPT_PATHS:
|
|
153
|
+
if path.startswith(p):
|
|
154
|
+
return None
|
|
149
155
|
|
|
150
|
-
#
|
|
156
|
+
# 2) Si la IP está temporalmente bloqueada
|
|
151
157
|
if client_ip in TEMP_BLOCK:
|
|
152
158
|
if time.time() - TEMP_BLOCK[client_ip] < BLOCK_DURATION:
|
|
153
159
|
logger.warning(f"IP bloqueada temporalmente: {client_ip}")
|
|
154
160
|
return JsonResponse(
|
|
155
|
-
{
|
|
156
|
-
"detail": "Acceso temporalmente bloqueado por actividad sospechosa",
|
|
157
|
-
"alerts": ["IP bloqueada temporalmente"],
|
|
158
|
-
},
|
|
159
|
-
status=403,
|
|
161
|
+
{"detail": "Acceso temporalmente bloqueado"}, status=403
|
|
160
162
|
)
|
|
161
163
|
else:
|
|
162
164
|
del TEMP_BLOCK[client_ip]
|
|
163
165
|
|
|
164
|
-
#
|
|
166
|
+
# 3) Bypass: JWT válido o token firmado corto (p. ej. inmediatamente después del login)
|
|
165
167
|
if is_valid_jwt(request):
|
|
166
168
|
return None
|
|
167
169
|
if is_valid_signed_bypass(request):
|
|
168
170
|
return None
|
|
169
171
|
|
|
170
|
-
#
|
|
172
|
+
# 4) Detectar SQLi
|
|
171
173
|
text = extract_payload_text(request)
|
|
172
174
|
if not text:
|
|
173
175
|
return None
|
|
174
176
|
|
|
175
177
|
flagged, matches = detect_sqli_text(text)
|
|
176
178
|
if flagged:
|
|
177
|
-
# Bloquea temporalmente la IP
|
|
178
179
|
TEMP_BLOCK[client_ip] = time.time()
|
|
179
180
|
logger.warning(
|
|
180
181
|
f"Intento de ataque detectado desde IP: {client_ip}, detalles: {matches}, payload: {text}"
|
|
181
182
|
)
|
|
182
183
|
return JsonResponse(
|
|
183
184
|
{
|
|
184
|
-
"detail": "Request bloqueado: posible
|
|
185
|
+
"detail": "Request bloqueado: posible inyección SQL",
|
|
185
186
|
"alerts": matches,
|
|
186
187
|
},
|
|
187
188
|
status=403,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: GuardianUnivalle-Benito-Yucra
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.10
|
|
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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: GuardianUnivalle-Benito-Yucra
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.10
|
|
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.
|
|
7
|
+
version = "0.1.10"
|
|
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" }
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|