XSS

Les attaques XSS, ou Cross Site Scripting sont parmi les plus courantes sur le réseau. Elles consistent à faire s'exécuter un script, généralement JavaScript, par le navigateur. Imaginons pas exemple un blog qui autoriserait le HTML dans les commentaires sans filtrer les saisies. On pourrait ainsi insérer des scripts dans les commentaires. Ces scripts s'exécuteraient dans le navigateur de toute personne consultant le commentaire comme s'ils étaient des scripts légitimes de l'application. A partir de là, tout est possible, le script peut effectuer toutes les opérations autorisées à l'utilisateur. Si l'administrateur du blog consulte un commentaire piégé, le script pourra par exemple effacer les billets, modifier les droits d'accès au blog, etc. Le jeu pour les auteurs de ce type de script consiste à contourner les protections mises en place au niveau serveur pour réussir à insérer du code qui s'affichera et s'exécutera dans le navigateur des utilisateurs. Par exemple en cachant le code dans l'URL d'une image ou d'un lien. Ces attaques utilisent le fait que le navigateur fait confiance par défaut à tout le contenu en provenance du site Web.

Content Security Policy

Pour y remédier, CSP propose une approche basée sur le principe d'une liste blanche : le site liste explicitement les contenus légitimes. Si un site indique qu'il utilise CSP, le navigateur ne fera donc confiance qu'aux scripts contenus dans cette liste blanche et refusera d'exécuter les autres.

CSP a en fait une double fonction:

  • il essaie de prévenir l'exécution de code non désiré, et protège donc les utilisateurs;
  • lorsqu'il détecte une tentative de violation des politiques de sécurité, il en informe le site. Les responsables de celui-ci peuvent donc être rapidement prévenus de toute tentative d'attaque contre leur site, et prendre les mesures pour la bloquer;

Un site indique au navigateur qu'il implémente CSP via l'entête HTTP X-Content-Security-Policy. Celui-ci peut soit contenir la description de la politique, soit l'URI d'un document la décrivant.

Les politiques ne s'appliquent bien sûr qu'aux contenus chargés depuis un site distant, pas aux extensions ou aux scripts locaux de l'utilisateur (type bookmarklets).

Un prototype de CSP a d'abord été implémentée sous forme d'une extension, avant d'intégrer récemment le cœur de Firefox. Les politiques de sécurité seront donc disponibles dans une des prochaines versions du navigateur, la 3.6 ou la 3.7.

Comportement par défaut

