web-dev-qa-db-fra.com

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

J'essaie de comprendre le problème avec CSRF et les moyens appropriés pour l'éviter. (Ressources que j'ai lues, que je comprends et avec lesquelles je suis d'accord: Fiche de prévention de la fraude OWASP CSRF , Questions sur la CSRF .)

Si je comprends bien, la vulnérabilité autour de CSRF est introduite par l'hypothèse selon laquelle (du point de vue du serveur Web) un cookie de session valide dans une demande HTTP entrante reflète les souhaits d'un utilisateur authentifié. Mais tous les cookies du domaine d'origine sont liés de manière magique à la requête par le navigateur. Par conséquent, tout le serveur peut réellement déduire de la présence d'un cookie de session valide dans une requête, c'est que la requête provient d'un navigateur disposant d'une session authentifiée. il ne peut plus supposer quoi que ce soit à propos du code exécuté dans ce navigateur, ni s'il reflète réellement les souhaits de l'utilisateur. Pour éviter cela, vous devez inclure des informations d'authentification supplémentaires (le "jeton CSRF") dans la demande, acheminées par un autre moyen que la gestion automatique des cookies dans le navigateur. En gros, le cookie de session authentifie l'utilisateur/navigateur et le jeton CSRF authentifie le code exécuté dans le navigateur.

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 (en mutation). Le jeton CSRF effectue ensuite un aller-retour entre serveurs, ce qui prouve au serveur que la page à l'origine de la demande est approuvée par (généré par, même) ce serveur.

Sur ma question, qui concerne la méthode de transport spécifique utilisée pour ce jeton CSRF lors de ce voyage 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 d'activer Javascript. dans le client, extrayez-le du cookie et attachez-le 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 une extension de modèle côté serveur, attaché directement au code/marquage qui le renverra à le serveur, par exemple en tant qu’entrée de formulaire masquée. Cet exemple est une manière de faire plus Web 1.0 mais devrait être généralisé à un client plus lourd comme JS.)

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

Je me rends compte que les frameworks ci-dessus n'utilisent pas de cookies pour l'ensemble du voyage 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, ce qui ferme la vulnérabilité. Mais même utiliser Set-Cookie comme transport en aval est potentiellement trompeur et dangereux; le navigateur va maintenant attacher le jeton CSRF à chaque demande, y compris les demandes authentiques XSRF malveillantes; au mieux, la requête est plus volumineuse que nécessaire et au pire, une partie du code serveur bien intentionnée mais erronée pourrait en fait tenter de l’utiliser, ce qui serait vraiment mauvais. De plus, comme le destinataire réel du jeton CSRF est le Javascript côté client, cela signifie que ce cookie ne peut pas être protégé avec http uniquement. Donc, envoyer le jeton CSRF en aval dans un en-tête Set-Cookie me semble assez suboptimal.

206
metamatt

Une bonne raison, que vous avez évoquée plus tôt, est qu'une fois le cookie CSRF reçu, il est ensuite disponible pour être utilisé dans toute l'application dans le script client afin d'être utilisé à la fois dans les formulaires normaux et dans AJAX POST. Cela aura du sens dans une application lourde en JavaScript telle que celle utilisée par AngularJS (utiliser AngularJS ne nécessite pas que l'application soit une application à une seule page, il serait donc utile lorsque l'état doit circuler entre différentes requêtes 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 décrite. Celles-ci sont basées sur le modèle de jeton de synchroniseur .

Demander une approche corporelle

  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é dans la session.

Avantages:

  • Simple à mettre en œuvre.
  • Fonctionne avec AJAX.
  • Fonctionne avec des formulaires.
  • Le cookie peut réellement être HTTP uniquement .

Inconvénients:

  • Tous les formulaires doivent générer le champ masqué en HTML.
  • Tout AJAX POST doit également inclure la valeur.
  • La page doit savoir à l'avance qu'elle nécessite le jeton CSRF pour pouvoir l'inclure dans le contenu de la page. Ainsi, toutes les pages doivent contenir la valeur du jeton quelque part, ce qui risque de prendre beaucoup de temps à implémenter pour un site volumineux.

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 requête AJAX est demandée pour extraire 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é dans la session.

Avantages:

Inconvénients:

  • Ne fonctionne pas sans une demande AJAX pour obtenir la valeur d'en-tête.
  • Tous les formulaires doivent avoir la valeur ajoutée à son code HTML dynamiquement.
  • Tout AJAX POST doit également inclure la valeur.
  • La page doit d'abord lancer une demande AJAX pour obtenir le jeton CSRF. Cela signifie donc un aller-retour supplémentaire à chaque fois.
  • Pourrait aussi bien avoir simplement envoyé le jeton sur la page, ce qui sauverait la requête 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é dans la session.

Avantages:

Inconvénients:

  • Ne fonctionne pas avec les formulaires.
  • Tous les AJAX POST 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 requête AJAX est demandée pour extraire 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é dans la session.

Avantages:

