Jouez en toute sécurité avec des iFrames en bac à sable

Créer une expérience enrichie sur le Web actuel implique presque inévitablement des composants de représentation vectorielle continue et du contenu sur lesquels vous n'avez aucun contrôle réel. Les widgets tiers peuvent stimuler l'engagement et jouer un rôle essentiel dans l'expérience utilisateur, et le contenu généré par l'utilisateur est parfois encore plus important que le contenu natif d'un site. Abstention de l’une ou l’autre n’est pas vraiment une option, mais augmentent le risque que quelque chose de incorrectTM se produise sur votre site. Chaque que vous intégrez (chaque annonce, chaque widget de réseau social) vecteur d'attaque pour ceux dont l'intention est malveillante:

Content Security Policy (CSP) peut atténuer les risques associés à ces deux types de contenus la possibilité d'ajouter à la liste blanche des sources de script et d'autres contenus. Il s'agit d'une étape importante dans la bonne direction, mais il est important de noter que la protection offerte par la plupart des directives CSP est binaire: la ressource est autorisée ou non. Parfois, il serait utile de dire « Je ne suis pas je fait confiance à cette source de contenu, mais c'est très joli ! Intégrer s'il te plaît, mais ne le laisse pas endommager mon site. »

moindre privilège

Concrètement, nous cherchons un moyen d'accorder aux contenus n'intégrer que le niveau de capacité minimum nécessaire pour accomplir son travail. Si un widget n'a pas besoin d'ouvrir une nouvelle fenêtre, ce qui supprime l'accès à window.open blessée. Si Flash n'est pas nécessaire, la désactivation de la prise en charge des plug-ins problème. Si nous suivons le principe du moins possible, nous assurons une sécurité optimale. le droit d'accès, et de bloquer chaque fonctionnalité qui n'est pas directement pertinente par rapport à celle que nous aimerions utiliser. Résultat : nous n'avons plus besoin de croire aveuglément qu'un élément du contenu intégré ne bénéficiera pas des privilèges qu'il ne devrait pas utiliser. Il n'aura tout simplement pas accès à la fonctionnalité en premier lieu.

Les éléments iframe constituent la première étape d'élaboration d'un framework adapté à ce type de solution. Le chargement d'un composant non approuvé dans une iframe permet de mesurer la séparation entre votre application et le contenu à charger. Contenu encadré n'aura pas accès au DOM de votre page ni aux données que vous avez stockées localement. pouvoir dessiner à des positions arbitraires sur la page ; sa portée est limitée contour du cadre. Cependant, la séparation n'est pas vraiment solide. La page contenue propose tout de même un certain nombre d'options en cas de comportement agaçant ou malveillant: la lecture automatique les vidéos, les plug-ins et les pop-ups sont la partie visible de l'iceberg.

Attribut sandbox de l'élément iframe nous donne exactement ce dont nous avons besoin pour resserrer les restrictions sur le contenu encadré. Nous pouvons demander au navigateur de charger le contenu d'un frame spécifique avec un accès en n'autorisant que le sous-ensemble des capacités nécessaires pour faire quoi que ce soit le travail doit faire.

Faire un petit test, mais vérifier

"Tweet" de Twitter est un excellent exemple de fonctionnalité pouvant être de façon sécurisée dans votre site via un bac à sable. Twitter vous permet d'intégrer via un iFrame par le code suivant:

<iframe src="https://platform.twitter.com/widgets/tweet_button.html"
        style="border: 0; width:130px; height:20px;"></iframe>

Pour déterminer les éléments à verrouiller, examinons attentivement les capacités requis par le bouton. Le code HTML chargé dans le frame exécute un peu de à partir des serveurs Twitter et génère une fenêtre pop-up contenant l'interface de tweeting. Cette interface a besoin d'accéder au compte Twitter des cookies pour associer le tweet au compte approprié et doit pouvoir pour envoyer le formulaire pour tweeter. C'est à peu près tout ; le cadre n'a pas besoin de de plug-ins, il n'a pas besoin de naviguer dans la fenêtre de premier niveau ni le nombre d'autres bits de fonctionnalité. Puisqu'il n'a pas besoin de ces privilèges, supprimons-les en bac à sable au contenu du frame.

