Dopo aver introdotto in linee generali come funziona una webapp, alcuni dei possibili attacchi e aver quasi concluso la serie di OWASP, iniziamo a sporcarci le mani con degli esercizi pratici e mirati, che permetteranno di capire alcune delle debolezze che possono affliggere un’applicazione web mal programmata.
Utilizzerò la macchina virtuale offerta da PentesterLab, la quale è composta da diverse tipologie di esercizi, divisi per categoria e ordinati per difficoltà. Per chi non sapesse come configurare la VM, ne avevo parlato nel primo articolo di Kioptrix.
Per quanto riguarda gli attacchi XSS, ne ho spiegato il funzionamento nell’articolo Owasp Top 10 - A3 Cross Site Scripting, mentre a questo link trovate i principali tipi di attacchi.
La prima azione da eseguire è controllare se l’applicazione esegue qualche tipo di filtraggio o sanitizza i dati. Per farlo, inserisco nella GET la stringa
'";!<XSS>=()
e controllo la pagina sorgente
Visto che non viene filtrata, inietto la stringa base di un attacco XSS
<script>alert('XSS')</script>
Il codice della pagina richiesta è
<code class="php"><span class="line"><span class="o"><?</span><span class="nx">php</span> <span class="k">require_once</span> <span class="s1">'../header.php'</span><span class="p">;</span> <span class="cp">?></span>
</span><span class="line"><span class="x"><html></span>
</span><span class="line"><span class="x">Hello </span>
</span><span class="line"><span class="cp"><?php</span>
</span><span class="line"> <span class="k">echo</span> <span class="nv">$_GET</span><span class="p">[</span><span class="s2">"name"</span><span class="p">];</span>
</span><span class="line"><span class="cp">?></span>
</span><span class="line"><span class="cp"><?php</span> <span class="k">require_once</span> <span class="s1">'../footer.php'</span><span class="p">;</span> <span class="cp">?></span></span></code>
Come si può vedere anche dal codice, non c’è nessun controllo, ma viene stampata direttamente la variabile passata con una GET.
Eseguo lo stesso procedimento di prima, e il tag XSS viene inserito. Stavolta però inserendo la stringa base non succede nulla. Guardando il codice sorgente, viene eliminata
In questo caso, è possibile che il tag <script>
venga filtrato in qualche modo. Provo quindi a cambiare qualche minuscola in maiuscola
Il codice della pagina era
<code class="php"><span class="line"><span class="o"><?</span><span class="nx">php</span> <span class="k">require_once</span> <span class="s1">'../header.php'</span><span class="p">;</span> <span class="cp">?></span>
</span><span class="line"><span class="x">Hello </span>
</span><span class="line"><span class="cp"><?php</span>
</span><span class="line"> <span class="nv">$name</span> <span class="o">=</span> <span class="nv">$_GET</span><span class="p">[</span><span class="s2">"name"</span><span class="p">];</span>
</span><span class="line"> <span class="nv">$name</span> <span class="o">=</span> <span class="nb">preg_replace</span><span class="p">(</span><span class="s2">"/<script>/"</span><span class="p">,</span><span class="s2">""</span><span class="p">,</span> <span class="nv">$name</span><span class="p">);</span>
</span><span class="line"> <span class="nv">$name</span> <span class="o">=</span> <span class="nb">preg_replace</span><span class="p">(</span><span class="s2">"/<\/script>/"</span><span class="p">,</span><span class="s2">""</span><span class="p">,</span> <span class="nv">$name</span><span class="p">);</span>
</span><span class="line"><span class="k">echo</span> <span class="nv">$name</span><span class="p">;</span>
</span><span class="line"><span class="cp">?></span>
</span><span class="line"><span class="cp"><?php</span> <span class="k">require_once</span> <span class="s1">'../footer.php'</span><span class="p">;</span> <span class="cp">?></span></span></code>
Inserendo la stringa di test, viene creato il tag XSS, ma nessuno dei codici precedenti funziona. È quindi probabile che venga filtrato il tag script in ogni sua forma. Visto che esistono altre possibilità, scelgo di utilizzare il tag img in una della sue varianti
<img src=/ onmouseover="alert('xss')">
Il codice della pagina era cosi strutturato
<code class="php"><span class="line"><span class="o"><?</span><span class="nx">php</span> <span class="k">require_once</span> <span class="s1">'../header.php'</span><span class="p">;</span> <span class="cp">?></span>
</span><span class="line"><span class="x">Hello </span>
</span><span class="line"><span class="cp"><?php</span>
</span><span class="line"> <span class="nv">$name</span> <span class="o">=</span> <span class="nv">$_GET</span><span class="p">[</span><span class="s2">"name"</span><span class="p">];</span>
</span><span class="line"> <span class="nv">$name</span> <span class="o">=</span> <span class="nb">preg_replace</span><span class="p">(</span><span class="s2">"/<script>/i"</span><span class="p">,</span><span class="s2">""</span><span class="p">,</span> <span class="nv">$name</span><span class="p">);</span>
</span><span class="line"> <span class="nv">$name</span> <span class="o">=</span> <span class="nb">preg_replace</span><span class="p">(</span><span class="s2">"/<\/script>/i"</span><span class="p">,</span><span class="s2">""</span><span class="p">,</span> <span class="nv">$name</span><span class="p">);</span>
</span><span class="line"><span class="k">echo</span> <span class="nv">$name</span><span class="p">;</span>
</span><span class="line"><span class="cp">?></span>
</span><span class="line"><span class="cp"><?php</span> <span class="k">require_once</span> <span class="s1">'../footer.php'</span><span class="p">;</span> <span class="cp">?></span></span></code>
Rispetto a prima è stata inserita la i nella funzione replace, che funge da modificatore di pattern in PHP, confrontando la stringa minuscola a maiuscola.
Come prima, la stringa di test funziona. In questo caso inserendo il tag classico, viene stampato un errore, sicuramente a causa di un filtraggio sullo stesso. Provando diverse tipologie di attacco, sembra che sia filtrata la stringa script, infatti tutti quelli che contengono la stessa vengoni bloccati a meno in inserire un carattere di tab ( ) o newline ( ). Ne inserisco una che non la contenga
<svg/onload=alert('xss')>
Il codice della pagina era
<code class="php"><span class="line"><span class="o"><?</span><span class="nx">php</span> <span class="k">require_once</span> <span class="s1">'../header.php'</span><span class="p">;</span>
</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">'/script/i'</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"</span><span class="p">);</span>
</span><span class="line"><span class="p">}</span>
</span><span class="line"><span class="cp">?></span>
</span>
<span class="line"><span class="x">Hello </span><span class="cp"><?php</span> <span class="k">echo</span> <span class="nv">$_GET</span><span class="p">[</span><span class="s2">"name"</span><span class="p">];</span> <span class="cp">?></span>
</span><span class="line"><span class="cp"><?php</span> <span class="k">require_once</span> <span class="s1">'../footer.php'</span><span class="p">;</span> <span class="cp">?></span>
</span></code>
La stringa di test funziona, ma ogni volta che è presente alert viene stampato errore, quindi il filtraggio avviene su quella parola. Basta modificare il pattern ed utilizzare, ad esempio
<script>prompt('XSS')</script>
Il codice era
<code class="php"><span class="line"><span class="o"><?</span><span class="nx">php</span> <span class="k">require_once</span> <span class="s1">'../header.php'</span><span class="p">;</span>
</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">'/alert/i'</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"</span><span class="p">);</span>
</span><span class="line"><span class="p">}</span>
</span><span class="line"><span class="cp">?></span>
</span>
<span class="line"><span class="x">Hello </span><span class="cp"><?php</span> <span class="k">echo</span> <span class="nv">$_GET</span><span class="p">[</span><span class="s2">"name"</span><span class="p">];</span> <span class="cp">?></span>
</span><span class="line"><span class="cp"><?php</span> <span class="k">require_once</span> <span class="s1">'../footer.php'</span><span class="p">;</span> <span class="cp">?></span></span></code>
Stavolta la stringa di test non funziona. Andando a controllare il codice sorgente della pagina, si nota che la stringa viene inserita in una variabile e poi stampata
Visto che c’è un tag _script _aperto, basta chiuderlo e iniettare la stringa maligna.
</script><script>alert('XSS')</script>
Nel codice non è eseguito infatti nessun controllo, viene solamente stampata la variabile $a
<code class="php"><span class="line"><span class="o"><?</span><span class="nx">php</span> <span class="k">require_once</span> <span class="s1">'../header.php'</span><span class="p">;</span> <span class="cp">?></span>
</span><span class="line"><span class="x">Hello </span>
</span><span class="line"><span class="x"><script></span>
</span><span class="line"><span class="x"> var $a= "</span><span class="cp"><?php</span> <span class="k">echo</span> <span class="nv">$_GET</span><span class="p">[</span><span class="s2">"name"</span><span class="p">];</span> <span class="cp">?></span><span class="x">";</span>
</span><span class="line"><span class="x"></script></span>
</span><span class="line"> <span class="cp"><?php</span> <span class="k">require_once</span> <span class="s1">'../footer.php'</span><span class="p">;</span> <span class="cp">?></span></span></code>
Neanche stavolta la stringa di test viene stampata, e, se inserisco il tag script (o qualsiasi altro tag), esso viene codificato e non riconosciuto quindi dall’applicazione come codice javascript
Le possibilità ora sono poche, poichè l’applicazione non accetta nessun tag. Dopo qualche tentativo, si nota che nel codice sorgente le _" _vengono modificate, mentre l’apostrofo ' no. Scelgo quindi un payload con questa caratteristiche
';alert('xss')//'
Il codice utillizzava la funzione htmlentities, la quale viene ‘rotta’ se non viene impostata la flag giusta. Di default utilizza ENT_COMPAT, la quale converte i doppi apici ma non il singolo (come si legge nella documentazione).
<code class="php"><span class="line"><span class="o"><?</span><span class="nx">php</span> <span class="k">require_once</span> <span class="s1">'../header.php'</span><span class="p">;</span> <span class="cp">?></span>
</span><span class="line"><span class="x">Hello </span>
</span><span class="line"><span class="x"><script></span>
</span><span class="line"><span class="x"> var $a= '</span><span class="cp"><?php</span> <span class="k">echo</span> <span class="nb">htmlentities</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="cp">?></span><span class="x">';</span>
</span><span class="line"><span class="x"></script></span>
</span>
<span class="line"><span class="cp"><?php</span> <span class="k">require_once</span> <span class="s1">'../footer.php'</span><span class="p">;</span> <span class="cp">?></span></span></code>
In questo esempio sembra che il programmatore abbia fatto tutti i controlli del caso, Ora la vulnerabilità infatti non si presenta nel form, bensì nell’URL. Basta trovare un modo per chiudere il tag form ed iniettare la stringa malevola.
"> <script>alert('xss')</script>
Il codice presenta l’errore nella stampa di $_SERVER. Non essendo sanitizzato in alcun modo, possiamo chiudere il tag ed iniettare codice malevolo.
<code class="php"><span class="line"><span class="o"><?</span><span class="nx">php</span>
</span><span class="line"> <span class="k">require_once</span> <span class="s1">'../header.php'</span><span class="p">;</span>
</span>
<span class="line"> <span class="k">if</span> <span class="p">(</span><span class="nb">isset</span><span class="p">(</span><span class="nv">$_POST</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">echo</span> <span class="s2">"HELLO "</span><span class="o">.</span><span class="nb">htmlentities</span><span class="p">(</span><span class="nv">$_POST</span><span class="p">[</span><span class="s2">"name"</span><span class="p">]);</span>
</span><span class="line"> <span class="p">}</span>
</span><span class="line"><span class="cp">?></span>
</span><span class="line"><span class="x"><form action="</span><span class="cp"><?php</span> <span class="k">echo</span> <span class="nv">$_SERVER</span><span class="p">[</span><span class="s1">'PHP_SELF'</span><span class="p">];</span> <span class="cp">?></span><span class="x">" method="POST"></span>
</span><span class="line"><span class="x"> Your name:<input type="text" name="name" /></span>
</span><span class="line"><span class="x"> <input type="submit" name="submit"/></span>
</span>
<span class="line"><span class="cp"><?php</span>
</span><span class="line"> <span class="k">require_once</span> <span class="s1">'../footer.php'</span><span class="p">;</span>
</span><span class="line"><span class="cp">?></span></span></code>
Purtroppo sembra che Firefox prevenga l’attacco DOM based XSS, e non mi permetta di eseguirlo. La stringa per risolvere questo esempio è la più classica, basta appenderla dopo il cancelletto.
Come si è visto nell’articolo, ci possono essere moltissime tipologie di attacchi Cross Site Scripting, bisogna solo capire come l’applicazione web reagisce alle stringhe iniettate, verificando caso per caso. Per chi volesse esercitarsi di nuovo, Google ha creato una piattaforma.