Pourquoi est-il courant de placer des jetons de prévention CSRF dans les cookies?

284

J'essaie de comprendre tout le problème du CSRF et les moyens appropriés de le prévenir. (Ressources que j'ai lues, comprises et approuvées: Aide-mémoire sur la prévention de la CSRF de l'OWASP , Questions sur la CSRF .)

Si je comprends bien, la vulnérabilité autour de CSRF est introduite par l'hypothèse que (du point de vue du serveur Web) un cookie de session valide dans une requête HTTP entrante reflète les souhaits d'un utilisateur authentifié. Mais tous les cookies du domaine d'origine sont magiquement attachés à la demande par le navigateur, donc tout ce que le serveur peut inférer de la présence d'un cookie de session valide dans une demande est que la demande provient d'un navigateur qui a une session authentifiée; il ne peut plus rien supposer du codeen cours d'exécution dans ce navigateur, ou s'il reflète vraiment les souhaits des utilisateurs. Le moyen d'empêcher cela est d'inclure des informations d'authentification supplémentaires (le "jeton CSRF") dans la demande, véhiculées par un autre moyen que la gestion automatique des cookies du navigateur. En gros, le cookie de session authentifie l'utilisateur / navigateur et le jeton CSRF authentifie le code exécuté dans le navigateur.

Donc, en résumé, si vous utilisez un cookie de session pour authentifier les utilisateurs de votre application Web, vous devez également ajouter un jeton CSRF à chaque réponse et exiger un jeton CSRF correspondant dans chaque demande (de mutation). Le jeton CSRF effectue ensuite un aller-retour du serveur au navigateur pour revenir au serveur, prouvant au serveur que la page faisant la demande est approuvée par (générée par, même) ce serveur.

Passons à ma question, qui concerne la méthode de transport spécifique utilisée pour ce jeton CSRF lors de cet aller-retour.

Il semble courant (par exemple dans AngularJS , Django , Rails ) d'envoyer le jeton CSRF du serveur au client sous forme de cookie (c'est-à-dire dans un en-tête Set-Cookie), puis de laisser Javascript dans le client le gratter du cookie et le joindre en tant qu'en-tête XSRF-TOKEN distinct à renvoyer au serveur.

(Une autre méthode est celle recommandée par exemple par Express , où le jeton CSRF généré par le serveur est inclus dans le corps de la réponse via l'expansion du modèle côté serveur, attaché directement au code / balisage qui le fournira au serveur, par exemple en tant qu'entrée de formulaire masquée. Cet exemple est une façon de faire plus Web 1.0, mais généraliserait bien à un client plus lourd en JS.)

Pourquoi est-il si courant d'utiliser Set-Cookie comme transport en aval pour le jeton CSRF / pourquoi est-ce une bonne idée? J'imagine que les auteurs de tous ces cadres ont soigneusement examiné leurs options et ne se sont pas trompés. Mais à première vue, l'utilisation de cookies pour contourner ce qui est essentiellement une limitation de conception des cookies semble idiote. En fait, si vous utilisiez des cookies comme transport aller-retour (Set-Cookie: en-tête en aval pour que le serveur indique au navigateur le token CSRF, et Cookie: en-tête en amont pour que le navigateur le retourne au serveur), vous réintroduiriez la vulnérabilité que vous essaient de réparer.

Je me rends compte que les cadres ci-dessus n'utilisent pas de cookies pour l'ensemble du trajet aller-retour pour le jeton CSRF; ils utilisent Set-Cookie en aval, puis quelque chose d'autre (par exemple un en-tête X-CSRF-Token) en amont, et cela ferme la vulnérabilité. Mais même utiliser Set-Cookie comme transport en aval est potentiellement trompeur et dangereux; le navigateur attachera maintenant le jeton CSRF à chaque demande, y compris les demandes XSRF malveillantes authentiques; au mieux, cela rend la demande plus importante qu'elle ne devrait l'être et au pire, un morceau de code de serveur bien intentionné mais mal orienté pourrait en fait essayer de l'utiliser, ce qui serait vraiment mauvais. De plus, étant donné que le destinataire prévu du jeton CSRF est le Javascript côté client, cela signifie que ce cookie ne peut pas être protégé avec http uniquement.

metamatt
la source
C'est une excellente question de trouver le bon endroit.
KTA

Réponses:

263

