GuardianUnivalle-Benito-Yucra 0.1.9__tar.gz → 0.1.11__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.11}/GuardianUnivalle_Benito_Yucra/detectores/detector_sql.py +35 -42
- {guardianunivalle_benito_yucra-0.1.9 → guardianunivalle_benito_yucra-0.1.11}/GuardianUnivalle_Benito_Yucra.egg-info/PKG-INFO +1 -1
- {guardianunivalle_benito_yucra-0.1.9 → guardianunivalle_benito_yucra-0.1.11}/PKG-INFO +1 -1
- {guardianunivalle_benito_yucra-0.1.9 → guardianunivalle_benito_yucra-0.1.11}/pyproject.toml +1 -1
- {guardianunivalle_benito_yucra-0.1.9 → guardianunivalle_benito_yucra-0.1.11}/GuardianUnivalle_Benito_Yucra/__init__.py +0 -0
- {guardianunivalle_benito_yucra-0.1.9 → guardianunivalle_benito_yucra-0.1.11}/GuardianUnivalle_Benito_Yucra/auditoria/registro_auditoria.py +0 -0
- {guardianunivalle_benito_yucra-0.1.9 → guardianunivalle_benito_yucra-0.1.11}/GuardianUnivalle_Benito_Yucra/criptografia/cifrado_aead.py +0 -0
- {guardianunivalle_benito_yucra-0.1.9 → guardianunivalle_benito_yucra-0.1.11}/GuardianUnivalle_Benito_Yucra/criptografia/intercambio_claves.py +0 -0
- {guardianunivalle_benito_yucra-0.1.9 → guardianunivalle_benito_yucra-0.1.11}/GuardianUnivalle_Benito_Yucra/criptografia/kdf.py +0 -0
- {guardianunivalle_benito_yucra-0.1.9 → guardianunivalle_benito_yucra-0.1.11}/GuardianUnivalle_Benito_Yucra/detectores/detector_csrf.py +0 -0
- {guardianunivalle_benito_yucra-0.1.9 → guardianunivalle_benito_yucra-0.1.11}/GuardianUnivalle_Benito_Yucra/detectores/detector_dos.py +0 -0
- {guardianunivalle_benito_yucra-0.1.9 → guardianunivalle_benito_yucra-0.1.11}/GuardianUnivalle_Benito_Yucra/detectores/detector_keylogger.py +0 -0
- {guardianunivalle_benito_yucra-0.1.9 → guardianunivalle_benito_yucra-0.1.11}/GuardianUnivalle_Benito_Yucra/detectores/detector_xss.py +0 -0
- {guardianunivalle_benito_yucra-0.1.9 → guardianunivalle_benito_yucra-0.1.11}/GuardianUnivalle_Benito_Yucra/middleware_web/middleware_web.py +0 -0
- {guardianunivalle_benito_yucra-0.1.9 → guardianunivalle_benito_yucra-0.1.11}/GuardianUnivalle_Benito_Yucra/mitigacion/limitador_peticion.py +0 -0
- {guardianunivalle_benito_yucra-0.1.9 → guardianunivalle_benito_yucra-0.1.11}/GuardianUnivalle_Benito_Yucra/mitigacion/lista_bloqueo.py +0 -0
- {guardianunivalle_benito_yucra-0.1.9 → guardianunivalle_benito_yucra-0.1.11}/GuardianUnivalle_Benito_Yucra/puntuacion/puntuacion_amenaza.py +0 -0
- {guardianunivalle_benito_yucra-0.1.9 → guardianunivalle_benito_yucra-0.1.11}/GuardianUnivalle_Benito_Yucra/utilidades.py +0 -0
- {guardianunivalle_benito_yucra-0.1.9 → guardianunivalle_benito_yucra-0.1.11}/GuardianUnivalle_Benito_Yucra.egg-info/SOURCES.txt +0 -0
- {guardianunivalle_benito_yucra-0.1.9 → guardianunivalle_benito_yucra-0.1.11}/GuardianUnivalle_Benito_Yucra.egg-info/dependency_links.txt +0 -0
- {guardianunivalle_benito_yucra-0.1.9 → guardianunivalle_benito_yucra-0.1.11}/GuardianUnivalle_Benito_Yucra.egg-info/requires.txt +0 -0
- {guardianunivalle_benito_yucra-0.1.9 → guardianunivalle_benito_yucra-0.1.11}/GuardianUnivalle_Benito_Yucra.egg-info/top_level.txt +0 -0
- {guardianunivalle_benito_yucra-0.1.9 → guardianunivalle_benito_yucra-0.1.11}/LICENSE +0 -0
- {guardianunivalle_benito_yucra-0.1.9 → guardianunivalle_benito_yucra-0.1.11}/README.md +0 -0
- {guardianunivalle_benito_yucra-0.1.9 → guardianunivalle_benito_yucra-0.1.11}/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,40 @@ 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)
|
|
149
146
|
|
|
150
|
-
#
|
|
147
|
+
# 1) Rutas exentas
|
|
148
|
+
EXEMPT_PATHS = getattr(settings, "SQLI_EXEMPT_PATHS", [])
|
|
149
|
+
for p in EXEMPT_PATHS:
|
|
150
|
+
if path.startswith(p):
|
|
151
|
+
return None
|
|
152
|
+
|
|
153
|
+
# 2) IP bloqueada temporalmente
|
|
151
154
|
if client_ip in TEMP_BLOCK:
|
|
152
155
|
if time.time() - TEMP_BLOCK[client_ip] < BLOCK_DURATION:
|
|
153
156
|
logger.warning(f"IP bloqueada temporalmente: {client_ip}")
|
|
154
157
|
return JsonResponse(
|
|
155
|
-
{
|
|
156
|
-
"detail": "Acceso temporalmente bloqueado por actividad sospechosa",
|
|
157
|
-
"alerts": ["IP bloqueada temporalmente"],
|
|
158
|
-
},
|
|
159
|
-
status=403,
|
|
158
|
+
{"detail": "Acceso temporalmente bloqueado"}, status=403
|
|
160
159
|
)
|
|
161
160
|
else:
|
|
162
161
|
del TEMP_BLOCK[client_ip]
|
|
163
162
|
|
|
164
|
-
#
|
|
165
|
-
if is_valid_jwt(request):
|
|
166
|
-
return None
|
|
167
|
-
if is_valid_signed_bypass(request):
|
|
168
|
-
return None
|
|
169
|
-
|
|
170
|
-
# --- Detección de SQLi ---
|
|
163
|
+
# 3) Extraer payload
|
|
171
164
|
text = extract_payload_text(request)
|
|
172
165
|
if not text:
|
|
173
166
|
return None
|
|
174
167
|
|
|
168
|
+
# 4) Detectar SQLi
|
|
175
169
|
flagged, matches = detect_sqli_text(text)
|
|
176
170
|
if flagged:
|
|
177
|
-
# Bloquea temporalmente la IP
|
|
178
171
|
TEMP_BLOCK[client_ip] = time.time()
|
|
179
172
|
logger.warning(
|
|
180
173
|
f"Intento de ataque detectado desde IP: {client_ip}, detalles: {matches}, payload: {text}"
|
|
181
174
|
)
|
|
182
175
|
return JsonResponse(
|
|
183
176
|
{
|
|
184
|
-
"detail": "Request bloqueado: posible
|
|
177
|
+
"detail": "Request bloqueado: posible inyección SQL",
|
|
185
178
|
"alerts": matches,
|
|
186
179
|
},
|
|
187
180
|
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.11
|
|
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.11
|
|
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.11"
|
|
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
|