Comprendre le jeton d’authenticité Rails

Je rencontre quelques problèmes concernant le jeton d’authenticité dans Rails, comme je l’ai déjà fait à maintes resockets.

Mais je ne veux vraiment pas résoudre ce problème et continuer. Je voudrais vraiment comprendre le jeton Authenticité. Eh bien, ma question est la suivante: avez-vous une source d’information complète sur ce sujet ou passeriez-vous votre temps à expliquer en détail ici?

Ce qui se produit

Lorsque l’utilisateur affiche un formulaire pour créer, mettre à jour ou détruire une ressource, l’application Rails crée un authenticity_token aléatoire, stocke ce jeton dans la session et le place dans un champ masqué du formulaire. Lorsque l’utilisateur soumet le formulaire, Rails recherche l’ authenticity_token , le compare à celui stocké dans la session et, s’il correspond, la demande est autorisée à continuer.

Pourquoi ça arrive

Étant donné que le jeton d’authenticité est stocké dans la session, le client ne peut pas connaître sa valeur. Cela empêche les utilisateurs d’envoyer des formulaires à une application Rails sans afficher le formulaire dans cette application elle-même. Imaginez que vous utilisez le service A, que vous vous êtes connecté au service et que tout va bien. Imaginez maintenant que vous êtes allé utiliser le service B et que vous avez vu une image que vous aimez et que vous avez appuyé sur la photo pour en voir une plus grande taille. Maintenant, si du code maléfique était présent au service B, il pourrait envoyer une demande au service A (auquel vous êtes connecté) et demander de supprimer votre compte en envoyant une demande à l’ http://serviceA.com/close_account . C’est ce que l’on appelle CSRF (Cross Site Request Forgery) .

Si le service A utilise des jetons d’authenticité, ce vecteur d’attaque n’est plus applicable, car la demande du service B ne contiendrait pas le jeton d’authenticité correct et ne serait pas autorisée à continuer.

Les documents API décrivent les détails de la balise meta:

La protection CSRF est activée avec la méthode protect_from_forgery , qui vérifie le jeton et réinitialise la session si elle ne correspond pas à ce qui était attendu. Un appel à cette méthode est généré pour les nouvelles applications Rails par défaut. Le paramètre de jeton est nommé authenticity_token par défaut. Le nom et la valeur de ce jeton doivent être ajoutés à chaque csrf_meta_tags qui rend les formulaires en incluant csrf_meta_tags dans la tête HTML.

Remarques

Gardez à l’esprit que Rails ne vérifie que les méthodes non idempotentes (POST, PUT / PATCH et DELETE). La requête GET n’est pas vérifiée pour le jeton d’authenticité. Pourquoi? car la spécification HTTP stipule que les requêtes GET sont idempotentes et ne doit pas créer, altérer ou détruire des ressources sur le serveur et que la demande doit être idempotente (si vous exécutez la même commande plusieurs fois, vous devriez obtenir le même résultat).

L’implémentation réelle est également un peu plus compliquée que celle définie au début, garantissant une meilleure sécurité. Rails n’émet pas le même jeton stocké avec chaque formulaire. Il ne génère pas et ne stocke pas non plus un jeton différent à chaque fois. Il génère et stocke un hachage cryptographique dans une session et émet de nouveaux jetons cryptographiques, qui peuvent être comparés à celui stocké, chaque fois qu’une page est rendue. Voir request_forgery_protection.rb .

Cours

Utilisez authenticity_token pour protéger vos méthodes non idempotentes (POST, PUT / PATCH et DELETE). Veillez également à ne pas autoriser les requêtes GET susceptibles de modifier les ressources sur le serveur.


EDIT: Vérifiez le commentaire de @erturne concernant les requêtes GET étant idempotentes. Il l’explique mieux que je ne l’ai fait ici.

Le jeton d’authenticité est conçu pour que vous sachiez que votre formulaire est soumis à partir de votre site Web. Il est généré à partir de la machine sur laquelle il s’exécute avec un identificateur unique que seule votre machine peut connaître, consortingbuant ainsi à prévenir les attaques par falsification de requêtes intersites.

Si vous rencontrez des difficultés avec les rails refusant l’access à votre script AJAX, vous pouvez utiliser

 <%= form_authenticity_token %> 

pour générer le bon jeton lorsque vous créez votre formulaire.

Vous pouvez en savoir plus à ce sujet dans la documentation .

Qu’est ce que CSRF?

Le jeton d’authenticité est une contre-mesure à la contrefaçon de requête intersite (CSRF). Qu’est-ce que CSRF, vous demandez?

C’est un moyen pour un attaquant de détourner des sessions sans même connaître les jetons de session.

