GuardianUnivalle-Benito-Yucra 0.1.29__py3-none-any.whl → 0.1.30__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.
- GuardianUnivalle_Benito_Yucra/detectores/detector_csrf.py +222 -4
- GuardianUnivalle_Benito_Yucra/detectores/detector_dos.py +152 -3
- GuardianUnivalle_Benito_Yucra/detectores/detector_keylogger.py +167 -8
- GuardianUnivalle_Benito_Yucra/detectores/detector_sql.py +140 -41
- GuardianUnivalle_Benito_Yucra/detectores/detector_xss.py +88 -30
- {guardianunivalle_benito_yucra-0.1.29.dist-info → guardianunivalle_benito_yucra-0.1.30.dist-info}/METADATA +1 -1
- {guardianunivalle_benito_yucra-0.1.29.dist-info → guardianunivalle_benito_yucra-0.1.30.dist-info}/RECORD +10 -10
- {guardianunivalle_benito_yucra-0.1.29.dist-info → guardianunivalle_benito_yucra-0.1.30.dist-info}/WHEEL +0 -0
- {guardianunivalle_benito_yucra-0.1.29.dist-info → guardianunivalle_benito_yucra-0.1.30.dist-info}/licenses/LICENSE +0 -0
- {guardianunivalle_benito_yucra-0.1.29.dist-info → guardianunivalle_benito_yucra-0.1.30.dist-info}/top_level.txt +0 -0
|
@@ -1,15 +1,233 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CSRF Defense Middleware
|
|
3
|
+
========================
|
|
4
|
+
Detecta y registra posibles ataques CSRF (Cross-Site Request Forgery).
|
|
5
|
+
|
|
6
|
+
Algoritmos relacionados:
|
|
7
|
+
* Uso de secreto aleatorio criptográfico (generar_token_csrf).
|
|
8
|
+
* Validación simple por comparación (validar_token_csrf).
|
|
9
|
+
* Contribución a fórmula de amenaza S:
|
|
10
|
+
S_csrf = w_csrf * intentos_csrf
|
|
11
|
+
S_csrf = 0.2 * 1
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
1
15
|
import secrets
|
|
2
|
-
|
|
16
|
+
import logging
|
|
17
|
+
import re
|
|
18
|
+
import json
|
|
19
|
+
from typing import List
|
|
20
|
+
from urllib.parse import urlparse
|
|
21
|
+
from django.conf import settings
|
|
22
|
+
from django.utils.deprecation import MiddlewareMixin
|
|
23
|
+
|
|
24
|
+
# ======================================================
|
|
25
|
+
# === CONFIGURACIÓN DE LOGGER ===
|
|
26
|
+
# ======================================================
|
|
27
|
+
logger = logging.getLogger("csrfdefense")
|
|
28
|
+
logger.setLevel(logging.INFO)
|
|
29
|
+
if not logger.handlers:
|
|
30
|
+
handler = logging.StreamHandler()
|
|
31
|
+
handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s"))
|
|
32
|
+
logger.addHandler(handler)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# ======================================================
|
|
36
|
+
# === FUNCIONES AUXILIARES DE TOKEN CSRF ===
|
|
37
|
+
# ======================================================
|
|
38
|
+
def registrar_evento(tipo: str, mensaje: str):
|
|
39
|
+
"""Registra eventos importantes en los logs."""
|
|
40
|
+
logger.warning(f"[{tipo}] {mensaje}")
|
|
41
|
+
|
|
3
42
|
|
|
4
43
|
def generar_token_csrf() -> str:
|
|
5
|
-
|
|
44
|
+
"""Genera un token CSRF seguro."""
|
|
45
|
+
token = secrets.token_hex(32)
|
|
46
|
+
registrar_evento("CSRF", "Token CSRF generado")
|
|
47
|
+
return token
|
|
48
|
+
|
|
6
49
|
|
|
7
50
|
def validar_token_csrf(token: str, token_sesion: str) -> bool:
|
|
51
|
+
"""Valida que el token recibido coincida con el token en sesión."""
|
|
8
52
|
valido = token == token_sesion
|
|
9
53
|
if not valido:
|
|
10
|
-
registrar_evento("CSRF", "Intento de CSRF detectado")
|
|
54
|
+
registrar_evento("CSRF", "Intento de CSRF detectado (token no coincide)")
|
|
11
55
|
return valido
|
|
12
56
|
|
|
57
|
+
|
|
58
|
+
# ======================================================
|
|
59
|
+
# === CONSTANTES Y CONFIGURACIONES ===
|
|
60
|
+
# ======================================================
|
|
61
|
+
STATE_CHANGING_METHODS = {"POST", "PUT", "PATCH", "DELETE"}
|
|
62
|
+
CSRF_HEADER_NAMES = (
|
|
63
|
+
"HTTP_X_CSRFTOKEN",
|
|
64
|
+
"HTTP_X_CSRF_TOKEN",
|
|
65
|
+
)
|
|
66
|
+
CSRF_COOKIE_NAME = getattr(settings, "CSRF_COOKIE_NAME", "csrftoken")
|
|
67
|
+
POST_FIELD_NAME = "csrfmiddlewaretoken"
|
|
68
|
+
|
|
69
|
+
SUSPICIOUS_CT_PATTERNS = [
|
|
70
|
+
re.compile(r"application/x-www-form-urlencoded", re.I),
|
|
71
|
+
re.compile(r"multipart/form-data", re.I),
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
# ======================================================
|
|
76
|
+
# === FUNCIONES DE APOYO ===
|
|
77
|
+
# ======================================================
|
|
78
|
+
def get_client_ip(request):
|
|
79
|
+
x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR")
|
|
80
|
+
if x_forwarded_for:
|
|
81
|
+
return x_forwarded_for.split(",")[0].strip()
|
|
82
|
+
return request.META.get("REMOTE_ADDR", "")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def host_from_header(header_value: str) -> str | None:
|
|
86
|
+
if not header_value:
|
|
87
|
+
return None
|
|
88
|
+
try:
|
|
89
|
+
parsed = urlparse(header_value)
|
|
90
|
+
if parsed.netloc:
|
|
91
|
+
return parsed.netloc.split(":")[0]
|
|
92
|
+
return header_value.split(":")[0]
|
|
93
|
+
except Exception:
|
|
94
|
+
return None
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def origin_matches_host(request) -> bool:
|
|
98
|
+
"""Verifica si Origin/Referer coinciden con Host."""
|
|
99
|
+
host_header = request.META.get("HTTP_HOST") or request.META.get("SERVER_NAME")
|
|
100
|
+
if not host_header:
|
|
101
|
+
return True
|
|
102
|
+
|
|
103
|
+
host = host_header.split(":")[0]
|
|
104
|
+
origin = request.META.get("HTTP_ORIGIN", "")
|
|
105
|
+
referer = request.META.get("HTTP_REFERER", "")
|
|
106
|
+
|
|
107
|
+
origin_host = host_from_header(origin)
|
|
108
|
+
referer_host = host_from_header(referer)
|
|
109
|
+
|
|
110
|
+
if origin_host and origin_host == host:
|
|
111
|
+
return True
|
|
112
|
+
if referer_host and referer_host == host:
|
|
113
|
+
return True
|
|
114
|
+
if not origin and not referer:
|
|
115
|
+
return True
|
|
116
|
+
|
|
117
|
+
return False
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def has_csrf_token(request) -> bool:
|
|
121
|
+
"""Comprueba si hay signos de token CSRF presente."""
|
|
122
|
+
for h in CSRF_HEADER_NAMES:
|
|
123
|
+
if request.META.get(h):
|
|
124
|
+
return True
|
|
125
|
+
|
|
126
|
+
cookie_val = request.COOKIES.get(CSRF_COOKIE_NAME)
|
|
127
|
+
if cookie_val:
|
|
128
|
+
return True
|
|
129
|
+
|
|
130
|
+
try:
|
|
131
|
+
if request.method == "POST" and hasattr(request, "POST"):
|
|
132
|
+
if request.POST.get(POST_FIELD_NAME):
|
|
133
|
+
return True
|
|
134
|
+
except Exception:
|
|
135
|
+
pass
|
|
136
|
+
|
|
137
|
+
return False
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def extract_payload_text(request) -> str:
|
|
141
|
+
"""Extrae contenido útil de la solicitud para análisis."""
|
|
142
|
+
parts: List[str] = []
|
|
143
|
+
try:
|
|
144
|
+
body = request.body.decode("utf-8", errors="ignore")
|
|
145
|
+
if body:
|
|
146
|
+
parts.append(body)
|
|
147
|
+
except Exception:
|
|
148
|
+
pass
|
|
149
|
+
qs = request.META.get("QUERY_STRING", "")
|
|
150
|
+
if qs:
|
|
151
|
+
parts.append(qs)
|
|
152
|
+
parts.append(request.META.get("HTTP_USER_AGENT", ""))
|
|
153
|
+
parts.append(request.META.get("HTTP_REFERER", ""))
|
|
154
|
+
return " ".join([p for p in parts if p])
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
# ======================================================
|
|
158
|
+
# === MIDDLEWARE DE DEFENSA CSRF ===
|
|
159
|
+
# ======================================================
|
|
160
|
+
class CSRFDefenseMiddleware(MiddlewareMixin):
|
|
161
|
+
"""
|
|
162
|
+
Middleware para DETECTAR intentos de CSRF:
|
|
163
|
+
- Marca request.sql_attack_info con 'tipos': ['CSRF'] y 'descripcion' con razones.
|
|
164
|
+
- No bloquea la petición directamente, permite que AuditoriaMiddleware lo maneje.
|
|
165
|
+
"""
|
|
166
|
+
|
|
167
|
+
def process_request(self, request):
|
|
168
|
+
client_ip = get_client_ip(request)
|
|
169
|
+
trusted_ips = getattr(settings, "CSRF_DEFENSE_TRUSTED_IPS", [])
|
|
170
|
+
if client_ip in trusted_ips:
|
|
171
|
+
return None
|
|
172
|
+
|
|
173
|
+
excluded_paths = getattr(settings, "CSRF_DEFENSE_EXCLUDED_PATHS", [])
|
|
174
|
+
if any(request.path.startswith(p) for p in excluded_paths):
|
|
175
|
+
return None
|
|
176
|
+
|
|
177
|
+
method = (request.method or "").upper()
|
|
178
|
+
if method not in STATE_CHANGING_METHODS:
|
|
179
|
+
return None
|
|
180
|
+
|
|
181
|
+
descripcion: List[str] = []
|
|
182
|
+
payload = extract_payload_text(request)
|
|
183
|
+
|
|
184
|
+
# 1) Falta token CSRF
|
|
185
|
+
if not has_csrf_token(request):
|
|
186
|
+
descripcion.append("Falta token CSRF en cookie/header/form")
|
|
187
|
+
|
|
188
|
+
# 2) Origin/Referer no coinciden
|
|
189
|
+
if not origin_matches_host(request):
|
|
190
|
+
descripcion.append(
|
|
191
|
+
"Origin/Referer no coinciden con Host (posible cross-site)"
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
# 3) Content-Type sospechoso
|
|
195
|
+
content_type = request.META.get("CONTENT_TYPE", "") or ""
|
|
196
|
+
for patt in SUSPICIOUS_CT_PATTERNS:
|
|
197
|
+
if patt.search(content_type):
|
|
198
|
+
descripcion.append(f"Content-Type sospechoso: {content_type}")
|
|
199
|
+
break
|
|
200
|
+
|
|
201
|
+
# 4) Referer ausente
|
|
202
|
+
referer = request.META.get("HTTP_REFERER", "")
|
|
203
|
+
if not referer and not any(request.META.get(h) for h in CSRF_HEADER_NAMES):
|
|
204
|
+
descripcion.append("Referer ausente y sin X-CSRFToken")
|
|
205
|
+
|
|
206
|
+
# Si hay señales, calculamos puntaje y registramos
|
|
207
|
+
if descripcion:
|
|
208
|
+
w_csrf = getattr(settings, "CSRF_DEFENSE_WEIGHT", 0.2)
|
|
209
|
+
intentos_csrf = len(descripcion)
|
|
210
|
+
s_csrf = w_csrf * intentos_csrf
|
|
211
|
+
|
|
212
|
+
request.sql_attack_info = {
|
|
213
|
+
"ip": client_ip,
|
|
214
|
+
"tipos": ["CSRF"],
|
|
215
|
+
"descripcion": descripcion,
|
|
216
|
+
"payload": payload,
|
|
217
|
+
"score": s_csrf,
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
logger.warning(
|
|
221
|
+
"CSRF detectado desde IP %s: %s ; payload: %.200s ; score: %.2f",
|
|
222
|
+
client_ip,
|
|
223
|
+
descripcion,
|
|
224
|
+
payload,
|
|
225
|
+
s_csrf,
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
return None
|
|
229
|
+
|
|
230
|
+
|
|
13
231
|
"""
|
|
14
232
|
Algoritmos relacionados:
|
|
15
233
|
*Uso de secreto aleatorio criptográfico.
|
|
@@ -18,4 +236,4 @@ Contribución a fórmula de amenaza S:
|
|
|
18
236
|
S_csrf = w_csrf * intentos_csrf
|
|
19
237
|
S_csrf = 0.2 * 1
|
|
20
238
|
donde w_csrf es peso asignado a CSRF y intentos_csrf es la cantidad de intentos detectados.
|
|
21
|
-
"""
|
|
239
|
+
"""
|
|
@@ -1,11 +1,160 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Detector de ataques de tipo DoS (Denial of Service)
|
|
3
|
+
====================================================
|
|
4
|
+
|
|
5
|
+
Este módulo forma parte del sistema de detección de amenazas.
|
|
6
|
+
Detecta tasas de petición anómalas en base a límites configurables,
|
|
7
|
+
captura datos del atacante (IP, agente, cabeceras)
|
|
8
|
+
y registra los incidentes para su auditoría.
|
|
9
|
+
|
|
10
|
+
Componentes:
|
|
11
|
+
- DOSDefenseMiddleware: Middleware principal de detección.
|
|
12
|
+
- detectar_dos(): Evalúa si la tasa supera el umbral permitido.
|
|
13
|
+
- calcular_nivel_amenaza_dos(): Calcula la severidad proporcional.
|
|
14
|
+
- registrar_evento(): Registra los incidentes en auditoría.
|
|
15
|
+
|
|
16
|
+
Algoritmos relacionados:
|
|
17
|
+
* Rate Limiting basado en ventana deslizante.
|
|
18
|
+
* Cálculo de score: S_dos = w_dos * (tasa_peticion / limite)
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
import time
|
|
23
|
+
import logging
|
|
24
|
+
import json
|
|
25
|
+
from typing import Dict, List
|
|
26
|
+
from django.conf import settings
|
|
27
|
+
from django.utils.deprecation import MiddlewareMixin
|
|
1
28
|
from ..mitigacion.limitador_peticion import limitar_peticion
|
|
2
29
|
from ..auditoria.registro_auditoria import registrar_evento
|
|
3
30
|
|
|
4
|
-
|
|
31
|
+
# =====================================================
|
|
32
|
+
# === CONFIGURACIÓN DEL LOGGER ===
|
|
33
|
+
# =====================================================
|
|
34
|
+
logger = logging.getLogger("dosdefense")
|
|
35
|
+
logger.setLevel(logging.INFO)
|
|
36
|
+
if not logger.handlers:
|
|
37
|
+
handler = logging.StreamHandler()
|
|
38
|
+
handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s"))
|
|
39
|
+
logger.addHandler(handler)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# =====================================================
|
|
43
|
+
# === PARÁMETROS DE CONFIGURACIÓN BASE ===
|
|
44
|
+
# =====================================================
|
|
45
|
+
LIMITE_PETICIONES = getattr(settings, "DOS_LIMITE_PETICIONES", 100) # por minuto
|
|
46
|
+
VENTANA_SEGUNDOS = getattr(settings, "DOS_VENTANA_SEGUNDOS", 60)
|
|
47
|
+
PESO_DOS = getattr(settings, "DOS_PESO", 0.6)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# =====================================================
|
|
51
|
+
# === REGISTRO TEMPORAL DE SOLICITUDES POR IP ===
|
|
52
|
+
# =====================================================
|
|
53
|
+
# En producción se recomienda usar Redis o Memcached
|
|
54
|
+
# para este tipo de conteo, aquí usamos una memoria temporal.
|
|
55
|
+
REGISTRO_SOLICITUDES: Dict[str, List[float]] = {}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# =====================================================
|
|
59
|
+
# === FUNCIONES AUXILIARES ===
|
|
60
|
+
# =====================================================
|
|
61
|
+
def get_client_ip(request) -> str:
|
|
62
|
+
"""Obtiene la IP real del cliente (considera proxies)."""
|
|
63
|
+
x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR")
|
|
64
|
+
if x_forwarded_for:
|
|
65
|
+
return x_forwarded_for.split(",")[0].strip()
|
|
66
|
+
return request.META.get("REMOTE_ADDR", "")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def limpiar_registro(ip: str):
|
|
70
|
+
"""Limpia peticiones antiguas fuera de la ventana de tiempo."""
|
|
71
|
+
ahora = time.time()
|
|
72
|
+
REGISTRO_SOLICITUDES[ip] = [
|
|
73
|
+
t for t in REGISTRO_SOLICITUDES.get(ip, []) if ahora - t < VENTANA_SEGUNDOS
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def calcular_nivel_amenaza_dos(
|
|
78
|
+
tasa_peticion: int, limite: int = LIMITE_PETICIONES
|
|
79
|
+
) -> float:
|
|
80
|
+
"""
|
|
81
|
+
Calcula la puntuación de amenaza DoS basada en el peso configurado.
|
|
82
|
+
Fórmula: S_dos = w_dos * (tasa_peticion / limite)
|
|
83
|
+
"""
|
|
84
|
+
proporcion = tasa_peticion / limite
|
|
85
|
+
s_dos = PESO_DOS * proporcion
|
|
86
|
+
return round(min(s_dos, 1.0), 3)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def detectar_dos(ip: str, tasa_peticion: int, limite: int = LIMITE_PETICIONES) -> bool:
|
|
90
|
+
"""Evalúa si la tasa de peticiones excede el umbral permitido."""
|
|
5
91
|
if tasa_peticion > limite:
|
|
6
|
-
registrar_evento(
|
|
92
|
+
registrar_evento(
|
|
93
|
+
tipo="DoS",
|
|
94
|
+
descripcion=f"Alta tasa de peticiones desde {ip}: {tasa_peticion} req/min (límite {limite})",
|
|
95
|
+
severidad="ALTA",
|
|
96
|
+
)
|
|
97
|
+
limitar_peticion() # Acción de mitigación
|
|
7
98
|
return True
|
|
8
99
|
return False
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
# =====================================================
|
|
103
|
+
# === MIDDLEWARE DE DETECCIÓN DE DoS ===
|
|
104
|
+
# =====================================================
|
|
105
|
+
class DOSDefenseMiddleware(MiddlewareMixin):
|
|
106
|
+
"""
|
|
107
|
+
Middleware para detección y registro de ataques DoS.
|
|
108
|
+
- Captura IP, agente y cabeceras sospechosas.
|
|
109
|
+
- Evalúa la frecuencia de peticiones por IP.
|
|
110
|
+
- Marca request.sql_attack_info con información del intento.
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
def process_request(self, request):
|
|
114
|
+
client_ip = get_client_ip(request)
|
|
115
|
+
user_agent = request.META.get("HTTP_USER_AGENT", "Desconocido")
|
|
116
|
+
referer = request.META.get("HTTP_REFERER", "")
|
|
117
|
+
path = request.path
|
|
118
|
+
|
|
119
|
+
# Limpieza de registro anterior
|
|
120
|
+
limpiar_registro(client_ip)
|
|
121
|
+
|
|
122
|
+
# Registrar nueva petición
|
|
123
|
+
REGISTRO_SOLICITUDES.setdefault(client_ip, []).append(time.time())
|
|
124
|
+
tasa = len(REGISTRO_SOLICITUDES[client_ip])
|
|
125
|
+
nivel = calcular_nivel_amenaza_dos(tasa)
|
|
126
|
+
es_dos = detectar_dos(client_ip, tasa)
|
|
127
|
+
|
|
128
|
+
if es_dos:
|
|
129
|
+
descripcion = [
|
|
130
|
+
f"Tasa de {tasa} req/min excede límite {LIMITE_PETICIONES}",
|
|
131
|
+
f"User-Agent: {user_agent}",
|
|
132
|
+
f"Referer: {referer or 'N/A'}",
|
|
133
|
+
f"Ruta: {path}",
|
|
134
|
+
]
|
|
135
|
+
|
|
136
|
+
# Log profesional
|
|
137
|
+
logger.warning(
|
|
138
|
+
"DoS detectado desde IP %s: %s ; nivel: %.2f",
|
|
139
|
+
client_ip,
|
|
140
|
+
descripcion,
|
|
141
|
+
nivel,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
# Enviar a sistema de auditoría
|
|
145
|
+
request.sql_attack_info = {
|
|
146
|
+
"ip": client_ip,
|
|
147
|
+
"tipos": ["DoS"],
|
|
148
|
+
"descripcion": descripcion,
|
|
149
|
+
"payload": json.dumps(
|
|
150
|
+
{"user_agent": user_agent, "referer": referer, "path": path}
|
|
151
|
+
),
|
|
152
|
+
"score": nivel,
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return None
|
|
156
|
+
|
|
157
|
+
|
|
9
158
|
"""
|
|
10
159
|
Algoritmos relacionados:
|
|
11
160
|
*Rate Limiting, listas de bloqueo.
|
|
@@ -14,4 +163,4 @@ Contribución a fórmula de amenaza S:
|
|
|
14
163
|
S_dos = w_dos * (tasa_peticion / limite)
|
|
15
164
|
S_dos = 0.6 * (150 / 100)
|
|
16
165
|
donde w_dos es peso asignado a DoS y tasa_peticion / limite es la proporción de la tasa actual sobre el límite.
|
|
17
|
-
"""
|
|
166
|
+
"""
|
|
@@ -1,13 +1,172 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Detector extendido de Keyloggers
|
|
3
|
+
================================
|
|
4
|
+
|
|
5
|
+
Módulo avanzado de detección de keyloggers y software espía en el sistema.
|
|
6
|
+
Incluye revisión de procesos activos, archivos ejecutables sospechosos y
|
|
7
|
+
aplicaciones instaladas en el sistema operativo Windows.
|
|
8
|
+
|
|
9
|
+
Componentes:
|
|
10
|
+
- Escaneo de procesos activos.
|
|
11
|
+
- Detección de archivos con extensiones críticas (.exe, .dll, .scr, .bat, .cmd, .msi).
|
|
12
|
+
- Revisión de aplicaciones instaladas (si se ejecuta en Windows).
|
|
13
|
+
- Cálculo de nivel de amenaza y registro de auditoría.
|
|
14
|
+
|
|
15
|
+
Algoritmos:
|
|
16
|
+
* Revisión de procesos (psutil)
|
|
17
|
+
* Análisis de archivos con extensiones críticas
|
|
18
|
+
* Detección de software instalado
|
|
19
|
+
* Registro cifrado con AES-256 + SHA-512
|
|
20
|
+
* Fórmula: S_keylogger = w_keylogger * (procesos + archivos + instalaciones)
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
1
24
|
import psutil
|
|
25
|
+
import os
|
|
26
|
+
import logging
|
|
27
|
+
import platform
|
|
28
|
+
import subprocess
|
|
29
|
+
from typing import List, Dict
|
|
30
|
+
from django.conf import settings
|
|
2
31
|
from ..auditoria.registro_auditoria import registrar_evento
|
|
3
32
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
33
|
+
# =====================================================
|
|
34
|
+
# === CONFIGURACIÓN DEL LOGGER ===
|
|
35
|
+
# =====================================================
|
|
36
|
+
logger = logging.getLogger("keyloggerdefense")
|
|
37
|
+
logger.setLevel(logging.INFO)
|
|
38
|
+
if not logger.handlers:
|
|
39
|
+
handler = logging.StreamHandler()
|
|
40
|
+
handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s"))
|
|
41
|
+
logger.addHandler(handler)
|
|
42
|
+
|
|
43
|
+
# =====================================================
|
|
44
|
+
# === CONFIGURACIÓN DE PARÁMETROS ===
|
|
45
|
+
# =====================================================
|
|
46
|
+
PESO_KEYLOGGER = getattr(settings, "KEYLOGGER_PESO", 0.4)
|
|
47
|
+
EXTENSIONES_SOSPECHOSAS = [".exe", ".dll", ".scr", ".bat", ".cmd", ".msi"]
|
|
48
|
+
CARPETAS_CRITICAS = [
|
|
49
|
+
"C:\\Users\\Public",
|
|
50
|
+
"C:\\Users\\%USERNAME%\\AppData\\Roaming",
|
|
51
|
+
"C:\\Users\\%USERNAME%\\AppData\\Local\\Temp",
|
|
52
|
+
"C:\\ProgramData",
|
|
53
|
+
"C:\\Windows\\Temp",
|
|
54
|
+
]
|
|
55
|
+
PATRONES_NOMBRES = ["keylogger", "spy", "hook", "keyboard", "capture", "stealer"]
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# =====================================================
|
|
59
|
+
# === FUNCIONES AUXILIARES ===
|
|
60
|
+
# =====================================================
|
|
61
|
+
def calcular_score_keylogger(total_items: int) -> float:
|
|
62
|
+
"""Calcula el nivel de amenaza normalizado."""
|
|
63
|
+
return round(min(PESO_KEYLOGGER * total_items, 1.0), 3)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def detectar_procesos_sospechosos() -> List[Dict]:
|
|
67
|
+
"""Escanea procesos activos y detecta posibles keyloggers."""
|
|
68
|
+
hallazgos = []
|
|
69
|
+
for proc in psutil.process_iter(["pid", "name", "exe"]):
|
|
70
|
+
try:
|
|
71
|
+
nombre = proc.info.get("name", "").lower()
|
|
72
|
+
if any(pat in nombre for pat in PATRONES_NOMBRES):
|
|
73
|
+
hallazgos.append(proc.info)
|
|
74
|
+
registrar_evento("Keylogger", f"Proceso sospechoso: {proc.info}")
|
|
75
|
+
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
|
76
|
+
continue
|
|
77
|
+
return hallazgos
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def detectar_archivos_sospechosos() -> List[str]:
|
|
81
|
+
"""
|
|
82
|
+
Busca archivos con extensiones peligrosas y nombres relacionados
|
|
83
|
+
a keyloggers en carpetas críticas del sistema.
|
|
84
|
+
"""
|
|
85
|
+
hallazgos = []
|
|
86
|
+
for base in CARPETAS_CRITICAS:
|
|
87
|
+
base = os.path.expandvars(base) # reemplaza %USERNAME%
|
|
88
|
+
if not os.path.exists(base):
|
|
89
|
+
continue
|
|
90
|
+
for root, _, files in os.walk(base):
|
|
91
|
+
for file in files:
|
|
92
|
+
if any(file.lower().endswith(ext) for ext in EXTENSIONES_SOSPECHOSAS):
|
|
93
|
+
if any(pat in file.lower() for pat in PATRONES_NOMBRES):
|
|
94
|
+
ruta = os.path.join(root, file)
|
|
95
|
+
hallazgos.append(ruta)
|
|
96
|
+
registrar_evento("Keylogger", f"Archivo sospechoso: {ruta}")
|
|
97
|
+
return hallazgos
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def detectar_programas_instalados() -> List[str]:
|
|
101
|
+
"""
|
|
102
|
+
Analiza programas instalados en el sistema (solo Windows)
|
|
103
|
+
para encontrar software potencialmente malicioso.
|
|
104
|
+
"""
|
|
105
|
+
hallazgos = []
|
|
106
|
+
if platform.system() != "Windows":
|
|
107
|
+
return hallazgos
|
|
108
|
+
|
|
109
|
+
try:
|
|
110
|
+
salida = subprocess.check_output(
|
|
111
|
+
["wmic", "product", "get", "name"], stderr=subprocess.DEVNULL
|
|
112
|
+
).decode("utf-8", errors="ignore")
|
|
113
|
+
|
|
114
|
+
for linea in salida.splitlines():
|
|
115
|
+
nombre = linea.strip().lower()
|
|
116
|
+
if any(pat in nombre for pat in PATRONES_NOMBRES):
|
|
117
|
+
hallazgos.append(nombre)
|
|
118
|
+
registrar_evento("Keylogger", f"Software sospechoso: {nombre}")
|
|
119
|
+
except Exception as e:
|
|
120
|
+
logger.error("Error al listar programas instalados: %s", e)
|
|
121
|
+
|
|
122
|
+
return hallazgos
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
# =====================================================
|
|
126
|
+
# === CLASE PRINCIPAL DE DETECCIÓN ===
|
|
127
|
+
# =====================================================
|
|
128
|
+
class KEYLOGGERDefense:
|
|
129
|
+
"""
|
|
130
|
+
Escanea procesos, archivos y programas para detectar keyloggers
|
|
131
|
+
o software espía potencialmente malicioso.
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
def ejecutar_escaneo(self):
|
|
135
|
+
procesos = detectar_procesos_sospechosos()
|
|
136
|
+
archivos = detectar_archivos_sospechosos()
|
|
137
|
+
programas = detectar_programas_instalados()
|
|
138
|
+
|
|
139
|
+
total_hallazgos = len(procesos) + len(archivos) + len(programas)
|
|
140
|
+
score = calcular_score_keylogger(total_hallazgos)
|
|
141
|
+
|
|
142
|
+
if total_hallazgos > 0:
|
|
143
|
+
descripcion = [
|
|
144
|
+
f"Procesos sospechosos: {len(procesos)}",
|
|
145
|
+
f"Archivos sospechosos: {len(archivos)}",
|
|
146
|
+
f"Programas sospechosos: {len(programas)}",
|
|
147
|
+
]
|
|
148
|
+
|
|
149
|
+
logger.warning("Keylogger detectado: %s ; nivel: %.2f", descripcion, score)
|
|
150
|
+
|
|
151
|
+
evento = {
|
|
152
|
+
"tipo": "Keylogger",
|
|
153
|
+
"descripcion": descripcion,
|
|
154
|
+
"procesos": procesos,
|
|
155
|
+
"archivos": archivos,
|
|
156
|
+
"programas": programas,
|
|
157
|
+
"score": score,
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
registrar_evento(
|
|
161
|
+
tipo="Keylogger",
|
|
162
|
+
descripcion=f"Detectados {total_hallazgos} elementos sospechosos.",
|
|
163
|
+
severidad="ALTA" if score >= 0.5 else "MEDIA",
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
return evento
|
|
167
|
+
else:
|
|
168
|
+
logger.info("Sin procesos, archivos o programas sospechosos detectados.")
|
|
169
|
+
return {"tipo": "Keylogger", "descripcion": "Sin hallazgos", "score": 0.0}
|
|
11
170
|
|
|
12
171
|
|
|
13
172
|
"""
|
|
@@ -18,4 +177,4 @@ S_keylogger = w_keylogger * numero_procesos_sospechosos
|
|
|
18
177
|
S_keylogger = 0.4 * 2
|
|
19
178
|
donde w_keylogger es peso asignado a keyloggers y numero_procesos_sospechosos es la cantidad de procesos detectados.
|
|
20
179
|
|
|
21
|
-
"""
|
|
180
|
+
"""
|
|
@@ -1,7 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
# sql_defense.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
import re
|
|
6
|
+
from typing import List, Tuple
|
|
2
7
|
from django.conf import settings
|
|
3
|
-
|
|
8
|
+
from django.utils.deprecation import MiddlewareMixin
|
|
4
9
|
|
|
10
|
+
# =====================================================
|
|
11
|
+
# === CONFIGURACIÓN DEL LOGGER ===
|
|
12
|
+
# =====================================================
|
|
5
13
|
logger = logging.getLogger("sqlidefense")
|
|
6
14
|
logger.setLevel(logging.INFO)
|
|
7
15
|
if not logger.handlers:
|
|
@@ -9,80 +17,171 @@ if not logger.handlers:
|
|
|
9
17
|
handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s"))
|
|
10
18
|
logger.addHandler(handler)
|
|
11
19
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
20
|
+
|
|
21
|
+
# =====================================================
|
|
22
|
+
# === PATRONES DE DETECCIÓN DE SQLi ===
|
|
23
|
+
# =====================================================
|
|
24
|
+
SQLI_PATTERNS: List[Tuple[re.Pattern, str]] = [
|
|
25
|
+
# Inyección clásica con UNION SELECT
|
|
26
|
+
(re.compile(r"\bunion\b\s+(all\s+)?\bselect\b", re.I), "Uso de UNION SELECT"),
|
|
27
|
+
# Combinaciones OR/AND en consultas WHERE
|
|
15
28
|
(
|
|
16
29
|
re.compile(r"\bselect\b.*\bfrom\b.*\bwhere\b.*\b(or|and)\b.*=", re.I),
|
|
17
30
|
"SELECT con OR/AND",
|
|
18
31
|
),
|
|
19
|
-
|
|
32
|
+
# Comparaciones tautológicas (1=1)
|
|
33
|
+
(
|
|
34
|
+
re.compile(r"\b(or|and)\s+\d+\s*=\s*\d+", re.I),
|
|
35
|
+
"Expresión tautológica OR/AND 1=1",
|
|
36
|
+
),
|
|
37
|
+
# Manipulación de tablas
|
|
20
38
|
(
|
|
21
39
|
re.compile(r"\b(drop|truncate|delete|insert|update)\b", re.I),
|
|
22
|
-
"
|
|
40
|
+
"Comando de manipulación de tabla",
|
|
23
41
|
),
|
|
42
|
+
# Comentarios sospechosos o terminadores
|
|
24
43
|
(re.compile(r"(--|#|;)", re.I), "Comentario o terminador sospechoso"),
|
|
25
|
-
|
|
44
|
+
# Ejecución directa de procedimientos
|
|
45
|
+
(re.compile(r"exec\s*\(", re.I), "Ejecución de procedimiento almacenado"),
|
|
46
|
+
# Subconsultas y SELECT anidados sospechosos
|
|
47
|
+
(re.compile(r"\(\s*select\b.*\)", re.I), "Subconsulta sospechosa"),
|
|
26
48
|
]
|
|
27
49
|
|
|
28
50
|
|
|
29
|
-
|
|
30
|
-
|
|
51
|
+
# =====================================================
|
|
52
|
+
# === FUNCIONES AUXILIARES SQLi ===
|
|
53
|
+
# =====================================================
|
|
54
|
+
def extract_payload_text(request) -> str:
|
|
55
|
+
"""
|
|
56
|
+
Extrae texto de interés desde el cuerpo, querystring,
|
|
57
|
+
encabezados y referencias para analizar posible SQLi.
|
|
58
|
+
"""
|
|
59
|
+
parts: List[str] = []
|
|
60
|
+
|
|
31
61
|
try:
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
62
|
+
content_type = request.META.get("CONTENT_TYPE", "")
|
|
63
|
+
if "application/json" in content_type:
|
|
64
|
+
data = json.loads(request.body.decode("utf-8") or "{}")
|
|
65
|
+
parts.append(json.dumps(data))
|
|
35
66
|
else:
|
|
36
|
-
|
|
37
|
-
|
|
67
|
+
body = request.body.decode("utf-8", errors="ignore")
|
|
68
|
+
if body:
|
|
69
|
+
parts.append(body)
|
|
70
|
+
except Exception:
|
|
38
71
|
pass
|
|
39
|
-
|
|
40
|
-
|
|
72
|
+
|
|
73
|
+
qs = request.META.get("QUERY_STRING", "")
|
|
74
|
+
if qs:
|
|
75
|
+
parts.append(qs)
|
|
76
|
+
|
|
41
77
|
parts.append(request.META.get("HTTP_USER_AGENT", ""))
|
|
42
78
|
parts.append(request.META.get("HTTP_REFERER", ""))
|
|
79
|
+
|
|
43
80
|
return " ".join([p for p in parts if p])
|
|
44
81
|
|
|
45
82
|
|
|
46
|
-
def detect_sql_attack(text):
|
|
47
|
-
|
|
48
|
-
|
|
83
|
+
def detect_sql_attack(text: str) -> Tuple[bool, List[str]]:
|
|
84
|
+
"""
|
|
85
|
+
Recorre el texto buscando patrones típicos de inyección SQL.
|
|
86
|
+
Retorna (True, lista_de_descripciones) si se detecta algún patrón.
|
|
87
|
+
"""
|
|
88
|
+
descripcion: List[str] = []
|
|
89
|
+
|
|
90
|
+
for patt, msg in SQLI_PATTERNS:
|
|
49
91
|
if patt.search(text):
|
|
50
92
|
descripcion.append(msg)
|
|
93
|
+
|
|
51
94
|
return (len(descripcion) > 0, descripcion)
|
|
52
95
|
|
|
53
96
|
|
|
54
|
-
def get_client_ip(request):
|
|
97
|
+
def get_client_ip(request) -> str:
|
|
98
|
+
"""
|
|
99
|
+
Obtiene la IP real del cliente considerando X-Forwarded-For.
|
|
100
|
+
"""
|
|
55
101
|
x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR")
|
|
56
102
|
if x_forwarded_for:
|
|
57
103
|
return x_forwarded_for.split(",")[0].strip()
|
|
58
|
-
return request.META.get("REMOTE_ADDR")
|
|
104
|
+
return request.META.get("REMOTE_ADDR", "")
|
|
59
105
|
|
|
60
106
|
|
|
107
|
+
# =====================================================
|
|
108
|
+
# === MIDDLEWARE DE DEFENSA SQLi ===
|
|
109
|
+
# =====================================================
|
|
61
110
|
class SQLIDefenseMiddleware(MiddlewareMixin):
|
|
111
|
+
"""
|
|
112
|
+
Middleware profesional de detección de inyección SQL.
|
|
113
|
+
- Detecta patrones en parámetros, cuerpo y cabeceras.
|
|
114
|
+
- No bloquea directamente; marca el intento para auditoría.
|
|
115
|
+
"""
|
|
116
|
+
|
|
62
117
|
def process_request(self, request):
|
|
118
|
+
# ---------------------------------------------
|
|
119
|
+
# 1. Filtrar IPs confiables
|
|
120
|
+
# ---------------------------------------------
|
|
63
121
|
client_ip = get_client_ip(request)
|
|
64
|
-
trusted_ips = getattr(settings, "SQLI_DEFENSE_TRUSTED_IPS", [])
|
|
65
|
-
|
|
122
|
+
trusted_ips: List[str] = getattr(settings, "SQLI_DEFENSE_TRUSTED_IPS", [])
|
|
66
123
|
if client_ip in trusted_ips:
|
|
67
124
|
return None
|
|
68
125
|
|
|
69
|
-
|
|
70
|
-
|
|
126
|
+
# ---------------------------------------------
|
|
127
|
+
# 2. Extraer payload de la solicitud
|
|
128
|
+
# ---------------------------------------------
|
|
129
|
+
payload = extract_payload_text(request)
|
|
130
|
+
if not payload:
|
|
71
131
|
return None
|
|
72
132
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
"tipos": ["SQL"],
|
|
79
|
-
"descripcion": descripcion,
|
|
80
|
-
"payload": text,
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
logger.warning(
|
|
84
|
-
f"Ataque SQL detectado desde IP {client_ip}: {descripcion}, payload: {text}"
|
|
85
|
-
)
|
|
86
|
-
|
|
87
|
-
# No devolvemos JsonResponse, solo marcamos el ataque
|
|
133
|
+
# ---------------------------------------------
|
|
134
|
+
# 3. Analizar contenido en busca de patrones SQLi
|
|
135
|
+
# ---------------------------------------------
|
|
136
|
+
flagged, descripcion = detect_sql_attack(payload)
|
|
137
|
+
if not flagged:
|
|
88
138
|
return None
|
|
139
|
+
|
|
140
|
+
# ---------------------------------------------
|
|
141
|
+
# 4. Calcular puntaje de amenaza S_sqli
|
|
142
|
+
# ---------------------------------------------
|
|
143
|
+
w_sqli = getattr(settings, "SQLI_DEFENSE_WEIGHT", 0.4)
|
|
144
|
+
detecciones_sqli = len(descripcion)
|
|
145
|
+
s_sqli = w_sqli * detecciones_sqli
|
|
146
|
+
|
|
147
|
+
# ---------------------------------------------
|
|
148
|
+
# 5. Registrar e informar el intento
|
|
149
|
+
# ---------------------------------------------
|
|
150
|
+
logger.warning(
|
|
151
|
+
"Inyección SQL detectada desde IP %s: %s ; payload: %.200s ; score: %.2f",
|
|
152
|
+
client_ip,
|
|
153
|
+
descripcion,
|
|
154
|
+
payload,
|
|
155
|
+
s_sqli,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
# Marcar información del ataque para el sistema de auditoría
|
|
159
|
+
request.sql_attack_info = {
|
|
160
|
+
"ip": client_ip,
|
|
161
|
+
"tipos": ["SQLi"],
|
|
162
|
+
"descripcion": descripcion,
|
|
163
|
+
"payload": payload,
|
|
164
|
+
"score": s_sqli,
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return None
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
# =====================================================
|
|
171
|
+
# === INFORMACIÓN EXTRA ===
|
|
172
|
+
# =====================================================
|
|
173
|
+
"""
|
|
174
|
+
Algoritmos relacionados:
|
|
175
|
+
- Se recomienda almacenar logs SQLi cifrados (AES-GCM)
|
|
176
|
+
para proteger evidencia de intentos maliciosos.
|
|
177
|
+
|
|
178
|
+
Cálculo de puntaje de amenaza:
|
|
179
|
+
S_sqli = w_sqli * detecciones_sqli
|
|
180
|
+
Ejemplo: S_sqli = 0.4 * 3 = 1.2
|
|
181
|
+
|
|
182
|
+
Integración:
|
|
183
|
+
Este middleware puede combinarse con:
|
|
184
|
+
- CSRFDefenseMiddleware
|
|
185
|
+
- XSSDefenseMiddleware
|
|
186
|
+
para calcular un score total de amenaza y decidir bloqueo.
|
|
187
|
+
"""
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# xss_defense.py
|
|
1
2
|
from __future__ import annotations
|
|
2
3
|
import json
|
|
3
4
|
import logging
|
|
@@ -6,7 +7,9 @@ from typing import List, Tuple
|
|
|
6
7
|
from django.conf import settings
|
|
7
8
|
from django.utils.deprecation import MiddlewareMixin
|
|
8
9
|
|
|
9
|
-
#
|
|
10
|
+
# =====================================================
|
|
11
|
+
# === CONFIGURACIÓN LOGGER ===
|
|
12
|
+
# =====================================================
|
|
10
13
|
logger = logging.getLogger("xssdefense")
|
|
11
14
|
logger.setLevel(logging.INFO)
|
|
12
15
|
if not logger.handlers:
|
|
@@ -14,7 +17,9 @@ if not logger.handlers:
|
|
|
14
17
|
handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s"))
|
|
15
18
|
logger.addHandler(handler)
|
|
16
19
|
|
|
17
|
-
#
|
|
20
|
+
# =====================================================
|
|
21
|
+
# === INTENTAR CARGAR BLEACH ===
|
|
22
|
+
# =====================================================
|
|
18
23
|
try:
|
|
19
24
|
import bleach
|
|
20
25
|
|
|
@@ -22,32 +27,51 @@ try:
|
|
|
22
27
|
except Exception:
|
|
23
28
|
_BLEACH_AVAILABLE = False
|
|
24
29
|
|
|
25
|
-
#
|
|
30
|
+
# =====================================================
|
|
31
|
+
# === PATRONES DE DETECCIÓN XSS ===
|
|
32
|
+
# =====================================================
|
|
26
33
|
XSS_PATTERNS: List[Tuple[re.Pattern, str]] = [
|
|
27
34
|
(re.compile(r"<\s*script\b", re.I), "Etiqueta <script>"),
|
|
28
|
-
(re.compile(r"on\w+\s*=", re.I), "Atributo evento (on*)"),
|
|
35
|
+
(re.compile(r"on\w+\s*=", re.I), "Atributo de evento (on*)"),
|
|
29
36
|
(re.compile(r"javascript:\s*", re.I), "URI javascript:"),
|
|
30
37
|
(re.compile(r"<\s*iframe\b", re.I), "Etiqueta <iframe>"),
|
|
31
38
|
(re.compile(r"<\s*embed\b", re.I), "Etiqueta <embed>"),
|
|
39
|
+
(re.compile(r"<\s*object\b", re.I), "Etiqueta <object>"),
|
|
40
|
+
(re.compile(r"document\.cookie", re.I), "Acceso a document.cookie"),
|
|
41
|
+
(re.compile(r"alert\s*\(", re.I), "Uso de alert() potencial"),
|
|
32
42
|
]
|
|
33
43
|
|
|
34
44
|
|
|
35
|
-
#
|
|
45
|
+
# =====================================================
|
|
46
|
+
# === FUNCIONES AUXILIARES XSS ===
|
|
47
|
+
# =====================================================
|
|
36
48
|
def detect_xss_text(text: str) -> Tuple[bool, List[str]]:
|
|
49
|
+
"""
|
|
50
|
+
Busca patrones de XSS conocidos dentro de un texto.
|
|
51
|
+
Devuelve (True, lista_de_coincidencias) si hay indicios.
|
|
52
|
+
"""
|
|
37
53
|
matches: List[str] = []
|
|
38
54
|
if not text:
|
|
39
55
|
return False, matches
|
|
56
|
+
|
|
40
57
|
for patt, message in XSS_PATTERNS:
|
|
41
58
|
if patt.search(text):
|
|
42
59
|
matches.append(message)
|
|
60
|
+
|
|
43
61
|
return len(matches) > 0, matches
|
|
44
62
|
|
|
45
63
|
|
|
46
64
|
def sanitize_input_basic(text: str) -> str:
|
|
65
|
+
"""
|
|
66
|
+
Sanitiza una cadena eliminando etiquetas o caracteres peligrosos.
|
|
67
|
+
Usa bleach si está disponible, de lo contrario hace escape manual.
|
|
68
|
+
"""
|
|
47
69
|
if text is None:
|
|
48
70
|
return text
|
|
71
|
+
|
|
49
72
|
if _BLEACH_AVAILABLE:
|
|
50
73
|
return bleach.clean(text, tags=[], attributes={}, protocols=[], strip=True)
|
|
74
|
+
|
|
51
75
|
replacements = [
|
|
52
76
|
("&", "&"),
|
|
53
77
|
("<", "<"),
|
|
@@ -63,45 +87,65 @@ def sanitize_input_basic(text: str) -> str:
|
|
|
63
87
|
|
|
64
88
|
|
|
65
89
|
def extract_payload_text(request) -> str:
|
|
90
|
+
"""
|
|
91
|
+
Extrae un texto combinado con información del cuerpo, querystring,
|
|
92
|
+
agente de usuario y referer para análisis XSS.
|
|
93
|
+
"""
|
|
66
94
|
parts: List[str] = []
|
|
95
|
+
|
|
67
96
|
try:
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
)
|
|
74
|
-
)
|
|
97
|
+
content_type = request.META.get("CONTENT_TYPE", "")
|
|
98
|
+
|
|
99
|
+
if "application/json" in content_type:
|
|
100
|
+
body_data = json.loads(request.body.decode("utf-8") or "{}")
|
|
101
|
+
parts.append(json.dumps(body_data, ensure_ascii=False))
|
|
75
102
|
else:
|
|
76
|
-
|
|
103
|
+
body_text = request.body.decode("utf-8", errors="ignore")
|
|
104
|
+
if body_text:
|
|
105
|
+
parts.append(body_text)
|
|
77
106
|
except Exception:
|
|
78
107
|
pass
|
|
108
|
+
|
|
79
109
|
qs = request.META.get("QUERY_STRING", "")
|
|
80
110
|
if qs:
|
|
81
111
|
parts.append(qs)
|
|
112
|
+
|
|
82
113
|
parts.append(request.META.get("HTTP_USER_AGENT", ""))
|
|
83
114
|
parts.append(request.META.get("HTTP_REFERER", ""))
|
|
115
|
+
|
|
84
116
|
return " ".join([p for p in parts if p])
|
|
85
117
|
|
|
86
118
|
|
|
87
|
-
#
|
|
119
|
+
# =====================================================
|
|
120
|
+
# === MIDDLEWARE DE DEFENSA XSS ===
|
|
121
|
+
# =====================================================
|
|
88
122
|
class XSSDefenseMiddleware(MiddlewareMixin):
|
|
89
123
|
"""
|
|
90
|
-
Middleware
|
|
91
|
-
|
|
92
|
-
|
|
124
|
+
Middleware profesional de detección XSS.
|
|
125
|
+
- Detecta patrones maliciosos en solicitudes sospechosas.
|
|
126
|
+
- No bloquea directamente, solo marca el ataque para auditoría.
|
|
127
|
+
- Se integra con AuditoriaMiddleware (request.sql_attack_info).
|
|
93
128
|
"""
|
|
94
129
|
|
|
95
130
|
def process_request(self, request):
|
|
131
|
+
# ---------------------------------------------
|
|
132
|
+
# 1. Filtrar IPs de confianza
|
|
133
|
+
# ---------------------------------------------
|
|
96
134
|
trusted_ips: List[str] = getattr(settings, "XSS_DEFENSE_TRUSTED_IPS", [])
|
|
97
|
-
|
|
98
|
-
if
|
|
99
|
-
return None
|
|
135
|
+
client_ip = request.META.get("REMOTE_ADDR", "")
|
|
136
|
+
if client_ip in trusted_ips:
|
|
137
|
+
return None
|
|
100
138
|
|
|
139
|
+
# ---------------------------------------------
|
|
140
|
+
# 2. Excluir rutas seguras
|
|
141
|
+
# ---------------------------------------------
|
|
101
142
|
excluded_paths: List[str] = getattr(settings, "XSS_DEFENSE_EXCLUDED_PATHS", [])
|
|
102
143
|
if any(request.path.startswith(p) for p in excluded_paths):
|
|
103
144
|
return None
|
|
104
145
|
|
|
146
|
+
# ---------------------------------------------
|
|
147
|
+
# 3. Extraer y analizar payload
|
|
148
|
+
# ---------------------------------------------
|
|
105
149
|
payload = extract_payload_text(request)
|
|
106
150
|
if not payload:
|
|
107
151
|
return None
|
|
@@ -110,30 +154,44 @@ class XSSDefenseMiddleware(MiddlewareMixin):
|
|
|
110
154
|
if not flagged:
|
|
111
155
|
return None
|
|
112
156
|
|
|
157
|
+
# ---------------------------------------------
|
|
158
|
+
# 4. Calcular puntaje de amenaza S_xss
|
|
159
|
+
# ---------------------------------------------
|
|
160
|
+
w_xss = getattr(settings, "XSS_DEFENSE_WEIGHT", 0.3)
|
|
161
|
+
detecciones_xss = len(matches)
|
|
162
|
+
s_xss = w_xss * detecciones_xss
|
|
163
|
+
|
|
164
|
+
# ---------------------------------------------
|
|
165
|
+
# 5. Loggear y marcar en el request
|
|
166
|
+
# ---------------------------------------------
|
|
113
167
|
logger.warning(
|
|
114
|
-
"XSS detectado desde IP %s: %s ; payload
|
|
115
|
-
|
|
168
|
+
"XSS detectado desde IP %s: %s ; payload: %.200s ; score: %.2f",
|
|
169
|
+
client_ip,
|
|
116
170
|
matches,
|
|
117
171
|
payload,
|
|
172
|
+
s_xss,
|
|
118
173
|
)
|
|
119
174
|
|
|
120
|
-
# Solo marcamos el ataque, no bloqueamos aquí
|
|
121
175
|
request.sql_attack_info = {
|
|
122
|
-
"ip":
|
|
176
|
+
"ip": client_ip,
|
|
123
177
|
"tipos": ["XSS"],
|
|
124
178
|
"descripcion": matches,
|
|
125
179
|
"payload": payload,
|
|
180
|
+
"score": s_xss,
|
|
126
181
|
}
|
|
127
182
|
|
|
128
183
|
return None
|
|
129
184
|
|
|
130
185
|
|
|
131
|
-
|
|
186
|
+
# =====================================================
|
|
187
|
+
# === INFORMACIÓN EXTRA ===
|
|
188
|
+
# =====================================================
|
|
189
|
+
"""
|
|
132
190
|
Algoritmos relacionados:
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
S_xss = w_xss * detecciones_xss
|
|
136
|
-
S_xss = 0.3 * 2
|
|
137
|
-
donde w_xss es peso asignado a XSS y detecciones_xss es la cantidad de patrones detectados.
|
|
191
|
+
- Se recomienda almacenar los payloads XSS cifrados con AES-GCM
|
|
192
|
+
para confidencialidad e integridad.
|
|
138
193
|
|
|
194
|
+
Contribución a fórmula de amenaza S:
|
|
195
|
+
S_xss = w_xss * detecciones_xss
|
|
196
|
+
Ejemplo: S_xss = 0.3 * 2 = 0.6
|
|
139
197
|
"""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: GuardianUnivalle-Benito-Yucra
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.30
|
|
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,17 +4,17 @@ GuardianUnivalle_Benito_Yucra/auditoria/registro_auditoria.py,sha256=YxEtF6ZJj8M
|
|
|
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=
|
|
8
|
-
GuardianUnivalle_Benito_Yucra/detectores/detector_dos.py,sha256=
|
|
9
|
-
GuardianUnivalle_Benito_Yucra/detectores/detector_keylogger.py,sha256=
|
|
10
|
-
GuardianUnivalle_Benito_Yucra/detectores/detector_sql.py,sha256=
|
|
11
|
-
GuardianUnivalle_Benito_Yucra/detectores/detector_xss.py,sha256=
|
|
7
|
+
GuardianUnivalle_Benito_Yucra/detectores/detector_csrf.py,sha256=8gynkJlxF2XfHPLdWaMS048b5Tuhznsb4JI3bqggC0g,7892
|
|
8
|
+
GuardianUnivalle_Benito_Yucra/detectores/detector_dos.py,sha256=ech5UmUwK84EPSVZvjI0Q4Pq7w8WGymuYc77LgAKiiA,6163
|
|
9
|
+
GuardianUnivalle_Benito_Yucra/detectores/detector_keylogger.py,sha256=T46HXHT3LZz3kke4GcxOM8urJSy-Vq2BEDfo9_F56Pw,6717
|
|
10
|
+
GuardianUnivalle_Benito_Yucra/detectores/detector_sql.py,sha256=o2IXqF3Nbsh5roPihyLal42iCzgxfyX3D6Ef-cxOpVo,6644
|
|
11
|
+
GuardianUnivalle_Benito_Yucra/detectores/detector_xss.py,sha256=kdXJsoDjvBVccrO2QiE65Q527sUjy1WmlzYg8rDjmqA,6795
|
|
12
12
|
GuardianUnivalle_Benito_Yucra/middleware_web/middleware_web.py,sha256=23pLLYqliUoMrIC6ZEwz3hKXeDjWfHSm9vYPWGmDDik,495
|
|
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.
|
|
17
|
-
guardianunivalle_benito_yucra-0.1.
|
|
18
|
-
guardianunivalle_benito_yucra-0.1.
|
|
19
|
-
guardianunivalle_benito_yucra-0.1.
|
|
20
|
-
guardianunivalle_benito_yucra-0.1.
|
|
16
|
+
guardianunivalle_benito_yucra-0.1.30.dist-info/licenses/LICENSE,sha256=5e4IdL542v1E8Ft0A24GZjrxZeTsVK7XrS3mZEUhPtM,37
|
|
17
|
+
guardianunivalle_benito_yucra-0.1.30.dist-info/METADATA,sha256=yS2g75_JDicESJXiuBDwCJXOYiyCe0lrXzlSjEB8XNM,1893
|
|
18
|
+
guardianunivalle_benito_yucra-0.1.30.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
19
|
+
guardianunivalle_benito_yucra-0.1.30.dist-info/top_level.txt,sha256=HTWfZM64WAV_QYr5cnXnLuabQt92dvlxqlR3pCwpbDQ,30
|
|
20
|
+
guardianunivalle_benito_yucra-0.1.30.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|