GuardianUnivalle-Benito-Yucra 0.1.61__tar.gz → 0.1.63__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.61 → guardianunivalle_benito_yucra-0.1.63}/GuardianUnivalle_Benito_Yucra/detectores/detector_csrf.py +47 -44
- {guardianunivalle_benito_yucra-0.1.61 → guardianunivalle_benito_yucra-0.1.63}/GuardianUnivalle_Benito_Yucra/detectores/detector_sql.py +26 -63
- {guardianunivalle_benito_yucra-0.1.61 → guardianunivalle_benito_yucra-0.1.63}/GuardianUnivalle_Benito_Yucra.egg-info/PKG-INFO +1 -1
- {guardianunivalle_benito_yucra-0.1.61 → guardianunivalle_benito_yucra-0.1.63}/PKG-INFO +1 -1
- {guardianunivalle_benito_yucra-0.1.61 → guardianunivalle_benito_yucra-0.1.63}/pyproject.toml +1 -1
- {guardianunivalle_benito_yucra-0.1.61 → guardianunivalle_benito_yucra-0.1.63}/GuardianUnivalle_Benito_Yucra/__init__.py +0 -0
- {guardianunivalle_benito_yucra-0.1.61 → guardianunivalle_benito_yucra-0.1.63}/GuardianUnivalle_Benito_Yucra/auditoria/registro_auditoria.py +0 -0
- {guardianunivalle_benito_yucra-0.1.61 → guardianunivalle_benito_yucra-0.1.63}/GuardianUnivalle_Benito_Yucra/criptografia/cifrado_aead.py +0 -0
- {guardianunivalle_benito_yucra-0.1.61 → guardianunivalle_benito_yucra-0.1.63}/GuardianUnivalle_Benito_Yucra/criptografia/intercambio_claves.py +0 -0
- {guardianunivalle_benito_yucra-0.1.61 → guardianunivalle_benito_yucra-0.1.63}/GuardianUnivalle_Benito_Yucra/criptografia/kdf.py +0 -0
- {guardianunivalle_benito_yucra-0.1.61 → guardianunivalle_benito_yucra-0.1.63}/GuardianUnivalle_Benito_Yucra/detectores/detector_dos.py +0 -0
- {guardianunivalle_benito_yucra-0.1.61 → guardianunivalle_benito_yucra-0.1.63}/GuardianUnivalle_Benito_Yucra/detectores/detector_keylogger.py +0 -0
- {guardianunivalle_benito_yucra-0.1.61 → guardianunivalle_benito_yucra-0.1.63}/GuardianUnivalle_Benito_Yucra/detectores/detector_xss.py +0 -0
- {guardianunivalle_benito_yucra-0.1.61 → guardianunivalle_benito_yucra-0.1.63}/GuardianUnivalle_Benito_Yucra/middleware_web/middleware_web.py +0 -0
- {guardianunivalle_benito_yucra-0.1.61 → guardianunivalle_benito_yucra-0.1.63}/GuardianUnivalle_Benito_Yucra/mitigacion/limitador_peticion.py +0 -0
- {guardianunivalle_benito_yucra-0.1.61 → guardianunivalle_benito_yucra-0.1.63}/GuardianUnivalle_Benito_Yucra/mitigacion/lista_bloqueo.py +0 -0
- {guardianunivalle_benito_yucra-0.1.61 → guardianunivalle_benito_yucra-0.1.63}/GuardianUnivalle_Benito_Yucra/puntuacion/puntuacion_amenaza.py +0 -0
- {guardianunivalle_benito_yucra-0.1.61 → guardianunivalle_benito_yucra-0.1.63}/GuardianUnivalle_Benito_Yucra/utilidades.py +0 -0
- {guardianunivalle_benito_yucra-0.1.61 → guardianunivalle_benito_yucra-0.1.63}/GuardianUnivalle_Benito_Yucra.egg-info/SOURCES.txt +0 -0
- {guardianunivalle_benito_yucra-0.1.61 → guardianunivalle_benito_yucra-0.1.63}/GuardianUnivalle_Benito_Yucra.egg-info/dependency_links.txt +0 -0
- {guardianunivalle_benito_yucra-0.1.61 → guardianunivalle_benito_yucra-0.1.63}/GuardianUnivalle_Benito_Yucra.egg-info/requires.txt +0 -0
- {guardianunivalle_benito_yucra-0.1.61 → guardianunivalle_benito_yucra-0.1.63}/GuardianUnivalle_Benito_Yucra.egg-info/top_level.txt +0 -0
- {guardianunivalle_benito_yucra-0.1.61 → guardianunivalle_benito_yucra-0.1.63}/LICENSE +0 -0
- {guardianunivalle_benito_yucra-0.1.61 → guardianunivalle_benito_yucra-0.1.63}/README.md +0 -0
- {guardianunivalle_benito_yucra-0.1.61 → guardianunivalle_benito_yucra-0.1.63}/setup.cfg +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# CSRF defense (
|
|
1
|
+
# CSRF defense (versión reforzada)
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
import secrets
|
|
4
4
|
import logging
|
|
@@ -21,23 +21,24 @@ CSRF_HEADER_NAMES = ("HTTP_X_CSRFTOKEN", "HTTP_X_CSRF_TOKEN")
|
|
|
21
21
|
CSRF_COOKIE_NAME = getattr(settings, "CSRF_COOKIE_NAME", "csrftoken")
|
|
22
22
|
POST_FIELD_NAME = "csrfmiddlewaretoken"
|
|
23
23
|
|
|
24
|
-
#
|
|
25
|
-
# porque muchas APIs legítimas usan JSON.
|
|
24
|
+
# Patrón de Content-Type sospechoso
|
|
26
25
|
SUSPICIOUS_CT_PATTERNS = [
|
|
27
26
|
re.compile(r"text/plain", re.I),
|
|
28
27
|
re.compile(r"application/x-www-form-urlencoded", re.I),
|
|
29
28
|
re.compile(r"multipart/form-data", re.I),
|
|
30
29
|
]
|
|
31
30
|
|
|
32
|
-
#
|
|
31
|
+
# Parámetros sensibles típicos de CSRF
|
|
32
|
+
SENSITIVE_PARAMS = [
|
|
33
|
+
"password", "csrfmiddlewaretoken", "token", "amount", "transfer", "delete", "update"
|
|
34
|
+
]
|
|
35
|
+
|
|
33
36
|
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
37
|
CSRF_DEFENSE_EXCLUDED_API_PREFIXES = getattr(settings, "CSRF_DEFENSE_EXCLUDED_API_PREFIXES", ["/api/"])
|
|
36
38
|
|
|
37
39
|
def get_client_ip(request):
|
|
38
40
|
x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR")
|
|
39
41
|
if x_forwarded_for:
|
|
40
|
-
# toma la primera IP real
|
|
41
42
|
ips = [ip.strip() for ip in x_forwarded_for.split(",") if ip.strip()]
|
|
42
43
|
if ips:
|
|
43
44
|
return ips[0]
|
|
@@ -63,25 +64,22 @@ def origin_matches_host(request) -> bool:
|
|
|
63
64
|
referer = request.META.get("HTTP_REFERER", "")
|
|
64
65
|
origin_host = host_from_header(origin)
|
|
65
66
|
referer_host = host_from_header(referer)
|
|
66
|
-
#
|
|
67
|
+
# Bloquear obvious javascript: referers
|
|
67
68
|
if any(re.search(r"(javascript:|<script|data:text/html)", h or "", re.I) for h in [origin, referer]):
|
|
68
69
|
return False
|
|
69
70
|
if origin_host and origin_host == host:
|
|
70
71
|
return True
|
|
71
72
|
if referer_host and referer_host == host:
|
|
72
73
|
return True
|
|
73
|
-
# si no hay origin ni referer, lo consideramos neutral (no marcar)
|
|
74
74
|
if not origin and not referer:
|
|
75
75
|
return True
|
|
76
76
|
return False
|
|
77
77
|
|
|
78
78
|
def has_csrf_token(request) -> bool:
|
|
79
|
-
# busca header, cookie o campo form
|
|
80
79
|
for h in CSRF_HEADER_NAMES:
|
|
81
80
|
if request.META.get(h):
|
|
82
81
|
return True
|
|
83
|
-
|
|
84
|
-
if cookie_val:
|
|
82
|
+
if request.COOKIES.get(CSRF_COOKIE_NAME):
|
|
85
83
|
return True
|
|
86
84
|
try:
|
|
87
85
|
if request.method == "POST" and hasattr(request, "POST"):
|
|
@@ -106,12 +104,25 @@ def extract_payload_text(request) -> str:
|
|
|
106
104
|
parts.append(request.META.get("HTTP_REFERER", ""))
|
|
107
105
|
return " ".join([p for p in parts if p])
|
|
108
106
|
|
|
107
|
+
def extract_parameters(request) -> List[str]:
|
|
108
|
+
params = []
|
|
109
|
+
if hasattr(request, "POST"):
|
|
110
|
+
params.extend(request.POST.keys())
|
|
111
|
+
if hasattr(request, "GET"):
|
|
112
|
+
params.extend(request.GET.keys())
|
|
113
|
+
try:
|
|
114
|
+
if request.body and "application/json" in (request.META.get("CONTENT_TYPE") or ""):
|
|
115
|
+
data = json.loads(request.body)
|
|
116
|
+
params.extend(data.keys())
|
|
117
|
+
except Exception:
|
|
118
|
+
pass
|
|
119
|
+
return params
|
|
120
|
+
|
|
109
121
|
class CSRFDefenseMiddleware(MiddlewareMixin):
|
|
110
122
|
def process_request(self, request):
|
|
111
|
-
#
|
|
123
|
+
# Excluir APIs JSON si se configuró así
|
|
112
124
|
for prefix in CSRF_DEFENSE_EXCLUDED_API_PREFIXES:
|
|
113
125
|
if request.path.startswith(prefix):
|
|
114
|
-
# debug log opcional
|
|
115
126
|
logger.debug(f"[CSRFDefense] Skip analysis for API prefix {prefix} path {request.path}")
|
|
116
127
|
return None
|
|
117
128
|
|
|
@@ -130,6 +141,7 @@ class CSRFDefenseMiddleware(MiddlewareMixin):
|
|
|
130
141
|
|
|
131
142
|
descripcion: List[str] = []
|
|
132
143
|
payload = extract_payload_text(request)
|
|
144
|
+
params = extract_parameters(request)
|
|
133
145
|
|
|
134
146
|
# 1) Falta token CSRF
|
|
135
147
|
if not has_csrf_token(request):
|
|
@@ -139,24 +151,33 @@ class CSRFDefenseMiddleware(MiddlewareMixin):
|
|
|
139
151
|
if not origin_matches_host(request):
|
|
140
152
|
descripcion.append("Origin/Referer no coinciden con Host (posible cross-site)")
|
|
141
153
|
|
|
142
|
-
# 3) Content-Type sospechoso
|
|
154
|
+
# 3) Content-Type sospechoso
|
|
143
155
|
content_type = (request.META.get("CONTENT_TYPE") or "")
|
|
144
156
|
for patt in SUSPICIOUS_CT_PATTERNS:
|
|
145
157
|
if patt.search(content_type):
|
|
146
158
|
descripcion.append(f"Content-Type sospechoso: {content_type}")
|
|
147
159
|
break
|
|
148
160
|
|
|
149
|
-
# 4) Referer ausente y sin
|
|
161
|
+
# 4) Referer ausente y sin token CSRF
|
|
150
162
|
referer = request.META.get("HTTP_REFERER", "")
|
|
151
163
|
if not referer and not any(request.META.get(h) for h in CSRF_HEADER_NAMES):
|
|
152
164
|
descripcion.append("Referer ausente y sin X-CSRFToken")
|
|
153
165
|
|
|
154
|
-
#
|
|
166
|
+
# 5) Parámetros sensibles en GET/JSON
|
|
167
|
+
for p in params:
|
|
168
|
+
if p.lower() in SENSITIVE_PARAMS and method == "GET":
|
|
169
|
+
descripcion.append(f"Parámetro sensible '{p}' enviado en GET (posible CSRF)")
|
|
170
|
+
|
|
171
|
+
# 6) JSON sospechoso desde dominio externo
|
|
172
|
+
if "application/json" in content_type:
|
|
173
|
+
origin = request.META.get("HTTP_ORIGIN") or ""
|
|
174
|
+
if origin and host_from_header(origin) != (request.META.get("HTTP_HOST") or "").split(":")[0]:
|
|
175
|
+
descripcion.append("JSON POST desde origen externo (posible CSRF)")
|
|
176
|
+
|
|
177
|
+
# Señales >= umbral => marcar
|
|
155
178
|
if descripcion and len(descripcion) >= CSRF_DEFENSE_MIN_SIGNALS:
|
|
156
179
|
w_csrf = getattr(settings, "CSRF_DEFENSE_WEIGHT", 0.2)
|
|
157
|
-
|
|
158
|
-
s_csrf = w_csrf * intentos_csrf
|
|
159
|
-
|
|
180
|
+
s_csrf = w_csrf * len(descripcion)
|
|
160
181
|
request.csrf_attack_info = {
|
|
161
182
|
"ip": client_ip,
|
|
162
183
|
"tipos": ["CSRF"],
|
|
@@ -164,40 +185,22 @@ class CSRFDefenseMiddleware(MiddlewareMixin):
|
|
|
164
185
|
"payload": payload,
|
|
165
186
|
"score": s_csrf,
|
|
166
187
|
}
|
|
167
|
-
|
|
168
188
|
logger.warning(
|
|
169
189
|
"CSRF detectado desde IP %s: %s ; path=%s ; Content-Type=%s ; score=%.2f",
|
|
170
190
|
client_ip, descripcion, request.path, content_type, s_csrf
|
|
171
191
|
)
|
|
172
192
|
else:
|
|
173
|
-
# debug útil: saber por qué NO se marcó
|
|
174
193
|
if descripcion:
|
|
175
194
|
logger.debug(f"[CSRFDefense] low-signals ({len(descripcion)}) not marking: {descripcion}")
|
|
176
195
|
|
|
177
196
|
return None
|
|
178
197
|
|
|
179
198
|
"""
|
|
180
|
-
CSRF Defense Middleware
|
|
181
|
-
|
|
182
|
-
Detecta
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
* Integración con detección XSS/SQL Injection mediante registro unificado.
|
|
188
|
-
|
|
189
|
-
Fórmula de amenaza:
|
|
190
|
-
S_csrf = w_csrf * intentos_csrf
|
|
191
|
-
S_csrf = 0.2 * 1
|
|
192
|
-
"""
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
"""
|
|
196
|
-
Algoritmos relacionados:
|
|
197
|
-
*Uso de secreto aleatorio criptográfico.
|
|
198
|
-
*Opcionalmente derivación con PBKDF2 / Argon2 para reforzar token.
|
|
199
|
-
Contribución a fórmula de amenaza S:
|
|
200
|
-
S_csrf = w_csrf * intentos_csrf
|
|
201
|
-
S_csrf = 0.2 * 1
|
|
202
|
-
donde w_csrf es peso asignado a CSRF y intentos_csrf es la cantidad de intentos detectados.
|
|
199
|
+
CSRF Defense Middleware - Reforzado
|
|
200
|
+
===================================
|
|
201
|
+
- Detecta múltiples categorías de CSRF: clásico, login, logout, password change, file/action, JSON API.
|
|
202
|
+
- Escanea payloads POST, GET y JSON.
|
|
203
|
+
- Detecta parámetros sensibles enviados en GET o JSON desde origen externo.
|
|
204
|
+
- Scoring configurable y logging detallado.
|
|
205
|
+
- Fácil integración con auditoría XSS/SQLi.
|
|
203
206
|
"""
|
|
@@ -10,6 +10,9 @@ import urllib.parse
|
|
|
10
10
|
import html
|
|
11
11
|
from typing import List, Tuple, Dict
|
|
12
12
|
|
|
13
|
+
# ----------------------------
|
|
14
|
+
# Configuración del logger
|
|
15
|
+
# ----------------------------
|
|
13
16
|
logger = logging.getLogger("sqlidefense")
|
|
14
17
|
logger.setLevel(logging.INFO)
|
|
15
18
|
if not logger.handlers:
|
|
@@ -18,7 +21,7 @@ if not logger.handlers:
|
|
|
18
21
|
logger.addHandler(handler)
|
|
19
22
|
|
|
20
23
|
# =====================================================
|
|
21
|
-
# ===
|
|
24
|
+
# === PATRONES DE ATAQUE SQL DEFINIDOS ===
|
|
22
25
|
# =====================================================
|
|
23
26
|
SQL_PATTERNS: List[Tuple[re.Pattern, str, float]] = [
|
|
24
27
|
# ------------------ In‑Band / Exfiltration (muy alto) ------------------
|
|
@@ -50,7 +53,7 @@ SQL_PATTERNS: List[Tuple[re.Pattern, str, float]] = [
|
|
|
50
53
|
# ------------------ Metadata / Recon (alto) ------------------
|
|
51
54
|
(re.compile(r"\binformation_schema\b", re.I), "INFORMATION_SCHEMA (recon meta‑datos)", 0.92),
|
|
52
55
|
(re.compile(r"\b(information_schema\.tables|information_schema\.columns)\b", re.I), "INFORMATION_SCHEMA.tables/columns", 0.92),
|
|
53
|
-
(re.compile(r"\b(sys\.tables|sys\.objects|sys\.databases|pg_catalog|pg_tables|pg_user)\b", re.I), "
|
|
56
|
+
(re.compile(r"\b(sys\.tables|sys\.objects|sys\.databases|pg_catalog|pg_tables|pg_user)\b", re.I), "Catálogos del sistema (MSSQL/Postgres)", 0.9),
|
|
54
57
|
|
|
55
58
|
# ------------------ DML/DDL Destructivo (alto) ------------------
|
|
56
59
|
(re.compile(r"\b(drop\s+table|truncate\s+table|drop\s+database|drop\s+schema)\b", re.I), "DROP/TRUNCATE (DDL destructivo)", 0.95),
|
|
@@ -107,27 +110,23 @@ SQL_PATTERNS: List[Tuple[re.Pattern, str, float]] = [
|
|
|
107
110
|
(re.compile(r"\b(or)\b\s+1\s*=\s*1\b", re.I), "OR 1=1 tautology", 0.85),
|
|
108
111
|
]
|
|
109
112
|
|
|
110
|
-
|
|
111
113
|
IGNORED_FIELDS = ["password", "csrfmiddlewaretoken", "token", "auth"]
|
|
112
114
|
|
|
115
|
+
# ----------------------------
|
|
116
|
+
# Obtener IP real del cliente
|
|
117
|
+
# ----------------------------
|
|
113
118
|
def get_client_ip(request):
|
|
114
|
-
"""
|
|
115
|
-
Obtiene la IP real del cliente.
|
|
116
|
-
Primero revisa 'X-Forwarded-For', luego 'REMOTE_ADDR'.
|
|
117
|
-
"""
|
|
118
119
|
x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR")
|
|
119
120
|
if x_forwarded_for:
|
|
120
|
-
# Render y otros proxies envían múltiples IPs separados por coma
|
|
121
121
|
ips = [ip.strip() for ip in x_forwarded_for.split(",") if ip.strip()]
|
|
122
122
|
if ips:
|
|
123
|
-
return ips[0]
|
|
124
|
-
# Si no hay X-Forwarded-For, tomar REMOTE_ADDR
|
|
123
|
+
return ips[0]
|
|
125
124
|
return request.META.get("REMOTE_ADDR", "")
|
|
126
125
|
|
|
127
|
-
|
|
128
|
-
|
|
126
|
+
# ----------------------------
|
|
127
|
+
# Extraer payload de la solicitud
|
|
128
|
+
# ----------------------------
|
|
129
129
|
def extract_payload(request):
|
|
130
|
-
"""Extrae datos útiles de la solicitud para análisis."""
|
|
131
130
|
parts = []
|
|
132
131
|
try:
|
|
133
132
|
if "application/json" in request.META.get("CONTENT_TYPE", ""):
|
|
@@ -146,50 +145,29 @@ def extract_payload(request):
|
|
|
146
145
|
|
|
147
146
|
return " ".join(parts)
|
|
148
147
|
|
|
149
|
-
|
|
150
148
|
# ----------------------------
|
|
151
149
|
# Normalización / preprocesamiento
|
|
152
150
|
# ----------------------------
|
|
153
151
|
def normalize_input(s: str) -> str:
|
|
154
|
-
"""
|
|
155
|
-
Normaliza la entrada:
|
|
156
|
-
- decode URL encoding
|
|
157
|
-
- unescape HTML entities
|
|
158
|
-
- sustituye escapes de hex (\xNN) por su literal
|
|
159
|
-
- colapsa múltiples espacios y newlines
|
|
160
|
-
- lower() opcional (NO se hace porque algunos patrones usan case-insensitive)
|
|
161
|
-
"""
|
|
162
152
|
if not s:
|
|
163
153
|
return ""
|
|
164
154
|
try:
|
|
165
|
-
# URL decode
|
|
166
155
|
s_dec = urllib.parse.unquote_plus(s)
|
|
167
156
|
except Exception:
|
|
168
157
|
s_dec = s
|
|
169
158
|
try:
|
|
170
|
-
# Unescape HTML entities
|
|
171
159
|
s_dec = html.unescape(s_dec)
|
|
172
160
|
except Exception:
|
|
173
161
|
pass
|
|
174
|
-
#
|
|
175
|
-
s_dec = re.sub(r"\\x([0-9a-fA-F]{2})", r"\\x\1", s_dec)
|
|
176
|
-
# Collapse whitespace
|
|
162
|
+
# Reemplazo seguro de secuencias \xNN
|
|
163
|
+
s_dec = re.sub(r"\\x([0-9a-fA-F]{2})", r"\\x\g<1>", s_dec)
|
|
177
164
|
s_dec = re.sub(r"\s+", " ", s_dec)
|
|
178
165
|
return s_dec.strip()
|
|
166
|
+
|
|
179
167
|
# ----------------------------
|
|
180
|
-
# Detector
|
|
168
|
+
# Detector SQLi
|
|
181
169
|
# ----------------------------
|
|
182
170
|
def detect_sql_injection(text: str) -> Dict:
|
|
183
|
-
"""
|
|
184
|
-
Detecta coincidencias con SQL_PATTERNS en el texto normalizado.
|
|
185
|
-
Devuelve:
|
|
186
|
-
{
|
|
187
|
-
"score": float,
|
|
188
|
-
"matches": [("Etiqueta", "regex pattern string", weight), ...],
|
|
189
|
-
"descriptions": [ "Etiqueta", ... ],
|
|
190
|
-
"sample": excerpt (primeros 1200 chars)
|
|
191
|
-
}
|
|
192
|
-
"""
|
|
193
171
|
norm = normalize_input(text or "")
|
|
194
172
|
score = 0.0
|
|
195
173
|
matches = []
|
|
@@ -203,17 +181,10 @@ def detect_sql_injection(text: str) -> Dict:
|
|
|
203
181
|
return {
|
|
204
182
|
"score": round(score, 3),
|
|
205
183
|
"matches": matches,
|
|
206
|
-
"descriptions": list(dict.fromkeys(descriptions)),
|
|
184
|
+
"descriptions": list(dict.fromkeys(descriptions)),
|
|
207
185
|
"sample": norm[:1200],
|
|
208
186
|
}
|
|
209
187
|
|
|
210
|
-
# ----------------------------
|
|
211
|
-
# Umbrales sugeridos
|
|
212
|
-
# ----------------------------
|
|
213
|
-
# - >= 1.8 : ALTA (bloquear + registrar)
|
|
214
|
-
# - 1.0 - <1.8 : MEDIA (registrar, desafío/2FA, throttling)
|
|
215
|
-
# - 0.5 - <1.0 : BAJA (registrar, monitorear)
|
|
216
|
-
# - <0.5 : INFORMATIVO (log heurístico)
|
|
217
188
|
DEFAULT_THRESHOLDS = {
|
|
218
189
|
"HIGH": 1.8,
|
|
219
190
|
"MEDIUM": 1.0,
|
|
@@ -221,17 +192,9 @@ DEFAULT_THRESHOLDS = {
|
|
|
221
192
|
}
|
|
222
193
|
|
|
223
194
|
# ----------------------------
|
|
224
|
-
#
|
|
195
|
+
# Middleware SQLi
|
|
225
196
|
# ----------------------------
|
|
226
|
-
# payload = extract_payload(request) # tu función actual
|
|
227
|
-
# result = detect_sql_injection(payload)
|
|
228
|
-
# if result["score"] >= DEFAULT_THRESHOLDS["HIGH"]:
|
|
229
|
-
# bloquear_y_registrar()
|
|
230
|
-
# else:
|
|
231
|
-
# registrar_solo()
|
|
232
197
|
class SQLIDefenseMiddleware(MiddlewareMixin):
|
|
233
|
-
"""Middleware de detección SQL Injection."""
|
|
234
|
-
|
|
235
198
|
def process_request(self, request):
|
|
236
199
|
client_ip = get_client_ip(request)
|
|
237
200
|
trusted_ips = getattr(settings, "SQLI_DEFENSE_TRUSTED_IPS", [])
|
|
@@ -246,35 +209,35 @@ class SQLIDefenseMiddleware(MiddlewareMixin):
|
|
|
246
209
|
return None
|
|
247
210
|
|
|
248
211
|
payload = extract_payload(request)
|
|
249
|
-
|
|
212
|
+
result = detect_sql_injection(payload)
|
|
213
|
+
score = result["score"]
|
|
214
|
+
descripciones = result["descriptions"]
|
|
250
215
|
|
|
251
216
|
if score == 0:
|
|
252
217
|
return None
|
|
253
218
|
|
|
254
|
-
# Registrar ataque
|
|
219
|
+
# Registrar ataque
|
|
255
220
|
logger.warning(
|
|
256
221
|
f"[SQLiDetect] IP={client_ip} Host={host} Referer={referer} "
|
|
257
222
|
f"Score={score:.2f} Desc={descripciones} Payload={payload[:500]}"
|
|
258
223
|
)
|
|
259
224
|
|
|
260
|
-
# Guardar
|
|
225
|
+
# Guardar info en request
|
|
261
226
|
request.sql_attack_info = {
|
|
262
227
|
"ip": client_ip,
|
|
263
228
|
"tipos": ["SQLi"],
|
|
264
229
|
"descripcion": descripciones,
|
|
265
|
-
"payload": payload[:1000],
|
|
230
|
+
"payload": payload[:1000],
|
|
266
231
|
"score": round(score, 2),
|
|
267
|
-
"url": request.build_absolute_uri(),
|
|
232
|
+
"url": request.build_absolute_uri(),
|
|
268
233
|
}
|
|
269
234
|
|
|
270
235
|
return None
|
|
271
236
|
|
|
272
|
-
|
|
273
|
-
|
|
274
237
|
# =====================================================
|
|
275
238
|
# === INFORMACIÓN EXTRA ===
|
|
276
239
|
# =====================================================
|
|
277
|
-
"""
|
|
240
|
+
r"""
|
|
278
241
|
Algoritmos relacionados:
|
|
279
242
|
- Se recomienda almacenar logs SQLi cifrados (AES-GCM)
|
|
280
243
|
para proteger evidencia de intentos maliciosos.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: GuardianUnivalle-Benito-Yucra
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.63
|
|
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.63
|
|
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
|
{guardianunivalle_benito_yucra-0.1.61 → guardianunivalle_benito_yucra-0.1.63}/pyproject.toml
RENAMED
|
@@ -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.63"
|
|
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
|