CSP implémente un certain nombre de politiques par défaut, qui peuvent être débrayées. Sont donc interdits :

  • l'exécution de code placé directement dans la page, que ce soit au sein de balises script, dans les liens (<a href="javascript..." />) ou les gestionnaires d'évènements (onclick...)[1];
  • l'exécution de code à partir d'une chaîne de caractère, donc par exemple l'utilisation de la fonction eval;
  • l'utilisation d'URI de type data:...;
  • les binding XBL, hormis ceux provenant du chrome (je crois que Firefox est le seul navigateur à implémenter XBL, donc si vous ne comprenez pas ce que ça veut dire, ça n'est pas bien grave);

La politique de sécurité permet ensuite de définir, pour chaque type de contenu (script, images, etc), les URI à partir desquelles le navigateur est autorisé à télécharger le contenu.

Syntaxe

Une politique de sécurité se compose de directives, séparées par des point-virgules. Chaque directive comporte un nom et une liste de valeurs, séparées par des espaces. Les directives disponibles sont:

  • allow définit la politique par défaut. Elle doit toujours être définie;
  • options permet de définir... des options. Pour l'instant seules deux valeurs sont possibles : eval-script pour autoriser les fonction de type eval, et inline-script, pour permettre la présence de code directement dans la page;
  • font-src pour la source des fontes chargées via la propriété CSS font-src;
  • frame-src pour la source des frames et des iframes;
  • img-src pour la source des images et de la favicon;
  • media-src pour la source des éléments audio et video;
  • object-src pour la source des éléments applet, embed et object;
  • script-src pour la source des scripts
  • style-src pour la source des feuilles de style externes;
  • xhr-src pour les requêtes XMLHttpRequest;
  • frame-ancestors définit les sources autorisées à afficher le contenu à l'intérieur d'une frame. Cela permet donc d'interdire qu'une page soit insérée dans une autre à l'aide d'une iframe (une technique couramment utilisée dans les attaques par clickjacking);
  • policy-uri : URI d'un fichier contenant la description de la politique;
  • report-uri : URI où envoyer un rapport en cas de tentative de violation d'une règle

La source peut être soit self, soit none soit une URI. self signifie que ne sont autorisés que les contenus délivré par le même scheme, le même hôte et le même port. Les URI peuvent contenir des méta-caractères, par exemple *.mozilla.org.

Par défaut, les sites ne peuvent pas mélanger de contenu sécurisé et non sécurisé. Pour l'autoriser, il faudra le déclarer explicitement, par exemple:

X-Content-Security-Policy: allow http://*.clochix.net https://*.clochix.net

La description des politiques de sécurité et le comportement à adopter en cas de violation peuvent être décrits dans des documents externes, via policy-uri et report-uri, mais ceux-ci doivent avoir la même origine que le document auquel elles s'appliquent;

Enfin, la spécification précise de plus le comportement à adopter en présence de plusieurs plusieurs politiques.

Les rapports

Si le navigateur détecte qu'une page essaie de violer une des règles définies, il peut envoyer via une requête HTTP POST, un rapport à l'URI définie via report-uri. Ce rapport est un document XML.

Les rapports contiennent la requête avec tous ses entêtes, l'URI du contenu bloqué, la directive à l'origine du blocage, et l'ensemble des directives appliquées. Un rapport pourra ressembler à

<csp-report>
 <request>GET /index.html HTTP/1.1</request>
 <request-headers><![CDATA[
          Host: example.com
          User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0
          Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
          Accept-Language: en-us,en;q=0.5
          Accept-Encoding: gzip,deflate
          Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
          Keep-Alive: 300
          Connection: keep-alive
 ]]></request-headers>
 <blocked-uri>http://evil.com/some_image.png</blocked-uri>
 <violated-directive>img-src self</violated-directive>
 <original-policy>allow none; img-src *, allow self; img-src self</original-policy>
</csp-report>

Ce qui signifie qu'une page a essayé de charger une image depuis un site externe, alors que seules les images du site d'origine sont autorisées.

Pour faciliter la mise au point des directives, on pourra remplacer X-Content-Security-Policy par X-Content-Security-Policy-Report-Only. Dans ce cas le navigateur se contentera d'envoyer des rapport sans bloquer le contenu qui viole la directive.

Exemples

Autoriser l'exécution de code placé directement dans la page:

X-Content-Security-Policy: allow 'self'; script-src inline;

ou

X-Content-Security-Policy: allow 'self'; options inline-script;

Autoriser l'utilisation des fonctions qui exécutent du code à partir d'une chaîne de caractère:

X-Content-Security-Policy: allow 'self'; script-src eval;

ou

X-Content-Security-Policy: allow 'self'; options eval-script;

Autoriser les images à utiliser le scheme data: :

X-Content-Security-Policy: allow self; img-src data;

Autoriser les images de toute origine, les plugins de certains sites, les sites uniquement depuis le domaine trustedscripts.example.com et le reste du contenu uniquement depuis le serveur d'origine:

X-Content-Security-Policy: allow 'self'; img-src *; \
                           object-src media1.com media2.com *.cdn.com; \
                           script-src trustedscripts.example.com

N'autoriser que le contenu sécurité par SSL:

X-Content-Security-Policy: allow https://*:443

Pour aller plus loin

Outre le Content Security Policy trois autres projets sont en cours pour renforcer la sécurité de Firefox :

Origin vise à prévenir les attaques dites CSRF. Celles-ci consistent à obtenir du navigateur qu'il effectue une requête déclenchant une action. Par exemple si l'utilisateur est connecté à un webmail, on essaiera de lui faire afficher une image dont l'URL correspond à une action du webmail. Le navigateur, pour récupérer l'image, exécutera la requête et déclenchera l'action. Les CSRF ne fonctionnent que pour les actions accessibles via des requêtes HTTP GET$$pour celles nécessitant un HTTP POST, existent des attaques proches, le vol de clic (clickjacking), qui consistent à amener l'utilisateur à cliquer sur un bouton qui déclenchera le POST. Pour lutter contre les CSRF, Brandon Sterne et les équipes de Mozilla proposent que dans certains cas, le navigateur ajoute aux entêtes qu'il envoie au serveur l'origine de la requête (le site d'où elle provient), charge au serveur de déterminer s'il accepte de la traiter ou non. La proposition est en cours de soumission à l'IETF pour devenir un standard et un prototype est attendu pour la fin 2009.

Force-TLS propose aux sites d'ajouter un entête HTTP indiquant qu'ils ne doivent à l'avenir être contactés que via une connexion HTTPS. Un prototype est disponible sous la forme d'une extension.

Le "gros" morceau est bien sûr Electrolysis qui vise à utiliser des processus séparés pour l'affichage du navigateur, des pages et des plugins, afin de circonscrire au maximum les potentiels problèmes (plantages ou attaques).

En conclusion

La mise en œuvre de politiques de sécurité via le Content Security Policy ne sera pas forcément simple, car pour être efficace il faudra appliquer des règles strictes, et bon nombre d'applications existantes devront être modifiées pour en tirer partie. Mais à terme, cela ne peut que s'avérer payant, car cela fournit un garde-fou précieux contre bon-nombre d'attaques.

Il ne reste plus qu'à espérer que la spécification soit reprises par les autres fabriquants de navigateurs et devienne une norme.

Ajout du 07.10 : billet de Chris Blizzard sur HMO

Notes

[1] en terme d'accessibilité, il est cependant recommandé de préférer les onclick aux addEventListener pour exécuter du code, afin qu'un lecteur d'écran par exemple sache que le clic sur un élément déclenche une action. Mais ce n'est qu'un pis-aller. La vrai solution passe par l'utilisation de WAI-ARIA pour indiquer le rôle de l'élément