GuardianUnivalle-Benito-Yucra 0.1.60__tar.gz → 0.1.62__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.62/GuardianUnivalle_Benito_Yucra/detectores/detector_sql.py +254 -0
- {guardianunivalle_benito_yucra-0.1.60 → guardianunivalle_benito_yucra-0.1.62}/GuardianUnivalle_Benito_Yucra/detectores/detector_xss.py +60 -10
- {guardianunivalle_benito_yucra-0.1.60 → guardianunivalle_benito_yucra-0.1.62}/GuardianUnivalle_Benito_Yucra.egg-info/PKG-INFO +1 -1
- {guardianunivalle_benito_yucra-0.1.60 → guardianunivalle_benito_yucra-0.1.62}/PKG-INFO +1 -1
- {guardianunivalle_benito_yucra-0.1.60 → guardianunivalle_benito_yucra-0.1.62}/pyproject.toml +1 -1
- guardianunivalle_benito_yucra-0.1.60/GuardianUnivalle_Benito_Yucra/detectores/detector_sql.py +0 -147
- {guardianunivalle_benito_yucra-0.1.60 → guardianunivalle_benito_yucra-0.1.62}/GuardianUnivalle_Benito_Yucra/__init__.py +0 -0
- {guardianunivalle_benito_yucra-0.1.60 → guardianunivalle_benito_yucra-0.1.62}/GuardianUnivalle_Benito_Yucra/auditoria/registro_auditoria.py +0 -0
- {guardianunivalle_benito_yucra-0.1.60 → guardianunivalle_benito_yucra-0.1.62}/GuardianUnivalle_Benito_Yucra/criptografia/cifrado_aead.py +0 -0
- {guardianunivalle_benito_yucra-0.1.60 → guardianunivalle_benito_yucra-0.1.62}/GuardianUnivalle_Benito_Yucra/criptografia/intercambio_claves.py +0 -0
- {guardianunivalle_benito_yucra-0.1.60 → guardianunivalle_benito_yucra-0.1.62}/GuardianUnivalle_Benito_Yucra/criptografia/kdf.py +0 -0
- {guardianunivalle_benito_yucra-0.1.60 → guardianunivalle_benito_yucra-0.1.62}/GuardianUnivalle_Benito_Yucra/detectores/detector_csrf.py +0 -0
- {guardianunivalle_benito_yucra-0.1.60 → guardianunivalle_benito_yucra-0.1.62}/GuardianUnivalle_Benito_Yucra/detectores/detector_dos.py +0 -0
- {guardianunivalle_benito_yucra-0.1.60 → guardianunivalle_benito_yucra-0.1.62}/GuardianUnivalle_Benito_Yucra/detectores/detector_keylogger.py +0 -0
- {guardianunivalle_benito_yucra-0.1.60 → guardianunivalle_benito_yucra-0.1.62}/GuardianUnivalle_Benito_Yucra/middleware_web/middleware_web.py +0 -0
- {guardianunivalle_benito_yucra-0.1.60 → guardianunivalle_benito_yucra-0.1.62}/GuardianUnivalle_Benito_Yucra/mitigacion/limitador_peticion.py +0 -0
- {guardianunivalle_benito_yucra-0.1.60 → guardianunivalle_benito_yucra-0.1.62}/GuardianUnivalle_Benito_Yucra/mitigacion/lista_bloqueo.py +0 -0
- {guardianunivalle_benito_yucra-0.1.60 → guardianunivalle_benito_yucra-0.1.62}/GuardianUnivalle_Benito_Yucra/puntuacion/puntuacion_amenaza.py +0 -0
- {guardianunivalle_benito_yucra-0.1.60 → guardianunivalle_benito_yucra-0.1.62}/GuardianUnivalle_Benito_Yucra/utilidades.py +0 -0
- {guardianunivalle_benito_yucra-0.1.60 → guardianunivalle_benito_yucra-0.1.62}/GuardianUnivalle_Benito_Yucra.egg-info/SOURCES.txt +0 -0
- {guardianunivalle_benito_yucra-0.1.60 → guardianunivalle_benito_yucra-0.1.62}/GuardianUnivalle_Benito_Yucra.egg-info/dependency_links.txt +0 -0
- {guardianunivalle_benito_yucra-0.1.60 → guardianunivalle_benito_yucra-0.1.62}/GuardianUnivalle_Benito_Yucra.egg-info/requires.txt +0 -0
- {guardianunivalle_benito_yucra-0.1.60 → guardianunivalle_benito_yucra-0.1.62}/GuardianUnivalle_Benito_Yucra.egg-info/top_level.txt +0 -0
- {guardianunivalle_benito_yucra-0.1.60 → guardianunivalle_benito_yucra-0.1.62}/LICENSE +0 -0
- {guardianunivalle_benito_yucra-0.1.60 → guardianunivalle_benito_yucra-0.1.62}/README.md +0 -0
- {guardianunivalle_benito_yucra-0.1.60 → guardianunivalle_benito_yucra-0.1.62}/setup.cfg +0 -0
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
# sql_defense.py
|
|
2
|
+
# GuardianUnivalle_Benito_Yucra/detectores/detector_sql.py
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import logging
|
|
6
|
+
import re
|
|
7
|
+
from django.utils.deprecation import MiddlewareMixin
|
|
8
|
+
from django.conf import settings
|
|
9
|
+
import urllib.parse
|
|
10
|
+
import html
|
|
11
|
+
from typing import List, Tuple, Dict
|
|
12
|
+
|
|
13
|
+
# ----------------------------
|
|
14
|
+
# Configuración del logger
|
|
15
|
+
# ----------------------------
|
|
16
|
+
logger = logging.getLogger("sqlidefense")
|
|
17
|
+
logger.setLevel(logging.INFO)
|
|
18
|
+
if not logger.handlers:
|
|
19
|
+
handler = logging.StreamHandler()
|
|
20
|
+
handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s"))
|
|
21
|
+
logger.addHandler(handler)
|
|
22
|
+
|
|
23
|
+
# =====================================================
|
|
24
|
+
# === PATRONES DE ATAQUE SQL DEFINIDOS ===
|
|
25
|
+
# =====================================================
|
|
26
|
+
SQL_PATTERNS: List[Tuple[re.Pattern, str, float]] = [
|
|
27
|
+
# ------------------ In‑Band / Exfiltration (muy alto) ------------------
|
|
28
|
+
(re.compile(r"\bunion\b\s+(all\s+)?\bselect\b", re.I), "UNION SELECT (exfiltración)", 0.95),
|
|
29
|
+
(re.compile(r"\bselect\b\s+.*\bfrom\b\s+.+\bwhere\b", re.I | re.S), "SELECT ... FROM ... WHERE (consulta completa)", 0.7),
|
|
30
|
+
(re.compile(r"\binto\s+outfile\b|\binto\s+dumpfile\b", re.I), "INTO OUTFILE / INTO DUMPFILE (volcado a fichero)", 0.98),
|
|
31
|
+
(re.compile(r"\bload_file\s*\(", re.I), "LOAD_FILE() (lectura fichero MySQL)", 0.95),
|
|
32
|
+
(re.compile(r"\b(pg_read_file|pg_read_binary_file|pg_ls_dir)\s*\(", re.I), "pg_read_file / funciones lectura Postgres", 0.95),
|
|
33
|
+
(re.compile(r"\bfile_read\b|\bfile_get_contents\b", re.I), "Indicadores de lectura de fichero en código", 0.85),
|
|
34
|
+
|
|
35
|
+
# ------------------ Time‑based / Blind (muy alto) ------------------
|
|
36
|
+
(re.compile(r"\b(sleep|benchmark|pg_sleep|dbms_lock\.sleep|waitfor\s+delay)\b\s*\(", re.I), "SLEEP/pg_sleep/WAITFOR DELAY (time‑based blind)", 0.98),
|
|
37
|
+
(re.compile(r"\bbenchmark\s*\(", re.I), "BENCHMARK() MySQL (time/DoS)", 0.9),
|
|
38
|
+
|
|
39
|
+
# ------------------ Error‑based extraction (muy alto) ------------------
|
|
40
|
+
(re.compile(r"\b(updatexml|extractvalue|xmltype|utl_http\.request|dbms_xmlquery)\b\s*\(", re.I), "Funciones que devuelven errores con contenido (error‑based)", 0.95),
|
|
41
|
+
(re.compile(r"\bconvert\(\s*.*\s+using\s+.*\)", re.I), "CONVERT ... USING (encoding conversions potenciales)", 0.7),
|
|
42
|
+
|
|
43
|
+
# ------------------ OOB / Callbacks / Exfiltration (muy alto) ------------------
|
|
44
|
+
(re.compile(r"\b(nslookup|dnslookup|xp_dirtree|xp_dirtree\(|xp_regread|xp\w+)\b", re.I),
|
|
45
|
+
"Funciones/procs que pueden generar exfiltración OOB (DNS/SMB/SMB callbacks)", 0.95),
|
|
46
|
+
(re.compile(r"\b(utl_http\.request|utl_tcp\.socket|http_client|apex_web_service\.make_rest_request)\b", re.I),
|
|
47
|
+
"UTL_HTTP/HTTP callbacks (Oracle/PLSQL HTTP OOB)", 0.95),
|
|
48
|
+
|
|
49
|
+
# ------------------ Execution / OS commands (muy alto) ------------------
|
|
50
|
+
(re.compile(r"\bxp_cmdshell\b|\bexec\s+xp\w+|\bsp_oacreate\b", re.I), "xp_cmdshell / sp_oacreate (ejecución OS MSSQL/Oracle)", 0.98),
|
|
51
|
+
(re.compile(r"\b(exec\s+master\..*xp\w+|sp_executesql|execute\s+immediate|EXEC\s+UTE)\b", re.I), "Ejecución dinámica / sp_executesql / EXECUTE IMMEDIATE", 0.95),
|
|
52
|
+
|
|
53
|
+
# ------------------ Metadata / Recon (alto) ------------------
|
|
54
|
+
(re.compile(r"\binformation_schema\b", re.I), "INFORMATION_SCHEMA (recon meta‑datos)", 0.92),
|
|
55
|
+
(re.compile(r"\b(information_schema\.tables|information_schema\.columns)\b", re.I), "INFORMATION_SCHEMA.tables/columns", 0.92),
|
|
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),
|
|
57
|
+
|
|
58
|
+
# ------------------ DML/DDL Destructivo (alto) ------------------
|
|
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),
|
|
60
|
+
(re.compile(r"\b(delete\s+from|update\s+.+\s+set|insert\s+into)\b", re.I), "DML (DELETE/UPDATE/INSERT potencialmente destructivo)", 0.85),
|
|
61
|
+
|
|
62
|
+
# ------------------ Stacked queries (medio‑alto) ------------------
|
|
63
|
+
(re.compile(r";\s*(select|insert|update|delete|drop|create|truncate)\b", re.I), "Stacked queries (uso de ';' para apilar)", 0.88),
|
|
64
|
+
|
|
65
|
+
# ------------------ Tautologías / Boolean Blind (medio‑alto) ------------------
|
|
66
|
+
(re.compile(r"\b(or|and)\b\s+(['\"]?\d+['\"]?)\s*=\s*\1", re.I), "Tautología OR/AND 'x'='x' o 1=1", 0.85),
|
|
67
|
+
(re.compile(r"(['\"]).{0,10}\1\s*or\s*['\"][^']*['\"]\s*=\s*['\"][^']*['\"]", re.I), "Tautología clásica en cadenas (OR '1'='1')", 0.8),
|
|
68
|
+
|
|
69
|
+
# ------------------ Blind‑boolean extraction functions (medio) ------------------
|
|
70
|
+
(re.compile(r"\b(substring|substr|mid|left|right)\b\s*\(", re.I), "SUBSTRING/SUBSTR/LEFT/RIGHT (blind extraction)", 0.82),
|
|
71
|
+
(re.compile(r"\b(ascii|char|chr|nchr)\b\s*\(", re.I), "ASCII/CHAR/CHR (byte/char extraction)", 0.8),
|
|
72
|
+
|
|
73
|
+
# ------------------ Error / XPATH / XML (alto) ------------------
|
|
74
|
+
(re.compile(r"\b(updatexml|extractvalue|xmltype|xmlelement)\b\s*\(", re.I), "updatexml/extractvalue/xmltype (error/XPath leaks)", 0.93),
|
|
75
|
+
|
|
76
|
+
# ------------------ File system / I/O (alto) ------------------
|
|
77
|
+
(re.compile(r"\binto\s+outfile\b|\binto\s+dumpfile\b", re.I), "INTO OUTFILE / DUMPFILE (escritura en servidor)", 0.97),
|
|
78
|
+
(re.compile(r"\bopenrowset\b|\bbulk\s+insert\b|\bcopy\s+to\b", re.I), "OPENROWSET / BULK INSERT / COPY TO (exportación)", 0.92),
|
|
79
|
+
|
|
80
|
+
# ------------------ Encoding / Obfuscation (medio) ------------------
|
|
81
|
+
(re.compile(r"0x[0-9a-fA-F]+", re.I), "Hex literal (0x...) (ofuscación)", 0.6),
|
|
82
|
+
(re.compile(r"\\x[0-9a-fA-F]{2}", re.I), "Escapes hex tipo \\xNN (ofuscación)", 0.6),
|
|
83
|
+
(re.compile(r"&#x[0-9a-fA-F]+;|&#\d+;", re.I), "Entidades HTML / entidades numéricas (ofuscación)", 0.6),
|
|
84
|
+
(re.compile(r"\bchar\s*\(\s*\d+\s*\)", re.I), "CHAR(n) usado para construir cadenas (ofuscación)", 0.65),
|
|
85
|
+
(re.compile(r"\bconcat\(", re.I), "CONCAT() (construcción dinámica de strings)", 0.6),
|
|
86
|
+
|
|
87
|
+
# ------------------ SQL in attributes / URL encoded (medio) ------------------
|
|
88
|
+
(re.compile(r"%3[dD]|%27|%22|%3C|%3E|%3B", re.I), "URL encoding típico (%27, %3C, etc.)", 0.4),
|
|
89
|
+
|
|
90
|
+
# ------------------ Comments / terminators (informativo) ------------------
|
|
91
|
+
(re.compile(r"(--\s|#\s|/\*[\s\S]*\*/)", re.I), "Comentarios SQL (--) o /* */ o #", 0.45),
|
|
92
|
+
|
|
93
|
+
# ------------------ ORM / NonSQL indicators (informativo) ------------------
|
|
94
|
+
(re.compile(r"\b\$where\b|\b\$ne\b|\b\$regex\b", re.I), "NoSQL / MongoDB indicators ($where/$ne/$regex)", 0.5),
|
|
95
|
+
|
|
96
|
+
# ------------------ Tool fingerprints (informativo) ------------------
|
|
97
|
+
(re.compile(r"sqlmap", re.I), "Indicador de herramienta sqlmap en payload", 0.5),
|
|
98
|
+
(re.compile(r"hydra|nmap|nikto", re.I), "Indicador de herramientas de auditoría/scan", 0.3),
|
|
99
|
+
|
|
100
|
+
# ------------------ Misc risky tokens (informativo) ------------------
|
|
101
|
+
(re.compile(r"\bexecute\b\s*\(", re.I), "execute(...) (ejecución dinámica)", 0.7),
|
|
102
|
+
(re.compile(r"\bdeclare\b\s+@?\w+", re.I), "DECLARE variable (MSSQL/PLSQL declarations)", 0.7),
|
|
103
|
+
|
|
104
|
+
# ------------------ Low‑level heuristics (bajo) ------------------
|
|
105
|
+
(re.compile(r"\bselect\b\s+.*\bfrom\b", re.I), "Estructura SELECT FROM (heurístico)", 0.25),
|
|
106
|
+
(re.compile(r"\binsert\b\s+into\b", re.I), "INSERT INTO (heurístico)", 0.3),
|
|
107
|
+
|
|
108
|
+
# ------------------ Catch‑all aggressive patterns (usar con cuidado) ------------------
|
|
109
|
+
(re.compile(r"(['\"]).*?;\s*(drop|truncate|delete|update|insert)\b", re.I | re.S), "Cadena con terminador y DDL/DML (potencial ataque)", 0.9),
|
|
110
|
+
(re.compile(r"\b(or)\b\s+1\s*=\s*1\b", re.I), "OR 1=1 tautology", 0.85),
|
|
111
|
+
]
|
|
112
|
+
|
|
113
|
+
IGNORED_FIELDS = ["password", "csrfmiddlewaretoken", "token", "auth"]
|
|
114
|
+
|
|
115
|
+
# ----------------------------
|
|
116
|
+
# Obtener IP real del cliente
|
|
117
|
+
# ----------------------------
|
|
118
|
+
def get_client_ip(request):
|
|
119
|
+
x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR")
|
|
120
|
+
if x_forwarded_for:
|
|
121
|
+
ips = [ip.strip() for ip in x_forwarded_for.split(",") if ip.strip()]
|
|
122
|
+
if ips:
|
|
123
|
+
return ips[0]
|
|
124
|
+
return request.META.get("REMOTE_ADDR", "")
|
|
125
|
+
|
|
126
|
+
# ----------------------------
|
|
127
|
+
# Extraer payload de la solicitud
|
|
128
|
+
# ----------------------------
|
|
129
|
+
def extract_payload(request):
|
|
130
|
+
parts = []
|
|
131
|
+
try:
|
|
132
|
+
if "application/json" in request.META.get("CONTENT_TYPE", ""):
|
|
133
|
+
data = json.loads(request.body.decode("utf-8") or "{}")
|
|
134
|
+
parts.append(json.dumps(data))
|
|
135
|
+
else:
|
|
136
|
+
body = request.body.decode("utf-8", errors="ignore")
|
|
137
|
+
if body:
|
|
138
|
+
parts.append(body)
|
|
139
|
+
except Exception:
|
|
140
|
+
pass
|
|
141
|
+
|
|
142
|
+
qs = request.META.get("QUERY_STRING", "")
|
|
143
|
+
if qs:
|
|
144
|
+
parts.append(qs)
|
|
145
|
+
|
|
146
|
+
return " ".join(parts)
|
|
147
|
+
|
|
148
|
+
# ----------------------------
|
|
149
|
+
# Normalización / preprocesamiento
|
|
150
|
+
# ----------------------------
|
|
151
|
+
def normalize_input(s: str) -> str:
|
|
152
|
+
if not s:
|
|
153
|
+
return ""
|
|
154
|
+
try:
|
|
155
|
+
s_dec = urllib.parse.unquote_plus(s)
|
|
156
|
+
except Exception:
|
|
157
|
+
s_dec = s
|
|
158
|
+
try:
|
|
159
|
+
s_dec = html.unescape(s_dec)
|
|
160
|
+
except Exception:
|
|
161
|
+
pass
|
|
162
|
+
# Reemplazo seguro de secuencias \xNN
|
|
163
|
+
s_dec = re.sub(r"\\x([0-9a-fA-F]{2})", r"\\x\g<1>", s_dec)
|
|
164
|
+
s_dec = re.sub(r"\s+", " ", s_dec)
|
|
165
|
+
return s_dec.strip()
|
|
166
|
+
|
|
167
|
+
# ----------------------------
|
|
168
|
+
# Detector SQLi
|
|
169
|
+
# ----------------------------
|
|
170
|
+
def detect_sql_injection(text: str) -> Dict:
|
|
171
|
+
norm = normalize_input(text or "")
|
|
172
|
+
score = 0.0
|
|
173
|
+
matches = []
|
|
174
|
+
descriptions = []
|
|
175
|
+
for pattern, desc, weight in SQL_PATTERNS:
|
|
176
|
+
if pattern.search(norm):
|
|
177
|
+
score += weight
|
|
178
|
+
matches.append((desc, pattern.pattern, weight))
|
|
179
|
+
descriptions.append(desc)
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
"score": round(score, 3),
|
|
183
|
+
"matches": matches,
|
|
184
|
+
"descriptions": list(dict.fromkeys(descriptions)),
|
|
185
|
+
"sample": norm[:1200],
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
DEFAULT_THRESHOLDS = {
|
|
189
|
+
"HIGH": 1.8,
|
|
190
|
+
"MEDIUM": 1.0,
|
|
191
|
+
"LOW": 0.5,
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
# ----------------------------
|
|
195
|
+
# Middleware SQLi
|
|
196
|
+
# ----------------------------
|
|
197
|
+
class SQLIDefenseMiddleware(MiddlewareMixin):
|
|
198
|
+
def process_request(self, request):
|
|
199
|
+
client_ip = get_client_ip(request)
|
|
200
|
+
trusted_ips = getattr(settings, "SQLI_DEFENSE_TRUSTED_IPS", [])
|
|
201
|
+
trusted_urls = getattr(settings, "SQLI_DEFENSE_TRUSTED_URLS", [])
|
|
202
|
+
|
|
203
|
+
if client_ip in trusted_ips:
|
|
204
|
+
return None
|
|
205
|
+
|
|
206
|
+
referer = request.META.get("HTTP_REFERER", "")
|
|
207
|
+
host = request.get_host()
|
|
208
|
+
if any(url in referer for url in trusted_urls) or any(url in host for url in trusted_urls):
|
|
209
|
+
return None
|
|
210
|
+
|
|
211
|
+
payload = extract_payload(request)
|
|
212
|
+
result = detect_sql_injection(payload)
|
|
213
|
+
score = result["score"]
|
|
214
|
+
descripciones = result["descriptions"]
|
|
215
|
+
|
|
216
|
+
if score == 0:
|
|
217
|
+
return None
|
|
218
|
+
|
|
219
|
+
# Registrar ataque
|
|
220
|
+
logger.warning(
|
|
221
|
+
f"[SQLiDetect] IP={client_ip} Host={host} Referer={referer} "
|
|
222
|
+
f"Score={score:.2f} Desc={descripciones} Payload={payload[:500]}"
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
# Guardar info en request
|
|
226
|
+
request.sql_attack_info = {
|
|
227
|
+
"ip": client_ip,
|
|
228
|
+
"tipos": ["SQLi"],
|
|
229
|
+
"descripcion": descripciones,
|
|
230
|
+
"payload": payload[:1000],
|
|
231
|
+
"score": round(score, 2),
|
|
232
|
+
"url": request.build_absolute_uri(),
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return None
|
|
236
|
+
|
|
237
|
+
# =====================================================
|
|
238
|
+
# === INFORMACIÓN EXTRA ===
|
|
239
|
+
# =====================================================
|
|
240
|
+
r"""
|
|
241
|
+
Algoritmos relacionados:
|
|
242
|
+
- Se recomienda almacenar logs SQLi cifrados (AES-GCM)
|
|
243
|
+
para proteger evidencia de intentos maliciosos.
|
|
244
|
+
|
|
245
|
+
Cálculo de puntaje de amenaza:
|
|
246
|
+
S_sqli = w_sqli * detecciones_sqli
|
|
247
|
+
Ejemplo: S_sqli = 0.4 * 3 = 1.2
|
|
248
|
+
|
|
249
|
+
Integración:
|
|
250
|
+
Este middleware puede combinarse con:
|
|
251
|
+
- CSRFDefenseMiddleware
|
|
252
|
+
- XSSDefenseMiddleware
|
|
253
|
+
para calcular un score total de amenaza y decidir bloqueo.
|
|
254
|
+
"""
|
|
@@ -33,17 +33,67 @@ except Exception:
|
|
|
33
33
|
# - pesos mayores = más severo (por ejemplo <script> o javascript:)
|
|
34
34
|
# - esto permite un scoring acumulativo y menos falsos positivos
|
|
35
35
|
# -------------------------------------------------
|
|
36
|
+
|
|
36
37
|
XSS_PATTERNS: List[Tuple[re.Pattern, str, float]] = [
|
|
37
|
-
|
|
38
|
-
(re.compile(r"
|
|
39
|
-
(re.compile(r"<\s*
|
|
40
|
-
(re.compile(r"
|
|
41
|
-
|
|
42
|
-
(re.compile(r"
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
#
|
|
46
|
-
(re.compile(r"
|
|
38
|
+
# ---------- Máxima severidad / ejecución directa ----------
|
|
39
|
+
(re.compile(r"<\s*script\b", re.I), "Etiqueta <script> (directa)", 0.95),
|
|
40
|
+
(re.compile(r"<\s*s\s*c\s*r\s*i\s*p\s*t\b", re.I), "Etiqueta <script> ofuscada", 0.90),
|
|
41
|
+
(re.compile(r"\b(document\.cookie|document\.write|document\.location|location\.href|window\.location)\b", re.I),
|
|
42
|
+
"Acceso a document / location (cookie/location/write)", 0.90),
|
|
43
|
+
(re.compile(r"\b(eval|setTimeout|setInterval|Function|new Function)\s*\(", re.I),
|
|
44
|
+
"Ejecución dinámica (eval/Function/setTimeout)", 0.88),
|
|
45
|
+
|
|
46
|
+
# ---------- URIs peligrosas ----------
|
|
47
|
+
(re.compile(r"\bjavascript\s*:", re.I), "URI javascript:", 0.85),
|
|
48
|
+
(re.compile(r"\bdata\s*:\s*text\/html\b", re.I), "URI data:text/html", 0.82),
|
|
49
|
+
(re.compile(r"\bdata\s*:\s*text\/html;base64\b", re.I), "URI data:text/html;base64", 0.82),
|
|
50
|
+
(re.compile(r"\bvbscript\s*:", re.I), "URI vbscript:", 0.7),
|
|
51
|
+
|
|
52
|
+
# ---------- Etiquetas y vectores alternativos ----------
|
|
53
|
+
(re.compile(r"<\s*(iframe|embed|object|svg|math|meta)\b", re.I), "IFrame/Embed/Object/SVG/Meta", 0.88),
|
|
54
|
+
(re.compile(r"<\s*img\b[^>]*\bonerror\b", re.I), "<img ... onerror>", 0.86),
|
|
55
|
+
(re.compile(r"<\s*svg\b[^>]*\bonload\b", re.I), "SVG con onload/on* (SVG vector)", 0.84),
|
|
56
|
+
|
|
57
|
+
# ---------- Atributos de evento (on*) ----------
|
|
58
|
+
(re.compile(r"\s+on[a-zA-Z]+\s*=", re.I), "Atributo de evento (on*)", 0.80),
|
|
59
|
+
(re.compile(r"<\s*(a|img|body|div|span|form|input|button)\b[^>]*on[a-zA-Z]+\s*=", re.I),
|
|
60
|
+
"Elemento con evento on* (a,img,body,...)", 0.82),
|
|
61
|
+
|
|
62
|
+
# ---------- Inyección en contextos JS / JSON / script ----------
|
|
63
|
+
(re.compile(r"<\s*script[^>]*>.*?</\s*script\s*>", re.I | re.S), "Script inline completo", 0.92),
|
|
64
|
+
(re.compile(r"'\s*;\s*alert\s*\(|\"\s*;\s*alert\s*\(", re.I), "Inyección en cadenas JS (breakout + alert)", 0.78),
|
|
65
|
+
(re.compile(r"\bJSON\.parse\(|\beval\(\s*JSON", re.I), "JSON parse/eval inseguro", 0.75),
|
|
66
|
+
|
|
67
|
+
# ---------- Encodings, entidades y mutaciones ----------
|
|
68
|
+
(re.compile(r"&#x[0-9a-fA-F]+;|&#\d+;", re.I), "Entidades HTML / encoding (posible bypass)", 0.70),
|
|
69
|
+
(re.compile(r"%3C\s*script|%3Cscript%3E", re.I), "Tags URL-encoded (%3Cscript)", 0.68),
|
|
70
|
+
(re.compile(r"(?:\\x3C|\\u003C)\s*script", re.I), "Escapes JS/Unicode que forman <script>", 0.68),
|
|
71
|
+
|
|
72
|
+
# ---------- DOM clobbering / nombres reservados ----------
|
|
73
|
+
(re.compile(r'\bid\s*=\s*"(?:form|image|submit|action|location|name)"', re.I), "IDs que causan DOM clobbering", 0.65),
|
|
74
|
+
(re.compile(r'\bname\s*=\s*"(?:form|submit|action|location)"', re.I), "Names que pueden clobber", 0.65),
|
|
75
|
+
|
|
76
|
+
# ---------- Atributos URI en tags (href/src) ----------
|
|
77
|
+
(re.compile(r'<\s*a\b[^>]*\bhref\s*=\s*[\'"]\s*javascript\s*:', re.I), "<a href=\"javascript:...\">", 0.84),
|
|
78
|
+
(re.compile(r'<\s*(img|script|iframe)\b[^>]*\bsrc\s*=\s*[\'"]\s*javascript\s*:', re.I),
|
|
79
|
+
"src=javascript: en tags", 0.84),
|
|
80
|
+
|
|
81
|
+
# ---------- Vectores en CSS / style ----------
|
|
82
|
+
(re.compile(r"\bstyle\s*=\s*[\"'][^\"']*(expression\s*\(|url\s*\(\s*javascript:)", re.I), "Estilo con expression() o url(javascript:)", 0.66),
|
|
83
|
+
(re.compile(r"@import\s+url\s*\(", re.I), "CSS @import posibles vectores", 0.45),
|
|
84
|
+
|
|
85
|
+
# ---------- Comentarios y CDATA para evasión ----------
|
|
86
|
+
(re.compile(r"<!\[CDATA\[|\/\/\s*<\s*!\s*\[CDATA\[", re.I), "CDATA o comentarios para evasión", 0.48),
|
|
87
|
+
(re.compile(r"<!--|-->", re.I), "Comentarios HTML (posible ofuscación/evitación)", 0.30),
|
|
88
|
+
|
|
89
|
+
# ---------- Polyglot / mutation / browser quirks ----------
|
|
90
|
+
(re.compile(r"(?:<\s*svg[^>]*>.*?<\s*/\s*svg\s*>)|(?:<\s*math[^>]*>)", re.I | re.S),
|
|
91
|
+
"SVG/MathML polyglot (vectores mutables)", 0.75),
|
|
92
|
+
(re.compile(r"(?:\balert\s*\(|\bconsole\.log\s*\()", re.I), "Indicadores de prueba (alert/console.log)", 0.40),
|
|
93
|
+
|
|
94
|
+
# ---------- Heurísticos de baja severidad (informativo) ----------
|
|
95
|
+
(re.compile(r"<\s*form\b", re.I), "Form (posible vector de ataque relacionado)", 0.25),
|
|
96
|
+
(re.compile(r"(onmouseover|onfocus|onmouseenter|onmouseleave)\b", re.I), "Eventos UI (mouseover/focus)", 0.45),
|
|
47
97
|
]
|
|
48
98
|
|
|
49
99
|
# -------------------------------------------------
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: GuardianUnivalle-Benito-Yucra
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.62
|
|
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.62
|
|
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.60 → guardianunivalle_benito_yucra-0.1.62}/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.62"
|
|
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" }
|
guardianunivalle_benito_yucra-0.1.60/GuardianUnivalle_Benito_Yucra/detectores/detector_sql.py
DELETED
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
# sql_defense.py
|
|
2
|
-
# GuardianUnivalle_Benito_Yucra/detectores/detector_sql.py
|
|
3
|
-
|
|
4
|
-
import json
|
|
5
|
-
import logging
|
|
6
|
-
import re
|
|
7
|
-
from django.utils.deprecation import MiddlewareMixin
|
|
8
|
-
from django.conf import settings
|
|
9
|
-
|
|
10
|
-
logger = logging.getLogger("sqlidefense")
|
|
11
|
-
logger.setLevel(logging.INFO)
|
|
12
|
-
if not logger.handlers:
|
|
13
|
-
handler = logging.StreamHandler()
|
|
14
|
-
handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s"))
|
|
15
|
-
logger.addHandler(handler)
|
|
16
|
-
|
|
17
|
-
# =====================================================
|
|
18
|
-
# === PATRONES DE ATAQUE SQL DEFINIDOS ===
|
|
19
|
-
# =====================================================
|
|
20
|
-
SQL_PATTERNS = [
|
|
21
|
-
# Patrones de Extracción de Datos y Evasión (Alto Peso)
|
|
22
|
-
(re.compile(r"\bunion\b\s+(all\s+)?\bselect\b", re.I), "Uso de UNION SELECT", 0.7),
|
|
23
|
-
(re.compile(r"\bor\b\s+['\"]?\d+['\"]?\s*=\s*['\"]?\d+['\"]?", re.I), "Tautología OR X=X", 0.6), # Mejorado
|
|
24
|
-
(re.compile(r"\b(sleep|benchmark|waitfor\s+delay)\b\s*\(", re.I), "Función de Tiempo (SQL Ciega)", 0.8), # Muy peligroso
|
|
25
|
-
(re.compile(r"\b(extractvalue|updatexml|convert)\b\s*\(", re.I), "Extracción Basada en Errores/Funciones", 0.75),
|
|
26
|
-
|
|
27
|
-
# Patrones de Control y Destrucción (Peso Medio)
|
|
28
|
-
(re.compile(r"\b(drop\s+table|truncate\s+table|delete\s+from|insert\s+into|update\s+set)\b", re.I), "Manipulación DML/DDL", 0.5),
|
|
29
|
-
(re.compile(r"\b(exec|execute|xp_cmdshell)\b", re.I), "Ejecución de Comando (OS o Stored Proc)", 0.6),
|
|
30
|
-
(re.compile(r";\s*(select|drop|insert|update)\b", re.I), "Apilamiento de Consultas (Separador ;)", 0.55), # Nuevo
|
|
31
|
-
|
|
32
|
-
# Patrones de Detección e Información (Bajo Peso)
|
|
33
|
-
(re.compile(r"(--|#|/\*|;)", re.I), "Comentario SQL o Separador de Consulta", 0.4),
|
|
34
|
-
(re.compile(r"\b(substring|substr|mid)\b\s*\(", re.I), "Función de Cadena (SQL Ciega Booleana)", 0.45), # Nuevo
|
|
35
|
-
(re.compile(r"\b(select)\b.+\b(from|where)\b", re.I), "Estructura SELECT-FROM-WHERE", 0.4), # Más específico
|
|
36
|
-
]
|
|
37
|
-
|
|
38
|
-
IGNORED_FIELDS = ["password", "csrfmiddlewaretoken", "token", "auth"]
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def get_client_ip(request):
|
|
42
|
-
"""
|
|
43
|
-
Obtiene la IP real del cliente.
|
|
44
|
-
Primero revisa 'X-Forwarded-For', luego 'REMOTE_ADDR'.
|
|
45
|
-
"""
|
|
46
|
-
x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR")
|
|
47
|
-
if x_forwarded_for:
|
|
48
|
-
# Render y otros proxies envían múltiples IPs separados por coma
|
|
49
|
-
ips = [ip.strip() for ip in x_forwarded_for.split(",") if ip.strip()]
|
|
50
|
-
if ips:
|
|
51
|
-
return ips[0] # la primera IP es la IP real del cliente
|
|
52
|
-
# Si no hay X-Forwarded-For, tomar REMOTE_ADDR
|
|
53
|
-
return request.META.get("REMOTE_ADDR", "")
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
def extract_payload(request):
|
|
58
|
-
"""Extrae datos útiles de la solicitud para análisis."""
|
|
59
|
-
parts = []
|
|
60
|
-
try:
|
|
61
|
-
if "application/json" in request.META.get("CONTENT_TYPE", ""):
|
|
62
|
-
data = json.loads(request.body.decode("utf-8") or "{}")
|
|
63
|
-
parts.append(json.dumps(data))
|
|
64
|
-
else:
|
|
65
|
-
body = request.body.decode("utf-8", errors="ignore")
|
|
66
|
-
if body:
|
|
67
|
-
parts.append(body)
|
|
68
|
-
except Exception:
|
|
69
|
-
pass
|
|
70
|
-
|
|
71
|
-
qs = request.META.get("QUERY_STRING", "")
|
|
72
|
-
if qs:
|
|
73
|
-
parts.append(qs)
|
|
74
|
-
|
|
75
|
-
return " ".join(parts)
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
def detect_sql_injection(value):
|
|
79
|
-
"""Detecta patrones sospechosos en una cadena."""
|
|
80
|
-
score = 0.0
|
|
81
|
-
descripciones = []
|
|
82
|
-
for pattern, desc, weight in SQL_PATTERNS:
|
|
83
|
-
if pattern.search(value):
|
|
84
|
-
score += weight
|
|
85
|
-
descripciones.append(desc)
|
|
86
|
-
return score, descripciones
|
|
87
|
-
|
|
88
|
-
class SQLIDefenseMiddleware(MiddlewareMixin):
|
|
89
|
-
"""Middleware de detección SQL Injection."""
|
|
90
|
-
|
|
91
|
-
def process_request(self, request):
|
|
92
|
-
client_ip = get_client_ip(request)
|
|
93
|
-
trusted_ips = getattr(settings, "SQLI_DEFENSE_TRUSTED_IPS", [])
|
|
94
|
-
trusted_urls = getattr(settings, "SQLI_DEFENSE_TRUSTED_URLS", [])
|
|
95
|
-
|
|
96
|
-
if client_ip in trusted_ips:
|
|
97
|
-
return None
|
|
98
|
-
|
|
99
|
-
referer = request.META.get("HTTP_REFERER", "")
|
|
100
|
-
host = request.get_host()
|
|
101
|
-
if any(url in referer for url in trusted_urls) or any(url in host for url in trusted_urls):
|
|
102
|
-
return None
|
|
103
|
-
|
|
104
|
-
payload = extract_payload(request)
|
|
105
|
-
score, descripciones = detect_sql_injection(payload)
|
|
106
|
-
|
|
107
|
-
if score == 0:
|
|
108
|
-
return None
|
|
109
|
-
|
|
110
|
-
# Registrar ataque completo
|
|
111
|
-
logger.warning(
|
|
112
|
-
f"[SQLiDetect] IP={client_ip} Host={host} Referer={referer} "
|
|
113
|
-
f"Score={score:.2f} Desc={descripciones} Payload={payload[:500]}"
|
|
114
|
-
)
|
|
115
|
-
|
|
116
|
-
# Guardar información del ataque en el request
|
|
117
|
-
request.sql_attack_info = {
|
|
118
|
-
"ip": client_ip,
|
|
119
|
-
"tipos": ["SQLi"],
|
|
120
|
-
"descripcion": descripciones,
|
|
121
|
-
"payload": payload[:1000], # guardar hasta 1000 caracteres
|
|
122
|
-
"score": round(score, 2),
|
|
123
|
-
"url": request.build_absolute_uri(), # registrar URL completa
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return None
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
# =====================================================
|
|
131
|
-
# === INFORMACIÓN EXTRA ===
|
|
132
|
-
# =====================================================
|
|
133
|
-
"""
|
|
134
|
-
Algoritmos relacionados:
|
|
135
|
-
- Se recomienda almacenar logs SQLi cifrados (AES-GCM)
|
|
136
|
-
para proteger evidencia de intentos maliciosos.
|
|
137
|
-
|
|
138
|
-
Cálculo de puntaje de amenaza:
|
|
139
|
-
S_sqli = w_sqli * detecciones_sqli
|
|
140
|
-
Ejemplo: S_sqli = 0.4 * 3 = 1.2
|
|
141
|
-
|
|
142
|
-
Integración:
|
|
143
|
-
Este middleware puede combinarse con:
|
|
144
|
-
- CSRFDefenseMiddleware
|
|
145
|
-
- XSSDefenseMiddleware
|
|
146
|
-
para calcular un score total de amenaza y decidir bloqueo.
|
|
147
|
-
"""
|
|
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
|