Utilizzo di eval nelle estensioni di Chrome

Il sistema di estensioni di Chrome applica un Criterio di sicurezza del contenuto (CSP) predefinito abbastanza rigoroso. Le limitazioni relative ai criteri sono semplici: lo script deve essere spostato fuori riga in file JavaScript separati, i gestori di eventi incorporati devono essere convertiti per utilizzare addEventListener e eval() è disattivato. Per le app di Chrome sono previste norme ancora più rigide e siamo molto soddisfatti delle proprietà di sicurezza offerte da questi criteri.

Tuttavia, siamo consapevoli che varie librerie utilizzano costrutti eval() e eval come new Function() per l'ottimizzazione delle prestazioni e la facilità di espressione. Le librerie di modelli sono particolarmente soggette a questo stile di implementazione. Sebbene alcuni (come Angular.js) supportino i CSP da subito, molti framework popolari non sono ancora stati aggiornati a un meccanismo compatibile con il mondo senza eval delle estensioni. Pertanto, la rimozione del supporto per questa funzionalità si è rivelata più problematica del previsto per gli sviluppatori.

Questo documento introduce il sandboxing come meccanismo sicuro per includere queste librerie nei tuoi progetti senza compromettere la sicurezza. Per brevità, utilizzeremo sempre il termine estensioni, ma il concetto si applica anche alle applicazioni.

Perché usare la sandbox?

eval è pericoloso all'interno di un'estensione perché il codice che esegue ha accesso a tutti gli elementi presenti nell'ambiente con autorizzazioni elevate dell'estensione. Sono disponibili molte API chrome.* potenti che potrebbero avere un impatto significativo sulla sicurezza e sulla privacy di un utente; la semplice esfiltrazione di dati è l'ultimo dei nostri problemi. La soluzione offerta è una sandbox in cui eval può eseguire codice senza accedere ai dati dell'estensione o alle API di alto valore dell'estensione. Nessun dato, nessuna API, nessun problema.

A tal fine, elenchiamo file HTML specifici all'interno del pacchetto dell'estensione come sandbox. Ogni volta che viene caricata una pagina con sandbox, questa viene spostata in un'origine univoca e l'accesso alle API chrome.* sarà negato. Se carichiamo questa pagina con sandbox nella nostra estensione tramite un iframe, possiamo passare i messaggi, consentirgli di agire su quei messaggi in qualche modo e attendere che restituisca un risultato. Questo semplice meccanismo di messaggistica ci offre tutto ciò di cui abbiamo bisogno per includere in modo sicuro il codice basato su eval nel flusso di lavoro della nostra estensione.

Creazione e utilizzo di una sandbox.

Se vuoi approfondire direttamente il codice, scarica l'estensione di esempio per il sandboxing. Si tratta di un esempio funzionante di una piccola API di messaggistica basata sulla libreria di modelli Handlebars e dovrebbe fornirti tutto ciò di cui hai bisogno per iniziare. Per chi desidera una spiegazione più approfondita, vediamo insieme l'esempio.

Elenca file nel file manifest

Ogni file che deve essere eseguito all'interno di una sandbox deve essere elencato nel manifest dell'estensione aggiungendo una proprietà sandbox. Si tratta di un passaggio fondamentale ed è facile da dimenticare, quindi verifica attentamente che il file sandbox sia elencato nel manifest. In questo esempio, eseguiamo il sandboxing del file denominato "sandbox.html". La voce del file manifest ha il seguente aspetto:

{
  ...,
  "sandbox": {
     "pages": ["sandbox.html"]
  },
  ...
}

Carica il file con sandbox

Per poter realizzare qualcosa di interessante con il file sandbox, dobbiamo caricarlo in un contesto in cui può essere risolto con il codice dell'estensione. Qui, sandbox.html è stato caricato nella pagina dell'evento (eventpage.html) dell'estensione tramite un iframe. eventpage.js contiene codice che invia un messaggio nella sandbox ogni volta che viene fatto clic sull'azione del browser individuando il iframe nella pagina ed eseguendo il metodo postMessage sul relativo contentWindow. Il messaggio è un oggetto con due proprietà: context e command. A breve li esamineremo entrambi.

chrome.browserAction.onClicked.addListener(function() {
 var iframe = document.getElementById('theFrame');
 var message = {
   command: 'render',
   context: {thing: 'world'}
 };
 iframe.contentWindow.postMessage(message, '*');
});
Per informazioni generali sull'API postMessage, consulta la documentazione postMessage sulla MDN . È abbastanza completa e vale la pena leggerla. In particolare, tieni presente che i dati possono essere trasmessi avanti e indietro solo se sono serializzabili. Le funzioni, ad esempio, non lo sono.

Fai qualcosa di pericoloso

Una volta caricato, sandbox.html carica la libreria Handlebars e crea e compila un modello incorporato nel modo in cui Handlebars suggerisce:

<script src="handlebars-1.0.0.beta.6.js"></script>
<script id="hello-world-template" type="text/x-handlebars-template">
  <div class="entry">
    <h1>Hello, !</h1>
  </div>
</script>
<script>
  var templates = [];
  var source = document.getElementById('hello-world-template').innerHTML;
  templates['hello'] = Handlebars.compile(source);
</script>

Questa operazione non ha esito negativo. Anche se in Handlebars.compile viene usato new Function, tutto funziona esattamente come previsto e viene creato un modello compilato in templates['hello'].

Trasmetti il risultato

Renderemo disponibile questo modello per l'uso impostando un listener di messaggi che accetti i comandi dalla pagina dell'evento. Utilizzeremo il codice command trasmesso per determinare cosa bisognerebbe fare (potresti immaginare di fare qualcosa di più del semplice rendering, magari creare modelli?) Se gestiscile in qualche modo?) e il valore context verrà trasferito direttamente nel modello per il rendering. Il codice HTML sottoposto a rendering verrà restituito alla pagina dell'evento in modo che l'estensione possa utilizzarlo in seguito:

<script>
  window.addEventListener('message', function(event) {
    var command = event.data.command;
    var name = event.data.name || 'hello';
    switch(command) {
      case 'render':
        event.source.postMessage({
          name: name,
          html: templates[name](event.data.context)
        }, event.origin);
        break;

      // case 'somethingElse':
      //   ...
    }
  });
</script>

Tornando alla pagina dell'evento, riceverai questo messaggio e faremo qualcosa di interessante con i dati di html che ci sono stati trasmessi. In questo caso, utilizzeremo semplicemente l'eco tramite una notifica desktop, ma è del tutto possibile utilizzare questo codice HTML in modo sicuro nell'interfaccia utente dell'estensione. L'inserimento tramite innerHTML non comporta un rischio significativo per la sicurezza, poiché anche una compromissione completa del codice con sandbox tramite un attacco intelligente non sarebbe in grado di inserire contenuti di script o plug-in pericolosi nel contesto dell'estensione con autorizzazione elevata.

Questo meccanismo rende i modelli semplici, ma non si limita ai modelli. Qualsiasi codice che non funziona subito in base a rigorosi criteri di sicurezza del contenuto può essere limitato tramite sandbox; in effetti, è spesso utile limitare la sandbox dei componenti delle estensioni che venrebbero eseguiti correttamente per limitare ogni parte del programma al minor numero di privilegi necessari per una corretta esecuzione. La presentazione Scrivere app web sicure ed estensioni di Chrome di Google I/O 2012 offre alcuni buoni esempi di questa tecnica in pratica e vale 56 minuti del vostro tempo.