Quelques questions sur la protection CSRF

Reproduction du processus d'identification sur ZdS avec curl

a marqué ce sujet comme résolu.

Hello,

Je suis un bille en sécurité et ce soir j’ai voulu en apprendre un peu plus sur la manière dont sont faites les requêtes lorsque des données sensibles sont en jeu.

Pour cela j’ai voulu me lancer un défi qui me paraissait tout simple : m’identifier sur ZdS avec curl.

En regardant les requêtes au moment du login, j’ai trouvé le cookie csrftoken ainsi qu’un champ de formulaire masqué csrfmiddlewaretoken, j’ai cherché à quoi ils servaient et j’ai appris pleins de trucs. :)

Bref, je commence donc par simuler ma première visite sur la page de connexion (sur la beta) :

1
2
3
curl -v -c cookies.txt -b cookies.txt \
  https://beta.zestedesavoir.com/membres/connexion/ \
  | grep csrfmiddlewaretoken

… afin de stocker le csrftoken dans le fichier de cookies et récupérer la valeur de csrfmiddlewaretoken.

1. première chose, je constate que ces 2 valeurs ne correspondent pas, pourquoi ? De ce que j’avais compris de CSRF je pensais que c’était censé être le cas.

Mais bref continuons : je simule la validation du formulaire de connexion, en passant le fichier de cookie et la valeur du csrfmiddlewaretoken précédemment obtenue :

1
2
3
curl -v -c cookies.txt -b cookies.txt \
  -d '{"username":"roipoussiere","password":"xxx","csrfmiddlewaretoken":"xDeB...V0Px","remember":"on"}' \
  https://beta.zestedesavoir.com/membres/connexion/

Là bim, je me fais jeter par ZdS qui m’explique qu’il me faut le referer :

Interdit (403). La vérification CSRF a échoué. La requête a été interrompue. Vous voyez ce message parce que ce site HTTPS exige que le navigateur Web envoie un en-tête « Referer », ce qu’il n’a pas fait. Cet en-tête est exigé pour des raisons de sécurité, afin de s’assurer que le navigateur n’ait pas été piraté par un intervenant externe.

2. Je ne comprend pas trop pourquoi le Referer est requis puisque justement il y a un système de token pour se prémunir des intervenants externes.

Mais soit :

1
2
3
4
curl -v -c cookies.txt -b cookies.txt \
  --referer https://beta.zestedesavoir.com/membres/connexion/ \
  -d '{"username":"roipoussiere","password":"xxx","csrfmiddlewaretoken":"xDeB...V0Px","remember":"on"}' \
  https://beta.zestedesavoir.com/membres/connexion/

Et re-bim :

Interdit (403). La vérification CSRF a échoué. La requête a été interrompue.

3. Pourquoi donc la vérification a échouée, que manque-il dans ma requête ?

Merci d’avance pour vos explications ! :)

+0 -0

Salut,

Concernant le troisième point, ça ne peut pas marcher parce que tu envoies les données en JSON sans le préciser le Content-Type (et d’ailleurs, même avec le header Content-Type, je ne sais même pas si ZdS accepte le JSON). Par défaut, c’est toujours du application/x-www-form-urlencoded — comme sur quasiment tous les sites. Passe les données en application/x-www-form-urlencoded et ça devrait marcher.

Ah, autre chose, si tu utilises un navigateur digne de ce nom (c’est à dire une version récente de Chrome, Firefox ou Safari) tu peux voir les détails de toutes les requêtes dans les outils de développement. Sur Chrome, il y a une petite case à cocher “Preserve log” dans l’onglet “Network” qui est super pratique pour débugger les POSTs et les PUTs. Et quand on fait un clic droit sur une requête, il y a même un “Copy as cURL” :D

+2 -0

Salut !

Pour le premier point, il est clair que stocker le jeton CSRF dans un cookie ne sera pas très sûr. C’est plutôt une clé qui permet au serveur de récupérer le vrai jeton qui lui est probablement stocké en session.

Pour le second point, c’est parce que malgré le premier, récupérer un cookie n’est pas la chose la plus difficile à faire pour un hacker. Mais un hacker, s’il devait utiliser un cookie qu’il aurait récupéré, devrait le faire rapidement, et souvent n’est pas sur le même site (récupération par un "proxy", par exemple, ou un script malicieux qui forcément tourne sur un autre hébergement). D’où cette vérification.
Je pense que c’est un peu plus complexe que ça, mais c’est l’idée