Scénario :

  • Visitez le site de votre banque, connectez-vous.
  • Ensuite, visitez le site de l’attaquant (par exemple, une annonce sponsorisée par une organisation non approuvée).
  • La page de l’attaquant comprend un formulaire avec les mêmes champs que le formulaire “Transfer Funds” de la banque.
  • L’attaquant connaît les informations de votre compte et dispose de champs de formulaire prédéfinis pour transférer de l’argent de votre compte vers le compte de l’attaquant.
  • La page de l’attaquant comprend Javascript qui soumet un formulaire à votre banque.
  • Lorsque le formulaire est soumis, le navigateur inclut vos cookies pour le site de la banque, y compris le jeton de session.
  • La banque transfère de l’argent sur le compte de l’attaquant.
  • Le formulaire peut être dans une iframe invisible, de sorte que vous ne savez jamais que l’attaque s’est produite.
  • Cela s’appelle Cross-Site Request Forgery (CSRF).

Solution CSRF :

  • Le serveur peut marquer des formulaires provenant du serveur lui-même
  • Chaque formulaire doit contenir un jeton d’authentification supplémentaire en tant que champ masqué.
  • Le jeton doit être imprévisible (l’attaquant ne peut pas le deviner).
  • Le serveur fournit un jeton valide dans les formulaires de ses pages.
  • Le serveur vérifie le jeton lors de la publication du formulaire, rejette les formulaires sans jeton approprié.
  • Exemple de jeton: identifiant de session chiffré avec la clé secrète du serveur.
  • Rails génère automatiquement de tels jetons: voir le champ de saisie authenticity_token dans chaque formulaire.

Exemple d’attaque minimale qui serait empêché

Sur mon site web evil.com je vous convainc de soumettre le formulaire suivant:

 

Si vous êtes connecté à votre banque via les cookies de session, les cookies seront envoyés et le transfert sera effectué sans que vous le sachiez.

C’est à dire que le jeton CSRF entre en jeu:

  • avec la réponse GET qui a renvoyé le formulaire, Rails envoie un paramètre aléatoire très long
  • lorsque le navigateur effectue la requête POST, il envoie le paramètre et le serveur l’accepte uniquement s’il correspond

Ainsi, le formulaire sur un navigateur authentique ressemblerait à ceci:

 

Ainsi, mon attaque échouerait, car elle n’envoyait pas le paramètre authenticity_token , et je n’aurais jamais pu le deviner car il s’agit d’un énorme nombre aléatoire.

Cette technique de prévention s’appelle Synchronizer Token Pattern .

Le modèle de jeton de synchronisation fonctionne grâce à la politique de même origine : si je pouvais faire une requête XHR GET à votre banque depuis evil.com et lire le résultat, je pourrais simplement lire un jeton et faire ensuite la demande. Je l’ai expliqué plus loin à: https://security.stackexchange.com/a/72569/53321

Je vous recommande fortement de lire le guide OWASP , à ce sujet et pour toute autre question de sécurité.

Comment Rails envoie les jetons

Couvert par: Rails: Comment fonctionne csrf_meta_tag?

Fondamentalement:

  • Les aides HTML comme form_tag ajoutent un champ caché au formulaire si ce n’est pas un formulaire GET

  • AJAX est traité automatiquement par jquery-ujs , qui lit le jeton des meta éléments ajoutés à votre en-tête par csrf_meta_tags (présent dans le modèle par défaut), et l’ajoute à toute requête effectuée.

    UJS tente également de mettre à jour le jeton dans des formulaires dans des fragments mis en cache obsolètes.

Autres approches préventives

Le Authenticity Token est la méthode des rails pour empêcher les attaques par falsification de requête intersites (CSRF ou XSRF) .

Pour simplifier, il s’assure que les demandes PUT / POST / DELETE (méthodes pouvant modifier le contenu) à votre application Web sont effectuées à partir du navigateur du client et non d’un tiers (un attaquant) ayant access à un cookie créé. du côté du client.

puisque le Authenticity Token est si important, et dans Rails 3.0+, vous pouvez utiliser

  <%= token_tag nil %> 

créer

  

nulle part

Attention, le mécanisme de jeton d’authenticité peut entraîner des conditions de concurrence si vous avez plusieurs demandes simultanées provenant du même client. Dans ce cas, votre serveur peut générer plusieurs jetons d’authenticité s’il ne doit y en avoir qu’un, et le client qui reçoit le jeton précédent dans un formulaire échoue lors de sa prochaine demande car le jeton de cookie de session a été remplacé. Il y a une écriture sur ce problème et une solution pas entièrement sortingviale ici: http://www.paulbutcher.com/2007/05/race-conditions-in-rails-sessions-and-how-to-fix-them/

Le jeton d’authenticité est utilisé pour empêcher les attaques de type Cross-Site Request Forgery (CSRF). Pour comprendre le jeton d’authenticité, vous devez d’abord comprendre les attaques CSRF.

CSRF

Supposons que vous soyez l’auteur de bank.com . Vous avez un formulaire sur votre site qui est utilisé pour transférer de l’argent vers un autre compte avec une demande GET:

