Explication du Same Origin Policy

mieux protéger une application internet

Ce site ne sera plus alimenté de contenu après août 2014. Tous les nouveaux articles seront redigés pour www.waitingforcode.com
Ajax est un concept déjà ancien dans le web. Sa démocratisation date des années 2005-2006. Malgré donc presque 10 ans d'existence sur le marché des applications web, il a encore quelques points qui peuvent poser des problèmes lors de développpement. Un point est le limite d'appels simultanés par les navigateurs. L'autre s'appelle Same Origin Policy (SOP) et c'est lui qu'on va traiter dans cet article.

Au début on verra de quoi s'agit-il. Dans la deuxième partie on présentera quelques méthodes qui permettent de résoudre les problèmes liés à SOP.

Qu'est-ce que c'est Same Origin Policy ?
Cette contrainte empêche le code client à récupérer les données ou les méthodes venues d'un autre site. Par un autre site, on comprend ici :
- un site dont le protocole est différent (par exemple http et https)
- un site dont le hôte est différent (par exemple : un domain.com et sub.domain.com; un www.domain.com et domain.com)
- un site dont le port est différent (par exemple 80 et 801)



Quel est donc le but du SOP ? Tout d'abord, il vise à empêcher les sites potentiellement dangereux à consulter les données pouvant être sensibles des sites considérés comme propres. Sans ce blocage, les dangers tels que Cross-site request forgery (CSRF) ou click jacking seraient beaucoup plus faciles à réaliser. Pour le comprendre, imaginons-nous la situation suivante.

Nous avons un site qui charge dynamiquement les actualités politiques. Un des nos partenaires est le site d'un étudiant des sciences politiques. Tout se passe bien jusqu'au jour où son blog basé sur Wordpress a été victime d'une attaque qui permet d'introduire le contenu par des personnes non autorisées. Maintenant en chargant un article de sa part, on peut exposer nos utilisateurs à des attaques déjà mentionnées par la non validation de ce qui se trouve dans le code.



Maintenant, avec la police SOP, le chargement de ce contenu serait impossible, donc on ne serait pas confrontés à ce type de vulnérabilité. On devrait soit penser à une solution côté serveur, soit implémenter un des workarounds de cette contrainte qu'on abordera dans le paragraphe suivant.

Workaround pour SOP
Plusieurs solutions existent pour rémedier à ce blocage. Mais avant de les expliquer, présentons brièvement le petit projet qui prouvera les moyens de contourner Same Origin Policy. On va donc avoir un fichier sop_test.php sur le serveur distant (en occurrence, sur bartosz-works.net) et qui va contenir une réponse en JSON. En local on aura un fichier test_sop.html qui va essayer de charger le contenu du premier fichier. Voici comment se présente notre document HTML :


<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script type="text/javascript">
function loadAjax() {
alert("ajax loaded");
$.ajax({
type: "GET",
url: "http://bartosz-works.net/sop_test.php?q=ABC&callback=test"
}).done(function(msg) {
alert("Received data : " + msg);
});
}
</script>
<a href="#" id="load" onclick="javascript: loadAjax(); return false;">load content</a>


Au premier clique sur le lien, on devrait voir le message suivant s'afficher dans la console de débuggage du navigateur utilisé (peut changer en fonction du navigateur, mais devrait dire la même chose partout) :

XMLHttpRequest cannot load http://otherdomain.com/sop_test.php?q=ABC&callback=test. Origin http://localhost is not allowed by Access-Control-Allow-Origin.


