Latte è sinonimo di sicurezza
Latte è l'unico sistema di templating per PHP con una protezione efficace contro la vulnerabilità critica Cross-site Scripting (XSS). E questo grazie al cosiddetto escaping sensibile al contesto. Vedremo,
- qual è il principio della vulnerabilità XSS e perché è così pericolosa
- perché Latte è così efficace nella difesa contro XSS
- come si può facilmente creare una falla di sicurezza nei template Twig, Blade e simili
Cross-site Scripting (XSS)
Il Cross-site Scripting (abbreviato XSS) è una delle vulnerabilità più comuni dei siti web e allo stesso tempo molto pericolosa. Permette a un aggressore di inserire uno script dannoso (il cosiddetto malware) in una pagina altrui, che viene eseguito nel browser di un utente ignaro.
Cosa può fare uno script del genere? Può, ad esempio, inviare all'aggressore qualsiasi contenuto dalla pagina compromessa, inclusi dati sensibili visualizzati dopo l'accesso. Può modificare la pagina o eseguire ulteriori richieste a nome dell'utente. Se si trattasse, ad esempio, di una webmail, potrebbe leggere messaggi sensibili, modificare il contenuto visualizzato o riconfigurare le impostazioni, ad esempio attivando l'inoltro di copie di tutti i messaggi all'indirizzo dell'aggressore per ottenere accesso anche alle email future.
Ecco perché XSS figura ai primi posti nelle classifiche delle vulnerabilità più pericolose. Se una vulnerabilità compare su un sito web, è necessario rimuoverla il prima possibile per prevenirne l'abuso.
Come nasce la vulnerabilità?
L'errore si verifica nel punto in cui la pagina web viene generata e le variabili vengono stampate. Immagina di creare una pagina di ricerca, e all'inizio ci sarà un paragrafo con il termine cercato nella forma:
echo '<p>Risultati della ricerca per <em>' . $search . '</em></p>';
Un aggressore può inserire nella casella di ricerca e quindi nella variabile $search
qualsiasi stringa, quindi
anche codice HTML come <script>alert("Hacked!")</script>
. Poiché l'output non è in alcun modo trattato,
diventa parte della pagina visualizzata:
<p>Risultati della ricerca per <em><script>alert("Hacked!")</script></em></p>
Il browser, invece di stampare la stringa cercata, esegue il JavaScript. E così l'aggressore prende il controllo della pagina.
Potresti obiettare che inserendo il codice nella variabile, il JavaScript viene eseguito, ma solo nel browser dell'aggressore. Come arriva alla vittima? Da questo punto di vista, distinguiamo diversi tipi di XSS. Nel nostro esempio con la ricerca, parliamo di reflected XSS. Qui è ancora necessario indurre la vittima a cliccare su un link che conterrà il codice dannoso nel parametro:
https://example.com/?search=<script>alert("Hacked!")</script>
Indurre l'utente a cliccare su un link richiede una certa ingegneria sociale, ma non è niente di complicato. Gli utenti
cliccano sui link, sia nelle email che sui social network, senza pensarci troppo. E il fatto che ci sia qualcosa di sospetto
nell'indirizzo può essere mascherato usando un accorciatore di URL, l'utente vede quindi solo bit.ly/xxx
.
Tuttavia, esiste anche una seconda forma di attacco molto più pericolosa, nota come stored XSS o persistent XSS, in cui l'aggressore riesce a salvare il codice dannoso sul server in modo che venga automaticamente inserito in alcune pagine.
Un esempio sono le pagine in cui gli utenti scrivono commenti. L'aggressore invia un post contenente codice e questo viene salvato sul server. Se le pagine non sono sufficientemente protette, verrà quindi eseguito nel browser di ogni visitatore.
Potrebbe sembrare che il nucleo dell'attacco consista nell'inserire nella pagina la stringa <script>
. In
realtà, i modi per inserire
JavaScript sono molti. Vediamo ad esempio un esempio di inserimento tramite un attributo HTML. Supponiamo di avere una
galleria fotografica in cui è possibile inserire una didascalia per le immagini, che viene stampata nell'attributo
alt
:
echo '<img src="' . $imageFile . '" alt="' . $imageAlt . '">';
All'aggressore basta inserire come didascalia una stringa abilmente costruita " onload="alert('Hacked!')
e se la
stampa non viene trattata, il codice risultante sarà simile a questo:
<img src="photo0145.webp" alt="" onload="alert('Hacked!')">
L'attributo onload
contraffatto diventa ora parte della pagina. Il browser esegue il codice contenuto in esso non
appena l'immagine viene scaricata. Hacked!
Come difendersi da XSS?
Qualsiasi tentativo di rilevare l'attacco utilizzando una blacklist, come ad esempio bloccare la stringa
<script>
ecc., è insufficiente. La base di una difesa funzionale è la sanitizzazione coerente di tutti
i dati stampati all'interno della pagina.
Principalmente si tratta di sostituire tutti i caratteri con un significato speciale con altre sequenze corrispondenti, cosa
che viene chiamata gergalmente escaping (il primo carattere della sequenza è chiamato carattere di escape, da cui il
nome). Ad esempio, nel testo HTML, il carattere <
ha un significato speciale, che quando non deve essere
interpretato come l'inizio di un tag, dobbiamo sostituirlo con una sequenza visivamente corrispondente, la cosiddetta entità HTML
<
. E il browser stamperà il segno di minore.
È molto importante distinguere il contesto in cui stampiamo i dati. Perché in contesti diversi, le stringhe vengono sanitizzate in modo diverso. In contesti diversi, caratteri diversi hanno un significato speciale. Ad esempio, l'escaping differisce nel testo HTML, negli attributi HTML, all'interno di alcuni elementi speciali, ecc. Lo discuteremo in dettaglio tra poco.
Il trattamento è meglio eseguirlo direttamente durante la stampa della stringa nella pagina, garantendo così che venga effettivamente eseguito e che venga eseguito esattamente una volta. La cosa migliore è se il trattamento viene gestito automaticamente direttamente dal sistema di templating. Perché se il trattamento non avviene automaticamente, il programmatore può dimenticarsene. E una singola dimenticanza significa che il sito è vulnerabile.
Tuttavia, XSS non riguarda solo la stampa di dati nei template, ma anche altre parti dell'applicazione che devono gestire
correttamente i dati non attendibili. Ad esempio, è necessario che il JavaScript nella tua applicazione non utilizzi
innerHTML
in connessione con essi, ma solo innerText
o textContent
. È necessario prestare
particolare attenzione alle funzioni che valutano le stringhe come JavaScript, ovvero eval()
, ma anche
setTimeout()
, o l'uso della funzione setAttribute()
con attributi di evento come onload
ecc. Questo però va oltre l'ambito coperto dai template.
La difesa ideale in 3 punti:
- riconosce il contesto in cui i dati vengono stampati
- sanitizza i dati secondo le regole del contesto dato (cioè “sensibile al contesto”)
- lo fa automaticamente
Escaping sensibile al contesto
Cosa si intende esattamente con la parola contesto? È un luogo nel documento con le proprie regole per il trattamento dei dati stampati. Dipende dal tipo di documento (HTML, XML, CSS, JavaScript, testo semplice, …) e può differire nelle sue parti specifiche. Ad esempio, in un documento HTML, ci sono molti posti (contesti) in cui si applicano regole molto diverse. Potresti essere sorpreso di quanti ce ne siano. Ecco i primi quattro:
<p>#text</p>
<img src="#atribut">
<textarea>#rawtext</textarea>
<!-- #komentář -->
Il contesto predefinito e di base di una pagina HTML è il testo HTML. Quali regole si applicano qui? I caratteri
<
e &
hanno un significato speciale, rappresentano l'inizio di un tag o di un'entità, quindi
dobbiamo eseguirne l'escaping sostituendoli con un'entità HTML (<
con <
, &
con &
).
Il secondo contesto più comune è il valore di un attributo HTML. Si differenzia dal testo in quanto qui le virgolette
"
o '
, che delimitano l'attributo, hanno un significato speciale. Devono essere scritte come entità per
non essere interpretate come la fine dell'attributo. Al contrario, nell'attributo è possibile utilizzare in sicurezza il
carattere <
, perché qui non ha alcun significato speciale, qui non può essere interpretato come l'inizio di un
tag o di un commento. Ma attenzione, in HTML è possibile scrivere i valori degli attributi anche senza virgolette, in tal caso
un'intera serie di caratteri ha un significato speciale, si tratta quindi di un altro contesto separato.
Potresti essere sorpreso, ma regole speciali si applicano all'interno degli elementi <textarea>
e
<title>
, dove il carattere <
non deve (ma può) essere escapato, a meno che non sia seguito da
/
. Ma questa è più una curiosità.
È interessante all'interno dei commenti HTML. Qui, infatti, l'escaping non viene eseguito utilizzando entità HTML. Addirittura, nessuna specifica indica come si dovrebbe eseguire l'escaping nei commenti. È solo necessario rispettare regole piuttosto curiose ed evitare al loro interno determinate combinazioni di caratteri.
I contesti possono anche essere annidati, cosa che accade quando inseriamo JavaScript o CSS in HTML. Questo può essere fatto in due modi diversi, con un elemento e con un attributo:
<script>#js-element</script>
<img onclick="#js-atribut">
<style>#css-element</style>
<p style="#css-atribut"></p>
Due percorsi e due diversi modi di eseguire l'escaping dei dati. All'interno degli elementi <script>
e
<style>
, così come nel caso dei commenti HTML, l'escaping tramite entità HTML non viene eseguito. Quando si
stampano dati all'interno di questi elementi, è necessario rispettare un'unica regola: il testo non deve contenere la sequenza
</script
rispettivamente </style
.
Al contrario, negli attributi style
e on***
si esegue l'escaping tramite entità HTML.
E ovviamente, all'interno del JavaScript o CSS annidato, si applicano le regole di escaping di questi linguaggi. Quindi una
stringa nell'attributo, ad esempio onload
, viene prima escapata secondo le regole JS e poi secondo le regole
dell'attributo HTML.
Uff… Come vedi, HTML è un documento molto complesso, dove i contesti si stratificano, e senza rendersi conto di dove esattamente sto stampando i dati (cioè in quale contesto), non si può dire come farlo correttamente.
Volete un esempio?
Prendiamo la stringa Rock'n'Roll
.
Se la stampi nel testo HTML, proprio in questo caso non è necessario effettuare alcuna sostituzione, perché la stringa non contiene alcun carattere con significato speciale. La situazione cambia se la stampi all'interno di un attributo HTML racchiuso tra apici singoli. In tal caso, è necessario eseguire l'escaping degli apici in entità HTML:
<div title='Rock'n'Roll'></div>
Questo è stato semplice. Una situazione molto più interessante si verifica durante l'annidamento dei contesti, ad esempio se la stringa fa parte di JavaScript.
Prima la stampiamo nel JavaScript stesso. Cioè, la racchiudiamo tra apici e contemporaneamente eseguiamo l'escaping degli
apici contenuti in essa usando il carattere \
:
'Rock\'n\'Roll'
Possiamo ancora aggiungere la chiamata a qualche funzione, affinché il codice faccia qualcosa:
alert('Rock\'n\'Roll');
Se inseriamo questo codice nel documento HTML usando <script>
, non è necessario modificare nient'altro,
perché non contiene la sequenza proibita </script
:
<script> alert('Rock\'n\'Roll'); </script>
Se però volessimo inserirlo in un attributo HTML, dobbiamo ancora eseguire l'escaping degli apici in entità HTML:
<div onclick='alert('Rock\'n\'Roll')'></div>
Ma il contesto annidato non deve essere necessariamente solo JS o CSS. Comunemente è anche un URL. I parametri nell'URL
vengono escapati convertendo i caratteri con significato speciale in sequenze che iniziano con %
. Esempio:
https://example.org/?a=Jazz&b=Rock%27n%27Roll
E quando stampiamo questa stringa in un attributo, applichiamo ancora l'escaping secondo questo contesto e sostituiamo
&
con &
:
<a href="https://example.org/?a=Jazz&b=Rock%27n%27Roll">
Se siete arrivati fin qui, congratulazioni, è stato estenuante. Ora avete una buona idea di cosa siano i contesti e l'escaping. E non dovete preoccuparvi che sia complicato. Latte fa tutto questo per voi automaticamente.
Latte vs sistemi ingenui
Abbiamo mostrato come si esegue correttamente l'escaping in un documento HTML e quanto sia fondamentale la conoscenza del contesto, cioè del luogo in cui stampiamo i dati. In altre parole, come funziona l'escaping sensibile al contesto. Sebbene sia un prerequisito indispensabile per una difesa funzionale contro XSS, Latte è l'unico sistema di templating per PHP che sa fare questo.
Come è possibile, quando tutti i sistemi oggi affermano di avere l'escaping automatico? L'escaping automatico senza conoscenza del contesto è un po' una sciocchezza, che crea una falsa impressione di sicurezza.
I sistemi di templating, come Twig, Laravel Blade e altri, non vedono alcuna struttura HTML nel template. Non vedono quindi nemmeno i contesti. Rispetto a Latte, sono ciechi e ingenui. Elaborano solo i propri tag, tutto il resto è per loro un flusso di caratteri irrilevante:
░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░
░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░░░░░░░░
░░░░░░░░░░░░░░░░░░░░{{ foo }}░░░░
- v textu: <span>{{ foo }}</span>
- v tagu: <span {{ foo }} ></span>
- v atributu: <span title='{{ foo }}'></span>
- v atributu bez uvozovek: <span title={{ foo }}></span>
- v atributu obsahujícím URL: <a href="{{ foo }}"></a>
- v atributu obsahujícím JavaScript: <img onload="{{ foo }}">
- v atributu obsahujícím CSS: <span style="{{ foo }}"></span>
- v JavaScriptu: <script>var = {{ foo }}</script>
- v CSS: <style>body { content: {{ foo }}; }</style>
- v komentáři: <!-- {{ foo }} -->
I sistemi ingenui convertono meccanicamente solo i caratteri < > & ' "
in entità HTML, che sebbene
sia un metodo di escaping valido nella maggior parte dei casi d'uso, non lo è affatto sempre. Non possono quindi né rilevare né
prevenire la creazione di varie falle di sicurezza, come mostreremo पुढे.
Latte vede il template come lo vedi tu. Capisce HTML, XML, riconosce tag, attributi, ecc. E grazie a ciò distingue i singoli contesti e tratta i dati di conseguenza. Offre così una protezione davvero efficace contro la vulnerabilità critica Cross-site Scripting.
░░░░░░░░░░░<span>{$foo}</span>
░░░░░░░░░░<span {$foo} ></span>
░░░░░░░░░░░░░░<span title='{$foo}'></span>
░░░░░░░░░░░░░░░░░░░░░░░░░░░<span title={$foo}></span>
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░<a href="{$foo}"></a>
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░<img onload="{$foo}">
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░<span style="{$foo}"></span>
░░░░░░░░░░░░░░░░░<script>░░░░░░{$foo}</script>
░░░░░░░░░<style>░░░░░░░░░░░░░░░░{$foo}░░░</style>
░░░░░░░░░░░░░░░<!--░{$foo}░-->
- v textu: <span>{$foo}</span>
- v tagu: <span {$foo} ></span>
- v atributu: <span title='{$foo}'></span>
- v atributu bez uvozovek: <span title={$foo}></span>
- v atributu obsahujícím URL: <a href="{$foo}"></a>
- v atributu obsahujícím JavaScript: <img onload="{$foo}">
- v atributu obsahujícím CSS: <span style="{$foo}"></span>
- v JavaScriptu: <script>var = {$foo}</script>
- v CSS: <style>body { content: {$foo}; }</style>
- v komentáři: <!-- {$foo} -->
Dimostrazione dal vivo
A sinistra vedi il template in Latte, a destra il codice HTML generato. La variabile $text
viene stampata più
volte qui, e ogni volta in un contesto leggermente diverso. E quindi anche escapata in modo leggermente diverso. Puoi modificare
tu stesso il codice del template, ad esempio cambiare il contenuto della variabile, ecc. Prova:
Non è fantastico! Latte esegue l'escaping sensibile al contesto automaticamente, quindi il programmatore:
- non deve pensare né sapere come si esegue l'escaping dove
- non può sbagliare
- non può dimenticare l'escaping
Questi non sono nemmeno tutti i contesti che Latte distingue durante la stampa e per i quali adatta il trattamento dei dati. Vedremo ora altri casi interessanti.
Come hackerare i sistemi ingenui
Su alcuni esempi pratici, mostreremo quanto sia importante la distinzione dei contesti e perché i sistemi di templating ingenui non forniscono una protezione sufficiente contro XSS, a differenza di Latte. Come rappresentante di un sistema ingenuo, useremo Twig negli esempi, ma lo stesso vale anche per altri sistemi.
Vulnerabilità dell'attributo
Cercheremo di iniettare codice dannoso nella pagina tramite un attributo HTML, come abbiamo mostrato sopra. Supponiamo di avere un template in Twig che renderizza un'immagine:
<img src={{ imageFile }} alt={{ imageAlt }}>
Notate che non ci sono virgolette attorno ai valori degli attributi. Il codificatore potrebbe averle dimenticate, cosa che semplicemente accade. Ad esempio, in React, il codice viene scritto così, senza virgolette, e un codificatore che alterna i linguaggi può quindi facilmente dimenticare le virgolette.
Un aggressore inserisce come didascalia dell'immagine una stringa abilmente costruita foo onload=alert('Hacked!')
.
Sappiamo già che Twig non può riconoscere se la variabile viene stampata nel flusso del testo HTML, all'interno di un attributo,
di un commento HTML, ecc., in breve non distingue i contesti. E converte meccanicamente solo i caratteri
< > & ' "
in entità HTML. Quindi il codice risultante sarà simile a questo:
<img src=photo0145.webp alt=foo onload=alert('Hacked!')>
E si è creata una falla di sicurezza!
L'attributo onload
contraffatto è diventato parte della pagina e il browser lo esegue immediatamente dopo aver
scaricato l'immagine.
Ora vediamo come Latte gestisce lo stesso template:
<img src={$imageFile} alt={$imageAlt}>
Latte vede il template come lo vedi tu. A differenza di Twig, capisce HTML e sa che la variabile viene stampata come valore di un attributo che non è tra virgolette. Pertanto le aggiunge. Quando l'aggressore inserisce la stessa didascalia, il codice risultante sarà simile a questo:
<img src="photo0145.webp" alt="foo onload=alert('Hacked!')">
Latte ha prevenuto con successo l'XSS.
Stampa di variabili in JavaScript
Grazie all'escaping sensibile al contesto, è possibile utilizzare in modo completamente nativo le variabili PHP all'interno di JavaScript.
<p onclick="alert({$movie})">{$movie}</p>
<script>var movie = {$movie};</script>
Se la variabile $movie
contiene la stringa 'Amarcord & 8 1/2'
, verrà generato il seguente
output. Notate che all'interno dell'HTML viene utilizzato un escaping diverso rispetto a quello all'interno di JavaScript e ancora
diverso nell'attributo onclick
:
<p onclick="alert("Amarcord & 8 1\/2")">Amarcord & 8 1/2</p>
<script>var movie = "Amarcord & 8 1\/2";</script>
Controllo dei link
Latte controlla automaticamente se la variabile utilizzata negli attributi src
o href
contiene un URL
web (cioè protocollo HTTP) e impedisce la visualizzazione di link che potrebbero rappresentare un rischio per la sicurezza.
{var $link = 'javascript:attack()'}
<a href={$link}>clicca</a>
Stampa:
<a href="">clicca</a>
Il controllo può essere disabilitato usando il filtro nocheck.
Limiti di Latte
Latte non è una protezione completamente completa contro XSS per l'intera applicazione. Non vorremmo che smetteste di pensare alla sicurezza quando usate Latte. L'obiettivo di Latte è garantire che un aggressore non possa modificare la struttura della pagina, contraffare elementi o attributi HTML. Ma non controlla la correttezza del contenuto dei dati stampati. O la correttezza del comportamento di JavaScript. Questo va oltre le competenze del sistema di templating. La verifica della correttezza dei dati, specialmente quelli inseriti dall'utente e quindi non attendibili, è un compito importante del programmatore.