Quiz: saprai difenderti dalla vulnerabilità XSS?
Metti alla prova le tue conoscenze nel quiz sulla sicurezza! Riuscirai a impedire a un attaccante di prendere il controllo della pagina HTML?

In tutti i compiti risolverai la stessa domanda: come stampare correttamente
la variabile $str
in una pagina HTML affinché non si crei una vulnerabilità XSS. La base
della difesa è l'escaping, che significa sostituire i caratteri con
significato speciale con le sequenze corrispondenti. Ad esempio, quando si
stampa una stringa nel testo HTML, in cui il carattere <
ha un
significato speciale (segnala l'inizio di un tag), lo sostituiamo con l'entità
HTML <
e il browser visualizzerà correttamente il simbolo
<
.
Stai attento, perché la vulnerabilità XSS è molto grave. Può causare la presa di controllo della pagina o persino dell'account utente da parte di un attaccante. Buona fortuna e che tu riesca a mantenere sicura la pagina HTML!
Prime tre domande
Indica quali caratteri e in che modo è necessario trattare nel primo, secondo e terzo esempio:
1) <p><?= $str ?></p>
2) <input value="<?= $str ?>">
3) <input value='<?= $str ?>'>
Se l'output non fosse trattato in alcun modo, diventerebbe parte della pagina
visualizzata. Se un attaccante inserisse nella variabile la stringa
'foo" onclick="evilCode()'
e l'output non fosse trattato,
causerebbe l'esecuzione del suo codice al clic sull'elemento:
$str = 'foo" onclick="evilCode()'
❌ senza trattamento: <input value="foo" onclick="evilCode()">
✅ con trattamento: <input value="foo" onclick="evilCode()">
Soluzioni dei singoli esempi:
- i caratteri
<
e&
rappresentano l'inizio di un tag HTML e di un'entità, li sostituiamo con<
e&
- i caratteri
"
e&
rappresentano la fine del valore dell'attributo e l'inizio di un'entità HTML, li sostituiamo con"
e&
- i caratteri
'
e&
rappresentano la fine del valore dell'attributo e l'inizio di un'entità HTML, li sostituiamo con'
e&
Per ogni risposta corretta ottieni un punto. Ovviamente, in tutti e tre i casi è possibile sostituire con entità anche altri caratteri, non c'è nulla di male, ma non è necessario.
Domanda n. 4
Continuiamo. Quali caratteri è necessario sostituire quando si stampa la variabile in questo contesto?
<input value=<?= $str ?>>
Soluzione: Come puoi vedere, qui mancano le virgolette. La cosa più semplice
è semplicemente aggiungere le virgolette e poi eseguire l'escaping come nella
domanda precedente. Esiste anche una seconda soluzione, ovvero sostituire nella
stringa con entità HTML lo spazio e tutti i caratteri che hanno un significato
speciale all'interno del tag, cioè >
, /
,
=
e alcuni
altri.
Domanda n. 5
Ora inizia a diventare più interessante. Quali caratteri è necessario trattare in questo contesto:
<script>
let foo = '<?= $str ?>';
</script>
Soluzione: All'interno del tag <script>
, le regole per
l'escaping sono determinate da JavaScript. Le entità HTML qui non vengono
utilizzate, tuttavia vale una regola speciale. Quindi quali caratteri
eseguiamo l'escaping? All'interno di una stringa JavaScript eseguiamo ovviamente
l'escaping del carattere '
, che la delimita, usando una barra
rovesciata, quindi lo sostituiamo con \'
. Poiché JavaScript non
supporta stringhe multilinea (solo come template
literal), dobbiamo eseguire l'escaping anche dei caratteri di fine riga.
Attenzione però, oltre ai soliti caratteri \n
e \r
,
JavaScript considera come fine riga anche i caratteri unicode
\u2028
e \u2029
, che dobbiamo eseguire l'escaping allo
stesso modo. E infine la regola speciale menzionata: nella stringa non deve
comparire </script
. Questo si può impedire ad esempio
sostituendolo con <\/script
.
Se lo sapevi, congratulazioni.
Domanda n. 6
Il contesto seguente sembra solo una variazione del precedente. Cosa ne pensi, il trattamento sarà diverso?
<p onclick="foo('<?= $str ?>')"></p>
Soluzione: Anche qui valgono le regole per l'escaping nelle stringhe
JavaScript, ma a differenza del contesto precedente, dove non si usavano entità
HTML, qui al contrario si usano. Quindi, prima eseguiamo l'escaping della
stringa JavaScript usando le barre rovesciate e poi sostituiamo i caratteri con
significato speciale ("
e &
) con entità HTML.
Attenzione, l'ordine corretto è importante.
Come puoi vedere, lo stesso letterale JavaScript può essere codificato
diversamente nell'elemento <script>
e diversamente
nell'attributo!
Domanda n. 7
Torniamo da JavaScript a HTML. Quali caratteri dobbiamo sostituire all'interno del commento e in che modo?
<!-- <?= $str ?> -->
Soluzione: all'interno di un commento HTML (e XML), tutti i tradizionali
caratteri speciali, come <
, &
, "
e
'
, possono apparire. È vietata, e questo forse ti sorprenderà, la
coppia di caratteri --
. L'escaping di questa sequenza non è
specificato, quindi sta a te decidere come sostituirla. Puoi intervallarli con
spazi. O magari sostituirli con ==
.
Domanda n. 8
Ci stiamo avvicinando alla fine, quindi proviamo a variare la domanda. Prova a pensare a cosa bisogna fare attenzione quando si stampa la variabile in questo contesto:
<a href="<?= $str ?>">...</a>
Soluzione: oltre all'escaping, è importante anche verificare che l'URL non
contenga uno schema pericoloso come javascript:
, perché un URL
così costruito chiamerebbe il codice dell'attaccante dopo il clic.
Domanda n. 9
Infine, una chicca per i veri intenditori. Si tratta di un esempio di
applicazione che utilizza un moderno framework JavaScript, nello specifico Vue.
Vediamo se ti viene in mente a cosa fare attenzione quando si stampa la
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à renderizzata nell'elemento
#app
. Vue interpreta il contenuto di questo elemento come il suo
template. E all'interno del template interpreta le
doppie parentesi graffe, che significano la stampa di una variabile o la
chiamata di codice javascript (ad es. {{ foo }}
).
Quindi, all'interno dell'elemento #app
, oltre ai caratteri
<
e &
, ha un significato speciale anche la
coppia {{
, che dobbiamo sostituire con un'altra sequenza
corrispondente, affinché Vue non la interpreti come il suo tag. La sostituzione
con entità HTML però in questo caso non aiuta. Come fare? Funziona un trucco:
tra le parentesi inseriamo un commento HTML vuoto {<!-- -->{
e Vue ignora tale sequenza.
Risultati del quiz
Come sei andato nel quiz? Quante risposte corrette hai? Se hai risposto correttamente ad almeno 4 domande, appartieni all'8% dei migliori risolutori – congratulazioni!
Tuttavia, per garantire la sicurezza del tuo sito web, è indispensabile trattare correttamente l'output in tutte le situazioni.
Se ti ha sorpreso quanti contesti diversi possono verificarsi in una normale pagina HTML, sappi che non li abbiamo menzionati neanche lontanamente tutti. Sarebbe stato un quiz molto più lungo. Tuttavia, non devi essere un esperto di escaping in ogni contesto, se il tuo sistema di template è in grado di gestirlo.
Proviamoli quindi.
Come si comportano i sistemi di template?
Tutti i moderni sistemi di template vantano la funzione di autoescaping, che esegue automaticamente l'escaping di tutte le variabili stampate. Se lo fanno correttamente, il tuo sito è al sicuro. Se lo fanno male, il sito è esposto al rischio di vulnerabilità XSS con tutte le gravi conseguenze.
Testeremo i popolari sistemi di template delle domande di questo quiz per scoprire quanto sia efficace il loro autoescaping. Inizia il dTest dei sistemi di template per PHP.
Twig ❌
Il primo della lista è il sistema di template Twig (versione 3.5), che viene utilizzato
più spesso in combinazione con il framework Symfony. Gli daremo il compito di
rispondere a tutte le domande del quiz. La variabile $str
sarà
sempre riempita con una stringa insidiosa e vedremo come se la caverà con la
sua stampa. I risultati li vedi a destra. Puoi anche esplorare le sue risposte e il
suo comportamento nel playground.
{% 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><'"&</p>
✅ <input value="<'"&">
✅ <input value='<'"&'>
❌ <input value=foo onclick=evilCode()>
❌ <script> let foo = '"u{2028}; </script>
❌ <p onclick="foo('"u{2028})"></p>
❌ <!-- -- --- -->
❌ <a href="javascript:evilCode()">...</a>
❌ <div id="app"> {{ foo }} </div>
Twig ha fallito in sei dei nove test!
Purtroppo, l'autoescaping automatico di Twig funziona solo nel testo HTML e negli attributi, e per di più solo se sono racchiusi tra virgolette. Non appena mancano le virgolette, Twig non segnala alcun errore e crea una falla di sicurezza XSS.
Questo è particolarmente fastidioso, perché è così che vengono scritti i valori degli attributi nelle librerie popolari come React o Svelte. Un programmatore che utilizza contemporaneamente Twig e React può quindi dimenticare le virgolette in modo del tutto naturale.
L'autoescaping di Twig fallisce anche in tutti gli altri esempi. Nei contesti
(5) e (6) è necessario eseguire l'escaping manualmente usando
{{ str|escape('js') }}
, per altri contesti Twig non offre nemmeno
una funzione di escaping. Non dispone nemmeno di protezione contro la stampa di
link dannosi (8) o supporto per template Vue (9).
Blade ❌❌
Il secondo partecipante è il sistema di template Blade (versione 10.9), che è strettamente integrato con Laravel e il suo ecosistema. Verificheremo nuovamente le sue capacità sulle nostre domande del quiz. Puoi anche esplorare le sue risposte nel playground.
@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><'"&</p>
✅ <input value="<'"&">
✅ <input value='<'"&'>
❌ <input value=foo onclick=evilCode()>
❌ <script> let foo = '" ; </script>
❌ <p onclick="foo('" )"></p>
❌ <!-- -- --- -->
❌ <a href="javascript:evilCode()">...</a>
❌❌ <div id="app"> <?php echo e(foo); ?> </div>
Blade ha fallito in sei dei nove test!
Il risultato è simile a quello di Twig. Vale ancora che l'autoescaping
automatico funziona solo nel testo HTML e negli attributi e solo se sono
racchiusi tra virgolette. L'autoescaping di Blade fallisce anche in tutti gli
altri esempi. Nei contesti (5) e (6) è necessario eseguire l'escaping
manualmente usando {{ Js::from($str) }}
. Per altri contesti Blade
non offre nemmeno una funzione di escaping. Non dispone di protezione contro la
stampa di link dannosi (8) né supporto per template Vue (9).
Ciò che è sorprendente, tuttavia, è il fallimento della direttiva
@php
in Blade, che causa la stampa del proprio codice PHP
direttamente nell'output, come si vede nell'ultima riga.
Smarty ❌❌❌
Ora testeremo il più vecchio sistema di template per PHP, ovvero Smarty (versione 4.3). Con grande sorpresa,
questo sistema non ha l'autoescaping automatico attivo. È quindi necessario
specificare ogni volta il filtro {$var|escape}
quando si stampano
le variabili, oppure attivare l'autoescaping HTML automatico. L'informazione a
riguardo è piuttosto nascosta nella documentazione.
{$str = "<'\"&"}
1) <p>{$str}</p>
2) <input value="{$str}">
3) <input value='{$str}'>
{$str = "foo onclick=evilCode()"}
4) <input value={$str}>
{$str = "'\"\n\u{2028}"}
5) <script> let foo = {$str}; </script>
6) <p onclick="foo({$str})"></p>
{$str = "-- ---"}
7) <!-- {$str} -->
{$str = "javascript:evilCode()"}
8) <a href="{$str}">...</a>
{$str = "{{ foo }}"}
9) <div id="app"> {$str} </div>
✅ <p><'"&</p>
✅ <input value="<'"&">
✅ <input value='<'"&'>
❌ <input value=foo onclick=evilCode()>
❌ <script> let foo = '"\u2028; </script>
❌ <p onclick="foo('"\u2028)"></p>
❌ <!-- -- --- -->
❌ <a href="javascript:evilCode()">...</a>
❌ <div id="app"> {{ foo }} </div>
Smarty ha fallito in sei dei nove test!
Il risultato è a prima vista simile a quello delle librerie precedenti.
Smarty è in grado di eseguire l'autoescaping automatico solo nel testo HTML e
negli attributi, e solo quando i valori sono racchiusi tra virgolette. In tutti
gli altri casi fallisce. Nei contesti (5) e (6) è necessario eseguire
l'escaping manualmente usando {$str|escape:javascript}
. Tuttavia,
questo è possibile solo quando l'autoescaping HTML automatico non è attivo,
altrimenti questi escaping entrano in conflitto tra loro. Smarty è quindi dal
punto di vista della sicurezza un fallimento totale di questo test.
Latte ✅
La tripletta si chiude con il sistema di template Latte (versione 3.0). Proveremo il suo autoescaping. Puoi anche esplorare le sue risposte e il suo comportamento nel playground.
{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><'"&</p>
✅ <input value="<'"&">
✅ <input value='<'"&'>
✅ <input value="foo onclick=evilCode()">
✅ <script> let foo = "'\"\n\u2028"; </script>
✅ <p onclick="foo("'\"\n\u2028")"></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 gestito
JavaScript sia nell'elemento <script>
che negli attributi ed
è riuscito a gestire anche la sequenza vietata nei commenti HTML.
Inoltre, ha prevenuto la situazione in cui il clic su un link contraffatto da un attaccante avrebbe potuto eseguire il suo codice. Ed è riuscito a gestire l'escaping dei tag per Vue.
Test bonus
Una delle capacità essenziali di tutti i sistemi di template è il lavoro
con i blocchi e l'ereditarietà dei template ad esso associata. Proveremo
quindi a dare a tutti i sistemi di template testati un altro compito. Creeremo
un blocco description
, che stamperemo in un attributo HTML. Nel
mondo reale, ovviamente, la definizione del blocco si troverebbe nel template
figlio e la sua stampa nel template genitore, ad esempio il layout. Questa è
solo una forma semplificata, ma sufficiente per testare l'autoescaping durante
la stampa dei blocchi. Come se la sono cavata?
Twig: fallito ❌ durante la stampa dei blocchi non tratta i caratteri
{% block description %}
rock n' roll
{% endblock %}
<meta name='description'
content='{{ block('description') }}'>
<meta name='description'
content=' rock n' roll '> ❌
Blade: fallito ❌ durante la stampa dei blocchi non tratta i caratteri
@section('description')
rock n' roll
@endsection
<meta name='description'
content='@yield('description')'>
<meta name='description'
content=' rock n' roll '> ❌
Latte: superato ✅ durante la stampa dei blocchi ha trattato correttamente i caratteri problematici
{block description}
rock n' roll
{/block}
<meta name='description'
content='{include description}'>
<meta name='description'
content=' rock n' 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 affatto 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 abbia vulnerabilità critiche, principalmente XSS (fonte: Acunetix Web Vulnerability Report). Come uscirne? Usare un sistema di template che distingue i contesti.
Latte è l'unico sistema di template in PHP che non percepisce il template solo come una stringa di caratteri, ma comprende l'HTML. Capisce cosa sono i tag, gli attributi, ecc. Distingue i contesti. E quindi esegue correttamente l'escaping nel testo HTML, diversamente all'interno di un tag HTML, diversamente 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>
Per inviare un commento, effettuare il login