Qu'est-ce qu'un jeton CSRF? Quelle est son importance et comment ça marche?

629

J'écris une application (Django, ça arrive) et je veux juste une idée de ce qu'est réellement un "token CSRF" et comment il protège les données. Les données de publication ne sont-elles pas sûres si vous n'utilisez pas de jetons CSRF?

Shawn
la source
13
Il s'agit d'un jeton secret spécifique à l'utilisateur dans toutes les soumissions de formulaires et les URL d'effets secondaires pour empêcher les falsifications de demandes intersites. Plus d'infos ici: en.wikipedia.org/wiki/Cross-site_request_forgery
Robert Harvey
1
semble qu'il y ait une fine ligne entre la protection d' une question et son interdiction pour être trop large: D
anton1980
2
Extrait de la triche de prévention OWASP Cross-Site Request Forgery (CSRF) : " Cross-Site Scripting n'est pas nécessaire pour que CSRF fonctionne. Cependant, toute vulnérabilité de script intersite peut être utilisée pour déjouer toutes les techniques d'atténuation CSRF [...]. En effet, une charge utile XSS peut simplement lire n'importe quelle page du site à l'aide d'un XMLHttpRequest [...]. Il est impératif qu'aucune vulnérabilité XSS ne soit présente pour garantir que les défenses CSRF ne peuvent pas être contournées. "
toraritte

Réponses:

1497

Cross-Site Request Forgery (CSRF) en quelques mots

  • Supposons que vous êtes actuellement connecté à votre banque en ligne à www.mybank.com
  • Supposons qu'un transfert d'argent mybank.comentraînera une demande (conceptuelle) du formulaire http://www.mybank.com/transfer?to=<SomeAccountnumber>;amount=<SomeAmount>. (Votre numéro de compte n'est pas nécessaire, car il est impliqué par votre connexion.)
  • Vous visitez www.cute-cat-pictures.org, sans savoir qu'il s'agit d'un site malveillant.
  • Si le propriétaire de ce site connaît la forme de la demande ci-dessus (facile!) Et devine correctement que vous êtes connecté mybank.com(nécessite un peu de chance!), Il pourrait inclure sur sa page une demande comme http://www.mybank.com/transfer?to=123456;amount=10000(où 123456est le numéro de son compte aux îles Caïmans) et 10000est un montant que vous pensiez auparavant être heureux de posséder).
  • Vous avez récupéré cette www.cute-cat-pictures.orgpage, donc votre navigateur fera cette demande.
  • Votre banque ne peut pas reconnaître cette origine de la demande: votre navigateur Web enverra la demande avec votre www.mybank.comcookie et elle semblera parfaitement légitime. Voilà votre argent!

C'est le monde sans jetons CSRF .

Maintenant, pour le meilleur avec les jetons CSRF :

  • La demande de transfert est prolongé avec un troisième argument: http://www.mybank.com/transfer?to=123456;amount=10000;token=31415926535897932384626433832795028841971.
  • Ce jeton est un énorme nombre aléatoire, impossible à deviner, qui mybank.comsera inclus sur leur propre page Web lorsqu'ils vous le serviront. C'est différent chaque fois qu'ils servent une page à qui que ce soit.
  • L'attaquant n'est pas en mesure de deviner le jeton, n'est pas en mesure de convaincre votre navigateur Web de le rendre (si le navigateur fonctionne correctement ...), et donc l'attaquant ne sera pas en mesure de créer une demande valide, car les demandes avec le un mauvais jeton (ou aucun jeton) sera refusé par www.mybank.com.

Résultat: vous conservez vos 10000unités monétaires. Je vous suggère d'en faire don à Wikipedia.

(Votre kilométrage peut varier.)

MODIFIER un commentaire qui mérite d'être lu:

Il serait intéressant de noter que le script de www.cute-cat-pictures.orgnormalement n'a pas accès à votre jeton anti-CSRF à www.mybank.comcause du contrôle d'accès HTTP. Cette note est importante pour certaines personnes qui envoient de manière déraisonnable un en-tête Access-Control-Allow-Origin: *pour chaque réponse de site Web sans savoir à quoi elles servent, simplement parce qu'elles ne peuvent pas utiliser l'API d'un autre site Web.