+0 -0

Pour le premier point, il est clair que stocker le jeton CSRF dans un cookie ne sera pas très sûr. C’est plutôt une clé qui permet au serveur de récupérer le vrai jeton qui lui est probablement stocké en session.

Non, le vrai jeton CSRF est toujours dans un <input> caché, dans le formulaire. Regarde la source de la page de connexion par exemple, il y a un <input type='hidden' name='csrfmiddlewaretoken' value='NdOJE4Dv5vb…' /> dedans, et la valeur change bien à chaque fois que tu rafraîchis. Le cookie csrftoken doit être une petite sécurité en plus (d’ailleurs, notez qu’il ne change pas lorsqu’on rafraîchis).

+0 -0

Ce que je veux dire, c’est qu’il faut bien savoir quel est le token pour le formulaire soumis, et celui qui est dans le cookie sert à trouver celui en session, qui lui est comparé avec celui du envoyé avec le formulaire.

Mais après un peu plus de réflexion et regard dans les entrailles d’un CMS, ce qui est dans le cookie pourrait être une clé entrant dans la génération des jetons CSRF, en fait.

+0 -0

Merci pour vos explications !

@entwanne : oui j’avais fait un copié-collé en laissant le même token pour la rédaction du message, mais bien sur j’avais exécuté de nouveau la 1ère commande avant.

Ok donc il y a bien 3 systèmes de protection :

  • le csrftoken qui est généré et stocké dans un cookie à chaque fois chargement de formulaire ;
  • le csrfmiddlewaretoken qui est généré et inséré dans un champ caché de formulaire à chaque chargement ;
  • le referer pour vérifier que la requête provient bien de ZdS et pas du site d’un hacker.

C’est pas mal ! :D Mais vu que les échanges se font en https, pourquoi le csrfmiddlewaretoken ne suffirait pas à lui seul pour se prémunir d’une attaque CSRF ?

@motet-a : En effet le problème venait du fait que j’encodais les données en json, maintenant ça fonctionne correctement, merci !

À toute fin utile je partage mon script bash qui demande l’identifiant et mot de passe, se connecte au site et affiche le sessionID, dont vous pourrez probablement vous inspirer pour hacker le site du FBI :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#!/bin/bash

cookiesPath="/tmp/zds.cookies"
sdzConnectURL="https://beta.zestedesavoir.com/membres/connexion/"
htmlTemp1="/tmp/zds1.html"
htmlTemp2="/tmp/zds2.html"

echo -n "ZdS username: "
read username

echo -n "ZdS password: "
read -s password
echo

if [ -f $cookiesPath ]; then
  echo "Removing previous cookies..."
  rm $cookiesPath
fi

echo -e "\n\n *** Connexion à ZdS... ***\n\n"
curl --verbose \
     --cookie-jar $cookiesPath \
     --output $htmlTemp1 \
     $sdzConnectURL

csrfMiddlewareToken=$(grep csrfmiddlewaretoken $htmlTemp1 | cut -d " " -f 10 | cut -d "'" -f2)
csrfToken=$(grep csrftoken $cookiesPath | cut -f 7)

echo "csrf middleware token: $csrfMiddlewareToken"
echo "csrf token: $csrfToken"

echo -e "\n\n *** Envoi des identifiants... *** \n\n"
curl --verbose \
     --cookie $cookiesPath \
     --cookie-jar $cookiesPath \
     --referer $sdzConnectURL \
     --data username=$username \
     --data password=$password \
     --data csrfmiddlewaretoken=$csrfMiddlewareToken \
     --data remember=on \
     --output $htmlTemp2 \
     $sdzConnectURL
echo -e "\n\n *** Fait. *** \n\n"

errorMessage=$(awk '/alert-box/{getline; print}' $htmlTemp2 | awk -F'[<>]' '{print $3}' )
csrfToken=$(grep csrftoken $cookiesPath | cut -f 7)
sessionID=$(grep sessionid $cookiesPath | cut -f 7)

echo $errorMessage
echo "csrf token: $csrfToken"
echo "session id: $sessionID"
+2 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte