La creazione di un'esperienza completa sul web di oggi comporta quasi inevitabilmente l'inserimento di componenti e contenuti su cui non hai alcun controllo reale. 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:
I Criteri di sicurezza del contenuto (CSP) possono ridurre i rischi associati a entrambi questi tipi di contenuti consentendo di inserire nella lista consentita origini attendibili specifiche di script e altri contenuti. 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, browser, ma non fare in modo che blocchi il mio sito."
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 danoso impedire l'accesso a window.open. Se non richiede Flash, la disattivazione del 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 sufficiente
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
offre ancora una serie di opzioni per comportamenti fastidiosi o dannosi: i video con riproduzione automatica, i plug-in e i popup sono solo la punta dell'iceberg.
L'attributo sandbox
dell'elemento iframe
ci fornisce tutto ciò di cui abbiamo bisogno per rafforzare le limitazioni relative ai contenuti incorniciati. Possiamo indicare al browser di caricare i contenuti di un frame specifico in un ambiente con privilegi ridotti, consentendo solo il sottoinsieme di funzionalità necessarie per svolgere le operazioni richieste.
Fiducia, ma verifica
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 delle funzionalità
Nell'esempio riportato sopra abbiamo visto alcuni dei possibili flag di sandboxing. Ora analizziamo più nel dettaglio il funzionamento interno dell'attributo.
Dato un iframe con un attributo sandbox vuoto, il documento incorniciato verrà sottoposto a sandbox completa, con le seguenti limitazioni:
- 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 memorizzati nei cookie di qualsiasi origine o in altri meccanismi di archiviazione (archiviazione DOM, DB 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
window.top.location
genererà un'eccezione e il clic sul link contarget="_top"
non avrà alcun effetto. - Le funzionalità che si attivano automaticamente (elementi di modulo con messa a fuoco automatica, riproduzione automatica dei video e così via) sono bloccate.
- Impossibile ottenere il blocco del cursore.
- L'attributo
seamless
viene ignorato iniframes
contenuto nel documento incorniciato.
Si tratta di un approccio molto severo e un documento caricato in un iframe
in modalità sandbox completa 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 cursore.allow-same-origin
consente al documento di mantenere la sua origine; le pagine caricate dahttps://example.com/
manterranno l'accesso ai dati dell'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 presente quanto sopra, possiamo valutare esattamente il motivo per cui abbiamo scelto l'insieme specifico di flag di sandboxing 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, poiché 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, altrimenti i cookie di twitter.com non sarebbero accessibili e l'utente non potrebbe accedere per pubblicare il modulo.
È importante notare che gli indicatori di sandboxing applicati a un frame si applicano anche a eventuali finestre o 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 i plug-in, la barra di navigazione superiore e il 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?
Ti faccio 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 ha bug e praticamente ogni applicazione è vulnerabile allo sfruttamento in un modo o nell'altro. La sandbox del codice significa che anche se un malintenzionato riesce a compromettere la tua applicazione, non avrà accesso completo all'origine dell'applicazione, ma potrà solo fare ciò che l'applicazione potrebbe fare. È comunque un problema, ma non così grave come potrebbe essere.
Puoi ridurre ulteriormente il rischio suddividendo l'applicazione in componenti logici e inserendo ogni componente in una sandbox con i privilegi minimi possibili. 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 abile trova un modo per corrompere un renderer, non è andato molto lontano, poiché il renderer non può fare molto di interessante da solo: tutti gli accessi con privilegi elevati devono essere inoltrati tramite 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.
Eseguire il sandboxing in sicurezza 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. Incredibile, 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. Ridurremo il rischio che si verifichino "cose brutte"™ assicurandoci che il codice venga eseguito all'interno di una sandbox, il che lo rende molto 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 abbiamo un documento minimo che ascolta semplicemente i messaggi
dal suo elemento principale collegandosi all'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 inviarti il risultato del nostro duro lavoro al termine dell'operazione. 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, riportiamo il risultato alla 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 per il tuo codice suddividendo le applicazioni monolitiche in componenti monouso. Ognuno può essere inserito in una semplice API di messaggistica, come abbiamo scritto sopra. La finestra principale con privilegi elevati può fungere da controllore e gestore, inviando messaggi a moduli specifici che dispongono ciascuno del minor numero possibile di privilegi per svolgere le proprie attività, ascoltando i risultati e assicurandosi che ogni modulo riceva solo le informazioni di cui ha bisogno.
Tieni presente, però, che devi fare molta attenzione quando gestisci contenuti incorniciati provenienti dalla stessa origine dell'elemento 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.
Giocare nella sandbox
La sandboxing è ora disponibile in diversi browser: Firefox 17 e versioni successive, IE10 e versioni successive e Chrome al momento della stesura di questo articolo (caniuse, ovviamente, ha una tabella di supporto 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 il momento la sandbox è un altro livello di protezione per rafforzare le tue difese, non è una difesa completa su cui puoi fare affidamento. Tuttavia, i livelli sono eccellenti. Ti suggerisco di usarlo.
Per approfondire
"Privilege Separation in HTML5 Applications" è un interessante articolo che illustra la progettazione di un piccolo framework e la sua applicazione a tre app HTML5 esistenti.
La sandboxing può essere ancora più flessibile se combinata con altri due nuovi attributi iframe:
srcdoc
eseamless
. Il primo ti consente di compilare un frame con contenuti senza l'overhead di una richiesta HTTP, mentre il secondo consente di applicare lo stile ai contenuti incorniciati. 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>