web-dev-qa-db-fra.com

RequestVerificationToken ne correspond pas

J'ai un problème avec le mécanisme anti CRSF MVC. Le cookie et l'entrée de formulaire retournés ne correspondent pas. Je reçois une erreur à chaque fois, uniquement sur une page spécifique. Dans le reste de l'application, cela fonctionne bien.

Le serveur retourne HTTP 500 Internal Server Error et je peux voir sur le journal cette exception:

[System.Web.Mvc.HttpAntiForgeryException]: {"Un jeton anti-contrefaçon requis n'a pas été fourni ou n'était pas valide."}

C'est l'entrée cachée que le serveur génère est:

<input name="__RequestVerificationToken" type="hidden" value="QK8P7rjyZE6Vm5seY7Fr704YCOoFGdTIMzl1W7R0ZFpXSMjGKLG2T05DfFSYTxvtQCEx7DDT69DGsDB2+ZXFHY8oAjiKz0gw8BhDFywgmfIpoXnGpj7fONNzIIfvbrDrE9WJsMu6Io/0bDLM5WfKs0zktiNjyOWpfYrmnfINYmjW8NLOZFoz74xTcgTptAld">

Et voici le cookie retourné:

Set-Cookie:__RequestVerificationToken_L2VGbG93=skmTAVI8HCbfxDS+xhioIMIISL3UOBI7qJM1JbHjTtAqKl4W70pDUcTKMm0p3R3mrHDziE8vXw0C0OO4HArzWO1/e6py+v/cFdbe9maFgjl4jMiZ9Wc4YIhC6+IUXkk6yqJDJ8dCIr8qtGaYcD9IX+m7/SlVhu521KQSWJYRcaY=; path=/; HttpOnly

Lorsque j'examine ce que le serveur envoie, le cookie est exactement le même, mais la charge utile a un codage différent, je pense:

__RequestVerificationToken:QK8P7rjyZE6Vm5seY7Fr704YCOoFGdTIMzl1W7R0ZFpXSMjGKLG2T05DfFSYTxvtQCEx7DDT69DGsDB2%2BZXFHY8oAjiKz0gw8BhDFywgmfIpoXnGpj7fONNzIIfvbrDrE9WJsMu6Io%2F0bDLM5WfKs0zktiNjyOWpfYrmnfINYmjW8NLOZFoz74xTcgTptAld

Les différences sont dans deux caractères qui apparaissent encodés:

    /    ->   %2F  
    +    ->   %2B

Ce sont les seules différences que je peux trouver entre le champ de saisie caché et la charge utile après.

Quel pourrait être le problème à l'origine de l'échec de ValidateAntiForgeryToken lors de la vérification du jeton?

Cordialement.

34
vtortola

J'ai eu et résolu plusieurs problèmes avec ValidateAntiForgeryToken récemment, je vais donc partager mes découvertes avec vous.

Salt : Puisque vous mentionnez que cela ne se produit que sur une seule page, ma meilleure supposition est que vous utilisez différentes valeurs salt dans vos appels aux appels Html.AntiForgeryToken(salt) et ValidateAntiForgeryToken(salt).

[~ # ~] ajax [~ # ~] : comme l'a dit une autre réponse, utiliser AJAX peut nécessiter un supplément travailler pour s'assurer que le jeton est inclus dans le POST. Voici ma préférée solution simple et automatique pour ajouter le jeton à tous AJAX POST requêtes .
Dans votre question cependant, vous déclarez avoir vérifié que le jeton est en cours d'envoi. Avez-vous vérifié que vous n'envoyez le jeton qu'une seule fois? J'ai découvert qu'un appel AJAX à moi envoyait le jeton deux fois, ce qui combinait les valeurs et provoquait son échec.

Clé machine et cookies : ce problème est moche, facile à repérer (provoque des exceptions), mais pas très intuitif. Les cookies et jetons de validation sont encodés et décodés à l'aide d'une "clé machine" unique. Cela signifie que si vous avez une batterie de serveurs ou changez de serveur, votre cookie ne sera plus valide. La fermeture de votre navigateur résout le problème (car le cookie est un cookie de session). Cependant, certaines personnes laissent leurs fenêtres de navigateur ouvertes en arrière-plan pendant longtemps!
La solution consiste à définir une "clé machine" dans votre fichier de configuration. Cela indiquera à MVC d'utiliser la même clé sur tous les serveurs, garantissant que le cookie sera déchiffrable partout.

Bogues d'encodage : en utilisant un utilitaire de test appelé jMeter, nous avons tenté de charger-tester nos pages, seulement pour découvrir qu'il y avait un bogue à l'origine de notre jeton avoir 2 extra " autour de la valeur.
La solution est de diminuer votre confiance dans vos outils! Testez dans un navigateur et si cela fonctionne, créez un test qui extrait les valeurs de jeton et de cookie et définissez un point d'arrêt pour vérifier les résultats.

Si aucune de ces choses ne fonctionne pour vous, je vous recommande de consulter le code source MVC pour ValidateAntiForgeryTokenAttribute , en particulier la méthode OnAuthorization. Il vous aidera à voir les différentes étapes où la validation pourrait échouer. Vous pourriez même inspecter le Exception.StackTrace pour déterminer quelle partie échoue.

En remarque , je n'aime vraiment pas l'implémentation de ValidateAntiForgeryToken dans MVC, car:

  • Il y a environ 5 étapes de vérification qui peuvent échouer, mais il n'y a qu'un seul message d'erreur générique.
  • La classe est scellée, elle ne peut donc pas être étendue avec des fonctionnalités supplémentaires.
  • La méthode de cryptage est bizarre - elle initialise un Page et crée un artificiel ViewState pour crypter les jetons et les cookies. Semble exagéré.

J'ai donc saisi le code source et créé ma propre sous-classe spécialisée, qui s'est également avérée très utile pour déboguer ses problèmes, car je pouvais définir des points d'arrêt sur les méthodes de validation, et il était vraiment facile de déterminer quelle étape de validation échouait .

77
Scott Rippey

Si cela est envoyé en tant que demande Ajax, la configuration actuelle du framework n'est pas construite pour le faire naturellement.

Heureusement, Phil Haak a écrit un article de blog sur Nice traitant de CSRF et Ajax -> Preventing CSRF With Ajax qui explique en détail comment utiliser le cadre existant et le modifier pour qu'il fonctionne pour Ajax/Json.

4
Skuld

D'après mes découvertes récentes ...

Si vous définissez le type de contenu comme "application/x-www-form-urlencoded" dans la demande ajax, vous devez mettre l'AFRT dans les données

Si vous définissez le type de contenu sur "application/json", le jeton va dans la propriété ajax "headers" comme décrit par haack.

Sur le serveur, si vous recherchez le jeton de type de formulaire, l'utilisation de Vanilla AntiForgeryRequestTokenAttribute est correcte, mais si vous souhaitez valider les jetons envoyés dans l'en-tête, vous devez appeler AntiForgeryToken.OnAuthorize ... ou autre chose, en passant le jeton depuis le cookie (contexte http).

Ce n'est pas facile mais si c'était le cas, tout le monde le ferait :)

2
ComeIn