Quiz: Sapete difendervi dalle vulnerabilità XSS?
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 <
, 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" onclick="evilCode()">
Soluzioni per ogni esempio:
- i caratteri
<
e&
rappresentano l'inizio di un tag HTML e di un'entità; sostituirli con<
e&
- i caratteri
"
e&
rappresentano la fine di un valore di attributo e l'inizio di un'entità HTML; sostituirli con"
e&
- i caratteri
'
e&
rappresentano la fine di un valore di attributo e l'inizio di un'entità HTML; sostituirli con'
e&
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><'"&</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 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><'"&</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>
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><'"&</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
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' 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>
Per inviare un commento, effettuare il login