Une bonne raison, que vous avez en quelque sorte abordée, est qu'une fois que le cookie CSRF a été reçu, il est ensuite disponible pour une utilisation dans toute l'application dans le script client pour une utilisation à la fois dans les formulaires réguliers et les POST AJAX. Cela aura du sens dans une application JavaScript lourde telle que celle employée par AngularJS (l'utilisation d'AngularJS ne nécessite pas que l'application soit une application d'une seule page, il serait donc utile lorsque l'état doit circuler entre différentes demandes de page où la valeur CSRF ne peut normalement pas persister dans le navigateur).

Considérez les scénarios et processus suivants dans une application typique pour certains avantages et inconvénients de chaque approche que vous décrivez. Ceux-ci sont basés sur modèle de jeton de synchroniseur .

Demande d'approche du corps

  1. L'utilisateur se connecte avec succès.
  2. Le serveur émet un cookie d'authentification.
  3. L'utilisateur clique pour accéder à un formulaire.
  4. S'il n'est pas encore généré pour cette session, le serveur génère un jeton CSRF, le stocke dans la session utilisateur et le renvoie dans un champ masqué.
  5. L'utilisateur soumet le formulaire.
  6. Le serveur vérifie que le champ caché correspond au jeton stocké de la session.

Avantages:

  • Simple à mettre en œuvre.
  • Fonctionne avec AJAX.
  • Fonctionne avec les formulaires.
  • Le cookie peut en fait être uniquement HTTP .

Désavantages:

  • Tous les formulaires doivent afficher le champ masqué en HTML.
  • Tout POST AJAX doit également inclure la valeur.
  • La page doit savoir à l'avance qu'elle nécessite le jeton CSRF afin de pouvoir l'inclure dans le contenu de la page, de sorte que toutes les pages doivent contenir la valeur du jeton quelque part, ce qui pourrait prendre du temps à mettre en œuvre pour un grand site.

En-tête HTTP personnalisé (en aval)

  1. L'utilisateur se connecte avec succès.
  2. Le serveur émet un cookie d'authentification.
  3. L'utilisateur clique pour accéder à un formulaire.
  4. La page se charge dans le navigateur, puis une demande AJAX est effectuée pour récupérer le jeton CSRF.
  5. Le serveur génère un jeton CSRF (s'il n'est pas déjà généré pour la session), le stocke dans la session utilisateur et le renvoie dans un en-tête.
  6. L'utilisateur soumet le formulaire (le jeton est envoyé via un champ caché).
  7. Le serveur vérifie que le champ caché correspond au jeton stocké de la session.

Avantages:

Désavantages:

  • Ne fonctionne pas sans demande AJAX pour obtenir la valeur d'en-tête.
  • Tous les formulaires doivent avoir la valeur ajoutée à son code HTML de manière dynamique.
  • Tout POST AJAX doit également inclure la valeur.
  • La page doit d'abord faire une demande AJAX pour obtenir le jeton CSRF, cela signifie donc un aller-retour supplémentaire à chaque fois.
  • Pourrait tout aussi bien avoir simplement sorti le jeton sur la page qui enregistrerait la demande supplémentaire.

En-tête HTTP personnalisé (en amont)

  1. L'utilisateur se connecte avec succès.
  2. Le serveur émet un cookie d'authentification.
  3. L'utilisateur clique pour accéder à un formulaire.
  4. S'il n'est pas encore généré pour cette session, le serveur génère un jeton CSRF, le stocke dans la session utilisateur et le renvoie quelque part dans le contenu de la page.
  5. L'utilisateur soumet le formulaire via AJAX (le jeton est envoyé via l'en-tête).
  6. Le serveur vérifie que l'en-tête personnalisé correspond au jeton stocké de la session.

Avantages:

Désavantages:

  • Ne fonctionne pas avec les formulaires.
  • Tous les POST AJAX doivent inclure l'en-tête.

En-tête HTTP personnalisé (en amont et en aval)

  1. L'utilisateur se connecte avec succès.
  2. Le serveur émet un cookie d'authentification.
  3. L'utilisateur clique pour accéder à un formulaire.
  4. La page se charge dans le navigateur, puis une demande AJAX est effectuée pour récupérer le jeton CSRF.
  5. Le serveur génère un jeton CSRF (s'il n'est pas déjà généré pour la session), le stocke dans la session utilisateur et le renvoie dans un en-tête.
  6. L'utilisateur soumet le formulaire via AJAX (le jeton est envoyé via l'en-tête).
  7. Le serveur vérifie que l'en-tête personnalisé correspond au jeton stocké de la session.

Avantages:

Désavantages:

  • Ne fonctionne pas avec les formulaires.
  • Tous les POST AJAX doivent également inclure la valeur.
  • La page doit d'abord faire une demande AJAX pour obtenir le jeton CRSF, cela signifie donc un aller-retour supplémentaire à chaque fois.

