Utiliser l'évaluation dans les extensions Chrome

Le système d'extension de Chrome applique une Content Security Policy (CSP) par défaut assez stricte. Les restrictions imposées par nos règles sont simples: le script doit être déplacé hors ligne dans les fichiers JavaScript, les gestionnaires d'événements intégrés doivent être convertis pour utiliser addEventListener, et eval() est est désactivé. Les applications Chrome sont soumises à des règles encore plus strictes, et nous sommes très satisfaits des propriétés de sécurité qu'elles offrent.

Nous reconnaissons toutefois que de nombreuses bibliothèques utilisent des constructions semblables à eval() et eval, comme new Function(), pour optimiser les performances et faciliter l'expression. Les bibliothèques de modèles sont particulièrement sujettes à ce style d'implémentation. Certains (comme Angular.js) sont compatibles avec CSP. standard, de nombreux frameworks populaires n'ont pas encore adopté de mécanisme compatible avec "Extensions" dans un monde sans eval. La suppression de la prise en charge de cette fonctionnalité s'est donc révélée plus problématique que prévu pour les développeurs.

Ce document présente le bac à sable comme un mécanisme sécurisé permettant d'inclure ces bibliothèques dans vos projets sans compromettre la sécurité. Par souci de concision, nous utiliserons le terme extensions tout au long de la présentation, mais le concept s'applique également aux applications.

Pourquoi utiliser le bac à sable ?

eval est dangereux dans une extension, car le code qu'elle exécute a accès à tout ce qui se trouve dans le l'environnement à autorisations élevées de l'extension. De nombreuses API chrome.* puissantes sont disponibles et peuvent avoir un impact important sur la sécurité et la confidentialité d'un utilisateur. L'exfiltration de données simple n'est que le moindre de nos soucis. La solution proposée est un bac à sable dans lequel eval peut exécuter du code sans avoir accès aux données de l'extension ni aux API de valeur de l'extension. Pas de données, pas d'API, pas de problème.

Pour ce faire, nous classons certains fichiers HTML dans le package d'extension comme étant "bac à sable". Chaque fois qu'une page en bac à sable est chargée, elle est déplacée vers une origine unique et l'accès aux API chrome.* lui est refusé. Si nous chargeons cette page en bac à sable dans notre extension via un iframe, nous pouvons transmettre des messages, le laisser agir en fonction de ces messages et attendre qu'il nous renvoie résultat. Ce mécanisme de messagerie simple nous donne tout ce dont nous avons besoin pour inclure de façon sécurisée des eval dans le workflow de l'extension.

Créer et utiliser un bac à sable

Si vous souhaitez vous plonger directement dans le code, utilisez l'exemple d'extension de bac à sable et prenez désactivée. Il s'agit d'un exemple fonctionnel d'une petite API de messagerie basée sur le Handlebars. de création de modèles. Vous devriez obtenir tout ce dont vous avez besoin pour démarrer. Pour ceux d'entre vous qui un peu plus d'explications, examinons ensemble cet exemple ici.

Répertorier les fichiers dans le fichier manifeste

Chaque fichier qui doit être exécuté dans un bac à sable doit être listé dans le fichier manifeste de l'extension en ajoutant une propriété sandbox. Il s'agit d'une étape essentielle, mais facile à oublier. Veuillez donc vérifier que votre fichier dans un bac à sable est listé dans le fichier manifeste. Dans cet exemple, nous mettons en bac à sable le fichier astucieusement nommé "sandbox.html". L'entrée du fichier manifeste se présente comme suit:

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

Charger le fichier dans un bac à sable

Pour effectuer des opérations intéressantes avec le fichier en bac à sable, nous devons le charger dans un contexte où il peut être adressé par le code de l'extension. Ici, sandbox.html a été chargé dans la page d'événement (eventpage.html) de l'extension via un iframe. eventpage.js contient du code qui envoie un message dans le bac à sable chaque fois que l'action du navigateur est cliquée en recherchant le iframe sur la page et en exécutant la méthode postMessage sur son contentWindow. Le message est un objet contenant deux propriétés : context et command. Nous allons examiner ces deux options dans un instant.

chrome.browserAction.onClicked.addListener(function() {
 var iframe = document.getElementById('theFrame');
 var message = {
   command: 'render',
   context: {thing: 'world'}
 };
 iframe.contentWindow.postMessage(message, '*');
});
Pour en savoir plus sur l'API postMessage, consultez la documentation postMessage sur MDN. Il est assez complet et vaut la peine d'être lu. Notez en particulier que les données ne peuvent être transmises dans les deux sens que si elles sont sérialisables. Les fonctions, par exemple, ne le sont pas.

Faire quelque chose de dangereux

Lors du chargement de sandbox.html, il charge la bibliothèque Handlebars, puis crée et compile une ligne de la même manière que Handlebars suggère:

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

Cela n'est pas une fatalité ! Même si Handlebars.compile finit par utiliser new Function, tout fonctionne exactement comme prévu, et nous obtenons un modèle compilé dans templates['hello'].

Transmettre le résultat

Nous allons mettre ce modèle à disposition en configurant un écouteur de messages qui accepte les commandes sur la page "Événement". Nous utiliserons l'command transmise pour déterminer ce qui doit être fait (vous pouvez imaginer faire plus que simplement effectuer un rendu, peut-être créer des modèles ? (peut-être en les gérant d'une manière ou d'une autre ?), et le context sera transmis directement au modèle pour l'affichage. Code HTML affiché sera renvoyé à la page d'événement afin que l'extension puisse s'en servir ultérieurement:

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

De retour sur la page de l'événement, nous recevons ce message et effectuons une action intéressante avec les données html qui nous ont été transmises. Dans ce cas, nous allons simplement l'afficher via une notification pour ordinateur, mais il est tout à fait possible d'utiliser ce code HTML en toute sécurité dans l'UI de l'extension. L'insertion via innerHTML ne présente pas de risque de sécurité important, car même une compromission complète du code en bac à sable par une attaque intelligente ne pourrait pas injecter de contenu de script ou de plug-in dangereux dans le contexte d'extension à autorisation élevée.

Ce mécanisme facilite la création de modèles, mais ne se limite évidemment pas à la création de modèles. N'importe quelle valeur tout code qui ne fonctionne pas directement dans le cadre d'une Content Security Policy stricte peut être exécuté en bac à sable. dans Il est souvent utile de mettre en bac à sable les composants de vos extensions qui s'exécutent correctement de restreindre chaque élément de votre programme au plus petit ensemble de droits lui permettant exécuter correctement. La présentation Écrire des applications Web sécurisées et des extensions Chrome de Google I/O 2012 donne de bons exemples de ces techniques en action et vous fera gagner 56 minutes.