Quiz: Sapete difendervi dalle vulnerabilità XSS?

un anno fa Da David Grudl  

Mettete alla prova le vostre conoscenze e capacità di sicurezza in questo quiz! Siete in grado di impedire a un aggressore di prendere il controllo di una pagina HTML?

In tutti i compiti affronterete la stessa domanda: come visualizzare correttamente la variabile $str in una pagina HTML senza creare una vulnerabilità XSS. La base della difesa è l'escaping, ovvero la sostituzione dei caratteri con significato speciale con sequenze corrispondenti. Ad esempio, quando si invia una stringa in un testo HTML, in cui il carattere < ha un significato speciale (indica l'inizio di un tag), lo sostituiamo con l'entità HTML &lt;, e il browser visualizza correttamente il simbolo <.

Siate vigili, perché la vulnerabilità XSS è molto grave. Può portare un aggressore a prendere il controllo di una pagina o addirittura dell'account di un utente. Buona fortuna e che riusciate a mantenere la pagina HTML sicura!

La prima terna di domande

Specificare quali caratteri devono essere gestiti e come nel primo, secondo e terzo esempio:

1) <p><?= $str ?></p>
2) <input value="<?= $str ?>">
3) <input value='<?= $str ?>'>

Se l'output non venisse trattato in alcun modo, diventerebbe parte della pagina visualizzata. Se un utente malintenzionato riuscisse a inserire la stringa 'foo" onclick="evilCode()' in una variabile e l'output non venisse trattato, il codice verrebbe eseguito quando si fa clic sull'elemento:

$str = 'foo" onclick="evilCode()'
❌ not treated: <input value="foo" onclick="evilCode()">
✅ treated:     <input value="foo&quot; onclick=&quot;evilCode()">

Soluzioni per ogni esempio:

  1. i caratteri < e & rappresentano l'inizio di un tag HTML e di un'entità; sostituirli con &lt; e &amp;
  2. i caratteri " e & rappresentano la fine di un valore di attributo e l'inizio di un'entità HTML; sostituirli con &quot; e &amp;
  3. i caratteri ' e & rappresentano la fine di un valore di attributo e l'inizio di un'entità HTML; sostituirli con &apos; e &amp;

Si ottiene un punto per ogni risposta corretta. Naturalmente, in tutti e tre i casi, è possibile sostituire anche altri caratteri con entità; ciò non causa alcun danno, ma non è necessario.

Domanda n. 4

Quali caratteri devono essere sostituiti quando si visualizza una variabile in questo contesto?

<input value=<?= $str ?>>

Soluzione: Come si può vedere, qui mancano le virgolette. Il modo più semplice è aggiungere semplicemente le virgolette e poi fare l'escape come nella domanda precedente. Esiste anche una seconda soluzione, che consiste nel sostituire gli spazi e tutti i caratteri che hanno un significato speciale all'interno di un tag, come >, /, =, e alcuni altri con entità HTML.

Domanda n. 5

Ora la questione si fa più interessante. Quali personaggi devono essere trattati in questo contesto:

<script>
	let foo = '<?= $str ?>';
</script>

Soluzione: All'interno del tag <script> le regole di escape sono determinate da JavaScript. Le entità HTML non vengono utilizzate in questo caso, ma esiste una regola speciale. Quali sono i caratteri da sfuggire? All'interno della stringa JavaScript, naturalmente sfuggiamo al carattere ' che la delimita, utilizzando un backslash, sostituendolo con \'. Poiché JavaScript non supporta le stringhe multilinea (se non come template literals), dobbiamo anche sfuggire ai caratteri di newline. Tuttavia, si tenga presente che oltre ai soliti caratteri \n e \r, JavaScript considera anche i caratteri Unicode \u2028 e \u2029 come caratteri di newline, che devono essere evasi. Infine, la regola speciale citata: la stringa non deve contenere </script. Questo può essere evitato, ad esempio, sostituendolo con <\/script.

Se lo sapevate, complimenti.

Domanda n. 6

Il contesto seguente sembra essere solo una variazione del precedente. Pensate che il trattamento sarà diverso?

<p onclick="foo('<?= $str ?>')"></p>