Explorons maintenant les méthodes qui nous permettront de télécharger le contenu du fichier .php directement via JavaScript :



  1. Cross-Origin Resource Sharing (CORS)
    Grâce à cette spécification on peut indiquer au navigateur et au serveur comment ils doivent se comporter dans le cas d'une communication. L'idée est d'utiliser un en-tête HTTP appelé Origin dont le but est d'indiquer si le serveur et le navigateur se connaissent et, en fonction de cela, exécuter ou pas la requête.
    Le schéma de fonctionnement est donc le suivant. Le navigateur envoie d'abord une requête dans laquelle il inclut l'en-tête Origin. Le serveur, quant à lui, vérifie s'il accepte les appels depuis l'URL spécifiée dans cet en-tête. Il renvoie, avec la réponse, un en-tête Access-Control-Allow-Origin. S'il contient l'adresse du site qui a effectué la requête, le navigateur pourra récupérer les données depuis le serveur.
    Notre fichier HTML ne change pas. L'en-tête Origin est créé directement par le navigateur. On doit juste préparer un autre contenu du fichier PHP sur le serveur bartosz-works.net. Le voici :

    <?php
    header('Access-Control-Allow-Origin: http://localhost');
    echo "TEST";
    die();

    Maintenant, au moment de lancer le code, on verra un chargement réussi du fichier PHP, avec le schéma requête-réponse suivant:

    Request URL:http://bartosz-works.net/sop_test.php?q=ABC&callback=test
    Request Method:GET
    Status Code:200 OK
    Request Headers
    Accept:*/*
    Accept-Encoding:gzip,deflate,sdch
    Accept-Language:fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4
    Connection:keep-alive
    Host:bartosz-works.net
    Origin:http://localhost
    Referer:http://localhost/test_sop.html
    User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.94 Safari/537.36
    Query String Parameters
    q:ABC
    callback:test
    Response Headers
    Access-Control-Allow-Origin:http://localhost
    Connection:keep-alive
    Content-Encoding:gzip
    Content-Length:24
    Content-Type:text/html
    Date:Sat, 25 May 2013 06:48:51 GMT
    Server:Apache/2.2.X (OVH)
    Set-Cookie:240planBAK=R2339297792; path=/; expires=Sat, 25-May-2013 07:57:56 GMT
    Set-Cookie:240plan=R3729666659; path=/; expires=Sat, 25-May-2013 07:49:30 GMT
    Vary:Accept-Encoding
    X-Powered-By:PHP/5.3.16

    CORS est une technique puissante qui peut non seulement autoriser un domaine à lire les ressources du serveur, mais aussi déterminer quel type de requête est accepté (Access-Control-Allow-Methods) ou combien de temps la réponse peut être stockée en cache (Access-Control-Max-Age). Vous pouvez trouver plus d'informations à ce sujet dans la documentation W3C du CORS.

  2. JSONP
    Une autre méthode qui permet de manipuler les données issues d'un autre domaine s'appelle JSON with Padding (JSONP). Son fonctionnement est moins propre que celui du CORS, mais aussi efficace. Il exploite une possibilité d'inclure toutes les ressources (JavaScript, CSS, images) dans le document. La contrainte SOP n'y s'applique donc pas.
    Pour mettre en place JSONP, on doit placer le contenu du fichier externe dans une balise <script type="text/javascript" />. En plus, pendant l'appel Ajax, on doit indiquer une fonction callback qui sera appelée une fois la réponse obtenue. Regardons comment cela se met en place avec jQuery qui remplace automatiquement le fragment callback=? avec la méthode déterminée à côté. Notre document HTML se présentera donc de cette manière :



    Le code du fichier PHP sur le serveur se présente ainsi :

    header('Content-Type: text/javascript');
    header('Cache-Control: no-cache');
    header('Pragma: no-cache');

    $content = array("unixTime" => time());
    echo $_GET["callback"]."(".json_encode($content).")";
    die();

    La réponse sera donc au format : parseJsonResponse({"unixTime" : "9999"}). La présence de la méthode qui détermine qui se chargera de traiter la réponse est vitale pour le bon fonctionnement de ce workaround.

  3. window.postMessage
    JavaScript permet de mettre en place une sorte des écouteurs qui vont déclencher une action suite à un événement. Le schéma se présente ainsi :

    • Dans le document on définit quelle méthode sera appelée au moment de réception du message. Cette définition peut se faire à travers une des deux méthodes : window.attachEvent('onmessage', methodToLaunch); pour Internet Explorer ou window.addEventListener("message", methodToLaunch, false); pour // Opera/Mozilla/Webkit.

    • Ensuite l'autre document, inclut par exemple dans un iframe, envoie un événement au document-parent via cette méthode : window.parent.postMessage('test content','*').

    • Suite à cela, la fonction methodToLaunch(event) est invoquée dans le document-parent. Elle prend en paramètre un event qui peut récupérer : les données envoyées (event.data), la source (event.source) ou l'origine (event.origin) de l'invocation.


    Regardons maintenant comment se présente notre fichier HTML suite au modifications apportées pour un bon fonctionnement avec postMessage :

    <script type="text/javascript">
    $(document).ready(function() {
    window.addEventListener("message", receiveMessage, false);
    });
    function receiveMessage(event) {
    alert("event data : " + event.data);
    alert("event source : " + event.source);
    alert("event origin : " + event.origin);
    }
    </script>
    <iframe src="http://otherdomain.com/sop_test.php?q=ABC&callback=test" />

    Le fichier PHP a également changé. Son contenu se présente ainsi :

    <script type="text/javascript">window.parent.postMessage('Test message, can be JSON evaluated after by parent document','*')</script>

    Maintenant, en exécutant le fichier HTML, on verra 3 fenêtres JavaScript apparaître :

    event data : Test message, can be JSON evaluated after by parent document
    event source : [object Window]
    event origin : http://bartosz-works.net

    Maintenant, sur la base de ces informations, on peut aussi bien traiter les données issues d'un autre domaine que contrôler les domaines qui peuvent nous envoyer des informations.



  4. document.domain
    Une autre solution consiste à rajouter un code JavaScript suivant au début des deux fichiers. Le code se présente ainsi :

    document.domain = "mydomain.com";

    Cependant, cette solution s'adapter à des fichiers placés au sein du même domaine, par exemple pour sous-domaines x.mydomain.com et y.mydomain.com et au chargements via iframes.



On a clairement vu que Same Origin Policy a des effets bienveillants à chaque application. En plus, malgré cette sécurité supplémentaires, dont l'implémentation n'est pas à notre charge, on peut continuer à récupérer les informations placées dans les endroits extérieurs. Via des méthodes JavaScript ou serveur (en-tête), les données peuvent être facilement lues. Cependant, il faut toujours rester vigileant et s'assurer d'un minimun de filtrage et d'authorisation.
Bartosz KONIECZNY 21-04-2014 00:00 sécurité des applications web
Moi

Développeur d'applications Internet et journaliste passionné par l'adjectif français. Un aigle polonais orienté vers la progression, volant très haut et écoutant du zouk après les matches du foot français.

Vous appréciez mon travail ?

Pour contribuer au développement de ce site, ou pour remercier pour des articles rédigés, vous pouvez faire un don.

Un conseil Symfony2

Comment personnaliser la page 404 ?

Pour personnaliser la page 404 sous Symfony2 il faut surcharger le contrôleur par défaut. On peut l'achever en déterminant "exception_controller" dans le fichier de configuration. Supposons que nous utilisons le bundle ExceptionsErrorBundle pour gérer toute sorte des exceptions. Un des contrôleurs (NotFoundController) s'occupe de manipulations liées aux erreurs 404. Pour pouvoir utiliser ce contrôleur pour les erreurs type 404, il faut le déclarer dans le fichier de configuration (config.yml, config_dev.yml - en fonction de l'environnement) :

twig: 
    exception_controller: "ExceptionsErrorBundle :NotFound:handleException"