Tout d'abord, qu'est ce qu'une faille CSRF? Je ne ferais pas mieux que Wikipedia pour la définition :

En sécurité informatique, le Cross-Site Request Forgery, abrégé CSRF (parfois prononcé sea-surfing en anglais) ou XSRF, est un type de vulnérabilité des services d'authentification web.

L’objet de cette attaque est de transmettre à un utilisateur authentifié une requête HTTP falsifiée qui pointe sur une action interne au site, afin qu'il l'exécute sans en avoir conscience et en utilisant ses propres droits. L’utilisateur devient donc complice d’une attaque sans même s'en rendre compte. L'attaque étant actionnée par l'utilisateur, un grand nombre de systèmes d'authentification sont contournés.

Une explication par l'exemple, peut-être ?

Je vais prendre pour exemple un cas que j'ai eu à résoudre sur un site très connu.

Imaginons que j'ai un service Web me permettant de vérifier un numéro de carte cadeau, et que ce service disponible sur mon site internet me renvoi soit un code d'erreur, soit un **message me disant qu'il me reste XX€ sur ma carte cadeau.

On réalise donc un appel Ajax vers un webservice(a parte: EN 2015/2016, IL EST INCONCEVABLE D'UTILISER ENCORE CE MOT. C'EST UNE API !).
Ce service nous renvoie donc le message.
Par exemple :