Inconvénients:

  • Ne fonctionne pas avec les formulaires.
  • Tous les AJAX POST 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 une utilisation dans AJAX supplémentaire et dans des demandes de formulaire sans demande supplémentaire au serveur de récupérer le jeton CSRF.

Avantages:

  • Simple à mettre en œuvre.
  • Fonctionne avec AJAX.
  • Fonctionne avec des formulaires.
  • Ne nécessite pas nécessairement une demande AJAX pour obtenir la valeur du cookie. Toute requête 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é, comme il est stocké dans un cookie, la valeur peut être réutilisée sans demande supplémentaire.

Inconvénients:

  • Tous les formulaires doivent avoir la valeur ajoutée à son code HTML dynamiquement.
  • Tout AJAX POST 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 HTTP uniquement .

L’approche du cookie est donc assez dynamique et offre un moyen simple 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 en-tête ou en tant que valeur de formulaire). Une fois que le jeton CSRF a été reçu pour la session, il n'est pas nécessaire de le régénérer car un attaquant utilisant un exploit CSRF ne dispose d'aucune méthode pour récupérer ce jeton. Si un utilisateur malveillant tente de lire le jeton CSRF de l'utilisateur avec l'une des méthodes ci-dessus, le ( politique d'origine) l'empêche. Si un utilisateur malveillant tente de récupérer le serveur côté jeton CSRF (par exemple, via curl), 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 le cas. attaquant - il ne sera donc pas associé côté serveur à la session de la victime).

Outre le modèle de jeton de synchroniseur , il existe également la double cookie d'envoi , méthode de prévention CSRF, qui utilise bien sûr des cookies pour stocker un type de jeton CSRF. Ceci est plus facile à mettre en œuvre 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 de la manière habituelle avec la demande, mais elle est également répétée dans un champ masqué ou dans un en-tête, ce qu'un attaquant ne peut pas répliquer. ils ne peuvent pas lire la valeur en premier lieu. Cependant, il serait recommandé de choisir un autre cookie, autre que le cookie d'authentification, de manière à ce que le cookie d'authentification puisse être sécurisé en étant marqué HttpOnly. C'est donc une autre raison courante pour laquelle vous trouveriez la prévention CSRF en utilisant une méthode basée sur les cookies.

223
SilverlightFox

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 sera en mesure de provoquer une demande auprès du serveur contenant à la fois le cookie de jeton d'authentification et le cookie CSRF dans les en-têtes de la demande. Mais le serveur ne recherche pas le jeton CSRF en tant que cookie dans les en-têtes de requête, mais dans les données utiles de la requête. Et même si l'attaquant savait où placer le jeton CSRF dans la charge utile, il devrait en lire la valeur pour le mettre là. Mais la politique de l'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 la demande et l'attaquant n'a rien à faire pour le mettre là.

32
Tongfa

Ma meilleure supposition quant à la réponse: Pensez à ces 3 options pour obtenir le jeton CSRF du serveur au 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, corps de la demande (bien que démontré par le tutoriel Express que j'ai lié dans la question ), ne soit 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 devez placer le jeton dans la réponse générée peut varier considérablement (dans une entrée de formulaire masquée, dans un fragment de code JS ou dans une variable accessible par un autre code JS; peut-être même dans une URL, même si cela semble généralement mauvais) mettre des jetons CSRF). Ainsi, bien que réalisable avec une certaine personnalisation, le n ° 1 est un endroit difficile à adopter pour une approche unique.

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

Le troisième cookie, un cookie porté par un en-tête Set-Cookie, est facile à utiliser dans toutes les situations (le serveur de tout utilisateur pourra définir des en-têtes de cookie à la demande, et peu importe le type de cookie utilisé. les données sont dans le corps de la demande). Ainsi, malgré ses inconvénients, il s’agissait de la méthode la plus simple pour les cadres à mettre en œuvre à grande échelle.

8
metamatt

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

J'ai trouvé une solution qui me convient 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 rien de magique, je pense donc que ce principe peut être mis en œuvre dans tous les langages de programmation courants.

Ma solution sans cookies supplémentaires est simple:

Côté client

Stockez le jeton CSRF qui est renvoyé par le serveur après une connexion réussie dans une variable globale (si vous souhaitez utiliser le stockage Web au lieu d’un global 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 de code 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 de la connexion suivante, 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 de l'en-tête X-CSRF-TOKEN à la valeur stockée dans la session: celles-ci doivent correspondre. 

Les appels sensibles AJAX (données de formulaire POST et données JET GET), et le filtre côté serveur qui les capture, se trouvent sous le chemin/dataservice/*. Les demandes de connexion ne doivent pas toucher le filtre, elles sont donc sur un autre chemin. Les demandes de ressources HTML, CSS, JS et d'image ne figurent pas non plus sur le chemin/dataservice/* et ne sont donc pas filtrées. Ceux-ci ne contiennent rien de secret et ne peuvent pas nuire, donc c'est 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);
}
0
Stephan van Hoof