Soluzione: Anche in questo caso si applicano le regole di escape per le stringhe JavaScript, ma a differenza del contesto precedente, in cui le entità HTML non venivano sottoposte a escape, qui vengono sottoposte a escape. Quindi, prima si esegue l'escape della stringa JavaScript usando i backslash e poi si sostituiscono i caratteri speciali (" e &) con entità HTML. Attenzione, l'ordine corretto è importante.

Come si può vedere, lo stesso letterale JavaScript può essere codificato in modo diverso in un elemento <script> e in un attributo!

Domanda n. 7

Torniamo da JavaScript all'HTML. Quali caratteri dobbiamo sostituire all'interno del commento e come?

<!-- <?= $str ?> -->

Soluzione: All'interno di un commento HTML (e XML) possono comparire tutti i caratteri speciali tradizionali, come <, &, " e '. Ciò che è vietato, e che potrebbe sorprendere, è la coppia di caratteri --. L'escape di questa sequenza non è specificato, quindi sta a voi decidere come sostituirla. È possibile intervallarli con degli spazi. Oppure, ad esempio, sostituirli con ==.

Domanda n. 8

Ci stiamo avvicinando alla fine, quindi proviamo a variare la domanda. Provate a pensare a cosa dovete fare attenzione quando stampate una variabile in questo contesto:

<a href="<?= $str ?>">...</a>

Soluzione: Oltre all'escape, è importante verificare che l'URL non contenga uno schema pericoloso come javascript:, perché un URL così composto eseguirà il codice dell'aggressore quando viene cliccato.

Domanda n. 9

Infine, una chicca per veri intenditori. Questo è un esempio di applicazione che utilizza un framework JavaScript moderno, in particolare Vue. Vediamo se riuscite a capire a cosa fare attenzione quando si stampa una variabile all'interno dell'elemento #app:

<div id="app">
    <?= $str ?>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const app = new Vue({
    el: '#app',
    ...
})
</script>

Questo codice crea un'applicazione Vue che verrà resa nell'elemento #app. Vue interpreta il contenuto di questo elemento come suo modello. E all'interno del template, interpreta le doppie parentesi graffe, che rappresentano l'output di una variabile o il richiamo di codice JavaScript (ad esempio, {{ foo }}).

Quindi, all'interno dell'elemento #app, oltre ai caratteri < e &, anche la coppia {{ ha un significato speciale, che dobbiamo sostituire con un'altra sequenza appropriata per evitare che Vue la interpreti come un proprio tag. La sostituzione con entità HTML non aiuta in questo caso. Come fare? C'è un trucco: inserire un commento HTML vuoto tra le parentesi graffe {<!-- -->{e Vue ignorerà questa sequenza.

Risultati del quiz

Come siete andati nel quiz? Quante risposte corrette avete? Se hai risposto correttamente ad almeno 4 domande, sei tra i primi 8% dei risolutori – complimenti!

Tuttavia, per garantire la sicurezza del vostro sito web è necessario gestire correttamente l'output in tutte le situazioni.

Se siete rimasti sorpresi dal numero di contesti diversi che possono comparire in una tipica pagina HTML, sappiate che non li abbiamo ancora citati tutti. Questo renderebbe il quiz molto più lungo. Tuttavia, non è necessario essere esperti di escape in ogni contesto, se il sistema di template è in grado di gestirlo.

Quindi, mettiamoli alla prova.

Come si comportano i sistemi di template?

Tutti i moderni sistemi di template vantano una funzione di autoescaping che esegue automaticamente l'escape di tutte le variabili in uscita. Se lo fanno correttamente, il vostro sito web è al sicuro. Se lo fanno male, il sito è esposto al rischio di vulnerabilità XSS con tutte le sue gravi conseguenze.

Verranno testati i sistemi di templating più diffusi, a partire dalle domande di questo quiz, per determinare l'efficacia del loro auto-escaping. Iniziamo la rassegna dei sistemi di template PHP.

Twig ❌

Il primo è il sistema di template Twig (versione 3.5), più comunemente usato insieme al framework Symfony. Gli affideremo il compito di rispondere a tutte le domande del quiz. La variabile $str sarà sempre riempita con una stringa ingannevole e vedremo come gestisce il suo output. I risultati sono visibili sulla destra. È anche possibile esplorare le sue risposte e il suo comportamento sul campo di gioco.

   {% set str = "<'\"&" %}
1) <p>{{ str }}</p>
2) <input value="{{ str }}">
3) <input value='{{ str }}'>

   {% set str = "foo onclick=evilCode()" %}
