Per creare un'esperienza completa sul web odierno, è quasi inevitabile che vengano incorporati componenti e contenuti sui quali non hai alcun reale controllo. I widget di terze parti possono aumentare il coinvolgimento e svolgere un ruolo fondamentale nell'esperienza utente complessiva. Inoltre, a volte i contenuti generati dagli utenti sono persino più importanti dei contenuti nativi di un sito. Astenersi da entrambi non è un'opzione, ma entrambi aumentano il rischio che si verifichi Qualcosa di Cattivo™ sul tuo sito. Ogni widget che incorpori, ogni annuncio, ogni widget di social media, è un potenziale vettore di attacco per chi ha intenzioni malevole:
Il Criterio di sicurezza del contenuto (CSP) può mitigare i rischi associati a entrambi questi tipi di contenuti dandoti la possibilità di autorizzare fonti di script e altri contenuti appositamente attendibili. Si tratta di un passo importante nella giusta direzione, ma vale la pena notare che la protezione offerta dalla maggior parte delle direttive CSP è binaria: la risorsa è consentita o meno. A volte può essere utile dire "Non sono sicuro di fidarmi di questa fonte di contenuti, ma sono così belli! Incorporalo per favore, ma non permettere che il mio sito venga danneggiato."
Principio del privilegio minimo
In sostanza, stiamo cercando un meccanismo che ci consenta di concedere ai contenuti che includiamo solo il livello minimo di funzionalità necessario per svolgere il loro compito. Se un widget non ha bisogno di aprire una nuova finestra, non può essere male rimuovere l'accesso a window.open. Se non è necessario Flash, disattivare il supporto dei plug-in non dovrebbe rappresentare un problema. La sicurezza è massima se seguiamo il principio del privilegio minimo e blocchiamo ogni funzionalità non direttamente pertinente a quelle che vorremmo utilizzare. Di conseguenza, non dobbiamo più fidarci ciecamente del fatto che alcuni contenuti incorporati non utilizzino privilegi che non dovrebbero usare. Semplicemente non avrà accesso alla funzionalità.
Gli elementi iframe
sono il primo passo verso un buon framework per una soluzione di questo tipo.
Il caricamento di un componente non attendibile in un iframe
consente di separare in misura certa
la tua applicazione dai contenuti che vuoi caricare. I contenuti incorniciati
non avranno accesso al DOM della pagina o ai dati memorizzati localmente e non potranno
disegnare in posizioni arbitrarie della pagina. Il loro ambito è limitato al contornata del frame. Tuttavia, la separazione non è davvero solida. La pagina contenuta
continua a offrire una serie di opzioni per comportamenti fastidiosi o dannosi: video a riproduzione automatica, plug-in e popup sono solo la punta dell'iceberg.
L'attributo sandbox
dell'elemento iframe
ci offre esattamente ciò di cui abbiamo bisogno per rafforzare le limitazioni dei contenuti inquadrati. Possiamo indicare al browser di caricare i contenuti di un frame specifico in un ambiente con privilegi limitati, consentendo solo il sottoinsieme di funzionalità necessarie per svolgere qualsiasi lavoro.
Accorciare, ma verificare
Il pulsante "Tweet" di Twitter è un ottimo esempio di funzionalità che può essere incorporata in modo più sicuro nel tuo sito tramite una sandbox. Twitter ti consente di incorporare il pulsante tramite un iframe con il seguente codice:
<iframe src="https://platform.twitter.com/widgets/tweet_button.html"
style="border: 0; width:130px; height:20px;"></iframe>
Per capire cosa possiamo bloccare, esaminiamo attentamente le funzionalità richieste dal pulsante. Il codice HTML caricato nel frame esegue un po' di codice JavaScript dai server di Twitter e genera un popup con un'interfaccia per i tweet quando viene fatto clic. Questa interfaccia deve avere accesso ai cookie di Twitter per associare il tweet all'account corretto e deve essere in grado di inviare il modulo di tweet. È tutto qui: il frame non deve caricare plug-in, non deve navigare nella finestra di primo livello o in una serie di altre funzionalità. Dato che non ha bisogno di questi privilegi, rimuoviamoli mettendo in sandbox i contenuti del frame.
La sandbox funziona in base a una lista consentita. Iniziamo rimuovendo tutte le autorizzazioni possibili, quindi riattiviamo le singole funzionalità aggiungendo flag specifici alla configurazione della sandbox. Per il widget di Twitter, abbiamo scelto di attivare JavaScript, i popup, l'invio dei moduli e i cookie di twitter.com. Possiamo farlo aggiungendo un attributo sandbox
a iframe
con il seguente valore:
<iframe sandbox="allow-same-origin allow-scripts allow-popups allow-forms"
src="https://platform.twitter.com/widgets/tweet_button.html"
style="border: 0; width:130px; height:20px;"></iframe>
È tutto. Abbiamo assegnato al frame tutte le funzionalità richieste e il browser negherà l'accesso a tutti i privilegi che non abbiamo concesso esplicitamente tramite il valore dell'attributo sandbox
.
Controllo granulare sulle funzionalità
Abbiamo visto alcuni dei possibili flag di sandboxing nell'esempio riportato sopra. Ora analizziamo in modo più dettagliato il funzionamento interno dell'attributo.
Dato che un iframe con un attributo sandbox vuoto, il documento all'interno di frame verrà completamente limitato tramite sandbox, sottoponendolo alle seguenti restrizioni:
- Il codice JavaScript non verrà eseguito nel documento incorniciato. Non sono inclusi solo i script JavaScript caricati esplicitamente tramite i tag script, ma anche gli elementi di gestione degli eventi in linea e gli URL javascript:. Ciò significa anche che i contenuti contenuti nei tag noscript verranno visualizzati, esattamente come se l'utente avesse disattivato lo script.
- Il documento incorniciato viene caricato in un'origine univoca, il che significa che tutti i controlli di stessa origine non andranno a buon fine; le origini univoche non corrispondono mai ad altre origini, nemmeno a se stesse. Tra gli altri effetti, ciò significa che il documento non ha accesso ai dati archiviati nei cookie di qualsiasi origine o in qualsiasi altro meccanismo di archiviazione (archiviazione DOM, database indicizzato e così via).
- Il documento incorniciato non può creare nuove finestre o finestre di dialogo (ad esempio tramite
window.open
otarget="_blank"
). - I moduli non possono essere inviati.
- I plug-in non verranno caricati.
- Il documento incorniciato può navigare solo all'interno di se stesso, non nel documento principale di primo livello.
L'impostazione di
window.top.location
genererà un'eccezione e fare clic sul link contarget="_top"
non avrà alcun effetto. - Le funzionalità che si attivano automaticamente (elementi del modulo incentrati automaticamente, video con riproduzione automatica e così via) vengono bloccate.
- Impossibile ottenere il blocco del cursore.
- L'attributo
seamless
viene ignorato iniframes
contenuto nel documento incorniciato.
È una misura molto severa e un documento caricato in un iframe
completamente in sandbox rappresenta un rischio davvero molto ridotto. Naturalmente, non può fare molto di utile: potresti riuscire a cavartela con una sandbox completa per alcuni contenuti statici, ma la maggior parte delle volte ti consigliamo di alleggerire un po' le regole.
Ad eccezione dei plug-in, ciascuna di queste limitazioni può essere rimossa aggiungendo un flag al valore dell'attributo sandbox. I documenti in sandbox non possono mai eseguire plug-in, in quanto i plug-in sono codice nativo non sottoposto a sandbox, ma tutto il resto è consentito:
allow-forms
consente l'invio del modulo.allow-popups
consente (sorpresa!) i popup.allow-pointer-lock
consente (sorpresa!) il blocco del puntatore.allow-same-origin
consente al documento di mantenere la sua origine; le pagine caricate dahttps://example.com/
manterranno l'accesso ai dati di quell'origine.allow-scripts
consente l'esecuzione di JavaScript e anche l'attivazione automatica delle funzionalità (in quanto sarebbe banale implementarle tramite JavaScript).allow-top-navigation
consente al documento di uscire dal frame navigando nella finestra di primo livello.
Tenendo conto di questi aspetti, possiamo valutare esattamente il motivo per cui siamo arrivati a quel set specifico di flag sandbox nell'esempio di Twitter riportato sopra:
allow-scripts
è obbligatorio, poiché la pagina caricata nel frame esegue del codice JavaScript per gestire l'interazione dell'utente.allow-popups
è obbligatorio, perché il pulsante fa apparire un modulo di tweet in una nuova finestra.allow-forms
è obbligatorio, in quanto il modulo per i tweet deve essere inviabile.allow-same-origin
è necessario, perché i cookie di twitter.com non sarebbero accessibili e l'utente non potrebbe accedere per pubblicare il modulo.
Una cosa importante da notare è che i flag della sandbox applicati a un frame
si applicano anche a tutte le finestre o i frame creati nella sandbox. Ciò significa che dobbiamo
aggiungere allow-forms
alla sandbox del frame, anche se il modulo esiste solo
nella finestra in cui viene visualizzato il frame.
Con l'attributo sandbox
, il widget riceve solo le autorizzazioni di cui necessita e funzionalità come plug-in, navigazione in alto e blocco del cursore rimangono bloccate. Abbiamo ridotto il rischio di incorporare il widget, senza effetti negativi.
È una vittoria per tutti.
Separazione dei privilegi
È abbastanza evidente che il sandboxing dei contenuti di terze parti per eseguire il loro codice non attendibile in un ambiente con privilegi ridotti sia vantaggioso. E il tuo codice? Ti fidi di te stesso, giusto? Perché preoccuparsi della sandbox?
Vorrei porti una domanda: se il tuo codice non ha bisogno di plug-in, perché concedergli l'accesso ai plug-in? Nella migliore delle ipotesi, si tratta di un privilegio che non utilizzi mai, nella peggiore, è un potenziale vettore per consentire agli attaccanti di introdursi nel sistema. Il codice di tutti contiene bug e praticamente ogni applicazione è vulnerabile allo sfruttamento in un modo o nell'altro. La limitazione tramite sandbox del tuo codice implica che, anche se un utente malintenzionato sovviene alla tua applicazione, non avrà accesso completo all'origine dell'applicazione; potrà soltanto eseguire le operazioni che l'applicazione potrebbe fare. È comunque un brutto, ma non così male come potrebbe.
Puoi ridurre ulteriormente il rischio suddividendo l'applicazione in parti logiche e tramite sandbox ogni parte con il minimo privilegio possibile. Questa tecnica è molto comune nel codice nativo: Chrome, ad esempio, si suddivide in un processo del browser con privilegi elevati che ha accesso all'hard disk locale e può effettuare connessioni di rete e molti processi del renderer con privilegi ridotti che svolgono il lavoro più pesante di analisi dei contenuti non attendibili. I renderer non devono accedere al disco, il browser si occupa di fornire loro tutte le informazioni necessarie per eseguire il rendering di una pagina. Anche se un hacker astuto trova un modo per corrompere un renderer, non è andata molto lontano, dato che il renderer non può fare molto interesse da solo: tutti gli accessi con privilegi elevati devono essere indirizzati attraverso il processo del browser. Gli aggressori dovranno trovare diversi buchi in parti diverse del sistema per causare danni, il che riduce notevolmente il rischio di un attacco di successo.
Sandboxing sicuro di eval()
Con la sandbox e l'API postMessage
, il successo di questo modello è abbastanza semplice da applicare al web. Componenti della tua applicazione possono trovarsi in iframe
in sandbox e il documento principale può mediare la comunicazione tra di loro pubblicando messaggi e ascoltando le risposte. Questo tipo di struttura garantisce che gli exploit in qualsiasi componente dell'app causino il danno minimo possibile. Ha anche il vantaggio di costringerti a creare punti di integrazione chiari, in modo da sapere esattamente dove devi prestare attenzione alla convalida di input e output. Vediamo un esempio pratico,
solo per capire come potrebbe funzionare.
Evalbox è un'applicazione interessante che prende una stringa e la valuta come JavaScript. Wow, vero? Proprio quello che stavai aspettando in tutti questi lunghi anni. Si tratta ovviamente di un'applicazione piuttosto pericolosa, poiché consentire l'esecuzione di JavaScript arbitrario significa che tutti i dati offerti da un'origine sono a rischio. Riducono il rischio che ciò accada garantendo che il codice venga eseguito all'interno di una sandbox, in modo che sia un po' più sicuro. Analizzeremo il codice dall'interno verso l'esterno, iniziando dai contenuti del frame:
<!-- frame.html -->
<!DOCTYPE html>
<html>
<head>
<title>Evalbox's Frame</title>
<script>
window.addEventListener('message', function (e) {
var mainWindow = e.source;
var result = '';
try {
result = eval(e.data);
} catch (e) {
result = 'eval() threw an exception.';
}
mainWindow.postMessage(result, event.origin);
});
</script>
</head>
</html>
All'interno del frame è presente una quantità minima di documenti che si limita ad ascoltare i messaggi del relativo elemento padre collegando l'evento message
dell'oggetto window
.
Ogni volta che il componente principale esegue postMessage sui contenuti dell'iframe, questo evento si attiva e ci consente di accedere alla stringa che il componente principale vuole che venga eseguita.
Nel gestore, acquisiamo l'attributo source
dell'evento, ovvero la finestra principale. Lo utilizzeremo per rinviare il risultato
del nostro duro lavoro quando avremo finito. Poi faremo il grosso del lavoro, passando i dati che ci sono stati forniti a
eval()
. Questa chiamata è stata racchiusa in un blocco try, poiché le operazioni vietate
all'interno di un iframe
in sandbox genereranno spesso eccezioni DOM. Le cattureremo
e segnaleremo invece un messaggio di errore di facile comprensione. Infine, pubblichiamo il risultato
nella finestra principale. È abbastanza semplice.
Anche l'elemento principale è altrettanto semplice. Creeremo una piccola interfaccia utente con un textarea
per il codice e un button
per l'esecuzione e imposteremo frame.html
tramite un iframe
in sandbox, consentendo solo l'esecuzione dello script:
<textarea id='code'></textarea>
<button id='safe'>eval() in a sandboxed frame.</button>
<iframe sandbox='allow-scripts'
id='sandboxed'
src='frame.html'></iframe>
Ora collegheremo tutto per l'esecuzione. Innanzitutto, ascolteremo le risposte del iframe
e le alert()
ai nostri utenti. Presumibilmente un'applicazione reale
farebbe qualcosa di meno fastidioso:
window.addEventListener('message',
function (e) {
// Sandboxed iframes which lack the 'allow-same-origin'
// header have "null" rather than a valid origin. This means you still
// have to be careful about accepting data via the messaging API you
// create. Check that source, and validate those inputs!
var frame = document.getElementById('sandboxed');
if (e.origin === "null" && e.source === frame.contentWindow)
alert('Result: ' + e.data);
});
A questo punto, collegheremo un gestore di eventi ai clic sul pulsante button
. Quando l'utente fa clic, acquisiremo i contenuti correnti di textarea
e li passeremo al frame per l'esecuzione:
function evaluate() {
var frame = document.getElementById('sandboxed');
var code = document.getElementById('code').value;
// Note that we're sending the message to "*", rather than some specific
// origin. Sandboxed iframes which lack the 'allow-same-origin' header
// don't have an origin which you can target: you'll have to send to any
// origin, which might alow some esoteric attacks. Validate your output!
frame.contentWindow.postMessage(code, '*');
}
document.getElementById('safe').addEventListener('click', evaluate);
Facile, no? Abbiamo creato un'API di valutazione molto semplice e possiamo essere certi che il codice valutato non abbia accesso a informazioni sensibili come i cookie o lo spazio di archiviazione DOM. Allo stesso modo, il codice valutato non può caricare plug-in, aprire nuove finestre o svolgere una serie di altre attività fastidiose o dannose.
Puoi fare lo stesso con il tuo codice suddividendo le applicazioni monolitiche in componenti a uso specifico. Ognuna può essere racchiusa in una semplice API di messaggistica, proprio come abbiamo scritto sopra. La finestra principale con privilegi elevati può fungere da controller e supervisore, inviando messaggi in moduli specifici, ognuno dei quali dispone del minor numero di privilegi possibile per svolgere il proprio lavoro, ascoltare i risultati e garantire che ogni modulo riceva solo le informazioni necessarie.
Tieni presente, tuttavia, che devi prestare molta attenzione quando hai a che fare con contenuti racchiusi in frame che provengono dalla stessa origine del file principale. Se una pagina su
https://example.com/
incornicia un'altra pagina nella stessa origine con una sandbox
che include sia i flag allow-same-origin sia allow-scripts, then
la pagina incorniciata può raggiungere la pagina principale e rimuovere completamente l'attributo sandbox.
Gioca nella tua sandbox
La limitazione tramite sandbox è ora disponibile in diversi browser: Firefox 17 e versioni successive, IE10 e versioni successive e Chrome al momento della stesura di questo documento (caniuse, ovviamente, ha una tabella supportata aggiornata). L'applicazione dell'attributo sandbox
a iframes
che includi ti consente di concedere determinati privilegi ai
contenuti visualizzati, solo quelli necessari per il corretto funzionamento
dei contenuti. In questo modo hai la possibilità di ridurre il rischio associato all'inclusione di contenuti di terze parti, oltre a quanto già possibile con le norme sulla sicurezza dei contenuti.
Inoltre, il sandboxing è una tecnica efficace per ridurre il rischio che un malintenzionato astuto possa sfruttare le falle nel tuo codice. Separando un'applicazione monolitica in un insieme di servizi in sandbox, ciascuno responsabile di una piccola parte di funzionalità autosufficiente, gli attaccanti saranno costretti a compromettere non solo i contenuti di frame specifici, ma anche il relativo controller. Si tratta di un compito molto più difficile, soprattutto perché l'ambito del controller può essere notevolmente ridotto. Puoi dedicare le tue risorse per la sicurezza al controllo di questo codice se chiedi al browser di aiutarti con il resto.
Ciò non significa che la sandboxing sia una soluzione completa al problema della sicurezza su internet. Offre una difesa in profondità e, a meno che tu non abbia il controllo sui client dei tuoi utenti, non puoi ancora fare affidamento sul supporto del browser per tutti i tuoi utenti (se controlli i client dei tuoi utenti, ad esempio in un ambiente aziendale, è fantastico!). Un giorno... ma per ora la sandbox è un altro livello di protezione che rafforza le tue difese, non è una difesa completa su cui puoi fare affidamento. Tuttavia, i livelli sono eccellenti. Ti suggerisco di usarlo.
Per approfondire
"Separazione dei privilegi nelle applicazioni HTML5" è un articolo interessante che illustra la progettazione di un piccolo framework e la relativa applicazione a tre app HTML5 esistenti.
La sandboxing può essere ancora più flessibile se combinata con altri due nuovi attributi iframe:
srcdoc
eseamless
. La prima consente di completare un frame con i contenuti senza l'overhead di una richiesta HTTP, mentre la seconda consente di far confluire lo stile nei contenuti del frame. Al momento, entrambi hanno un supporto del browser piuttosto scarso (versioni beta di Chrome e WebKit), ma in futuro sarà una combinazione interessante. Ad esempio, puoi mettere in sandbox i commenti su un articolo tramite il seguente codice:<iframe sandbox seamless srcdoc="<p>This is a user's comment! It can't execute script! Hooray for safety!</p>"></iframe>