web-dev-qa-db-fra.com

Comment empêcher CSRF dans une application RESTful?

La contrefaçon de demande intersite (CSRF) est généralement empêchée avec l'une des méthodes suivantes:

  • Check referer - RESTful mais peu fiable
  • insérer un jeton dans le formulaire et stocker le jeton dans la session serveur - pas vraiment RESTful
  • uRI cryptiques à usage unique - non RESTful pour la même raison que les jetons
  • envoyer le mot de passe manuellement pour cette demande (pas le mot de passe mis en cache utilisé avec l'authentification HTTP) - RESTful mais pas pratique

Mon idée est d'utiliser un secret utilisateur, un identifiant de formulaire cryptique mais statique et JavaScript pour générer des jetons.

<form method="POST" action="/someresource" id="7099879082361234103">
    <input type="hidden" name="token" value="generateToken(...)">
    ...
</form>
  1. GET /usersecret/john_doe Récupéré par le JavaScript de l'utilisateur authentifié.
  2. Réponse: OK 89070135420357234586534346 Ce secret est conceptuellement statique, mais peut être modifié tous les jours/heures ... pour améliorer la sécurité. C'est la seule chose confidentielle.
  3. Lisez l'identifiant de formulaire cryptique (mais statique pour tous les utilisateurs!) Avec JavaScript, traitez-le avec le secret de l'utilisateur: generateToken(7099879082361234103, 89070135420357234586534346)
  4. Envoyez le formulaire avec le jeton généré au serveur.
  5. Étant donné que le serveur connaît le secret utilisateur et l'identifiant de formulaire, il est possible d'exécuter la même fonction generateToken que le client avant d'envoyer et de comparer les deux résultats. Ce n'est que lorsque les deux valeurs sont égales que l'action sera autorisée.

Quelque chose ne va pas avec cette approche, malgré le fait qu'elle ne fonctionne pas sans JavaScript?

Addendum:

74
deamon

Il y a beaucoup de réponses ici et des problèmes avec bon nombre d'entre elles.

Choses que vous ne devriez PAS faire:

  1. Si vous devez lire le jeton de session à partir de JavaScript, vous faites quelque chose d'horriblement mal. Votre cookie identifiant de session doit TOUJOURS avoir HTTPOnly défini dessus afin qu'il ne soit pas disponible pour les scripts.

    Cette protection unique permet de réduire considérablement l'impact de XSS, car un attaquant ne pourra plus obtenir un jeton de session utilisateur connecté, qui à toutes fins utiles est l'équivalent des informations d'identification dans l'application. Vous ne voulez pas qu'une seule erreur donne les clés du royaume.

  2. L'identifiant de session ne doit pas être écrit dans le contenu de la page. C'est pour les mêmes raisons que vous définissez HTTPOnly. Cela signifie que votre jeton csrf ne peut pas être votre identifiant de session. Ils doivent être des valeurs différentes.

Choses à faire:

  1. Suivez conseils de l'OWASP :

  2. Plus précisément, s'il s'agit d'une application REST vous pouvez exiger la double soumission de jetons CSRF . Si vous faites cela, assurez-vous simplement de la définir dans un -domaine (www.mydomain.com) et non un domaine parent (example.com), et que vous utilisez également l'attribut de cookie "samesite" qui gagne en popularité.

Créez simplement quelque chose de cryptographiquement aléatoire, stockez-le dans ASCII codage Hex ou Base64, et ajoutez-le en tant que cookie et à vos formulaires lorsque le serveur renvoie la page. Du côté serveur, assurez-vous que le cookie valeur correspond à la valeur du formulaire. Voila, vous avez tué CSRF, évité les invites supplémentaires pour vos utilisateurs et ne vous êtes pas ouvert à plus de vulnérabilités.

REMARQUE: Comme @krubo l'indique ci-dessous, la technique de double soumission il a été constaté qu'elle présente certaines faiblesses (voir double soumission) . Étant donné que cette faiblesse nécessite que:

  1. Vous définissez un cookie limité au domaine parent.
  2. Vous ne parvenez pas à définir HSTS .
  3. L'attaquant contrôle un emplacement réseau entre l'utilisateur et le serveur

Je pense en quelque sorte que la faiblesse se situe davantage dans la catégorie des "Cool Defcon Talk" plutôt que des "Realworld Security Risk". Dans tous les cas, si vous allez utiliser la double soumission, cela ne fait pas de mal de prendre quelques mesures supplémentaires pour vous protéger pleinement.

25
Doug

Suis-je bien:

  • Vous souhaitez une protection contre CSRF pour les utilisateurs connectés via les cookies.
  • Et en même temps, vous voulez une interface RESTful pour Basic, OAuth et Digest authentifié les demandes des applications.

Alors, pourquoi ne pas vérifier si les utilisateurs sont connectés via un cookie et appliquer CSRF seulement alors?

Je ne suis pas sûr mais est-il possible pour un autre site de forger des choses comme l'authentification de base ou les en-têtes?

Pour autant que je sache, CSRF est tout sur les cookies? L'authentification RESTful ne se produit pas avec les cookies.

10
antitoxic

Vous avez certainement besoin d'un état sur le serveur pour vous authentifier/autoriser. Il n'est pas nécessaire que ce soit la session http, vous pouvez la stocker dans un cache distribué (comme memcached) ou une base de données.

Si vous utilisez des cookies pour l'authentification, la solution la plus simple consiste à double-soumettre la valeur du cookie. Avant de soumettre le formulaire, lisez l'ID de session du cookie, stockez-le dans un champ masqué, puis soumettez-le. Côté serveur, confirmez que la valeur de la demande est la même que l'ID de session (que vous avez obtenu du cookie). Le mauvais script d'un autre domaine ne pourra pas lire l'identifiant de session du cookie, empêchant ainsi CSRF.

Ce schéma utilise un identifiant unique sur toute la session.

Si vous souhaitez plus de protection, générez un identifiant unique par session et par formulaire.

De plus, NE générez PAS de jetons dans JS. Tout le monde peut copier le code et l'exécuter à partir d'un domaine différent pour attaquer votre site.

9

L'ID de formulaire statique n'offre aucune protection; un attaquant peut le récupérer lui-même. N'oubliez pas que l'attaquant n'est pas contraint d'utiliser JavaScript sur le client; il peut récupérer l'ID de formulaire statique côté serveur.

Je ne suis pas sûr de bien comprendre la défense proposée; Où le GET /usersecret/john_doe viens de? Cela fait-il partie de la page JavaScript? Est-ce l'URL proposée littéralement? Si c'est le cas, je suppose que username n'est pas un secret, ce qui signifie que evil.ru peut récupérer des secrets d'utilisateur si un bogue de navigateur ou de plug-in autorise les requêtes GET interdomaines. Pourquoi ne pas stocker le secret de l'utilisateur dans un cookie lors de l'authentification plutôt que de laisser quiconque peut effectuer des GET interdomaines le récupérer?

Je lirais "Robust Defenses for Cross-Site Forgery" très attentivement avant d'implémenter mon propre système d'authentification que je voulais être résistant au CSRF. En fait, je reconsidérerais l'implémentation de mon propre système d'authentification.

6
Scott Wolchok

Il existe quelques méthodes dans le CSRF Prevention Cheat Sheet qui peuvent être utilisées par un service reposant. L'atténuation CSRF sans état la plus RESTful utilise l'origine ou référent HTTP pour vous assurer que les demandes proviennent d'un domaine auquel vous faites confiance.

3
rook

Quelque chose ne va pas avec cette approche, malgré le fait qu'elle ne fonctionne pas sans JavaScript?

Votre secret utilisateur n'est pas un secret si vous l'envoyez au client. Nous utilisons généralement ces secrets pour générer des hachages et les envoyer avec le formulaire, et les attendre pour la comparaison.

Si vous voulez être RESTful, la demande doit contenir toutes les informations sur la façon de la traiter. Les façons dont vous pouvez le faire:

  • Ajoutez un cookie de jeton csrf avec votre client REST et envoyez le même jeton en entrée masquée avec vos formulaires. Si le service et le client se trouvent sous des domaines différents, vous devez partager les informations d'identification. Sur le service il faut comparer les 2 tokens, et si ce sont les mêmes, la demande est valable ...

  • Vous pouvez ajouter le cookie de jeton csrf avec votre service REST et envoyer le même jeton avec les représentations de vos ressources (entrées masquées, etc ...). Tout le reste est identique à la fin de la solution précédente. Cette solution est à la limite de RESTfulness. (C'est correct jusqu'à ce que le client n'appelle pas le service pour modifier le cookie. Si le cookie est uniquement http, le client ne devrait pas le savoir, sinon, le client doit alors le définir.) Vous pouvez faire une solution plus complexe si vous ajoutez des jetons différents à chaque formulaire et ajoutez un délai d'expiration aux cookies. Vous pouvez également renvoyer le délai d'expiration avec les formulaires, afin que vous en connaissiez la raison. lorsqu'une validation de jeton échoue.

  • Vous pouvez avoir un secret utilisateur (différent selon chaque utilisateur) dans l'état de ressource sur votre service. En créant des représentations, vous pouvez générer un jeton (et un délai d'expiration) pour chaque formulaire. Vous pouvez générer un hachage à partir du jeton réel (et du délai d'expiration, de la méthode, de l'URL, etc.) et du secret utilisateur, et envoyer ce hachage avec le formulaire également. Vous gardez le "secret utilisateur" en secret bien sûr, donc vous ne l'envoyez jamais avec le formulaire. Après cela, si votre service reçoit une demande, vous pouvez générer à nouveau le hachage à partir des paramètres de la demande et du secret utilisateur, et les comparer. Si le ne correspond pas, la demande n'est pas valide ...

Aucun d'entre eux ne vous protégera si votre client REST est injectable en javascript, vous devez donc vérifier tout votre contenu utilisateur par rapport aux entités HTML et les supprimer tous, ou utiliser toujours TextNodes au lieu de innerHTML. Vous devez également vous protéger contre l'injection SQL et l'injection d'en-tête HTTP. N'utilisez jamais un simple FTP pour actualiser votre site. Et ainsi de suite ... Il existe de nombreuses façons d'injecter du code malveillant dans votre site ...

J'ai presque oublié de mentionner que les demandes GET sont toujours à lire par le service et par le client non plus. Par le service, cela est évident, par le fait que le client définissant une URL dans le navigateur doit résulter d'une représentation d'une ressource ou de plusieurs ressources, il ne doit jamais appeler une méthode POST/PUT/DELETE sur une ressource. Par exemple GET http://my.client.com/resource/delete -> DELETE http://my.api.com/resource est une très très mauvaise solution. Mais c'est une compétence très basique si vous voulez entraver la CSRF.

0
inf3rno