entrer la description de l'image ici

Un pirate informatique pourrait simplement envoyer une requête HTTP au serveur en indiquant GET /transfer?amount=$1000000&account-to=999999 , non?

entrer la description de l'image ici

Faux. L’attaque des pirates ne fonctionnera pas. Le serveur va essentiellement penser?

Hein? Qui est ce type qui essaie de lancer un transfert. Ce n’est pas le propriétaire du compte, c’est certain.

Comment le serveur le sait-il? Parce qu’il n’y a pas de cookie session_id authentifiant le demandeur.

Lorsque vous vous connectez avec votre nom d’utilisateur et votre mot de passe, le serveur définit un cookie session_id sur votre navigateur. De cette façon, vous n’avez pas à authentifier chaque demande avec votre nom d’utilisateur et votre mot de passe. Lorsque votre navigateur envoie le cookie session_id , le serveur sait:

Oh, c’est John Doe. Il s’est inscrit avec succès il y a 2,5 minutes. Il est bon d’y aller.

Un pirate pourrait penser:

Hmm. Une requête HTTP normale ne fonctionnera pas, mais si je pouvais mettre la main sur ce cookie session_id , je serais en or.

Le navigateur des utilisateurs a un tas de cookies définis pour le domaine bank.com . Chaque fois que l’utilisateur fait une demande sur le domaine bank.com , tous les cookies sont envoyés. Y compris le cookie session_id .

Donc, si un pirate pouvait vous demander de faire la demande GET qui transfère de l’argent sur son compte, il réussirait. Comment pourrait-il vous tromper en le faisant? Avec la contrefaçon de demande de site.

C’est assez simple, en fait. Le pirate informatique pourrait vous amener à visiter son site Web. Sur son site Web, il pourrait avoir le tag image suivant:

  

Lorsque le navigateur de l’utilisateur rencontre cette balise d’image, il effectuera une requête GET sur cette URL. Et comme la demande provient de son navigateur, elle enverra avec elle tous les cookies associés à bank.com . Si l’utilisateur s’est récemment connecté à bank.com … le cookie session_id sera défini et le serveur pensera que l’utilisateur a l’intention de transférer 1 000 000 $ sur le compte 999999!

entrer la description de l'image ici

Eh bien, ne visitez pas les sites dangereux et tout ira bien.

Cela ne suffit pas. Que se passe-t-il si quelqu’un publie cette image sur Facebook et qu’elle apparaît sur votre mur? Que se passe-t-il s’il est injecté dans un site que vous visitez avec une attaque XSS?

Ce n’est pas si grave. Seules les demandes GET sont vulnérables.

Pas vrai. Un formulaire qui envoie une requête POST peut être généré dynamicment. Voici l’exemple du guide Rails sur la sécurité :

 To the harmless survey 

Jeton d’authenticité

Lorsque votre ApplicationController a ceci:

 protect_from_forgery with: :exception 

Ce:

 <%= form_tag do %> Form contents <% end %> 

Est compilé dans ceci:

 
Form contents

En particulier, ce qui suit est généré:

  

Pour se protéger contre les attaques CSRF, si Rails ne voit pas le jeton d’authenticité envoyé avec une demande, la demande ne sera pas considérée comme sûre.

Comment un attaquant est-il censé savoir ce qu’est ce jeton? Une valeur différente est générée aléatoirement chaque fois que le formulaire est généré:

entrer la description de l'image ici

Une attaque XSS (Cross Site Scripting) – c’est comme ça. Mais c’est une vulnérabilité différente pour un autre jour.

Méthodes Où authenticity_token est requirejs

authenticity_token est requirejs dans le cas de méthodes idempotentes telles que post, put et delete, car les méthodes Idempotent affectent les données.

Pourquoi il est requirejs

Il est nécessaire d’empêcher les actions mauvaises. authenticity_token est stocké dans la session, chaque fois qu’un formulaire est créé sur des pages Web pour créer ou mettre à jour des ressources, un jeton d’authenticité est stocké dans un champ masqué et envoyé avec le formulaire sur le serveur. Avant d’exécuter l’action, l’utilisateur authentifié_token envoyé est vérifié avec authenticity_token stocké dans la session. Si authenticity_token est identique, alors le processus continue, sinon il n’effectue aucune action.

Qu’est ce qu’un authentification_token?

Il s’agit d’une chaîne aléatoire utilisée par l’application rails pour s’assurer que l’utilisateur demande ou effectue une action depuis la page de l’application, et non depuis une autre application ou un autre site.

Pourquoi une authentification_token est-elle nécessaire?

Pour protéger votre application ou votre site contre la falsification de requêtes intersites.

Comment append une authentification_token à un formulaire?

Si vous générez un formulaire à l’aide de la balise form_for, une authentification_token est automatiquement ajoutée, vous pouvez utiliser <%= csrf_meta_tag %> .