Evaluatie gebruiken in Chrome-extensies

Het extensiesysteem van Chrome dwingt een vrij strikt standaard Content Security Policy (CSP) af. De beleidsbeperkingen zijn duidelijk: het script moet buiten de regels worden verplaatst naar afzonderlijke JavaScript-bestanden, inline gebeurtenishandlers moeten worden geconverteerd om addEventListener te gebruiken, en eval() is uitgeschakeld. Chrome-apps hebben een nog strenger beleid en we zijn best tevreden met de beveiligingseigenschappen die dit beleid biedt.

We erkennen echter dat een verscheidenheid aan bibliotheken eval() en eval -achtige constructies zoals new Function() gebruiken voor prestatie-optimalisatie en expressiegemak. Sjabloonbibliotheken zijn bijzonder gevoelig voor deze implementatiestijl. Hoewel sommige (zoals Angular.js ) CSP kant-en-klaar ondersteunen, zijn veel populaire raamwerken nog niet bijgewerkt naar een mechanisme dat compatibel is met de eval -less-wereld van extensies. Het verwijderen van de ondersteuning voor die functionaliteit is voor ontwikkelaars daarom problematischer gebleken dan verwacht .

Dit document introduceert sandboxing als een veilig mechanisme om deze bibliotheken in uw projecten op te nemen zonder concessies te doen aan de beveiliging. Kortheidshalve gebruiken we overal de term extensies , maar het concept is ook van toepassing op toepassingen.

Waarom sandboxen?

eval is gevaarlijk binnen een extensie omdat de code die deze uitvoert toegang heeft tot alles in de omgeving met hoge machtigingen van de extensie. Er zijn een hele reeks krachtige chrome.* API's beschikbaar die ernstige gevolgen kunnen hebben voor de veiligheid en privacy van een gebruiker; eenvoudige data-exfiltratie is het minste van onze zorgen. De aangeboden oplossing is een sandbox waarin eval code kan uitvoeren zonder toegang tot de gegevens van de extensie of de hoogwaardige API's van de extensie. Geen data, geen API’s, geen probleem.

We bereiken dit door specifieke HTML-bestanden in het extensiepakket te vermelden als sandbox. Telkens wanneer een pagina in een sandbox wordt geladen, wordt deze verplaatst naar een unieke oorsprong en wordt de toegang tot chrome.* API's geweigerd. Als we deze in een sandbox geplaatste pagina via een iframe in onze extensie laden, kunnen we er berichten aan doorgeven, hem op de een of andere manier op die berichten laten reageren en wachten tot hij ons een resultaat terugstuurt. Dit eenvoudige berichtenmechanisme biedt ons alles wat we nodig hebben om op veilige wijze eval code op te nemen in de workflow van onze extensie.

Een sandbox maken en gebruiken.

Als je direct in de code wilt duiken, pak dan de sandboxing-voorbeeldextensie en start . Het is een werkend voorbeeld van een kleine berichten-API die bovenop de Handlebars- sjablonenbibliotheek is gebouwd en die je alles zou moeten geven wat je nodig hebt om aan de slag te gaan. Voor degenen onder u die wat meer uitleg willen, laten we dat voorbeeld hier samen doornemen.

Lijst bestanden in manifest

Elk bestand dat in een sandbox moet worden uitgevoerd, moet in het extensiemanifest worden vermeld door een sandbox eigenschap toe te voegen. Dit is een cruciale stap en u vergeet deze gemakkelijk. Controleer dus nogmaals of uw sandbox-bestand in het manifest wordt vermeld. In dit voorbeeld sandboxen we het bestand met de slimme naam "sandbox.html". De manifestinvoer ziet er als volgt uit:

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

Laad het sandboxbestand

Om iets interessants te doen met het sandbox-bestand, moeten we het in een context laden waarin het kan worden aangepakt door de code van de extensie. Hier is sandbox.html via een iframe in de evenementenpagina van de extensie ( eventpage.html ) geladen. eventpage.js bevat code die een bericht naar de sandbox stuurt wanneer op de browseractie wordt geklikt door het iframe op de pagina te vinden en de methode postMessage uit te voeren op de contentWindow ervan. Het bericht is een object dat twee eigenschappen bevat: context en command . We zullen er zo meteen op ingaan.

chrome.browserAction.onClicked.addListener(function() {
 var iframe = document.getElementById('theFrame');
 var message = {
   command: 'render',
   context: {thing: 'world'}
 };
 iframe.contentWindow.postMessage(message, '*');
});
Voor algemene informatie over de postMessage API kunt u de postMessage documentatie op MDN raadplegen. Het is behoorlijk compleet en de moeite waard om te lezen. Houd er vooral rekening mee dat gegevens alleen heen en weer kunnen worden doorgegeven als deze serialiseerbaar zijn. Functies zijn dat bijvoorbeeld niet.

Doe iets gevaarlijks

Wanneer sandbox.html wordt geladen, wordt de Handlebars-bibliotheek geladen en wordt een inline-sjabloon gemaakt en gecompileerd op de manier zoals Handlebars suggereert:

<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>

Dit mislukt niet! Hoewel Handlebars.compile uiteindelijk new Function gebruikt, werken de dingen precies zoals verwacht, en eindigen we met een gecompileerde sjabloon in templates['hello'] .

Geef het resultaat terug

We maken deze sjabloon beschikbaar voor gebruik door een berichtenlistener in te stellen die opdrachten van de gebeurtenispagina accepteert. We gebruiken het ingevoerde command om te bepalen wat er moet gebeuren (u kunt zich voorstellen dat u meer doet dan alleen maar renderen; misschien sjablonen maken? Misschien ze op de een of andere manier beheren?), en de context wordt rechtstreeks in de sjabloon doorgegeven voor weergave . De weergegeven HTML wordt teruggestuurd naar de evenementenpagina, zodat de extensie er later iets nuttigs mee kan doen:

<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>

Terug op de evenementenpagina ontvangen we dit bericht en doen we iets interessants met de html gegevens die ons zijn doorgegeven. In dit geval herhalen we het gewoon via een Desktop Notification , maar het is heel goed mogelijk om deze HTML veilig te gebruiken als onderdeel van de gebruikersinterface van de extensie. Het invoegen ervan via innerHTML vormt geen significant veiligheidsrisico, omdat zelfs een volledige compromittering van de sandbox-code door een slimme aanval niet in staat zou zijn om gevaarlijke script- of plug-ininhoud in de extensiecontext met hoge toestemming te injecteren.

Dit mechanisme maakt het maken van sjablonen eenvoudig, maar het is uiteraard niet beperkt tot het maken van sjablonen. Elke code die niet out-of-the-box werkt onder een strikt Content Security Policy kan in een sandbox worden geplaatst; in feite is het vaak nuttig om componenten van uw extensies in een sandbox te plaatsen die correct zouden werken, om elk onderdeel van uw programma te beperken tot de kleinste set rechten die nodig is om het correct uit te voeren. De presentatie Writing Secure Web Apps and Chrome Extensions van Google I/O 2012 geeft enkele goede voorbeelden van deze techniek in actie en is 56 minuten van uw tijd waard.