Lutz Prechelt
la source
36
Et évidemment, le jeton serait idéalement appelé jeton anti -CSRF, mais le nom est probablement assez compliqué tel qu'il est.
Lutz Prechelt
3
@LutzPrechelt merci. pourquoi javascript ne peut-il pas obtenir de jetons d'authenticité à partir du navigateur?
BKSpurgeon
72
Il serait intéressant de noter que le script de www.cute-cat-pictures.orgnormalement n'a pas accès à votre jeton anti-CSRF à www.mybank.comcause du contrôle d'accès HTTP. Cette note est importante pour certaines personnes qui envoient de manière déraisonnable un en-tête Access-Control-Allow-Origin: *pour chaque réponse de site Web sans savoir à quoi elles servent, simplement parce qu'elles ne peuvent pas utiliser l'API d'un autre site Web.
SOFe
9
@AugustinRiedinger Si l'attaquant ouvre la page Web sur son ordinateur - puisqu'il n'a pas le cookie de l'utilisateur connecté - il ne recevra pas le jeton csrf correspondant (chaque jeton csrf ne doit être valide que pour une session utilisateur spécifique). Si l'attaquant tente de charger la page Web contenant le jeton sur l'ordinateur de l'utilisateur, avec un script placé sur le site Web cute-cat-pictures, le navigateur l'empêchera de lire le www.mybank.com (et le jeton) en raison de la même politique d'origine.
Marcel
13
@LutzPrechelt Je pense qu'il ne suffit pas que le jeton soit toujours différent, il doit être associé à une session et le serveur doit vérifier que le jeton qu'il reçoit a été généré pour une session que le serveur identifie par le cookie reçu. Sinon, le pirate peut simplement visiter mybank lui-même et obtenir un jeton valide. Donc, si vous utilisez un nouveau jeton avec chaque formulaire, vous devez l'enregistrer associé à l'identifiant de session sur le serveur. Il est probablement plus facile d'utiliser le même jeton par session.
Marcel
222

Oui, les données de publication sont en sécurité. Mais l'origine de ces données ne l'est pas. De cette façon, quelqu'un peut inciter un utilisateur avec JS à se connecter à votre site, tout en parcourant la page Web de l'attaquant.

Afin d'éviter cela, django enverra une clé aléatoire à la fois dans le cookie et les données du formulaire. Ensuite, lorsque les utilisateurs effectueront des POST, il vérifiera si deux clés sont identiques. Dans le cas où l'utilisateur est trompé, le site Web tiers ne peut pas obtenir les cookies de votre site, provoquant ainsi une erreur d'authentification.

Dmitry Shevchenko
la source
@DmitryShevchenko Salut, essayant de comprendre en quoi cette méthode de cookie + saisie de formulaire est différente de la simple validation du référent côté serveur? Tous les exemples que je trouve sont liés à un pirate qui incite l'utilisateur à publier de son site vers le site réel.
Ethan
Ok, j'ai découvert pourquoi le référent n'est pas utilisé. Il est bloqué dans de nombreux cas, car il est parfois considéré comme contenant des informations sensibles. Les entreprises et leurs mandataires le font généralement. Cependant, si HTTPS est utilisé, il y a plus de chances qu'il ne soit pas bloqué.
Ethan
4
Il est facile de changer de référent, je ne dirais pas que c'est une information fiable. Le jeton CSRF, cependant, est généré à l'aide de la clé secrète du serveur et généralement lié à l'utilisateur
Dmitry Shevchenko
1
Je ne comprends pas vraiment pourquoi c'est une menace pour la sécurité. L'utilisateur sera connecté à un autre site ... mais le site d'origine n'aura aucun moyen de récupérer ces informations. Droite?
Aakil Fernandes
6
Eh bien, supposons que j'injecte une iframe malveillante de " bank.com/transfer?from=x&to=y " dans, disons, Facebook.com. Si vous êtes client de bank.com et que vous allez sur Facebook, cet iframe chargera la page de la banque, avec vos cookies (car le navigateur les enverra vers un domaine connu) et effectuera un transfert d'argent. Sans que tu saches quoi que ce soit.
Dmitry Shevchenko
74

Le site génère un jeton unique lorsqu'il crée la page de formulaire. Ce jeton est requis pour publier / récupérer des données sur le serveur.

Étant donné que le jeton est généré par votre site et fourni uniquement lorsque la page avec le formulaire est générée, certains autres sites ne peuvent pas imiter vos formulaires - ils n'auront pas le jeton et ne pourront donc pas publier sur votre site.