Le bac à sable fonctionne sur la base d'une liste blanche. Nous commençons par supprimer les autorisations possibles, puis réactivez les fonctionnalités individuelles en ajoutant des indicateurs spécifiques à la configuration du bac à sable. Pour le widget Twitter, nous avons ont décidé d'activer JavaScript, les pop-ups, l'envoi de formulaires et l'utilisation les cookies. Pour ce faire, ajoutez un attribut sandbox à iframe avec le paramètre la valeur suivante:

<iframe sandbox="allow-same-origin allow-scripts allow-popups allow-forms"
    src="https://platform.twitter.com/widgets/tweet_button.html"
    style="border: 0; width:130px; height:20px;"></iframe>

Et voilà ! Nous avons fourni au cadre toutes les capacités dont il a besoin, et le navigateur lui refusera l'accès aux privilèges que nous n'avons pas l'accordez explicitement via la valeur de l'attribut sandbox.

Contrôle précis des fonctionnalités

Dans l'exemple ci-dessus, nous avons vu quelques-uns des indicateurs de bac à sable possibles. explorer les rouages internes de l'attribut un peu plus en détail.

Dans le cas d'un iFrame avec un attribut de bac à sable vide, le document encadré sera entièrement en bac à sable, auquel cas il sera soumis aux restrictions suivantes:

  • JavaScript ne s'exécutera pas dans le document encadré. Cela inclut non seulement JavaScript explicitement chargé via des tags de script, mais également des gestionnaires d'événements intégrés et les URL javascript:. Cela signifie également que le contenu contenu dans des tags noscript s'affiche exactement comme si l'utilisateur avait désactivé le script lui-même.
  • Le document encadré est chargé dans une origine unique, ce qui signifie que tous les vérifications de l'origine commune échouent. des origines uniques ne correspondent jamais à d'autres origines, même eux-mêmes. Cela signifie, entre autres, que le document ne comporte aucun l'accès aux données stockées dans les cookies d'une origine quelconque ou tout autre mécanisme de stockage ; (stockage DOM, base de données indexée, etc.).
  • Le document encadré ne peut pas créer de fenêtres ni de boîtes de dialogue (via window.open ou target="_blank", par exemple).
  • Impossible d'envoyer des formulaires.
  • Les plug-ins ne se chargent pas.
  • Le document encadré ne peut naviguer que dans lui-même, et non dans son parent de premier niveau. Si vous définissez window.top.location, une exception est générée, et un clic sur le lien avec target="_top" n'aura aucun effet.
  • Fonctionnalités qui se déclenchent automatiquement (éléments de formulaire sélectionnés automatiquement, lecture automatique) vidéos, etc.) sont bloquées.
  • Impossible d'activer le verrouillage du pointeur.
  • L'attribut seamless est ignoré dans l'élément iframes du document encadré.

C'est plutôt draconien : un document est chargé dans un iframe entièrement exécuté en bac à sable. présente en effet très peu de risques. Bien sûr, elle ne peut pas non plus être très utile: vous peut s'en sortir avec un bac à sable complet pour du contenu statique, mais la plupart vous devrez parfois vous détendre un peu.

