Date de publication du RFC : Décembre 2011
Auteur(s) du RFC : A. Barth (Google)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF websec
Première rédaction de cet article le 12 décembre 2011
Chaque jour, plusieurs failles de sécurité sont annoncées frappant
un site Web, ou, plus rarement, un
navigateur. Ces failles proviennent souvent de
l'exécution de code, par exemple Java ou
JavaScript, téléchargé depuis un serveur qui
n'était pas digne de confiance, et exécuté avec davantage de privilèges
qu'il n'aurait fallu. Une des armes principales utilisées par la
sécurité du Web, pour essayer d'endiguer la marée de ces attaques, est
le concept d'origine. L'idée est que toute
ressource téléchargée (notamment le code) a une origine, et que cette
ressource peut ensuite accéder uniquement à ce qui a la même origine
qu'elle. Ainsi, un code JavaScript téléchargé depuis
www.example.com
aura le droit d'accéder à l'arbre
DOM de www.example.com
, ou
à ses cookies, mais pas à
ceux de manager.example.net
. Ce concept d'origine
semble
simple mais il y a plein de subtileries qui le rendent en fait très
complexe et, surtout, il n'avait jamais été défini précisément. Ce que
tente de faire ce RFC.
Pour comprendre l'importance de l'origine,
revoyons un navigateur Web en action. Imaginons
qu'il n'impose aucune limite au code (Java,
JavaScript, Silverlight,
etc) qu'il exécute. Un méchant pourrait alors mettre du
code malveillant JavaScript sur son serveur, faire de la
publicité (par exemple via le spam) pour
http://www.evil.example/scarlet-johansson-pictures/
,
et attendre les visites. Le navigateur récupérerait
l'HTML, puis le JavaScript inclus, et celui-ci
pourrait faire des choses comme lancer une DoS
par connexions HTTP répétées vers
http://www.victim.example/
. C'est pour éviter ce
genre d'attaques que tous les navigateurs ont la notion d'origine. Le
code récupéré sur le site du méchant va avoir l'origine
www.evil.example
, et le navigateur l'empêchera de
faire des requêtes HTTP vers un autre serveur que
www.evil.example
. En gros, le principe
traditionnel de sécurité est « Le contenu récupéré depuis l'origine X
peut interagir librement avec les ressources ayant cette même
origine. Le reste est interdit. » (section 1 du RFC, pour un résumé).
Mais ce principe très simple à énoncer et à comprendre dissimule pas mal de pièges. Ce RFC va donc essayer de les mettre en évidence. À noter qu'il ne fait que fournir un cadre, ce n'est pas un protocole, et il n'y a donc pas besoin de modifier les navigateurs pour obéir à ce texte. Les détails concrets du principe d'origine vont en effet dépendre du protocole utilisé et chacun (HTML, WebSockets du RFC 6455, etc) va donc devoir décliner ce principe selon ses caractéristiques propres. Par exemple, pour le futur HTML5, voici la définition.
Donc, on attaque avec la section 3, qui expose ce qu'est ce
« principe de même origine » (Same Origin
Policy). D'abord, la
confiance (section 3.1) est exprimée sous forme
d'un URI. Lorsque le serveur
www.niceguy.example
sert une page
HTML
https://www.niceguy.example/foo/bar.html
qui contient :
<script src="https://example.com/library.js"/>
Il annonce sa confiance envers l'URI
https://example.com/library.js
. Le code
JavaScript téléchargé via cet URI sera exécuté avec les privilèges de
https://www.niceguy.example/foo/bar.html
, qui a choisi de lui faire confiance.
Cette idée de « confiance par URI » peut poser des problèmes dans
certains cas. Par exemple, si on utilise le
STARTTLS
du RFC 2817, le
fait d'utiliser TLS ou pas n'apparait pas dans
l'URI et la page HTML ne peut donc pas exiger TLS. Les URI de plan
https:
n'ont pas ce défaut, l'exigence de TLS y
est explicite.
Mais il y a une question plus sérieuse : dans
https://www.niceguy.example/foo/bar.html
, quelle
est l'origine, celle dont vont dépendre les
autorisations ultérieures (section 3.2) ? Est-ce tout l'URI ? Ou bien
seulement le FQDN
www.niceguy.example
? Réponse : aucune des
deux.
Utiliser tout l'URL comme origine manquerait de souplesse. Un code
venu de http://www.niceguy.example/foo
ne
pourrait pas interagir avec du contenu récupéré en
http://www.niceguy.example/bar
. Mais n'utiliser
que le nom de domaine n'est pas génial non plus. Car cela permettrait
à du code JavaScript récupéré par
http://www.niceguy.example:8080/
de modifier un
arbre DOM de
https://www.niceguy.example/
(notez que la
seconde est sécurisée avec HTTPS ; ne se servir
que du nom de domaine comme origine permettrait à du contenu non sûr
de manipuler du contenu sûr).
Et faut-il utiliser tout le FQDN ? Cela
empêche http://portal.niceguy.example/
de jouer
avec http://manager.niceguy.example/
alors que
les deux sites sont sous la même autorité, celle de
niceguy.example
. Mais le problème est que la
détermination de cette autorité est difficile. Contrairement à ce que
croient les débutants, elle n'est pas forcément dans les deux derniers
composants du nom de domaine. Par exemple,
jones.co.uk
et smith.co.uk
ne sont pas sous la même autorité. Et, même lorsque c'est le cas, une
Université ne souhaite pas forcément que
http://students.example.edu/
(consacré aux pages
personnelles des étudiants) aient des droits sur
http://admin.example.edu/
. Bien sûr, aucun
système n'est parfait. Avec le FQDN,
http://example.edu/students/~mark
aura des droits
sur http://example.edu/grades/admin
. Mais ce
modèle fondé sur le FQDN est maintenant trop ancien pour le changer
(rappelez-vous qu'il a évolué informellement, notre RFC 6454 ne fait que le documenter a posteriori).
Donc, pour résumer le point important de la section 3.2 : une
origine est un tuple {FQDN, plan, port}. Par exemple,
http://example.com/
,
http://example.com:80
et
http://example.com/toto.html
ont tous les trois la même origine,
le tuple {example.com
, http
,
80}. En revanche, http://example.com:81/
a une
autre origine (port différent), de même que
ftp://example.com
(plan différent), ou
http://www.example.com/
(nom différent).
Ce principe de même origine ne répond pas à tous les risques de
sécurité. Il faut également intégrer la notion d'autorité (tel que ce
terme est utilisé en sécurité, notez que le RFC 3986 utilise ce mot dans un autre sens). L'autorité est
que le navigateur donne des droits différents selon le type de document. Une
image n'a aucun droit, c'est du contenu passif (du moins pour les formats traditionnels : avec des formats comme SVG, l'analyse de sécurité est bien plus complexe). En revanche, un
document HTML a le droit de déclencher le
chargement d'autres ressources (styles CSS mais
surtout code actif, par exemple JavaScript). Normalement, l'autorité
dépend du type MIME indiqué
(image/png
pour une image,
text/html
pour l'HTML). Si ce type est déterminé
par un utilisateur, de nouvelles vulnérabilités peuvent
survenir (et certains navigateurs sont assez imprudents pour deviner
le type MIME en examinant le contenu, une mauvaise pratique, connue
sous le nom de content sniffing). Pire, si l'utilisateur peut insérer du contenu qu'il choisit
dans des documents ayant une forte autorité (pages HTML), de nouveaux
risques peuvent survenir. Par exemple, une attaque
XSS débute par l'insertion de contenu dans une
page HTML, contenu qui sera ensuite interprété par le navigateur, et
exécuté avec l'origine de la page Web (cf. section 8.3). Or, ce cas est très fréquent
(pensez à un moteur de recherche qui affiche la requête originale sans
précaution, alorss qu'elle était choisie par un utilisateur
extérieur). La protection contre les XSS est complexe mais le conseil
de base est : modifiez tout contenu venu de l'extérieur avant de
l'inclure dans un document ayant de l'autorité (comme une page HTML),
de façon à ce qu'il ne puisse pas être interprété par le
navigateur. Le plus simple pour cela est d'utiliser exclusivement un
système de gabarit pour fabriquer les pages. Il
se chargera alors des précautions comme de transformer
les < en <.
Une fois déterminée l'origine, il reste à décider des privilèges donnés aux objets de la même origine (section 3.4). Pour les objets récupérés, on l'a vu, la politique est typiquement de ne donner accès qu'aux objets de même origine que le code. Mais pour les accès à d'autres objets, tout est bien plus complexe et la sécurité rentre ici en conflit avec l'utilisabilité. Par exemple, une application stricte du principe de même origine aux accès aux ressources interdirait à une page Web de mener à une autre page, d'origine différente, alors que c'est évidemment une fonction essentielle du Web. Moins caricatural, le cas d'images ou autres contenus embarqués dans une page Web. Bien que cela pose des problèmes de sécurité (pensez aux Web bugs), aucun navigateur n'impose que les images aient la même origine que la page qui les charge... De même, aucun navigateur n'interdit complètement d'envoyer des données à une autre origine, même si c'est la base de nombreuses CSRF.
Voilà, l'essentiel des principes du RFC tient dans cette section
3. Le reste, ce sont les détails pratiques. La section 4 formalise la
notion d'origine en en donnant une définition rigoureuse. Elle couvre
des cas rigolos comme les URI de plan file:
(autrefois, les navigateurs considéraient tous les fichiers locaux
comme ayant la même origine, aujourd'hui, les plus paranoïaques font
de chaque fichier une origine différente), ou bien explicite le cas
des ports par défaut de certains plans (vous avez remarqué
l'équivalence entre les URI http://example.com/
,
http://example.com:80
, plus haut ?). La section 5
décrit d'autres pièges comme le cas des plans où n'apparaissent pas de
noms de machine comme par exemple les data:
du RFC 2397 (un cas amusant car
deux URI data:
complètement identiques, bit par
bit, sont quand même d'origine différente). Et la section 6 explique
la sérialisation d'un URI, à des fins de comparaison simples avec
d'autres URI, pour déterminer s'ils ont la même origine. Par exemple,
http://www.example.com:80/
et
http://www.example.com/toto
se sérialisent tous
les deux en http://www.example.com
. Pour les URI
de type {scheme, host,
port} (les plus fréquents), une simple
comparaison de chaînes de caractères nous dira ensuite s'ils ont la
même origine (ici, oui).
Si on revient au premier exemple JavaScript que j'ai donné, celui
où https://www.niceguy.example/foo/bar.html
charge https://example.com/library.js
, on voit
que l'origine n'est pas l'URI du script mais celle de la page qui le
charge. Or, on peut imaginer que le serveur
example.com
voudrait bien connaître cette
origine, par exemple pour appliquer des règles différentes. ou tout
simplement pour informer le script du contexte dans lequel il va s'exécuter. C'est le rôle de l'en-tête
HTTP Origin:
, normalisé
dans la section 7, et qui peut être ajouté aux requêtes. Ainsi, dans
l'exemple ci-dessus, la requête HTTP ressemblerait à :
[Connexion à exemple.com...] GET /library.js HTTP/1.1 Host: example.com Origin: https://www.niceguy.example ...
On voit que l'origine indiquée est la forme sérialisée comme indiqué
plus haut. Ce n'est donc pas l'équivalent de
Referer:
qui indique un URI complet.
Cet en-tête est désormais dans le registre des en-têtes (cf. section 9).
Comme tout ce RFC décrit un mécanisme de sécurité, il est prudent de bien lire la section 8, Security Considerations, qui prend de la hauteur et rappelle les limites de ce mécanisme. Historiquement, d'autres modèles que le « principe de même origine » ont été utilisés (à noter que le RFC ne fournit pas de référence), comme le taint tracking ou l'exfiltration prevention mais pas avec le même succès.
D'abord, la notion d'origine est une notion unique qui s'applique à des problèmes très différents et peut donc ne pas être optimisée pour tous les cas.
Ensuite, elle dépend du DNS (section 8.1)
puisque la plupart des plans d'URI utilisent la notion
d'host et que la politique de même origine considère que deux serveurs sont les mêmes s'ils ont le
même nom. Si l'attaquant contrôle le DNS, plus aucune sécurité n'est
possible (on croit parler au vrai www.example.com
et on
parle en fait à une autre machine).
À noter que le RFC prend bien soin de ne pas parler de la principale vulnérabilité de ce concept d'host : l'absence de notion générale d'identité d'une machine dans l'Internet (cette notion n'existe que pour certains protocoles spécifiques comme SSH). En l'absence d'une telle notion, des attaques sont possibles sans que l'attaquant ait eu à compromettre le DNS, comme par exemple le changement d'adresse IP.
Autre piège, le fait que le principe de même origine, ne soit pas
apparu tout de suite et que certaines techniques de sécurité du Web
utilisent une autre unité d'isolation que l'origine (section
8.2). Le Web n'a pas en effet de sécurité cohérente. C'est le cas des
cookies (RFC 6265), qui se servent d'un autre concept, le « domaine
enregistré ». Le navigateur essaie de trouver, lorsqu'il récupère un
cookie, à quel « domaine enregistré » il appartient
(l'idée étant que sales.example.com
et
it.example.com
appartiennent au même domaine
enregistré, alors qu'ils n'ont pas la même origine). Il enverra
ensuite le cookie lors des connexions au même
domaine enregistré. Pour déterminer ce domaine, notez que les
cookies ne font pas de différence entre HTTP et
HTTPS (sauf utilisation de l'attribut Secure
) et
qu'un cookie acquis de manière sûre peut donc être
envoyé par un canal non sûr.
Notre RFC note que cette pratique d'utiliser le « domaine enregistré » est mauvaise et la déconseille. En effet :
a.co.jp
et b.co.jp
sont deux
domaines enregistrés différents. Il existe des listes publiques de
politiques d'enregistrement comme http://publicsuffix.org/
mais aucune n'est officielle et aucune
n'est à jour.Autre faiblesse du principe de même origine, l'autorité diffuse (section 8.3). L'autorité va dépendre de l'URI, pas du contenu. Or, un URI de confiance peut inclure du contenu qui ne l'est pas (pour le cas, fréquent, où une page Web inclut du contenu généré par l'utiisateur) et c'est alors un risque de XSS.
Résultat, le principe de même origine ne suffit pas à lui seul à protéger l'utilisateur (plusieurs attaques ont déjà été documentées.) Il faut donc ajouter d'autres pratiques de sécurité.
Notez que la question des mises en œuvre de ce RFC ne se pose pas, il a été réalisé après le déploiement du concept de même origine et tous les navigateurs Web utilisent aujourd'hui ce système. Toutefois, certains ne le font peut-être pas encore absolument comme décrit ici, par exemple en accordant au plan ou au port moins de poids qu'au host (il semble que cela ait été le cas de certaines versions d'Internet Explorer).
Une autre bonne lecture sur ce concept d'origine est l'article de Google pour les développeurs.
Version PDF de cette page (mais vous pouvez aussi imprimer depuis votre navigateur, il y a une feuille de style prévue pour cela)
Source XML de cette page (cette page est distribuée sous les termes de la licence GFDL)