tkone
la source
10
Un utilisateur peut-il récupérer la sortie du jeton dans la source, récupérer le cookie qui lui est envoyé, puis l'envoyer à partir d'un site tiers?
Jack Marchetti
9
@JackMarchetti oui. mais cela coûterait cher car chaque fois que vous voudriez soumettre le formulaire à partir d'un site tiers, vous devrez charger la page et analyser le jeton. Les jetons CSRF devraient être idéalement couplés à d'autres formes de sécurité si vous êtes préoccupé par ce vecteur d'attaque
tkone
4
J'ai la même question que @JackMarchetti, ce qui n'est pas clair est - si le jeton CSRF change à chaque connexion. S'il reste le même, qu'est-ce qui empêcherait un attaquant de se connecter, de saisir le jeton de demande, puis d'insérer ce jeton dans l'attaque?
Paul Preibisch
7
@PaulPreibisch, cela devrait changer à chaque chargement de page - pas à chaque connexion. De cette façon, l'attaquant devrait demander la page chaque fois qu'il souhaite soumettre le formulaire. Rend beaucoup plus difficile.
tkone
9
@tkone, cela ne rend pas vraiment les choses beaucoup plus difficiles. Si cela double simplement la quantité d'efforts et de temps. Il n'ajoute aucun type de traitement prohibitif. L'astuce consiste également à associer le jeton CSRF à un cookie spécifique au domaine et à envoyer ce cookie avec le formulaire. Le cookie et les données de publication du formulaire devraient être envoyés au serveur sur la demande POST. De cette façon, il faudrait une attaque de détournement de cookies pour pouvoir émuler une demande légitime.
Pedro Cordeiro
56

Le blog Cloud Under a une bonne explication des jetons CSRF.

Imaginez que vous disposiez d'un site Web comme un Twitter simplifié, hébergé sur a.com. Les utilisateurs connectés peuvent saisir du texte (un tweet) dans un formulaire qui est envoyé au serveur en tant que demande POST et publié lorsqu'ils cliquent sur le bouton Envoyer. Sur le serveur, l'utilisateur est identifié par un cookie contenant son identifiant de session unique, afin que votre serveur sache qui a posté le Tweet.

Le formulaire pourrait être aussi simple que cela:

 <form action="http://a.com/tweet" method="POST">
   <input type="text" name="tweet">
   <input type="submit">
 </form> 

Imaginez maintenant qu'un méchant copie et colle ce formulaire sur son site Web malveillant, disons b.com. Le formulaire fonctionnerait toujours. Tant qu'un utilisateur est connecté à votre Twitter (c'est-à-dire qu'il a un cookie de session valide pour a.com), la demande POST sera envoyée http://a.com/tweetet traitée comme d'habitude lorsque l'utilisateur clique sur le bouton Soumettre.

Jusqu'à présent, ce n'est pas un gros problème tant que l'utilisateur est informé de ce que fait exactement le formulaire, mais que se passe-t-il si notre méchant peaufine le formulaire comme ceci:

 <form action="https://example.com/tweet" method="POST">
   <input type="hidden" name="tweet" value="Buy great products at http://b.com/#iambad">
   <input type="submit" value="Click to win!">
 </form> 

Maintenant, si l'un de vos utilisateurs se retrouve sur le site Web du méchant et clique sur "Cliquez pour gagner!" , le formulaire est soumis à votre site Web, l'utilisateur est correctement identifié par l'ID de session dans le cookie et le Tweet caché est publié.

Si notre méchant était encore pire, il obligerait l'utilisateur innocent à soumettre ce formulaire dès qu'il ouvrirait sa page Web en utilisant JavaScript, peut-être même complètement caché dans une iframe invisible. Il s'agit essentiellement d'une falsification de demande intersite.

Un formulaire peut être facilement envoyé de partout à partout. C'est généralement une caractéristique commune, mais il existe de nombreux autres cas où il est important de n'autoriser l'envoi d'un formulaire que depuis le domaine auquel il appartient.

Les choses sont encore pires si votre application Web ne fait pas de distinction entre les requêtes POST et GET (par exemple en PHP en utilisant $ _REQUEST au lieu de $ _POST). Ne fais pas ça! Les demandes de modification des données peuvent être soumises aussi facilement que <img src="http://a.com/tweet?tweet=This+is+really+bad">, intégrées dans un site Web malveillant ou même un e-mail.

Comment puis-je m'assurer qu'un formulaire ne peut être soumis qu'à partir de mon propre site Web? C'est là qu'intervient le jeton CSRF. Un jeton CSRF est une chaîne aléatoire, difficile à deviner. Sur une page avec un formulaire que vous souhaitez protéger, le serveur générerait une chaîne aléatoire, le jeton CSRF, l'ajouterait au formulaire en tant que champ masqué et le mémoriserait en quelque sorte, soit en le stockant dans la session, soit en définissant un cookie contenant la valeur. Maintenant, le formulaire ressemblerait à ceci:

    <form action="https://example.com/tweet" method="POST">
      <input type="hidden" name="csrf-token" value="nc98P987bcpncYhoadjoiydc9ajDlcn">
      <input type="text" name="tweet">
      <input type="submit">
    </form> 

Lorsque l'utilisateur soumet le formulaire, le serveur doit simplement comparer la valeur du champ publié csrf-token (le nom n'a pas d'importance) avec le jeton CSRF mémorisé par le serveur. Si les deux chaînes sont égales, le serveur peut continuer à traiter le formulaire. Sinon, le serveur doit immédiatement arrêter de traiter le formulaire et répondre avec une erreur.