À l'exception des plug-ins, chacune de ces restrictions peut être levée en ajoutant un indicateur à la valeur de l'attribut sandbox. Les documents en bac à sable ne peuvent jamais exécuter des plug-ins, car les plug-ins sont du code natif sans bac à sable, mais tout le reste reste correct. jeu:

  • allow-forms permet l'envoi de formulaires.
  • allow-popups autorise les pop-ups (c'est un choc !).
  • allow-pointer-lock permet (surprise !) de verrouiller le pointeur.
  • allow-same-origin permet au document de conserver son origine. pages chargées de https://example.com/ conservera l'accès aux données de cette origine.
  • allow-scripts permet l'exécution JavaScript et permet également aux fonctionnalités de se déclenchent automatiquement (car leur implémentation est simple via JavaScript).
  • allow-top-navigation permet au document de sortir du cadre en en naviguant dans la fenêtre racine.

En gardant cela à l'esprit, nous pouvons évaluer exactement pourquoi nous avons obtenu ensemble d'indicateurs de bac à sable dans l'exemple Twitter ci-dessus:

  • allow-scripts est obligatoire, car la page chargée dans le frame exécute une du code JavaScript pour gérer les interactions des utilisateurs.
  • allow-popups est obligatoire, car le bouton fait apparaître un formulaire de tweet dans une nouvelle fenêtre.
  • Le champ allow-forms est obligatoire, car le formulaire pour tweeter doit pouvoir être envoyé.
  • allow-same-origin est nécessaire, car les cookies de twitter.com le seraient sinon être inaccessibles et l'utilisateur n'a pas pu se connecter pour publier le formulaire.

Il est important de noter que les indicateurs de bac à sable appliqués à un frame appliquer à toutes les fenêtres ou cadres créés dans le bac à sable. Cela signifie que nous avons d'ajouter allow-forms au bac à sable du frame, même si le formulaire n'existe dans la fenêtre que le cadre apparaît.

Une fois l'attribut sandbox en place, le widget n'obtient que les autorisations dont il nécessaires, et des fonctionnalités telles que les plug-ins, la navigation supérieure et le verrouillage du pointeur restent bloqués. Nous avons réduit le risque d'intégration du widget, sans aucun effet négatif. Tout le monde y gagne.

Séparation des privilèges

Ils exécutent des contenus tiers dans un système de bac à sable afin d'exécuter leur code non approuvé dans un un environnement à faible privilège est de toute évidence bénéfique. Mais qu'en est-il votre propre code ? Tu as confiance en soi, n'est-ce pas ? Pourquoi se soucier du bac à sable ?

Je changerais cette question: si votre code n'a pas besoin de plug-ins, pourquoi en donner l'accès aux plug-ins ? Au mieux, c'est un privilège que vous n'utilisez jamais, au pire, vecteur potentiel pour les attaquants d’avoir un pied dans la porte. Le code de chacun a de bugs. De plus, pratiquement toutes les applications sont vulnérables, d'une certaine façon à l'exploitation. ou une autre. Avec le bac à sable de votre propre code, même si un attaquant réussit perturbe votre application, celui-ci ne dispose pas d'un accès complet l'origine de l'application ; il ne pourra faire que ce que l'application pourrait faire. C'était mauvais, mais pas aussi mauvais qu'il pourrait l'être.

Vous pouvez réduire encore davantage le risque en divisant votre application en éléments logiques et en bac à sable chaque élément avec le minimum de privilèges possible. Cette technique est très courante dans le code natif: Chrome, par exemple, se brise tout seul. dans un processus de navigateur à privilèges élevés ayant accès au disque dur local et peuvent établir des connexions réseau, ainsi que de nombreux processus de moteur de rendu à faible privilège qui faire le gros du travail en analysant du contenu non fiable. Les moteurs de rendu n'ont pas besoin de toucher le disque, le navigateur s'occupe de leur donner toutes les informations dont ils ont besoin pour pour afficher une page. Même si un pirate informatique intelligent trouve un moyen de corrompre un moteur de rendu, n'est pas allé très loin, car le moteur de rendu ne peut pas générer beaucoup d'intérêt tout seul: tous les accès à privilèges élevés doivent être acheminés via le processus du navigateur. Les attaquants devront trouver plusieurs trous dans différentes parties du système afin de causer des dégâts, ce qui réduit considérablement le risque de pwn du produit.

Mise en bac à sable sécurisée de eval()

Grâce au système de bac à sable et L'API postMessage, la la réussite de ce modèle est assez simple à appliquer au Web. Des morceaux de votre application peut résider dans des iframe en bac à sable et le document parent peut pour établir la communication entre eux en publiant des messages et en écoutant des réponses. Ce type de structure garantit que les exploits l'application ne doit pas causer de dommages. Cela a aussi l'avantage de vous forcer créer des points d'intégration clairs, de sorte que vous sachiez exactement où vous devez être veillez à valider les entrées et les sorties. Prenons l'exemple d'un jouet, juste pour voir comment cela pourrait fonctionner.

Evalbox est une application très intéressante qui prend une chaîne et l'évalue en tant que JavaScript. Ouah, n’est-ce pas ? Juste ce que que vous attendiez depuis toutes ces longues années. C'est un jeu d'enfant application. En effet, autoriser l'exécution de JavaScript arbitraire implique et toutes les données qu'une origine offre sont prêtes à être récupérées. Nous allons atténuer le risque de Bad ThingsTM se produisant en s'assurant que le code est exécuté dans un bac à sable ce qui le rend beaucoup plus sûr. Nous allons parcourir le code à l'envers, en commençant par le contenu du cadre:

<!-- frame.html -->
<!DOCTYPE html>
<html>
    <head>
    <title>Evalbox's Frame</title>
    <script>
        window.addEventListener('message', function (e) {
        var mainWindow = e.source;
        var result = '';
        try {
            result = eval(e.data);
        } catch (e) {
            result = 'eval() threw an exception.';
        }
        mainWindow.postMessage(result, event.origin);
        });
    </script>
    </head>
</html>

À l'intérieur du cadre, nous avons un document minimal qui écoute simplement les messages de son parent en vous connectant à l'événement message de l'objet window. Chaque fois que le parent exécute postMessage sur le contenu de l'iFrame, cet événement se déclenchera, ce qui nous donnera accès à la chaîne exécuter.

Dans le gestionnaire, nous récupérons l'attribut source de l'événement, qui est le parent. fenêtre. Nous l'utiliserons pour renvoyer le résultat de notre dur labeur une fois que nous aurons terminé. Ensuite, nous ferons le plus gros du travail, en transmettant les données qui nous ont été données eval() Cet appel a été encapsulé dans un bloc try, car il s'agit d'opérations interdites dans un iframe en bac à sable génère fréquemment des exceptions DOM. nous allons attraper et signalez plutôt un message d'erreur. Enfin, nous publions le résultat à la fenêtre parent. C’est quelque chose assez simple.

Le parent est tout aussi simple. Nous allons créer une petite UI avec un textarea. pour le code et un button pour l'exécution. Nous allons extraire frame.html via un iframe en bac à sable, autorisant uniquement l'exécution de script:

<textarea id='code'></textarea>
<button id='safe'>eval() in a sandboxed frame.</button>
<iframe sandbox='allow-scripts'
        id='sandboxed'
        src='frame.html'></iframe>

Maintenant, nous allons connecter les choses pour l'exécution. Tout d'abord, nous écouterons les réponses de les iframe et les alert() à nos utilisateurs. Il s'agit vraisemblablement d'une véritable application ferait quelque chose de moins agaçant:

window.addEventListener('message',
    function (e) {
        // Sandboxed iframes which lack the 'allow-same-origin'
        // header have "null" rather than a valid origin. This means you still
        // have to be careful about accepting data via the messaging API you
        // create. Check that source, and validate those inputs!
        var frame = document.getElementById('sandboxed');
        if (e.origin === "null" &amp;&amp; e.source === frame.contentWindow)
        alert('Result: ' + e.data);
    });

Nous allons maintenant connecter un gestionnaire d'événements aux clics sur button. Lorsque l'utilisateur nous récupérons le contenu actuel de l'élément textarea et nous le transmettons frame pour l'exécution:

function evaluate() {
    var frame = document.getElementById('sandboxed');
    var code = document.getElementById('code').value;
    // Note that we're sending the message to "*", rather than some specific
    // origin. Sandboxed iframes which lack the 'allow-same-origin' header
    // don't have an origin which you can target: you'll have to send to any
    // origin, which might alow some esoteric attacks. Validate your output!
    frame.contentWindow.postMessage(code, '*');
}

document.getElementById('safe').addEventListener('click', evaluate);

Facile, n'est-ce pas ? Nous avons créé une API d'évaluation très simple, et nous pouvons être sûrs le code évalué n'a pas accès à des informations sensibles telles que des cookies ou DOM. De même, le code évalué ne peut pas charger de plug-ins, afficher de nouvelles fenêtres, ou toute autre activité agaçante ou malveillante.

Vous pouvez faire de même pour votre propre code en décomposant les applications monolithiques en composants à application unique. Chacun d'eux peut être encapsulé dans une API de messagerie simple, comme ce que nous avons écrit ci-dessus. La fenêtre parent à privilèges élevés peut faire office contrôleur et coordinateur, en envoyant des messages dans des modules spécifiques qui ont chacun le moins de droits possible pour faire leur travail, en écoutant les résultats et en veillant à ce que chaque module soit alimenté uniquement avec les informations dont il a besoin.

Notez toutefois que vous devez faire preuve d'une grande prudence lorsque vous traitez du contenu encadré ayant la même origine que le parent. Si une page sur https://example.com/ encadre une autre page de la même origine avec un bac à sable. qui inclut les options allow-same-origin et allow-scripts, puis la page encadrée peut atteindre le parent et supprimer l'attribut "sandbox" entièrement.

Jouez dans votre bac à sable

Le système de bac à sable est désormais disponible dans différents navigateurs: Firefox 17+, Internet Explorer 10+ et Google Chrome (caniuse, bien sûr, dispose d'une version tableau d'assistance). Application de sandbox à iframes que vous incluez vous permet d'accorder certains privilèges au du contenu qu'ils affichent, uniquement les droits nécessaires de votre contenu pour qu'il fonctionne correctement. Cela vous donne la possibilité de réduire le risque associées à l'inclusion de contenu tiers, en plus de ce qui est déjà possible avec Content Security Règlement.

De plus, le bac à sable est une technique efficace pour réduire le risque attaquant sera en mesure d’exploiter les trous dans votre propre code. En séparant un une application monolithique en un ensemble de services en bac à sable, chacun responsable petit fragment de fonctionnalité autonome, les attaquants seront obligés de ne pas ne compromettre que des frames spécifiques contenu, mais aussi leur contrôleur. Il s'agit d'un une tâche beaucoup plus difficile, d'autant plus que le contrôleur peut être considérablement réduit dans le champ d'application. Vous pouvez consacrer vos efforts liés à la sécurité à ce code si vous demandez au navigateur de vous aider pour le reste.

Cela ne veut pas dire que le bac à sable est une solution complète au problème de la sécurité sur Internet. Il offre une défense en profondeur et, à moins que vous n'ayez de contrôler l'expérience utilisateur vous ne pouvez pas encore compter sur la prise en charge des navigateurs vos utilisateurs (si vous contrôlez vos utilisateurs, dans un environnement d'entreprise, par exemple, hourrah !). Un jour... Mais pour l'instant, le bac à sable est une autre couche une protection renforcée pour renforcer vos défenses, mais il ne s'agit pas d'une défense complète, vous pouvez vous fier uniquement. Pourtant, les couches sont excellentes. Je vous suggère d'utiliser 1.

Documentation complémentaire

  • "Séparation des privilèges dans les applications HTML5" est un article intéressant qui traite de la conception d'un petit cadre, et son application à trois applications HTML5.

  • Le système de bac à sable est encore plus flexible lorsqu'il est associé à deux autres nouveaux cadres iFrame. attributs: srcdoc, et seamless. Le premier vous permet de remplir un cadre avec du contenu sans la surcharge de une requête HTTP, et cette dernière permet au style de circuler dans le contenu encadré. La prise en charge des navigateurs est assez mauvaise pour le moment (Chrome et WebKit nuisibles). mais ce sera une combinaison intéressante à l'avenir. Vous pourriez, Par exemple, vous pouvez ajouter des commentaires au bac à sable sur un article via le code suivant:

        <iframe sandbox seamless
                srcdoc="<p>This is a user's comment!
                           It can't execute script!
                           Hooray for safety!</p>"></iframe>