La pagina web contiene una tabella e un parametro da query string name. Analizzandone il comportamento, si può dedurre che il parametro sia utilizzato per una ORDER BY.
Analizzando il codice sorgente si nota che:
mysql_real_escape_string() effettua l’escape dei caratteri speciali (apici in primis)Si ricordi la sintassi per l’iniezione di codice:
INPUT_LEGITTIMO + CARATTERE_SEPARATORE_STATEMENT + STATEMENT_SQL_ARBITRARIO + CARATTERE_CHIUSURA
In questo caso non si può usare la UNION e si deve usare come carattere separatore il backtick `.
La prima domanda è: la funzione mysql_real_escape_string() filtra il carattere backtick? Non resta che provare a passare una stringa vuota come input malizioso. Se viene eseguita la query originaria e stampata la tabella → L’input è stato interpretato correttamente e quindi il carattere backtick non è stato filtrato.
La query ha successo
La seconda domanda è: che cosa si può iniettare al posto della UNION? Guardando l’output (e conoscendo il comportamente di ORDER BY) possiamo notare che la tabella viene sempre stampata integralmente, indipendentemente dal valore di order. Quindi un qualunque statement arbitrario non sarà in grado di provocare un output nell’HTML.
Un possibile abbozzo di attacco consiste nell’utilizzo di un distinguisher.
<aside> 💡
Un distinguisher è una qualunque procedura che consenta di distinguere la presenza di una proprietà dalla sua assenza.
</aside>
La deduzione di informazioni è indipendente dall’output dell’applicazione → si parla di enumerazione alla cieca (blind enumeration).
Siccome l’applicazione non produce output, si prova a verificare l’iniezione di uno statement che la faccia attendere per n secondi. → Funzione di sistema offerta da MySQL sleep().
sleep() implementa un meccanismo di attesa per n secondi, e al termine ritorna 0.
Si prova dunque ad agganciare l’esecuzione di sleep() con gli operatori logici AND oppure OR. Se venisse eseguita, si potrebbe costruire un distinguisher per una specifica condizione logica.
order=name` AND SLEEP(5)%23
order=name` OR SLEEP(5)%23
Il primo non ha effetto, mentre il secondo causa l’attesa di 5s per ciascun record della tabella.
Per capire meglio il meccanismo di esecuzione delle query e di ORDER BY, ci si connette al db. Dalla VM di WFP:
mysql -upentesterlab -ppentesterlab exercises
Sappiamo che ORDER BY funziona nel modo seguente:
filesort
Si verifica la presenza di indici sulla tabella:
SHOW index FROM users;
Non sono presenti indici.
Si studia il funzionamento degli operatori booleani in MySQL. Viene usata una logica a tre valori:
Un qualunque operando non intero, viene prima convertito nella sua forma intera.
La tabella di verità di AND in presenza dell’operando UNKNOWN diventa:
| AND | TRUE | FALSE | UNKNOWN |
|---|---|---|---|
| TRUE | TRUE | FALSE | UNKNOWN |
| FALSE | FALSE | FALSE | FALSE |
| UNKNOWN | UNKNOWN | FALSE | FALSE |
Se uno degli operandi è FALSE, il risultato è sempre FALSE.
La tabella di verità dell’OR invece diventa:
| OR | TRUE | FALSE | UNKNOWN |
|---|---|---|---|
| TRUE | TRUE | TRUE | TRUE |
| FALSE | TRUE | FALSE | UNKNOWN |
| UNKNOWN | TRUE | UNKNOWN | UNKNOWN |
Se uno degli operandi è TRUE, il risultato è sempre TRUE.
A seguito di queste informazioni possiamo meglio valutare l’effetto del codice iniettato precedentemente:
order=name` AND/OR SLEEP(5)%23
La query risultante è:
SELECT * FROM users ORDER BY `name` AND/OR SLEEP(5)#
La clausola da valutare parte da ORDER BY in avanti.
Come visto in precedenza, l’operando SLEEP(5) ritorna sempre 0 al termine dell’esecuzione → viene quindi valutato come FALSE.
ORDER BY `name` AND/OR FALSE
La colonna name può assumere i valori: admin, root, user1, user2. Ogni stringa non nulla non iniziante con un numero viene convertita in → FALSE.
Essendo la query calcolata con il metodo filesort, viene prima eseguita senza ordinamento. Ogni elemento della colonna name è quindi messo in AND/OR logico con FALSE:
name viene convertita in FALSE, la seconda parte (funzione SLEEP(5)) non viene nemmeno valutataSiamo pronti per la costruzione di un distinguisher. MySQL mette a disposizione lo statement IF(), per l’esecuzione di uno statement condizionato al verificarsi di una condizione logica. Si consideri l’input malizioso che segue:
name` OR IF(EXPRESSION, SLEEP(1), NULL)%23
Se l’espressione EXPRESSION ritorna TRUE, viene eseguita la funzione SLEEP(1) e si ritorna il suo valore 0, altrimenti si ritorna subito NULL.
Questo input malizioso è un distinguisher per EXPRESSION.
Il distinguisher viene utilizzato come un oracolo: si pongono domande booleane per restringere lo spazio delle possibili soluzioni, fino ad arrivare ad un’unica risposta.
Esempio: nome del database tramite la funzione database()
Possiamo innanzitutto calcolare la lunghezza della stringa che ritorna la funzione, tramite la funzione di sistema MySQL length(). L’espressione da testare con il distinguisher diventa: length(database()).
Si può testare l’espressione incrementalmente a partire da 1 fino a raggiungere la lunghezza corretta che genererà l’attesa.