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 delle proprietà di sicurezza offerte da queste norme.

Tuttavia, siamo consapevoli che una serie di librerie utilizzano costrutti simili a eval() e eval, come new Function(), per l'ottimizzazione del rendimento e la facilità di espressione. Le librerie di modelli sono particolarmente inclini a questo stile di implementazione. Sebbene alcuni (come Angular.js) supportino il CSP out of the box, molti framework popolari non sono ancora stati aggiornati a un meccanismo compatibile con il mondo senza eval delle estensioni. La rimozione del supporto di questa funzionalità si è quindi dimostrata più problematica 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 tutto 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 codice senza accedere ai dati o alle API di alto valore dell'estensione. Nessun dato, nessuna API, nessun problema.

Per farlo, elenchiamo file HTML specifici all'interno del pacchetto dell'estensione come in 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 sicurezza il codice basato su eval nel flusso di lavoro della nostra estensione.

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 avesse bisogno di un'ulteriore spiegazione, esaminiamo insieme questo 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 dimenticarlo, quindi verifica che il file in sandbox sia elencato nel file manifest. In questo esempio, stiamo eseguendo la sandbox del file astutamente chiamato "sandbox.html". La voce manifest ha il seguente aspetto:

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

Carica il file con sandbox

Per fare qualcosa di interessante con il file in sandbox, dobbiamo caricarlo in un contesto in cui possa essere indirizzato 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 trovando il iframe nella pagina ed eseguendo il metodo postMessage sul relativo contentWindow. Il messaggio è un oggetto contenente due proprietà: context e command. Analizzeremo entrambi tra poco.

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 su MDN. È abbastanza completa e vale la pena leggerla. In particolare, tieni presente che i dati possono essere trasmessi solo se sono serializzabili. Le funzioni, ad esempio, non lo sono.

Fare 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 va a buon fine. 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

Renderemo disponibile questo modello impostando un ascoltatore di messaggi che accetti i 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>

Nella pagina Event, riceveremo questo messaggio e faremo qualcosa di interessante con i html dati che ci sono stati passati. In questo caso, lo ripeteremo tramite una notifica desktop, ma è del tutto possibile utilizzare questo codice HTML in 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 semplifica la creazione di modelli, ma non è limitato a questo. Qualsiasi codice che non funziona immediatamente in base a norme di sicurezza dei contenuti rigorose può essere sottoposto a sandbox. In effetti, spesso è utile eseguire la sandbox dei componenti delle estensioni che verrebbero eseguiti correttamente per limitare ogni componente del programma al più piccolo insieme di privilegi necessari per l'esecuzione corretta. La presentazione Scrivere app web e estensioni di Chrome sicure del Google I/O 2012 fornisce alcuni buoni esempi di queste tecniche in azione e vale 56 minuti del tuo tempo.