Karim's Blog

Un peu de tout sur CSS, HTML, AngularJS, JavaScript, Php et le reste

Introduction aux Cross Site Request Forgeries ou Sea Surf

Notez que ce genre d'attaqueest spécifique aux applications web puisqu'elles exploitent essentiellement le navigateur de l'utilisateur.

Le principe est enfantin : on incite un internaute à se rendre sur une page afin qu'une requête spécifique soit déclenchée à son insu. Ainsi, par le fait qu'il se soit rendu sur la page,l'internaute devient complice de l'attaque.

Bien entendu, l'internaute en question est connecté (par exemple sur son espace membre, un backoffice d'administration, son interface de gestion de son compte bancaire...), et c'est bien là tout le danger de la faille...

Un exemple concret

Prenons un exemple concret afin que vous compreniez mieux le principe de ces failles. Certains scripts d'annuaires permettent l'affichage des sites les mieux notés en fonction du nombre de votes. A partir de celà, il est possible de décortiquer le code de l'annuaire, de trouver la fonction qui incrémente la variable du nombre de votes et de s'en servir. On peut, par exemple, l'inclure en tant qu'image avec la balise <img /> et lui appliquer la propriété CSS display:none;. Cela nous donnerait quelque chose comme :

<div style="display:none;">
<img src="http://www.example.com/vote.php?id=45" />
</div>

Au chargement de la page contenant ce code HTML, le navigateur exécutera une requête GET sur la ressource distante dont l'url est spécifiée par l'attribut "src" de la balise <img/>. Vous le savez peut-être, mais il est possible de renvoyer une image à partir d'un script PHP. C'est pourquoi il n'est pas étonnant de voir apparaître une url avec une page PHP et des paramètres, à la place d'un chemin absolu vers une véritable image.

Le navigateur, ne se souciant pas du type de fichier distant, va tenter de charger et d'afficher la fausse image. Si cela échoue, tant pis... La requête sur le fichier php a tout de même été exécutée ! Le fait d'utiliser display:none; rendra l'image invisible aux visiteurs. Ainsi, vous pourrez gonflez votre nombre de votes sans que personnes ne s'en rende compte. L'exemple ci-dessus est quelque peu simplet, mais la portée d'action de la faille n'est pas des moindres. En se basant sur le même principe, on aurait pu vous faire transférer une somme d'argent de votre compte, vous faire acheter un produit, etc.

Quels sont les moyens pour se protéger des CSRF ?

La seule chose à savoir c'est qu'il n'existe pas de technique ou de fonction 100% fiable et ultime pour vous protéger des CSRF, contrairement aux XSS pour lesquelles il suffit d'échapper les données de sortie. Néanmoins, vous pouvez rendre la tâche un peu plus ardue à ceux qui essaieraient ce genre d'attaque.

Vérifiez vos referers

Envoyé par votre navigateur, le referer permet de déterminer la provenance des requêtes. La vérification du referer n'est pas une fin en soi car on peut bloquer son envoi ou encore le modifier. Néanmoins, vous pouvez vous en servir pour diminuer le nombre d'attaques par CSRF.

Les tokens, pensons-y !

Cette technique permet de spécifier une durée de vie au formulaire. L'astuce consiste donc à vérifier que le formulaire n'a pas été saisi dans un délai trop court (robot) ou bien trop long. Voici un exemple :

<?php
$token = md5(uniqid(rand(), TRUE));
$_SESSION['token'] = $token;
$_SESSION['token_time'] = time();
?>

A partir de cela, il faudrait un champ caché dans notre formulaire (type="hidden") qui aurait pour valeur $token. Avec ces informations, il suffit alors de faire une petite condition comme celle-ci :

Vérification du jeton appliqué à un formulaire
<?php

// Définition de la durée de vie du jeton
// Ici il est de 180 secondes soit 3 minutes
define('DELAI_MAXIMUM', 180);

if($_POST['token'] == $_SESSION['token'];
&& (time() - $_SESSION['token_time']) <= DELAI_MAXIMUM)
{
actions();
}
else
{
die('Jeton expiré !');
}
?>

Vous pouvez également générer une suite de nombres aléatoires que vous mettrez dans chaque formulaire en champ caché (cf. paragraphe ci-dessus) A partir de cela, il faudra obligatoirement que cette suite de nombres soient contenue dans l'URL sans quoi votre code n'interprétera pas la requête. Vous devrez néanmoins choisir une suite de nombres assez longue afin que la chance qu'un pirate tombe sur un couple nombre / utilisateur valide soit infime.

Faites valider les actions critiques

Avant chaque action critique (ajout, suppression, édition, virement bancaire...), n'hésitez pas à soumettre un captcha à l'utilisateur afin d'être certains qu'il a bel et bien demandé l'exécution de la requête. Pour ne pas trop perdre en ergonomie, vous pouvez utiliser une vérification JavaScript (boîte de dialogue), mais cette pratique est très fortement déconseillée dans la mesure où l'exécution du script Javascript dépendra de la configuration du navigateur client.

L'un des plus bel exemple pour illustrer ces validations d'actions sont les formulaires dynamiques que l'on retrouve sur les sites bancaires. Il s'agit de formulaires qui vous demandent de saisir votre code avec la souris en pointant les chiffres adéquats (voir la capture ci-dessous).

Grille de saisie de password de la BNP Paribas

Bien sûr, l'ordre et la disposition des chiffres dans la grille sont générés aléatoirement à chaque rechargement de page. Quel en est l'intérêt ? La disposition aléatoire permet tout d'abord de limiter les CSRF mais également de ralentir les attaques avec les nouveaux systèmes capables de détecter et d'enregister les positions du curseur sur l'écran (via un logiciel ou un javascript intégré avec une XSS).

Configuration de PHP et bonnes habitudes de programmation

Limitez l'utiliation de $_GET et desactivez les register_globals sur votre serveur. Préférez également toujours le traitement des actions importantes avec la méthode POST plutôt que la méthode GET. Vous limiterez ainsi fortement les risques d'attaques CSRF puisqu'elles se propagent généralement via des injections XSS et des URL.

Utilisez également des captchas, demandez une seconde saisie du mot de passe ou effectuez des vérifications javascript pour valider les actions importantes (achat, vente, transfert) sur votre site. L'ergonomie en souffrira quelque peu, mais c'est le prix à payer pour limiter les risques.

Remarque : notez au passage que ces conseils sont en contradiction avec la philosophie du « Web 2.0 » où les développeurs s'efforcent de limiter le nombre d'écrans pour obtenir des interfaces ergonomiques et « user friendly » (ami avec l'utilisateur). L'utilisation massive d'Ajax peut conduire à de nombreuses failles XSS et CSRF, et c'est pour cette raison que des sites comme Facebook, Twitter... sont la cible quotidienne de pirates malintentionnés.

Comme il l'a été évoqué au début du tutoriel, il faut que l'utilisateur soit authentifié pour que l'attaque représente unintérêt pour le pirate. Parconséquent,vous pouvez commencer par limiter la durée de vie des sessions sur votre serveur en modifiantla directive session.gc_maxlifetime de votre php.ini.

Conclusion

Vous savez donc désormais ce qu'est une attaque par CSRF ainsi que différents moyens techniques pour vous en protéger. N'hésitez donc pas à les appliquer.Pour ceux qui se le demandent encore, je n'ai volontairement pas donné d'exemples véritablementnuisibles pour ne pas vous tenter de les mettre en oeuvre ;)