• [Sécurité PHP] Toujours vérifier le Referer lors d'un envoi par POST

    [Sécurité PHP] Toujours vérifier le Referer lors d'un envoi par POST

    © Photo de divarvel

    C'est un aspect très souvent négligé de la sécurité des sites web, et qui est pourtant critique.

    Par exemple, si Facebook n'avait pas prévu ce genre de sécurité, nous pourrions partir du postulat qu'il y a une chance non négligeable qu'un visiteur soit connecté à Facebook (par session ou cookies). Prenons alors une page web visitée par quelques dizaines ou centaines de visiteurs par jour, un blog par exemple. Sur cette page, nous allons mettre un formulaire identique à celui permettant de modifier le profil sur Facebook. Une ligne de javascript suffira à envoyer le formulaire ( $("id_form").submit() ), avec tout plein de grossièretés qui viendront égayer le profil de l'utilisateur

    C'est de cette manière qu'Olivier Duffez s'est fait pirater son compte Gmail puis le nom de domaine Webrankinfo.com ! En effet, le pirate s'est débrouillé pour que M. Duffez se retrouve sur une page envoyant un formulaire vers la page de création de filtre de Gmail. Et comme une session Gmail était ouverte sur l'ordinateur de M. Duffez, le formulaire en question a pu créer un filtre qui transférait tous les e-mails reçus au spammeur. Le spammeur a ensuite utilisé le formulaire "Mot de passe oublié" du registrar chez lequel est enregistré webrankinfo.com, et a donc reçu l'e-mail permettant de modifier le mot de passe.

    Alors ? N'est-ce pas critique comme faille ?
    Ce type d'attaque est appelé Cross-Site Request Forgery, et n'est pas très connu en raison d'un manque de communication à ce propos.
    Voyons comment l'étudier concrètement, et comment éviter ce genre de déboires.

     

    Pour vous montrer un exemple concret, créons une page affichant les données reçues par POST :
    http://data0.eklablog.com/skreo/perso/test_referer.php

    Code de la page :

    <pre>
    Données reçues par POST :
    $_POST = <?php
    print_r($_POST);
    ?>
    </pre>
    [code=php]<pre>
    Données reçues par POST :
    $_POST = <?php
    print_r($_POST);
    ?>
    </pre>[/code]

     

    Ensuite, sur un nom de domaine différent (ici skreo.net), créons un petit formulaire qui envoie des données à cette page :

    <form action="http://data0.eklablog.com/skreo/perso/test_referer.php" method="post" id="form_test_referer">
        Variable "var" : <input type="text" value="" name="var" /><br />
        <input type="submit" value="Envoyer" />
    </form>
    [code=html]<form action="http://data0.eklablog.com/skreo/perso/test_referer.php" method="post" id="form_test_referer">
        Variable "var" : <input type="text" value="" name="var" /><br />
        <input type="submit" value="Envoyer" />
    </form>[/code]

    Ce qui donne ceci :

    Variable "var" :

    Vous pouvez tester ce formulaire, vous voyez bien qu'on peut envoyer n'importe quelles données par POST vers un autre domaine. Un petit bout de code Javascript à la suite du formulaire permet d'envoyer le formulaire immédiatement sans même demander l'avis du visiteur :

    <script type="text/javascript">
    document.getElementById("form_test_referer").submit();
    </script>
    [code=html]<script type="text/javascript">
    document.getElementById("form_test_referer").submit();
    </script>[/code]

     

    Voyons maintenant comment empêcher dans la majorité des cas ce genre de hacks, avec l'aide du Referer. Je dis bien "dans la majorité des cas", car parfois le visiteur règle son navigateur internet pour que le referer soit vide.

    Nous allons donc comparer le Referer avec le nom de domaine de la page courante, et vider la variable $_POST s'il est différent. Reprenons notre fichier test_referer.php et nommons le test_referer2.php :

    <?php

    // Vérification du Referer pour les variables passées en POST
     if(isset($_SERVER['HTTP_REFERER'])
      && $_SERVER['HTTP_REFERER']!=''
      && substr($_SERVER['HTTP_REFERER'], 7, strlen($_SERVER['SERVER_NAME'])) != $_SERVER['SERVER_NAME']){
         $_POST = array();
     }

    ?>
    <pre>
    Données reçues par POST :
    $_POST = <?php
    print_r($_POST);
    ?>
    </pre>
    [code=php]<?php

    // Vérification du Referer pour les variables passées en POST
    if(isset($_SERVER['HTTP_REFERER'])
      && $_SERVER['HTTP_REFERER']!=''
      && substr($_SERVER['HTTP_REFERER'], 7, strlen($_SERVER['SERVER_NAME'])) != $_SERVER['SERVER_NAME']){
        $_POST = array();
    }

    ?>
    <pre>
    Données reçues par POST :
    $_POST = <?php
    print_r($_POST);
    ?>
    </pre>[/code]

    Reprenons notre formulaire pour tester, mais cette fois vers test_referer2.php :

    Variable "var" :

     

    Et le tour est joué !
    La page accepte toujours les envois de données par POST provenant du même nom de domaine, mais n'accepte pas les autres !

    Si vous avez un site web, je vous conseille très fortement de mettre en place cette sécurité, si ce n'est pas déjà le cas


    Tags Tags : , ,
  • Commentaires

    1
    Vendredi 26 Décembre 2008 à 15:56
    Ca ne risque pas de déconner, vu que c'est le navigateur qui choisit d'envoyer ou non un Referrer ?
    2
    Vendredi 26 Décembre 2008 à 16:15
    Nop, lorsqu'il n'est pas configuré spécifiquement, le navigateur envoie presque toujours le referer. Et si tu regardes le script, si aucun referer n'est renseigné, on laisse la variable $_POST tranquille ^^ Donc pour une grande majorité des visiteurs, cette sécurité est efficace.
    Par contre ça n'est pas efficace pour les gens qui modifient eux-même le referer, mais ces gens-là sont sensés en connaitre les risques...
    3
    Vendredi 26 Décembre 2008 à 16:24
    Sinon, il y a la solution de créer un token, envoyé par post, et de vérifier la validité côté serveur (merci Wikipedia ^^), mais c'est plutôt lourd comme méthode
    4
    Vendredi 26 Décembre 2008 à 16:43
    Tu parle d'une faille de sécurité, ok. Mais si je suis un hackers et que je change le referer que j'envoi (envoi d'une requete http simple), ta faille reste toujours active non ? Ca ne corrige pas vraiment la faille, ça la diminue juste un peu je trouve.
    5
    Vendredi 26 Décembre 2008 à 17:15
    @Defaite : Non, pas du tout, ce que tu n'as pas l'air d'avoir compris, c'est que cette faille utilise les sessions actives du navigateur du  visiteurs pour envoyer des formulaires. Là tu me parles de changer le referer de la requête. D'accord, c'est tout à fait possible, avec un petit script en php par exemple, mais alors tu n'as pas les données de session de l'utilisateur, et donc la faille n'est pas exploitable.
    Le truc, c'est que tu ne peux pas modifier le referer des requêtes que font les visiteurs. Seuls eux peuvent le faire, en configurant leur navigateur.
    6
    Samedi 27 Décembre 2008 à 09:37
    Le problème avec le referer c'est que c'est une donnée qui dépends du navigateur du visiteur. Ok par défaut il n'y a pas de problème, mais se baser uniquement dessus peut avoir de contreparties bloquantes (pour ma part je bloque souvent le réferer, si je ne peux plus accéder à) des services à cause de ca...).

    Les tokens sont quand à eux, passent partout, et ne sont pas si durs à mettre en place, et deviennent standards dans les frameworks comme symfony
    7
    Samedi 27 Décembre 2008 à 12:40
    Oui tu as tout à fait raison, je présente la solution du referer comme solution de base, mais elle n'est évidemment pas sûre à 100%.
    Les tokens sont très lourds à mettre en place à grande échelle : par exemple il ne faudrait pas le faire systématiquement sur EklaBlog, car presque chaque ouverture d'une fenêtre de modification fait appel à POST (par Ajax).
    Mais les tokens sont à utiliser pour les formulaires sensibles.
    8
    Samedi 27 Décembre 2008 à 13:02
    Oui, j'avoue c'est assez dur à mettre un site à jour dans son intégralité, si on y a pas pensé dès le début... Modifier chaque formulaire, c'est galère (mais pas plus que la méthode que tu donnes je pense) !
    Ps : autre méthode: la captcha... bon ok je sors
    9
    Samedi 27 Décembre 2008 à 13:48
    Le problème n'est pas tellement de mettre à jour un système déjà existant. Je parle surtout de performances. Parce que générer 1 token par seconde, 300 000 dans la journée, c'est pas très bon pour la base de donnée et les performances du serveur.

    Pour le Referer, il suffit au contraire de placer les 3 lignes de codes que je donne dans le fichier de configuration du site, et elles s'appliqueront sans problème à l'intégralité des pages.
    10
    Lundi 29 Décembre 2008 à 02:25
    Hey ça marche pas mal ton petit truc,
    Je me suis pris les alertes JS dans la tronche puis impossible de poster mon petit comment...
    http://www.deliciouscadaver.com/outil-de-spam-referer-genti.html
    :)
    11
    yesnet
    Mercredi 14 Janvier 2009 à 21:58
    mouai mouai... merci pour cet article, au moins çà permet d'en discuter. Car je ne suis pas pour le referer :
    - le referer est majoritairement utilisé pour tracer les visteurs (stats)
    - si l'internaute novice utilise un navigateur mal configuré, il ne pourra pas envoyer ledit formulaire. Et avant de savoir re-activer le referer, woualou
    - enfin et surtout, il devient impossible de bookmarker la page...
    Oui la sécurité est essentielle, mais d'autres moyens existent.
    12
    Jeudi 15 Janvier 2009 à 00:33
    @yesnet: Bookmarker la page ? En quoi cela devient-il impossible ?
    Pour le navigateur novice, c'est vrai que ça peut arriver, mais c'est extrêmement rare. Pour preuve, j'ai effectué un log sur les 15 derniers jours de toutes les requêtes bloquées par cette technique sur EklaBlog (10 à 15 000 VU/jour), et ça donne :
    - 27 spams bloqués :
    - 2 utilisateurs initiés bloqué (512banque qui a commenté plus haut, et un membre d'EklaBlog que je connais)
    - divarvel qui a eu un referer qui a merdé apparemment à cause de Iceweasel. Il faudra que je gère ce cas.
    - 4 requêtes faites sous IE7 qui apparemment a aussi mal géré le referer.
    13
    yesnet
    Jeudi 15 Janvier 2009 à 13:39
    Tu as raison on peut bookmarker la page.

    D'une façon générale, je suis contre exiger des réglages côté machine client. Il vaut mieux utiliser les fonctions côté serveur,c'est plus fiable car une seule source et les visiteurs restent libre de leur navigateur. C'est au serveur d'être l'unique référant, on ne doit rien imposer à la machine client.
    Si un super navigateur ultra protégé contre les données personnelles voit le jour, ou un navigateur minimaliste n'incluant par le referer, le formulaire ne marche plus.
    14
    Jeudi 15 Janvier 2009 à 16:46
    Hmmm vas dire ça aux concepteurs de Facebook (entre autre) qui utilisent ce type d'astuce. Au moins ça a le mérite de bien marcher dans une très grande majorité des cas, et à moindre frais.
    Si tu as une sécurité côté serveur à proposer, je t'écoute
    Même avec un token, il suffit que le pirate récupère la clé token juste avant de rediriger le visiteur vers la page avec le token en argument...
    15
    yesnet
    Jeudi 15 Janvier 2009 à 17:22
    Effectivement, tu m'as convaincu ;)
    16
    Vendredi 16 Janvier 2009 à 01:21
    C'est pas un problème avec iceweasel, c'est que j'ai bidouillé avec un plugin pour changer le referer :p
    17
    Vendredi 16 Janvier 2009 à 17:59
    Aaah tout s'explique ^^ En effet c'est une page Google particulière qui était en referer :p
    18
    Vendredi 22 Mai 2009 à 08:25
    Super, bravo  ça marche nickel  ton petit truc
    Tout comme 512banque (salut à toi ) et bien je me suis pris
     les alertes JS dans la tronche donc désactiver le truc pour poster mon petit commentaire.
    19
    dimebagplan
    Mardi 25 Mai 2010 à 03:51
    Bonjour, un grand merci pour cette article tres interessant.
    Je trouve en effet qu'on ne parle pas asser de ce genre de protection qui semble vraiment essentiel !

    Une question :
    [quote]
    Pour le Referer, il suffit au contraire de placer les 3 lignes de codes que je donne dans le fichier de configuration du site, et elles s'appliqueront sans problème à l'intégralité des pages.
    [/quote]

    Ca m'interesse, qu'entends tu par fichier de configuration du site ?
    tu crée une page php avec ce code (et d'autres types de protections je suppose) et ensuite tu fais un include au debut de chacunes de tes pages php ?

    Merci, a tres bientot et bonne continuation.
    20
    Vendredi 28 Mai 2010 à 01:02
    Oui, c'est simplement que j'ai tendance à faire un fichier centralisant ce genre de snippets. Place le bout de code donné dans ton script PHP, de préférence le plus haut possible, et ça marchera
    Tu peux bien sûr le mettre dans un nouveau fichier et l'inclure avec include ou require.
    Suivre le flux RSS des commentaires de cet article


    Ajouter un commentaire

    Nom / Pseudo :

    E-mail (facultatif) :

    Site Web (facultatif) :

    Commentaire :