GuardianUnivalle-Benito-Yucra 1.1.18__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.
- GuardianUnivalle_Benito_Yucra/__init__.py +25 -0
- GuardianUnivalle_Benito_Yucra/auditoria/registro_auditoria.py +40 -0
- GuardianUnivalle_Benito_Yucra/criptografia/cifrado_aead.py +25 -0
- GuardianUnivalle_Benito_Yucra/criptografia/intercambio_claves.py +23 -0
- GuardianUnivalle_Benito_Yucra/criptografia/kdf.py +23 -0
- GuardianUnivalle_Benito_Yucra/detectores/detector_csrf.py +488 -0
- GuardianUnivalle_Benito_Yucra/detectores/detector_dos.py +352 -0
- GuardianUnivalle_Benito_Yucra/detectores/detector_sql.py +641 -0
- GuardianUnivalle_Benito_Yucra/detectores/detector_xss.py +626 -0
- GuardianUnivalle_Benito_Yucra/middleware_web/middleware_web.py +13 -0
- GuardianUnivalle_Benito_Yucra/mitigacion/limitador_peticion.py +7 -0
- GuardianUnivalle_Benito_Yucra/mitigacion/lista_bloqueo.py +10 -0
- GuardianUnivalle_Benito_Yucra/puntuacion/puntuacion_amenaza.py +15 -0
- GuardianUnivalle_Benito_Yucra/utilidades.py +7 -0
- guardianunivalle_benito_yucra-1.1.18.dist-info/METADATA +279 -0
- guardianunivalle_benito_yucra-1.1.18.dist-info/RECORD +19 -0
- guardianunivalle_benito_yucra-1.1.18.dist-info/WHEEL +5 -0
- guardianunivalle_benito_yucra-1.1.18.dist-info/licenses/LICENSE +1 -0
- guardianunivalle_benito_yucra-1.1.18.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import time
|
|
3
|
+
import logging
|
|
4
|
+
import json
|
|
5
|
+
from collections import deque
|
|
6
|
+
from typing import Dict, List, Set
|
|
7
|
+
from django.conf import settings
|
|
8
|
+
from django.utils.deprecation import MiddlewareMixin
|
|
9
|
+
from django.http import HttpResponseForbidden
|
|
10
|
+
import requests # ⬅️ Necesario para la función de scraping
|
|
11
|
+
import re # ⬅️ Necesario para el parseo de IPs/CIDR
|
|
12
|
+
from ipaddress import ip_address, IPv4Address, IPv4Network # Necesario para el Escaneo Avanzado (CIDR)
|
|
13
|
+
|
|
14
|
+
# =====================================================
|
|
15
|
+
# === CONFIGURACIÓN GLOBAL Y LOGGER ===
|
|
16
|
+
# =====================================================
|
|
17
|
+
logger = logging.getLogger("dosdefense")
|
|
18
|
+
logger.setLevel(logging.INFO)
|
|
19
|
+
if not logger.handlers:
|
|
20
|
+
handler = logging.StreamHandler()
|
|
21
|
+
handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s"))
|
|
22
|
+
logger.addHandler(handler)
|
|
23
|
+
|
|
24
|
+
# =====================================================
|
|
25
|
+
# === CONFIGURACIÓN DE INTELIGENCIA DE AMENAZAS (THREAT INTEL) ===
|
|
26
|
+
# =====================================================
|
|
27
|
+
# URLs CONCEPTUALES de donde EXTRAERÍAS IPs/CIDR
|
|
28
|
+
IP_BLACKLIST_SOURCES = [
|
|
29
|
+
# 1. FireHOL (Agregador General de Nivel 1)
|
|
30
|
+
# Resultado: Éxito al obtener 4438 IPs/CIDR
|
|
31
|
+
"https://iplists.firehol.org/files/firehol_level1.netset",
|
|
32
|
+
|
|
33
|
+
# 2. Abuse.ch Feodo Tracker (Botnets C&C)
|
|
34
|
+
# Resultado: Éxito al obtener 2 IPs/CIDR (puede ser bajo, pero es funcional)
|
|
35
|
+
"https://feodotracker.abuse.ch/downloads/ipblocklist.txt",
|
|
36
|
+
|
|
37
|
+
# 3. Tor Project (Nodos de Salida)
|
|
38
|
+
# Resultado: Éxito al obtener 1166 IPs/CIDR
|
|
39
|
+
"https://check.torproject.org/torbulkexitlist?ip=1.1.1.1"
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
# Cabeceras para simular un navegador
|
|
43
|
+
SCRAPING_HEADERS = {
|
|
44
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
# =====================================================
|
|
48
|
+
# === FUNCIONES DE INTELIGENCIA DE AMENAZAS ===
|
|
49
|
+
# =====================================================
|
|
50
|
+
|
|
51
|
+
def fetch_and_parse_blacklists() -> Set[str]:
|
|
52
|
+
"""
|
|
53
|
+
Intenta obtener y parsear IPs/CIDR de varias fuentes externas.
|
|
54
|
+
"""
|
|
55
|
+
global_blacklist: Set[str] = set()
|
|
56
|
+
# Patrón Regex para IPs (admite también rangos CIDR)
|
|
57
|
+
|
|
58
|
+
ip_pattern = re.compile(r'\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(?:/\d{1,2})?\b')
|
|
59
|
+
|
|
60
|
+
for url in IP_BLACKLIST_SOURCES:
|
|
61
|
+
try:
|
|
62
|
+
response = requests.get(url, headers=SCRAPING_HEADERS, timeout=15)
|
|
63
|
+
response.raise_for_status()
|
|
64
|
+
|
|
65
|
+
found_ips = ip_pattern.findall(response.text)
|
|
66
|
+
|
|
67
|
+
# Limpieza
|
|
68
|
+
#cleaned_ips = {ip[0] for ip in found_ips if ip[0] not in ('0.0.0.0', '255.255.255.255')}
|
|
69
|
+
cleaned_ips = {ip for ip in found_ips if ip not in ('0.0.0.0', '255.255.255.255')}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
global_blacklist.update(cleaned_ips)
|
|
73
|
+
logger.info(f"[Threat Intel] Éxito al obtener {len(cleaned_ips)} IPs/CIDR de {url}")
|
|
74
|
+
|
|
75
|
+
except requests.exceptions.RequestException as e:
|
|
76
|
+
logger.error(f"[Threat Intel] Error de conexión con {url}: {e}")
|
|
77
|
+
except Exception as e:
|
|
78
|
+
logger.error(f"[Threat Intel] Error inesperado al parsear {url}: {e}")
|
|
79
|
+
|
|
80
|
+
if '127.0.0.1' in global_blacklist:
|
|
81
|
+
global_blacklist.remove('127.0.0.1')
|
|
82
|
+
|
|
83
|
+
return global_blacklist
|
|
84
|
+
|
|
85
|
+
def check_ip_in_advanced_blacklist(client_ip: str, global_blacklist_cidrs: Set[str]) -> bool:
|
|
86
|
+
"""
|
|
87
|
+
Escaneo avanzado: Chequea si una IP está en la lista negra, incluyendo rangos CIDR.
|
|
88
|
+
"""
|
|
89
|
+
if not global_blacklist_cidrs:
|
|
90
|
+
return False
|
|
91
|
+
|
|
92
|
+
try:
|
|
93
|
+
ip_a_chequear = IPv4Address(client_ip)
|
|
94
|
+
|
|
95
|
+
# 1. Chequeo rápido de IPs individuales
|
|
96
|
+
if client_ip in global_blacklist_cidrs:
|
|
97
|
+
return True
|
|
98
|
+
|
|
99
|
+
# 2. Chequeo de rangos CIDR (más lento)
|
|
100
|
+
for cidr_entry in global_blacklist_cidrs:
|
|
101
|
+
if '/' in cidr_entry:
|
|
102
|
+
try:
|
|
103
|
+
if ip_a_chequear in IPv4Network(cidr_entry, strict=False):
|
|
104
|
+
return True
|
|
105
|
+
except ValueError:
|
|
106
|
+
continue # No es una red CIDR válida, continuar
|
|
107
|
+
return False
|
|
108
|
+
|
|
109
|
+
except ValueError:
|
|
110
|
+
logger.error(f"IP del cliente inválida o no IPv4: {client_ip}")
|
|
111
|
+
return False
|
|
112
|
+
|
|
113
|
+
# =====================================================
|
|
114
|
+
# === PARÁMETROS DE CONFIGURACIÓN BASE Y SCORE ===
|
|
115
|
+
# =====================================================
|
|
116
|
+
LIMITE_PETICIONES = getattr(settings, "DOS_LIMITE_PETICIONES", 100)
|
|
117
|
+
VENTANA_SEGUNDOS = getattr(settings, "DOS_VENTANA_SEGUNDOS", 60)
|
|
118
|
+
PESO_DOS = getattr(settings, "DOS_PESO", 0.6)
|
|
119
|
+
LIMITE_ENDPOINTS_DISTINTOS = getattr(settings, "DOS_LIMITE_ENDPOINTS", 50)
|
|
120
|
+
TRUSTED_IPS = getattr(settings, "DOS_TRUSTED_IPS", [])
|
|
121
|
+
TIEMPO_BLOQUEO_SEGUNDOS = getattr(settings, "DOS_TIEMPO_BLOQUEO", 300)
|
|
122
|
+
|
|
123
|
+
# Parámetros del Score Avanzado
|
|
124
|
+
PESO_BLACKLIST = getattr(settings, "DOS_PESO_BLACKLIST", 0.3)
|
|
125
|
+
PESO_HEURISTICA = getattr(settings, "DOS_PESO_HEURISTICA", 0.1)
|
|
126
|
+
UMBRAL_BLOQUEO = getattr(settings, "DOS_UMBRAL_BLOQUEO", 0.8)
|
|
127
|
+
|
|
128
|
+
# === CARGA INICIAL DE LA LISTA NEGRA ===
|
|
129
|
+
try:
|
|
130
|
+
IP_BLACKLIST: Set[str] = fetch_and_parse_blacklists()
|
|
131
|
+
output_filename = "blacklist_cargada.txt"
|
|
132
|
+
with open(output_filename, 'w') as f:
|
|
133
|
+
# Escribe cada IP/CIDR en una nueva línea
|
|
134
|
+
for ip in sorted(list(IP_BLACKLIST)): # Usamos sorted() para orden alfabético/numérico
|
|
135
|
+
f.write(f"{ip}\n")
|
|
136
|
+
logger.info(f"Lista Negra Externa GUARDADA en {output_filename} para inspección.")
|
|
137
|
+
logger.info(f"Lista Negra Externa cargada con {len(IP_BLACKLIST)} IPs/CIDR.")
|
|
138
|
+
except Exception as e:
|
|
139
|
+
logger.error(f"Error al cargar la IP Blacklist: {e}. Usando lista vacía.")
|
|
140
|
+
IP_BLACKLIST = set()
|
|
141
|
+
|
|
142
|
+
# =====================================================
|
|
143
|
+
# === REGISTRO TEMPORAL EN MEMORIA ===
|
|
144
|
+
# =====================================================
|
|
145
|
+
REGISTRO_SOLICITUDES: Dict[str, deque] = {}
|
|
146
|
+
REGISTRO_ENDPOINTS: Dict[str, set] = {}
|
|
147
|
+
BLOQUEOS_TEMPORALES: Dict[str, float] = {}
|
|
148
|
+
|
|
149
|
+
# =====================================================
|
|
150
|
+
# === FUNCIONES AUXILIARES ===
|
|
151
|
+
# =====================================================
|
|
152
|
+
def get_client_ip(request) -> str:
|
|
153
|
+
"""Obtiene la IP real del cliente (considera proxies)."""
|
|
154
|
+
x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR")
|
|
155
|
+
if x_forwarded_for:
|
|
156
|
+
return x_forwarded_for.split(",")[0].strip()
|
|
157
|
+
return request.META.get("REMOTE_ADDR", "") or "0.0.0.0"
|
|
158
|
+
|
|
159
|
+
def limpiar_registro_global():
|
|
160
|
+
"""Elimina IPs sin actividad reciente y desbloquea IPs temporales."""
|
|
161
|
+
# ... (La implementación de limpiar_registro_global permanece igual)
|
|
162
|
+
ahora = time.time()
|
|
163
|
+
expiracion = VENTANA_SEGUNDOS * 2
|
|
164
|
+
inactivas = []
|
|
165
|
+
|
|
166
|
+
for ip, tiempos in REGISTRO_SOLICITUDES.items():
|
|
167
|
+
if tiempos and ahora - tiempos[-1] > expiracion:
|
|
168
|
+
inactivas.append(ip)
|
|
169
|
+
|
|
170
|
+
for ip in inactivas:
|
|
171
|
+
REGISTRO_SOLICITUDES.pop(ip, None)
|
|
172
|
+
REGISTRO_ENDPOINTS.pop(ip, None)
|
|
173
|
+
|
|
174
|
+
ips_a_desbloquear = [ip for ip, tiempo_desbloqueo in BLOQUEOS_TEMPORALES.items() if ahora > tiempo_desbloqueo]
|
|
175
|
+
for ip in ips_a_desbloquear:
|
|
176
|
+
BLOQUEOS_TEMPORALES.pop(ip, None)
|
|
177
|
+
logger.info(f"[Desbloqueo] IP {ip} desbloqueada automáticamente.")
|
|
178
|
+
|
|
179
|
+
def limpiar_registro(ip: str):
|
|
180
|
+
"""Limpia peticiones antiguas fuera de la ventana de tiempo."""
|
|
181
|
+
# ... (La implementación de limpiar_registro permanece igual)
|
|
182
|
+
ahora = time.time()
|
|
183
|
+
if ip not in REGISTRO_SOLICITUDES:
|
|
184
|
+
REGISTRO_SOLICITUDES[ip] = deque()
|
|
185
|
+
tiempos = REGISTRO_SOLICITUDES[ip]
|
|
186
|
+
while tiempos and ahora - tiempos[0] > VENTANA_SEGUNDOS:
|
|
187
|
+
tiempos.popleft()
|
|
188
|
+
|
|
189
|
+
def calcular_nivel_amenaza_dos(tasa_peticion: int, limite: int = LIMITE_PETICIONES) -> float:
|
|
190
|
+
"""Calcula la puntuación de amenaza DoS (Rate Limiting)."""
|
|
191
|
+
# ... (La implementación de calcular_nivel_amenaza_dos permanece igual)
|
|
192
|
+
proporcion = tasa_peticion / max(limite, 1)
|
|
193
|
+
s_dos = PESO_DOS * min(proporcion, 2.0)
|
|
194
|
+
return round(min(s_dos, 1.0), 3)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
# =====================================================
|
|
198
|
+
# === FUNCIONES INTERNAS DE SEGURIDAD Y AUDITORÍA ===
|
|
199
|
+
# =====================================================
|
|
200
|
+
def limitar_peticion(usuario_id: str):
|
|
201
|
+
"""Implementa la mitigación: Bloquea temporalmente la IP."""
|
|
202
|
+
ahora = time.time()
|
|
203
|
+
tiempo_desbloqueo = ahora + TIEMPO_BLOQUEO_SEGUNDOS
|
|
204
|
+
BLOQUEOS_TEMPORALES[usuario_id] = tiempo_desbloqueo
|
|
205
|
+
logger.warning(
|
|
206
|
+
f"[Bloqueo Activo] IP {usuario_id} bloqueada temporalmente hasta {time.ctime(tiempo_desbloqueo)}"
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
def registrar_evento(tipo: str, descripcion: str, severidad: str = "MEDIA"):
|
|
210
|
+
"""Simula el registro de auditoría de un evento de seguridad."""
|
|
211
|
+
evento = {
|
|
212
|
+
"tipo": tipo,
|
|
213
|
+
"descripcion": descripcion,
|
|
214
|
+
"severidad": severidad,
|
|
215
|
+
"timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
|
|
216
|
+
}
|
|
217
|
+
logger.info(f"[AUDITORÍA] {json.dumps(evento, ensure_ascii=False)}")
|
|
218
|
+
|
|
219
|
+
def detectar_dos(ip: str, tasa_peticion: int, limite: int = LIMITE_PETICIONES) -> bool:
|
|
220
|
+
"""Evalúa si la tasa de peticiones excede el umbral permitido y aplica mitigación."""
|
|
221
|
+
# ... (La implementación de detectar_dos permanece igual)
|
|
222
|
+
if tasa_peticion > limite:
|
|
223
|
+
registrar_evento(
|
|
224
|
+
tipo="DoS",
|
|
225
|
+
descripcion=f"Alta tasa de peticiones desde {ip}: {tasa_peticion} req/min (límite {limite})",
|
|
226
|
+
severidad="ALTA",
|
|
227
|
+
)
|
|
228
|
+
limitar_peticion(usuario_id=ip)
|
|
229
|
+
return True
|
|
230
|
+
elif tasa_peticion > limite * 0.75:
|
|
231
|
+
registrar_evento(
|
|
232
|
+
tipo="DoS",
|
|
233
|
+
descripcion=f"Posible saturación desde {ip}: {tasa_peticion} req/min",
|
|
234
|
+
severidad="MEDIA",
|
|
235
|
+
)
|
|
236
|
+
return False
|
|
237
|
+
|
|
238
|
+
def analizar_headers_avanzado(user_agent: str, referer: str) -> List[str]:
|
|
239
|
+
"""Detecta patrones sospechosos, penalizando User-Agents automatizados."""
|
|
240
|
+
# ... (La implementación de analizar_headers_avanzado permanece igual)
|
|
241
|
+
sospechas = []
|
|
242
|
+
|
|
243
|
+
if not user_agent or len(user_agent) < 10 or user_agent.lower() == "python-requests/2.25.1":
|
|
244
|
+
sospechas.append("User-Agent vacío/Defecto")
|
|
245
|
+
|
|
246
|
+
automation_keywords = ["curl", "python", "wget", "bot", "spider", "scraper", "headless", "phantom"]
|
|
247
|
+
if any(patron in user_agent.lower() for patron in automation_keywords):
|
|
248
|
+
sospechas.append("Herramienta de automatización detectada")
|
|
249
|
+
|
|
250
|
+
if referer and any(palabra in referer.lower() for palabra in ["attack", "scan"]):
|
|
251
|
+
sospechas.append("Referer indicando abuso")
|
|
252
|
+
|
|
253
|
+
return sospechas
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
# =====================================================
|
|
257
|
+
# === MIDDLEWARE PRINCIPAL DE DEFENSA DoS ===
|
|
258
|
+
# =====================================================
|
|
259
|
+
class DOSDefenseMiddleware(MiddlewareMixin):
|
|
260
|
+
"""
|
|
261
|
+
Middleware de detección, registro y mitigación de ataques DoS/Scraping avanzado.
|
|
262
|
+
"""
|
|
263
|
+
|
|
264
|
+
def process_request(self, request):
|
|
265
|
+
limpiar_registro_global()
|
|
266
|
+
|
|
267
|
+
client_ip = get_client_ip(request)
|
|
268
|
+
|
|
269
|
+
# 1. BLOQUEOS Y EXCEPCIONES PREVIAS
|
|
270
|
+
if client_ip in TRUSTED_IPS:
|
|
271
|
+
return None
|
|
272
|
+
|
|
273
|
+
# BLOQUEO TEMPORAL: IPs previamente bloqueadas por alto Score o DoS
|
|
274
|
+
if client_ip in BLOQUEOS_TEMPORALES and time.time() < BLOQUEOS_TEMPORALES[client_ip]:
|
|
275
|
+
registrar_evento(
|
|
276
|
+
tipo="Temporary Block",
|
|
277
|
+
descripcion=f"Bloqueo temporal por abuso previo: IP {client_ip}.",
|
|
278
|
+
severidad="ALTA",
|
|
279
|
+
)
|
|
280
|
+
return HttpResponseForbidden("Acceso denegado temporalmente por comportamiento sospechoso.")
|
|
281
|
+
|
|
282
|
+
# 2. ANÁLISIS DE LA PETICIÓN Y CÁLCULO DE MÉTRICAS BASE
|
|
283
|
+
user_agent = request.META.get("HTTP_USER_AGENT", "Desconocido")
|
|
284
|
+
referer = request.META.get("HTTP_REFERER", "")
|
|
285
|
+
path = request.path
|
|
286
|
+
|
|
287
|
+
# Mantener ventana deslizante y tasa
|
|
288
|
+
REGISTRO_ENDPOINTS.setdefault(client_ip, set()).add(path)
|
|
289
|
+
limpiar_registro(client_ip)
|
|
290
|
+
REGISTRO_SOLICITUDES[client_ip].append(time.time())
|
|
291
|
+
|
|
292
|
+
tasa = len(REGISTRO_SOLICITUDES[client_ip])
|
|
293
|
+
|
|
294
|
+
# 3. CÁLCULO DE LOS COMPONENTES DEL SCORE DE AMENAZA
|
|
295
|
+
|
|
296
|
+
# S_dos: Tasa de Petición (Rate Limiting)
|
|
297
|
+
nivel_dos = calcular_nivel_amenaza_dos(tasa)
|
|
298
|
+
|
|
299
|
+
# S_blacklist: Escaneo Avanzado (CIDR)
|
|
300
|
+
nivel_blacklist = PESO_BLACKLIST if check_ip_in_advanced_blacklist(client_ip, IP_BLACKLIST) else 0
|
|
301
|
+
|
|
302
|
+
# S_heuristica: Análisis de Comportamiento (Scraping/Escaneo)
|
|
303
|
+
sospechas_headers = analizar_headers_avanzado(user_agent, referer)
|
|
304
|
+
|
|
305
|
+
score_headers = 0.5 if sospechas_headers else 0
|
|
306
|
+
score_endpoints = 0.5 if len(REGISTRO_ENDPOINTS[client_ip]) > LIMITE_ENDPOINTS_DISTINTOS else 0
|
|
307
|
+
|
|
308
|
+
nivel_heuristica = PESO_HEURISTICA * (score_headers + score_endpoints)
|
|
309
|
+
|
|
310
|
+
# 4. CÁLCULO DEL SCORE TOTAL Y DECISIÓN DE MITIGACIÓN
|
|
311
|
+
|
|
312
|
+
S_total = nivel_dos + nivel_blacklist + nivel_heuristica
|
|
313
|
+
|
|
314
|
+
if S_total >= UMBRAL_BLOQUEO:
|
|
315
|
+
descripcion_log = [
|
|
316
|
+
f"Score Total: {S_total:.3f} > Umbral {UMBRAL_BLOQUEO}",
|
|
317
|
+
f"DoS: {nivel_dos:.3f}, Blacklist: {nivel_blacklist:.3f}, Heurística: {nivel_heuristica:.3f}"
|
|
318
|
+
]
|
|
319
|
+
registrar_evento(
|
|
320
|
+
tipo="Bloqueo por Score Total",
|
|
321
|
+
descripcion=" ; ".join(descripcion_log),
|
|
322
|
+
severidad="CRÍTICA",
|
|
323
|
+
)
|
|
324
|
+
limitar_peticion(usuario_id=client_ip)
|
|
325
|
+
return HttpResponseForbidden("Acceso denegado por alto Score de Amenaza.")
|
|
326
|
+
|
|
327
|
+
# 5. REGISTRO DE ADVERTENCIA (Si no se bloquea, pero es sospechoso)
|
|
328
|
+
|
|
329
|
+
if S_total > UMBRAL_BLOQUEO * 0.75 or (nivel_dos > 0) or len(sospechas_headers) > 0:
|
|
330
|
+
|
|
331
|
+
descripcion = sospechas_headers
|
|
332
|
+
if score_endpoints > 0:
|
|
333
|
+
descripcion.append("Número anormal de endpoints distintos accedidos (posible escaneo/scraping)")
|
|
334
|
+
|
|
335
|
+
descripcion.insert(0, f"Score Total: {S_total:.3f} (Tasa: {tasa} req/min)")
|
|
336
|
+
descripcion.append(f"Ruta: {path}")
|
|
337
|
+
|
|
338
|
+
logger.warning(
|
|
339
|
+
"Tráfico Sospechoso desde IP %s: %s",
|
|
340
|
+
client_ip,
|
|
341
|
+
" ; ".join(descripcion),
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
request.dos_attack_info = {
|
|
345
|
+
"ip": client_ip,
|
|
346
|
+
"tipos": ["DoS", "Scraping/Escaneo"],
|
|
347
|
+
"descripcion": descripcion,
|
|
348
|
+
"payload": json.dumps({"user_agent": user_agent, "referer": referer, "path": path}),
|
|
349
|
+
"score": S_total,
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return None
|