Pourquoi ça marche? Il y a plusieurs raisons pour lesquelles le méchant de notre exemple ci-dessus n'est pas en mesure d'obtenir le jeton CSRF:

La copie du code source statique de notre page vers un site Web différent serait inutile, car la valeur du champ caché change avec chaque utilisateur. Sans le site Web du méchant connaissant le jeton CSRF de l'utilisateur actuel, votre serveur rejetterait toujours la demande POST.

Parce que la page malveillante du méchant est chargée par le navigateur de votre utilisateur à partir d'un domaine différent (b.com au lieu de a.com), le méchant n'a aucune chance de coder un JavaScript, qui charge le contenu et donc le jeton CSRF actuel de notre utilisateur à partir de votre site web. En effet, les navigateurs Web n'autorisent pas les demandes AJAX interdomaines par défaut.

Le méchant n'est pas non plus en mesure d'accéder au cookie défini par votre serveur, car les domaines ne correspondraient pas.

Quand dois-je me protéger contre la contrefaçon de demande intersite? Si vous pouvez vous assurer de ne pas mélanger GET, POST et d'autres méthodes de demande comme décrit ci-dessus, un bon début serait de protéger toutes les demandes POST par défaut.

Vous n'avez pas à protéger les demandes PUT et DELETE, car comme expliqué ci-dessus, un formulaire HTML standard ne peut pas être soumis par un navigateur utilisant ces méthodes.

JavaScript d'autre part peut en effet faire d'autres types de demandes, par exemple en utilisant la fonction $ .ajax () de jQuery, mais n'oubliez pas que pour que les demandes AJAX fonctionnent, les domaines doivent correspondre (tant que vous ne configurez pas explicitement votre serveur Web autrement) .

Cela signifie que, souvent, vous n'avez même pas besoin d'ajouter un jeton CSRF aux demandes AJAX, même s'il s'agit de demandes POST, mais vous devrez vous assurer de ne contourner la vérification CSRF dans votre application Web que si la demande POST est en fait un Demande AJAX. Vous pouvez le faire en recherchant la présence d'un en-tête comme X-Requested-With, que les requêtes AJAX incluent généralement. Vous pouvez également définir un autre en-tête personnalisé et vérifier sa présence côté serveur. C'est sûr, car un navigateur n'ajoutera pas d'en-têtes personnalisés à une soumission de formulaire HTML régulière (voir ci-dessus), donc aucune chance pour M. Bad Guy de simuler ce comportement avec un formulaire.

Si vous avez un doute sur les demandes AJAX, car pour une raison quelconque, vous ne pouvez pas rechercher un en-tête comme X-Requested-With, passez simplement le jeton CSRF généré à votre JavaScript et ajoutez le jeton à la demande AJAX. Il existe plusieurs façons de procéder; soit l'ajouter à la charge utile comme le ferait un formulaire HTML normal, soit ajouter un en-tête personnalisé à la demande AJAX. Tant que votre serveur sait où le rechercher dans une demande entrante et est capable de le comparer à la valeur d'origine dont il se souvient de la session ou du cookie, vous êtes trié.

Dan
la source
Merci pour les informations détaillées. Lors de la publication, le site doit envoyer le token csrf au serveur, alors quand le client enverra-t-il ce token csrf au serveur? Est-ce lors de la demande d'options de contrôle en amont? S'il vous plaît elablorate sur cette partie ..
Sm Srikanth
@Dan Comment se fait-il que b.com puisse accéder aux cookies d'un autre site a.com?
zakir
8

La racine de tout cela est de s'assurer que les demandes proviennent des utilisateurs réels du site. Un jeton csrf est généré pour les formulaires et doit être lié aux sessions de l'utilisateur. Il est utilisé pour envoyer des requêtes au serveur, dans lequel le jeton les valide. C'est une façon de se protéger contre csrf, une autre serait de vérifier l'en-tête du référent.

Gladysbixly
la source
7
Ne vous fiez pas à l'en-tête du référent, il peut facilement être truqué.
kag
3
Ceci est la bonne réponse! Le jeton DOIT être lié à une session sur le serveur. Comparer les données Cookie + Form comme la réponse la plus votée suggère que c'est complètement faux. Ces composants font tous deux partie de la demande, que le client construit.
Lee Davis
3
En fait non. Le jeton DOIT être lié à chaque DEMANDE au serveur. Si vous le liez uniquement à la session, vous courez le risque que quelqu'un vole le jeton de la session et soumette une demande avec ce jeton. Donc, pour une sécurité maximale, le jeton doit être lié à chaque demande http.
chrisl08