<input type="text" name="code_cadeau" id="code_cadeau" />
<p id="msg_cadeau"></p>
$.ajax({
            type: GET,
            url : "http://example.org/api/" + $("#code_cadeau").val(),
            jsonp: "callback",
            dataType: "jsonp",
            success: function( data ) {
                var response = $.parseJSON(data);
                $("#msg_cadeau").html(response);
            }

On admet que le résultat de la requête sera de la forme:

[{
    "msg": "il reste 20€ sur la carte cadeau",
}]

ou bien:

[{
    "msg": "cette carte cadeau n'existe pas!"
}]

Comprendre comment on peut interroger l'API

On peut ainsi aisément deviner comment un éventuel pirate peut savoir si oui ou non, une carte est valable:
Tout d'abord, afin de restreindre sa requête, le pirate peut se baser soit sur une carte cadeau qu'il aura au préalable acheté, soit (la méthode la plus probable) en observant la validation JS qu'il y a sur l'input. Il étudie la construction de la carte cadeau, en faisant du reverse engineering.
Par exemple, la regex acceptant 9 chiffres uniquement, correspondante au champ #code_cadeau, sera ^[0-9]{9}$.
Le pirate génèrera alors des requêtes GET vers l'URL:

http://example.org/api/000000000

puis

http://example.org/api/000000001

etc.
En faisant une boucle dans n'importe quel langage, il lui suffit de récupérer le résultat de l'API afin de comprendre si oui ou non tel ou tel numéro est valide, et les stocker dans une base de données, par exemple, afin de les utiliser ou bien les revendre. Certains iront jusqu'à contacter le site en question, et le faire chanter afin d'obtenir une rançon (un cas extrême, mais possible).

Une solution ?

Évidemment, c'est le but de cet article. En fait, des solutions, il y en a beaucoup. Mais il ne faut pas oublier que sécuriser totalement ce type d'attaque est très difficile, voir impossible. Cependant, on va ralentir tellement l'attaquant qu'il sera impossible pour lui d'obtenir des résultats de façon rapide, à moindre coût (pour lui) et rentable.

Il y a, à mon sens, plusieurs solutions pour résoudre ce soucis:

  • placer un Token (la meilleure solution selon moi).
  • authentifier l'utilisateur
  • Demander une confirmation par mot de passe
  • limiter l'utilisateur par ip
  • limiter le nombre total d'utilisation du service en un temps donné
  • préférer des requêtes en POST qu'en GET

Le Token

Le token ou encore appelé le jeton, est tout simplement une clé, générée aléatoirement, que l'on va stocker autant côté front (avec un input type hidden) que côté back (dans une variable classique). Ce token généré est donc unique et propre à l'utilisateur (voir propre à la session ou à l'action).
Au moment de la validation d'un formulaire, ou dans le cas présent d'un appel Ajax, on compare le token back de celui front. S'ils diffèrent, on n'entre même pas dans la fonction de validation de carte cadeau et ton refuse l'accès: il s'agit très probablement d'un robot. Car le robot, en général, ne navigue pas réellement sur le site, mais n'utilise que les URL. Aussi, pinguer telle ou telle URL/API sera impossible.

Auth utilisateur

On peut demander à l'utilisateur de s'authentifier au préalable, pour qu'il ait accès à la fonction. Oui, s'il doit s'authentifier, cela fait une étape supplémentaire, on peut ralentir l'attaque. Et s'il y a un token sur l'identification et la création de compte, on renforce la difficulté.

Demander une confirmation du mot de passe de l'utilisateur.

Sur la fonction, si elle est de niveau critique (changement de mot de passe, modification d'email de contact ... par exemple), on peut lui demander une confirmation de l'action avec son mot de passe (et potentiellement encore un jeton de sécurité sur l'action).
On peut ralentir encore l'attaque.

Limiter les requêtes par IP.

Pour certaines fonctions, on peut limiter les fonctions selon un temps donné, par exemple il n'est pas "naturel" pour un simple utilisateur de demander plus de 10 fois par heure, par exemple, si une carte cadeau (différente de la précédente) est valide.

Limiter les requêtes en un temps donné.

Il n'est pas naturel non plus (pour un humain) de pouvoir effectuer plus de 5 requêtes par seconde à un service. On peut encore faire un bannissement de l'adresse IP et/ou de la session de l'utilisateur.

Utiliser des requêtes POST plutôt que GET.

Cela force l'utilisateur à faire réellement des requêtes Ajax (possiblement bloquables par un navigateur qui détecte une CSRF). Ainsi, il sera impossible de faire une fausse requête type image, du genre:

<img src="http://example.org/API/delete-image?numeroImage=99" />

Car on passerait le numeroImage en POST et non en GET comme dans cet exemple.

Un captcha

Au moins, en validant un formulaire par un captcha, on est sûrs d'avoir à faire à un humain. Le dernier reCaptcha de Google est en effet très résistant à ce genre de pratique, face aux robots intelligents.

Les fausses solutions.

Vérifier les pages référentes

Il est simple de modifier, masquer ou faire passer une page pour référente d'une autre: on ne peut pas dire avec certitude que telle ou telle page appelée est légitime car elle provient bien de notre site-même.

Bannir les adresses IP uniquement.

La plupart du temps, les adresses IP sont masquée, via un service type VPN, TOR ou encore un proxy. Les pirates les utilisant sont malins et ne restent pas plus de quelques secondes sur le réseau.

Bannir les adresses IP de TOR.

Il y a des services qui recensent les adresses IP de TOR (ou proxy etc.) en attribuant des notes de confiance. Cela revient, selon moi, à donner des coups d'épée dans l'eau, ou encore à se protéger d'un Superman avec un simple petit bouclier:
Il est facile, rapide, peu coûteux d'obtenir une adresse IP de Tor. N'importe qui peut devenir ou utiliser un nœud, donc une personne "de confiance" peut devenir/utiliser un nœud. Les adresses IP étant tournantes, bannir une adresse IP reviendrait à accrémenter sa base de données avec des adresses IP obsolètes rapidement, et à terme on ne ferait que bannir des utilisateurs réels et potentiels client!
On préfèrera bannir les utilisateurs par IP uniquement sur une courte période (quelques heures), ou pourquoi pas mettre en place un système un peu plus intelligent, qui ne bannirait définitivement un utilisateur que si les attaques via la même IP sont répétitifs.

Avec toutes ces méthodes, on ne pourra pas totalement arrêter un pirate, c'est certain. On pourra cependant tellement le ralentir qu'il ne sera pas viable pour lui de tenter quoi que ce soit sur notre site.
On passe par exemple de plusieurs milliers de requêtes par secondes, à simplement quelques requêtes par minute. Il préfèrera sûrement passer à un site moins sécurisé.

Mon article (et les solutions que je propose), n'est pas exhaustif: si vous avez d'autres solutions, ou des modifications dans mes solutions, n'hésitez pas à m'en faire part, autant dans les commentaires qu'en me contactant.

Crédit Image: lapresse.ca