Utilizzo di eval nelle estensioni di Chrome

Il sistema di estensioni di Chrome applica un Criterio di sicurezza del contenuto (CSP) predefinito piuttosto rigoroso. Le restrizioni dei criteri sono chiare: lo script deve essere spostato fuori riga in file separati I file JavaScript e i gestori di eventi incorporati devono essere convertiti per utilizzare addEventListener e eval() è disattivata. Le app di Chrome hanno norme ancora più rigide e siamo molto soddisfatti della sicurezza proprietà offerte da questi criteri.

Riconosciamo, tuttavia, che varie librerie utilizzano costrutti simili a eval() e eval, ad esempio new Function() per l'ottimizzazione delle prestazioni e la facilità di espressione. Le librerie di modelli particolarmente incline a questo tipo di implementazione. Mentre alcuni (come Angular.js) supportano CSP molti dei framework più diffusi non sono ancora stati aggiornati a un meccanismo compatibile estensioni in meno di eval. La rimozione del supporto per tale funzionalità si è quindi dimostrata maggiore problematici del previsto per gli sviluppatori.

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

Perché la sandbox?

eval è pericoloso all'interno di un'estensione perché il codice che esegue ha accesso a tutti gli elementi in nell'ambiente con autorizzazioni elevate dell'estensione. Sono disponibili numerose API chrome.* potenti che potrebbero avere un impatto significativo sulla sicurezza e sulla privacy di un utente; l'esfiltrazione di dati semplice è l'ultima delle nostre preoccupazioni. La soluzione offerta è una sandbox in cui eval può eseguire il codice senza accedere i dati dell'estensione o le 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 sottoposti a sandbox. Ogni volta che una pagina con sandbox viene caricata, viene spostata in un'origine unica e viene negata e l'accesso alle API di chrome.*. Se carichiamo questa pagina con sandbox nella nostra estensione tramite un iframe, possiamo trasmetterlo, lasciarlo agire in qualche modo su quei messaggi e attendere che ci ritrasmetta o il risultato finale. Questo semplice meccanismo di messaggistica ci offre tutto ciò di cui abbiamo bisogno per includere in modo sicuro le campagne eval nel flusso di lavoro delle nostre estensioni.

Creazione e utilizzo di una sandbox.

Se vuoi approfondire direttamente il codice, recupera l'estensione di esempio della sandbox e segui disattivata. È un esempio pratico di una piccola API di messaggistica basata sui manubri di modelli e dovrebbe fornirti tutto ciò di cui hai bisogno per iniziare. Per chi di voi una spiegazione in più, analizziamo insieme l'esempio.

Elenca i file nel file manifest

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

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

Carica il file con sandbox

Per poter fare qualcosa di interessante con il file con sandbox, dobbiamo caricarlo in un contesto in cui può essere affrontato dal codice dell'estensione. In questo caso, sandbox.html è stato caricato 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 cercando iframe nella pagina ed eseguendo il metodo postMessage nella tabella contentWindow. Il messaggio è un oggetto contenente due proprietà: context e command. Tra poco analizzeremo 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 di postMessage relativa a MDN . È abbastanza completo e vale la pena leggerlo. In particolare, tieni presente che i dati possono essere trasmessi solo se sono serializzabili. Le funzioni, ad esempio, non lo sono.

Fai qualcosa di pericoloso

Una volta caricato sandbox.html, carica la libreria Handlebars, quindi crea e compila una 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>

Non fallire! Anche se Handlebars.compile utilizza new Function, l'operazione funziona esattamente come previsto, per cui viene creato un modello compilato in templates['hello'].

Ritrasmetti il risultato

Rendiamo disponibile questo modello impostando un listener di messaggi che accetti comandi dalla pagina dell'evento. Utilizzeremo il valore command trasmesso per determinare l'operazione da eseguire (potresti immagina di fare qualcosa di più che eseguire il rendering; magari creando modelli? Magari gestendole in alcune ?) e context verrà trasmesso direttamente al modello per il rendering. L'HTML sottoposto a rendering verrà restituito alla pagina dell'evento in modo che l'estensione possa utilizzarlo in un secondo momento:

<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, riceveremo questo messaggio e faremo qualcosa di interessante con l'html dati che ci sono stati trasmessi. In questo caso, la segnaleremo tramite una notifica desktop, ma puoi utilizzare questo codice HTML in tutta sicurezza nell'interfaccia utente dell'estensione. Inserendo il dispositivo tramite innerHTML non rappresenta un rischio per la sicurezza significativo, come nemmeno una compromissione completa della sandbox mediante un attacco intelligente non sarebbe in grado di iniettare contenuti di script o plug-in pericolosi il contesto dell'estensione con autorizzazione elevata.

Questo meccanismo rende semplice la creazione di modelli, ma ovviamente non si limita alla creazione di modelli. Qualsiasi codice che non funziona subito dopo un rigido criterio di sicurezza del contenuto può essere limitato tramite sandbox; nel Infatti, spesso è utile creare una sandbox per i componenti delle estensioni che sarebbero eseguiti correttamente, limitare ogni parte del programma al minor numero di privilegi necessari affinché correttamente. La presentazione Scrittura di app web sicure ed estensioni di Chrome di Google I/O 2012 offre alcuni buoni esempi pratici di queste tecniche e vale la pena di 56 minuti nel tempo.