La faille CSRF, explications et contre-mesures
« Je me connecte comme toujours à mon blog, je décide d’écrire un nouveau post génial sur Harry Potter. Entre temps, je me balade sur d’autres sites et dès lors que je retourne sur mon blog, horreur, plus aucun post. Tous les articles écrits auparavant sont effacés et je ne comprends absolument pas ce qu’il s’est passé. Je n’ai touché à rien ! »
Cette histoire pourrait bien être le résultat d’une exploitation réussie de la faille CSRF, très facile à mettre en place et très redoutable.
Table des matières
Qu’est ce que la faille CSRF ?
Le nom CSRF vient de Cross-Site Request Forgery qui, si l’on essaie de donner une définition en français, signifie Falsification de requête inter-sites. On n’est pas plus avancé, je sais.
En fait, il s’agit d’effectuer une action visant un site ou une page précise en utilisant l’utilisateur comme déclencheur, sans qu’il en ait conscience.
On va deviner un lien qu’un utilisateur obtient habituellement, et tout simplement faire en sorte qu’il clique lui-même sur ce lien.
Un exemple ?
En reprenant l’histoire ci-dessus, on peut imaginer que sur un blog donné, le lien de suppression d’un article soit le suivant :
http://www.monblog.fr/del.php?id=1
Ici, id=1 signifie qu’on souhaite supprimer l’article dont l’identifiant est 1, typiquement il s’agit du premier article.
Si maintenant un visiteur non connecté à la page d’administration clique sur un tel lien, il aura un message du type :
Vous n’avez pas les droits pour supprimer cet article, veuillez vous connecter et réessayer.
Normal, un visiteur quelconque n’a pas le droit d’éditer ou supprimer les articles d’un blog qui ne lui appartient pas.
Mais si maintenant le visiteur quelconque connait ce lien, il lui suffit d’envoyer le lien à l’administrateur en faisant en sorte que ce dernier clique.
Que se passe-t-il une fois que l’administrateur clique ?
Le lien de suppression s’exécute avec succès car l’administrateur est bien connecté et son article se retrouve ainsi supprimé.
Si vous voulez voir un exemple réel, dirigez vous dans la partie Mise en situation de la faille CSRF.
Comment se protéger contre la faille CSRF ?
Habituellement, si vous utilisez un système de gestion de contenu (CMS) du type WordPress ou Joomla, qui est un minimum protégé, vous n’avez pas grand chose à faire. Enfin…presque.
Si, à l’inverse, vous utilisez un système vieux ou que vous avez programmé vous-même, ce risque est bien présent.
Pour se protéger contre la faille CSRF, on utilise habituellement 2 principes complémentaires :
L’authentification par jeton
Un jeton (aussi appelé token en anglais) est un nombre ou une chaîne de caractère aléatoire qui va être testée avant toute modification ou édition d’un article.
Il se présente habituellement sous forme de hash md5 comme celui-ci :
b6cf20590a57f4685c9bdc6c53d12ff8
Ce token doit être crée dans un fichier PHP qui sera appelé sur toutes les pages. Typiquement, il s’agit d’un fichier du type config.php ou functions.php.
On génère souvent un nombre « aléatoire » avec des fonctions utilisant le temps en PHP. Par exemple on peut générer un jeton avec :
md5(uniqid(mt_rand(), true));
La fonction uniqid() génère un identifiant unique basé sur le temps en microsecondes. Cependant PHP ne recommande pas cette fonction car elle ne génère pas des chaînes impossible à deviner à l’avance.
Du coup, on va plutôt utiliser :
md5(bin2hex(openssl_random_pseudo_bytes(6)));
qui est cette fois hautement sécurisé.
La fonction openssl_random_pseudo_bytes() génère une chaîne pseudo-aléatoire d’octets de taille 6 bits * 2 qu’on convertit ensuite en hexadécimal, 6 étant le nombre donné en paramètre de la fonction (on peut le changer).
Ainsi, dans un fichier PHP global on va écrire le code suivant :
<?php if (!isset($_SESSION['jeton'])) { $_SESSION['jeton'] = bin2hex(openssl_random_pseudo_bytes(6)); } ?>
Ce code signifie : Si le jeton de session n’est pas encore défini, on le génère aléatoirement et on l’enregistre pour la session courante.
Ensuite, à chaque connexion d’un utilisateur, on va devoir générer un jeton qui lui est propre.
Pour cela, on peut avant chaque connexion régénérer le jeton, en supprimant le jeton de la session précédente :
unset($_SESSION['jeton']);
Il reste ensuite à modifier dynamiquement les liens de suppression, admettons que le lien précédent était écrit de la forme :
<a href="http://www.monblog.fr/del.php?id=<? echo get_article_id(); ?>>Supprimer l'article</a>
On le remplace par :
<a href="http://www.monblog.fr/del.php?id=<? echo get_article_id() . "&jeton=". $_SESSION['jeton']; ?>>Supprimer l'article</a>
L’url de suppression s’affichera donc comme ceci :
http://www.monblog.fr/del.php?id=1&jeton=b6cf20590a57f4685c9bdc6c53d12ff8
Au lieu de s’afficher comme cela :
http://www.monblog.fr/del.php?id=1
Et enfin, dans notre fichier de suppression del.php, on va s’assurer qu’il existe un jeton et que ce jeton soit valide. Le fichier, avant toute modification se présentait comme cela :
<?php if(isset($_GET['id'])) { supprimer_article($_GET['id']); } else { die("Aucun ID d'article sélectionné."); } ?>
Il devient donc :
<?php if(isset($_GET['id']) && isset($_GET['jeton']) && ($_GET['jeton'] == $_SESSION['jeton'])) { supprimer_article($_GET['id']); } else { die("ID ou jeton de session invalide."); } ?>
Ce qui signifie : Si l’id de l’article est défini mais aussi le jeton et que ce jeton correspond au jeton de la session actuelle, alors on peut supprimer.
Dernière note, on utilise $_GET qui récupère les paramètres depuis une URL, il aurait été préférable est encore plus sécurisé d’utiliser $_POST avec un formulaire pour ne pas afficher de jeton dans les URLs.
La demande de confirmation
Que ce soit pour éviter les suppression par erreur ou les tentatives d’exploitation de la faille CSRF, il est indispensable de demander confirmation de suppression d’un article.
Ici, si je clique sur supprimer mon article et qu’il est immédiatement et définitivement supprimé, il faut réfléchir à trois fois avant de cliquer.
Cette méthode est beaucoup utilisée pour redéfinir un mot de passe : On demande d’abord l’ancien.
Attention aux techniques de sécurisation de la faille CSRF qui ne fonctionnent en fait pas vraiment.
On entend souvent dire que pour se prémunir contre la faille CSRF, il faut vérifier la variable PHP HTTP_REFERER qui indique de quel site provient l’utilisateur. Si il provient d’un site différent, il ne sera alors pas autorisé à éditer l’article.
Oui c’est une bonne idée à la base (et c’est toujours une protection complémentaire), sauf que le lien référent (HTTP_REFERER) peut être modifié (on dit spoofé).
Cette variable est définie par le navigateur, donc du côté client. C’est-à-dire que je peux très bien poster le lien http://www.monblog.fr/del.php?id=1 depuis un autre site, tout en faisant croire qu’il provient bien du même site, exemple avec cURL :
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'Host: www.monblog.fr',
'Referer: www.monblog.fr
', 'FauxHEADER: FauxHeaderQuiSeraEnvoye'));
Dernière remarque
Ajax (Asynchronous JavaScript and XML) permet d’effectuer des actions sans recharger la page. Typiquement c’est ce qu’il se passe lorsque vous cliquez sur le bouton J’aime de Facebook. La requête est envoyée en arrière plan et le bouton devient enfoncé lorsque la réponse du serveur est arrivée.
Du coup la question de la faille CSRF se pose, que se passe-t-il si l’on essaie de supprimer un post en utilisant Ajax ?
La réponse est simple, Ajax utilise la Same-origin policy (politique de la même origine) qui empêche l’exécution de code à travers sites. Le terme « origine » est défini à propos du protocole, du nom de domaine et du port : deux pages ont la même origine si et seulement si ces 3 valeurs sont les mêmes.
Envie d’en apprendre plus sur les failles web ?
Cette faille et bien d’autres est vue en détail dans mon cours vidéo sur les tests d’intrusion web.
Nous allons parler des fondamentaux : fonctionnement d’HTTP, d’HTTPs, de DNS et de l’architecture web de manière générale.
Nous allons également mettre en place un laboratoire de test avec des machines virtuelles pour héberger et scanner nos sites vulnérables afin d’apprendre sans rien casser.
Nous allons bien sûr parler de toutes les failles web (XSS, CSRF, SQL, LFI, RFI, …etc) en suivant le Top 10 OWASP mais aussi de tout ce qui gravite autour de la sécurité web : dénis de service, mauvaises configurations, données personnelles, reconnaissance, etc…
Impatient de commencer avec vous, je vous propose un code de réduction pour pouvoir rejoindre le cours dès maintenant : https://cyberini.com/cours/hacking-ethique-tests-intrusion-web/