Set-Cookie

  1. L'utilisateur se connecte avec succès.
  2. Le serveur émet un cookie d'authentification.
  3. L'utilisateur clique pour accéder à un formulaire.
  4. Le serveur génère un jeton CSRF, le stocke dans la session utilisateur et le renvoie dans un cookie.
  5. L'utilisateur soumet le formulaire via AJAX ou via le formulaire HTML.
  6. Le serveur vérifie que l'en-tête personnalisé (ou le champ de formulaire masqué) correspond au jeton stocké de la session.
  7. Le cookie est disponible dans le navigateur pour être utilisé dans des demandes AJAX et de formulaire supplémentaires sans demande supplémentaire au serveur pour récupérer le jeton CSRF.

Avantages:

  • Simple à mettre en œuvre.
  • Fonctionne avec AJAX.
  • Fonctionne avec les formulaires.
  • Ne nécessite pas nécessairement une demande AJAX pour obtenir la valeur du cookie. Toute demande HTTP peut la récupérer et elle peut être ajoutée à toutes les demandes de formulaires / AJAX via JavaScript.
  • Une fois que le jeton CSRF a été récupéré, car il est stocké dans un cookie, la valeur peut être réutilisée sans demandes supplémentaires.

Désavantages:

  • Tous les formulaires doivent avoir la valeur ajoutée à son code HTML de manière dynamique.
  • Tout POST AJAX doit également inclure la valeur.
  • Le cookie sera soumis pour chaque demande (c'est-à-dire tous les GET pour les images, CSS, JS, etc., qui ne sont pas impliqués dans le processus CSRF) augmentant la taille de la demande.
  • Le cookie ne peut pas être uniquement HTTP .

L'approche des cookies est donc assez dynamique, offrant un moyen facile de récupérer la valeur du cookie (toute requête HTTP) et de l'utiliser (JS peut ajouter la valeur à n'importe quel formulaire automatiquement et il peut être utilisé dans les requêtes AJAX soit comme en-tête, soit comme un forme). Une fois le jeton CSRF reçu pour la session, il n'est pas nécessaire de le régénérer car un attaquant utilisant un exploit CSRF n'a pas de méthode pour récupérer ce jeton. Si un utilisateur malveillant essaie de lire le jeton CSRF de l'utilisateur dans l'une des méthodes ci-dessus, cela sera empêché par la même politique d'origine . Si un utilisateur malveillant essaie de récupérer le côté serveur de jeton CSRF (par exemple viacurl), ce jeton ne sera pas associé au même compte d'utilisateur car le cookie de session d'authentification de la victime sera absent de la demande (ce serait l'attaquant - il ne sera donc pas associé côté serveur à la session de la victime).

En plus du modèle de jeton de synchronisation, il existe également le cookie de double soumissionMéthode de prévention CSRF, qui utilise bien sûr des cookies pour stocker un type de jeton CSRF. Ceci est plus facile à implémenter car il ne nécessite aucun état côté serveur pour le jeton CSRF. Le jeton CSRF pourrait en fait être le cookie d'authentification standard lors de l'utilisation de cette méthode, et cette valeur est soumise via les cookies comme d'habitude avec la demande, mais la valeur est également répétée dans un champ ou un en-tête masqué, dont un attaquant ne peut pas se répliquer comme ils ne peuvent pas lire la valeur en premier lieu. Il serait toutefois recommandé de choisir un autre cookie, autre que le cookie d'authentification, afin que le cookie d'authentification puisse être sécurisé en étant marqué HttpOnly. C'est donc une autre raison courante pour laquelle vous trouverez la prévention CSRF en utilisant une méthode basée sur les cookies.

SilverlightFox
la source
7
Je ne suis pas sûr de comprendre comment "la demande AJAX est effectuée pour récupérer le jeton CSRF" (étape 4 dans les deux sections "en-tête personnalisé: en aval") peut être effectuée en toute sécurité; puisqu'il s'agit d'une demande distincte, le serveur ne sait pas de qui il vient; comment sait-il qu'il est sûr de divulguer le jeton CSRF? Il me semble que si vous ne pouvez pas retirer le jeton du chargement de page initial, vous perdez (ce qui fait de l'en-tête de réponse en aval personnalisé un non-démarreur, malheureusement).
metamatt
6
Parce que le faussaire n'aura pas le cookie de session. Ils peuvent avoir leur propre cookie de session, mais comme le jeton CSRF est associé à une session, leur jeton CSRF ne correspondra pas à celui de la victime.
SilverlightFox
32
Dans ma compréhension de l'attaque CSRF, le faussaire a mon cookie de session. Eh bien, ils ne voient pas réellement le cookie, mais ils ont la possibilité de le fournir dans leurs demandes falsifiées, car les demandes proviennent de mon navigateur et mon navigateur fournit mon cookie de session. Du point de vue du serveur, le cookie de session ne peut donc pas à lui seul distinguer une demande légitime d'une demande falsifiée. C'est en fait l'attaque que nous essayons d'empêcher. BTW merci pour votre patience à discuter de cela, surtout si je suis confus à ce sujet.
metamatt
8
Ils ont la possibilité de fournir le cookie d'authentification, mais ils ne peuvent pas lire la réponse qui contient le jeton CSRF.
SilverlightFox
8
@metamatt Désolé pour la nécro, mais je le ferai pour les gens qui s'y promènent. D'après ce que je comprends, l'attaquant n'a généralement pas accès à la réponse. CSRF est principalement utilisé pour provoquer des effets secondaires , plutôt que de recueillir directement des données. Par exemple, un script d'attaque CSRF peut forcer un utilisateur privilégié à augmenter les privilèges de l'attaquant, désactiver un paramètre de sécurité ou forcer un utilisateur paypal connecté à envoyer un transfert à une adresse e-mail spécifique. Dans aucun de ces cas, l'attaquant ne se soucie de la réponse, qui est toujours envoyée au navigateur de la victime; seulement le résultat de l'attaque.
jonathanbruder
61

L'utilisation d'un cookie pour fournir le jeton CSRF au client ne permet pas une attaque réussie car l'attaquant ne peut pas lire la valeur du cookie et ne peut donc pas le placer là où la validation CSRF côté serveur l'exige.

L'attaquant pourra provoquer une requête auprès du serveur avec à la fois le cookie de jeton d'authentification et le cookie CSRF dans les en-têtes de requête. Mais le serveur ne recherche pas le jeton CSRF en tant que cookie dans les en-têtes de demande, il recherche la charge utile de la demande. Et même si l'attaquant sait où placer le jeton CSRF dans la charge utile, il devrait lire sa valeur pour le mettre là. Mais la politique d'origine croisée du navigateur empêche la lecture de toute valeur de cookie du site Web cible.

La même logique ne s'applique pas au cookie de jeton d'authentification, car le serveur l'attend dans les en-têtes de demande et l'attaquant n'a rien à faire de spécial pour le mettre là.

Tongfa
la source
Certes, un attaquant n'a pas besoin de lire le cookie en premier lieu. Ils peuvent simplement insérer une image sur le site piraté avec src='bank.com/transfer?to=hacker&amount=1000lequel le navigateur demandera, avec les cookies associés pour ce site ( bank.com)?
developius
2
CSRF sert à valider l'utilisateur côté client et non à protéger le site en général contre un compromis côté serveur comme vous le suggérez.
Tongfa
2
@developius envoyer le cookie ne suffit pas pour satisfaire la protection CSRF. Le cookie contient le jeton csrf, tel qu'il est envoyé par le serveur. Le client légitime doit lire le jeton csrf du cookie, puis le transmettre dans la demande quelque part, comme un en-tête ou dans la charge utile. La protection CSRF vérifie que la valeur du cookie correspond à la valeur de la demande, sinon la demande est rejetée. Par conséquent, l'attaquant doit lire le cookie.
Will M.
1
Cette réponse allait très bien à la question de l'affiche originale et était très claire. +1 Merci.
java-addict301
@Tongfa - merci, cela m'a aidé à mieux comprendre. Ai-je raison de supposer que le jeton CSRF ne doit PAS être placé dans l'en-tête? ça doit être quelque part dans le corps?
zerohedge
10

Ma meilleure supposition quant à la réponse: considérez ces 3 options pour savoir comment obtenir le jeton CSRF du serveur vers le navigateur.

  1. Dans le corps de la demande (pas un en-tête HTTP).
  2. Dans un en-tête HTTP personnalisé, pas Set-Cookie.
  3. En tant que cookie, dans un en-tête Set-Cookie.

Je pense que le premier, le corps de la demande (bien que démontré par le tutoriel Express que j'ai lié dans la question ), n'est pas aussi portable dans une grande variété de situations; tout le monde ne génère pas chaque réponse HTTP de manière dynamique; où vous finissez par avoir besoin de mettre le jeton dans la réponse générée peut varier considérablement (dans une entrée de formulaire cachée; dans un fragment de code JS ou une variable accessible par un autre code JS; peut-être même dans une URL bien que cela semble généralement un mauvais endroit pour mettre des jetons CSRF). Ainsi, bien qu'il soit réalisable avec une certaine personnalisation, le n ° 1 est un endroit difficile à faire pour une approche unique.

Le second, en-tête personnalisé, est attrayant mais ne fonctionne pas réellement, car bien que JS puisse obtenir les en-têtes d'un XHR qu'il a invoqué, il ne peut pas obtenir les en-têtes de la page à partir de laquelle il a été chargé .

Cela laisse le troisième, un cookie porté par un en-tête Set-Cookie, comme une approche facile à utiliser dans toutes les situations (le serveur de n'importe qui pourra définir des en-têtes de cookie par demande, et peu importe le type de les données se trouvent dans le corps de la demande). Malgré ses inconvénients, il s'agissait de la méthode la plus simple à mettre en œuvre largement pour les frameworks.

metamatt
la source
7
Je pourrais dire l'évidence, cela signifie que le cookie ne peut pas être httponly correct?
Photon
1
uniquement pour les requêtes ajax (où JS a besoin de connaître la valeur du cookie csrf afin de le renvoyer à la prochaine requête dans le deuxième canal (soit en tant que données de formulaire ou en-tête)). Il n'y a aucune raison d'exiger que le jeton csrf soit HttpOnly si le cookie de session est déjà HttpOnly (pour se protéger contre XSS) car le jeton csrf n'a pas de valeur en soi sans session associée.
cowbert
2

Outre le cookie de session (qui est un peu standard), je ne veux pas utiliser de cookies supplémentaires.

J'ai trouvé une solution qui fonctionne pour moi lors de la création d'une application Web à page unique (SPA), avec de nombreuses demandes AJAX. Remarque: J'utilise Java côté serveur et JQuery côté client, mais pas de choses magiques, donc je pense que ce principe peut être implémenté dans tous les langages de programmation populaires.

Ma solution sans cookies supplémentaires est simple:

Côté client

Stockez le jeton CSRF qui est retourné par le serveur après une connexion réussie dans une variable globale (si vous souhaitez utiliser le stockage Web au lieu d'une amende globale, bien sûr). Demandez à JQuery de fournir un en-tête X-CSRF-TOKEN dans chaque appel AJAX.

La page principale "index" contient cet extrait JavaScript:

// Intialize global variable CSRF_TOKEN to empty sting. 
// This variable is set after a succesful login
window.CSRF_TOKEN = '';

// the supplied callback to .ajaxSend() is called before an Ajax request is sent
$( document ).ajaxSend( function( event, jqXHR ) {
    jqXHR.setRequestHeader('X-CSRF-TOKEN', window.CSRF_TOKEN);
}); 

Du côté serveur

Lors d'une connexion réussie, créez un jeton CSRF aléatoire (et suffisamment long), stockez-le dans la session côté serveur et renvoyez-le au client. Filtrez certaines demandes entrantes (sensibles) en comparant la valeur d'en-tête X-CSRF-TOKEN à la valeur stockée dans la session: celles-ci doivent correspondre.

Les appels AJAX sensibles (données de formulaire POST et données GET JSON), et le filtre côté serveur les interceptant, se trouvent sous un chemin / dataservice / *. Les demandes de connexion ne doivent pas atteindre le filtre, elles sont donc sur un autre chemin. Les demandes de ressources HTML, CSS, JS et images ne sont pas non plus sur le chemin / dataservice / *, donc pas filtrées. Ceux-ci ne contiennent rien de secret et ne peuvent pas faire de mal, donc c'est très bien.

@WebFilter(urlPatterns = {"/dataservice/*"})
...
String sessionCSRFToken = req.getSession().getAttribute("CSRFToken") != null ? (String) req.getSession().getAttribute("CSRFToken") : null;
if (sessionCSRFToken == null || req.getHeader("X-CSRF-TOKEN") == null || !req.getHeader("X-CSRF-TOKEN").equals(sessionCSRFToken)) {
    resp.sendError(401);
} else
    chain.doFilter(request, response);
}   
Stephan van Hoof
la source
Je pense que vous voudriez CSRF sur une demande de connexion. Vous semblez également utiliser le jeton CSRF comme jeton de session de connexion. Cela fonctionne également pour les avoir en tant que jetons distincts, et vous pouvez ensuite utiliser CSRF sur n'importe quel point de terminaison, que l'utilisateur soit connecté ou non.
Tongfa