4) <input value={{ str }}>

   {% set str = "'\"\n\u{2028}" %}
5) <script>	let foo = '{{ str }}'; </script>
6) <p onclick="foo('{{ str }}')"></p>

   {% set str = "-- ---" %}
7) <!-- {{ str }} -->

   {% set str = "javascript:evilCode()" %}
8) <a href="{{ str }}">...</a>

   {% set str = "{{ foo }}" %}
9) <div id="app"> {{ str }} </div>

✅ <p>&lt;&#039;&quot;&amp;</p>
✅ <input value="&lt;&#039;&quot;&amp;">
✅ <input value='&lt;&#039;&quot;&amp;'>


❌ <input value=foo onclick=evilCode()>


❌ <script> let foo = &#039;&quot;u{2028}; </script>
❌ <p onclick="foo(&#039;&quot;u{2028})"></p>


❌ <!-- -- --- -->


❌ <a href="javascript:evilCode()">...</a>


❌ <div id="app"> {{ foo }} </div>

Twig ha fallito in sei test su nove!

Sfortunatamente, l'escape automatico di Twig funziona solo per il testo e gli attributi HTML, e anche in questo caso solo quando sono racchiusi tra virgolette. Non appena le virgolette mancano, Twig non segnala alcun errore e crea una falla di sicurezza XSS.

Questo è particolarmente spiacevole perché è così che vengono scritti i valori degli attributi in librerie popolari come React o Svelte. Un programmatore che utilizza sia Twig che React può naturalmente dimenticare le virgolette.

L'autoescaping di Twig fallisce anche in tutti gli altri esempi. Nei contesti (5) e (6), è necessario l'escape manuale usando {{ str|escape('js') }}mentre per gli altri contesti, Twig non offre nemmeno una funzione di escape. Manca anche la protezione contro la stampa di un link dannoso (8) o il supporto per i template di Vue (9).

Lama ❌❌

Il secondo partecipante è il sistema di template Blade (versione 10.9), strettamente integrato con Laravel e il suo ecosistema. Anche in questo caso, metteremo alla prova le sue capacità con le domande del quiz. Potete anche esplorare le sue risposte sul campo di gioco.

   @php($str = "<'\"&")
1) <p>{{ $str }}</p>
2) <input value="{{ $str }}">
3) <input value='{{ $str }}'>

   @php($str = "foo onclick=evilCode()")
4) <input value={{ $str }}>

   @php($str = "'\"\n\u{2028}")
5) <script> let foo = {{ $str }}; </script>
6) <p onclick="foo({{ $str }})"></p>

   @php($str = "-- ---")
7) <!-- {{ $str }} -->

   @php($str = "javascript:evilCode()")
8) <a href="{{ $str }}">...</a>

   @php($str = "{{ foo }}")
9) <div id="app"> {{ $str }} </div>

✅ <p>&lt;&#039;&quot;&amp;</p>
✅ <input value="&lt;&#039;&quot;&amp;">
✅ <input value='&lt;&#039;&quot;&amp;'>


❌ <input value=foo onclick=evilCode()>


❌ <script>	let foo = &#039;&quot; ; </script>
❌ <p onclick="foo(&#039;&quot; )"></p>


❌ <!-- -- --- -->


❌ <a href="javascript:evilCode()">...</a>


❌❌ <div id="app"> &lt;?php echo e(foo); ?&gt; </div>

La lama ha fallito in sei test su nove!

Il risultato è simile a quello di Twig. Anche in questo caso, l'escape automatico funziona solo per il testo e gli attributi HTML e solo se sono racchiusi tra virgolette. L'autoescaping di Blade fallisce anche in tutti gli altri esempi. Nei contesti (5) e (6), è necessario l'escape manuale usando {{ Js::from($str) }}. Per gli altri contesti, Blade non offre nemmeno una funzione di escape. Manca anche la protezione contro la stampa di un link dannoso (8) o il supporto per i template Vue (9).

Tuttavia, ciò che sorprende è il fallimento della direttiva @php in Blade, che causa l'output del proprio codice PHP direttamente nell'output, come si vede nell'ultima riga.

Latte ✅

Il trio è concluso dal sistema di template Latte (versione 3.0). Verrà testato il suo autoescaping. È inoltre possibile esplorare le sue risposte e il suo comportamento sul campo di gioco.

   {var $str = "<'\"&"}
1) <p>{$str}</p>
2) <input value="{$str}">
3) <input value='{$str}'>

   {var $str = "foo onclick=evilCode()"}
4) <input value={$str}>

   {var $str = "'\"\n\u{2028}"}
5) <script>	let foo = {$str}; </script>
6) <p onclick="foo({$str})"></p>

   {var $str = "-- ---"}
7) <!-- {$str} -->

   {var $str = "javascript:evilCode()"}
8) <a href="{$str}">...</a>

   {var $str = "{{ foo }}"}
9) <div id="app"> {$str} </div>

✅ <p>&lt;'"&amp;</p>
✅ <input value="&lt;&apos;&quot;&amp;">
✅ <input value='&lt;&apos;&quot;&amp;'>


✅ <input value="foo onclick=evilCode()">


✅ <script> let foo = "'\"\n\u2028"; </script>
✅ <p onclick="foo(&quot;&apos;\&quot;\n\u2028&quot;)"></p>


✅ <!--  - -  - - -  -->


✅ <a href="">...</a>


✅ <div id="app"> {<!-- -->{ foo }} </div>

Latte ha eccelso in tutti e nove i compiti!

È riuscito a gestire le virgolette mancanti negli attributi HTML, ha elaborato JavaScript sia negli elementi che negli attributi e ha gestito la sequenza proibita nei commenti HTML. <script> sia nell'elemento che negli attributi e ha gestito la sequenza proibita nei commenti HTML.

Inoltre, ha evitato che cliccando su un link dannoso fornito da un aggressore si potesse eseguire il suo codice. È riuscito a gestire l'escape dei tag per Vue.

Test bonus

Una delle funzionalità essenziali di tutti i sistemi di template è lavorare con i blocchi e la relativa ereditarietà dei template. Pertanto, daremo a tutti i sistemi di template testati un ulteriore compito. Creeremo un blocco description, che verrà stampato in un attributo HTML. Nel mondo reale, la definizione del blocco si troverebbe ovviamente nel template figlio e il suo output nel template padre, come il layout. Questa è solo una forma semplificata, ma è sufficiente per testare l'autoescaping durante l'output dei blocchi. Come si sono comportati?

Twig: fallito ❌ durante l'output dei blocchi, i caratteri non sono correttamente escape

{% block description %}
	rock n' roll
{% endblock %}

<meta name='description'
	content='{{ block('description') }}'>




<meta name='description'
	content=' rock n' roll '> ❌

Blade: fallito ❌ durante l'output dei blocchi, i caratteri non vengono evasi correttamente

@section('description')
	rock n' roll
@endsection

<meta name='description'
	content='@yield('description')'>




<meta name='description'
	content=' rock n' roll '> ❌

Latte: passato ✅ durante l'output dei blocchi, gestiva correttamente i caratteri problematici

{block description}
	rock n' roll
{/block}

<meta name='description'
	content='{include description}'>




<meta name='description'
	content=' rock n&apos; roll '> ✅

Perché così tanti siti web sono vulnerabili?

L'autoescaping in sistemi come Twig, Blade o Smarty funziona semplicemente sostituendo cinque caratteri <>"'& con entità HTML e non distingue il contesto. Pertanto, funziona solo in alcune situazioni e fallisce in tutte le altre. L'autoescaping ingenuo è una funzione pericolosa perché crea un falso senso di sicurezza.

Non sorprende quindi che attualmente oltre il 27% dei siti web presenti vulnerabilità critiche, soprattutto XSS (fonte: Acunetix Web Vulnerability Report). Come uscire da questa situazione? Utilizzare un sistema di template che distingua i contesti.

Latte è l'unico sistema di template PHP che non percepisce un template come una semplice stringa di caratteri, ma comprende l'HTML. Sa cosa sono i tag, gli attributi e così via. Distingue i contesti. E quindi esegue correttamente l'escape nel testo HTML, in modo diverso all'interno dei tag HTML, in modo diverso all'interno di JavaScript, ecc.

Latte rappresenta quindi l'unico sistema di template sicuro.


Inoltre, grazie alla sua comprensione dell'HTML, offre i meravigliosi n:attributi, che gli utenti adorano:

<ul n:if="$menu">
	<li n:foreach="$menu->getItems() as $item">{$item->title}</li>
</ul>