Prévention de XSRF/CSRF dans ASP.NET MVC et les pages web

par Rick Anderson

La falsification de requête intersites (également appelée XSRF ou CSRF) est une attaque contre des applications hébergées sur le web par laquelle un site web malveillant peut influencer l’interaction entre un navigateur client et un site web approuvé par ce navigateur. Ces attaques sont rendues possibles, car les navigateurs web envoient automatiquement des jetons d’authentification à chaque requête à un site web. L'exemple classique est le cookie d'authentification, comme le ticket d'authentification d'ASP.NET. Toutefois, les sites web qui utilisent n’importe quel mécanisme d’authentification permanente (comme l’authentification Windows, de base, etc.) peuvent être ciblés par ces attaques.

Une attaque XSRF est différente d'une attaque par hameçonnage (ou « phishing »). Les attaques par hameçonnage requièrent une interaction avec la victime. Lors d’une attaque par hameçonnage, un site web malveillant imite le site web cible, et la victime est dupe de fournir des informations sensibles à l’attaquant. Dans une attaque XSRF, il n'y a généralement pas d'interaction avec la victime. Au lieu de cela, l’attaquant s’appuie sur le navigateur qui envoie automatiquement tous les cookies pertinents au site web de destination.

Pour plus d’informations, consultez Open Web Application Security Project (OWASP) XSRF.

Anatomie d’une attaque

Pour passer en revue une attaque XSRF, envisagez un utilisateur qui souhaite effectuer des transactions bancaires en ligne. Cet utilisateur visite d’abord WoodgroveBank.com et se connecte, auquel cas l’en-tête de réponse contient son cookie d’authentification :

HTTP/1.1 200 OK
Date: Mon, 18 Jun 2012 21:22:33 GMT
X-AspNet-Version: 4.0.30319
Set-Cookie: .ASPXAUTH={authentication-token}; path=/; secure; HttpOnly;
{ Cache-Control, Content-Type, Location, Server and other keys/values not listed. }

Étant donné que le cookie d’authentification est un cookie de session, il est automatiquement effacé par le navigateur lorsque le processus du navigateur se termine. Toutefois, jusqu’à cette date, le navigateur inclut automatiquement le cookie à chaque demande de WoodgroveBank.com. L’utilisateur veut maintenant transférer 1 000 $ vers un autre compte, donc il remplit un formulaire sur le site bancaire, et le navigateur effectue cette requête au serveur :

POST /DoTransfer HTTP/1.1
Host: WoodgroveBank.com
Content-Type: application/x-www-form-urlencoded
Cookie: .ASPXAUTH={authentication-token}
toAcct=12345&amount=1,000.00

Étant donné que cette opération a un effet secondaire (elle initie une transaction monétaire), le site bancaire a choisi d’exiger un HTTP POST pour lancer cette opération. Le serveur lit le jeton d’authentification à partir de la demande, recherche le numéro de compte de l’utilisateur actuel, vérifie qu’il existe suffisamment de fonds, puis lance la transaction dans le compte de destination.

Sa banque en ligne complète, l’utilisateur s’éloigne du site bancaire et visite d’autres emplacements sur le web. L’un de ces sites , fabrikam.com, inclut le balisage suivant sur une page incorporée dans un <iframe> :

<form id="theForm" action="https://WoodgroveBank.com/DoTransfer" method="post">
    <input type="hidden" name="toAcct" value="67890" />
    <input type="hidden" name="amount" value="250.00" />
</form>
<script type="text/javascript">
    document.getElementById('theForm').submit();
</script>

Ce qui amène ensuite le navigateur à effectuer cette requête :

POST /DoTransfer HTTP/1.1
Host: WoodgroveBank.com
Content-Type: application/x-www-form-urlencoded
Cookie: .ASPXAUTH={authentication-token}
toAcct=67890&amount=250.00

L’attaquant exploite le fait que l’utilisateur dispose peut-être toujours d’un jeton d’authentification valide pour le site web cible, et elle utilise un petit extrait de code Javascript pour que le navigateur crée automatiquement un HTTP POST sur le site cible. Si le jeton d’authentification est toujours valide, le site bancaire lance un transfert de 250 $ dans le compte de l’attaquant de son choix.

Atténuations inefficaces

Il est intéressant de noter que dans le scénario ci-dessus, le fait que WoodgroveBank.com était accessible via SSL et qu’il y avait un cookie d’authentification SSL uniquement était insuffisant pour contrecarrer l’attaque. L’attaquant est en mesure de spécifier le schéma d’URI (https) dans son <élément de formulaire> , et le navigateur continuera à envoyer des cookies non expirés au site cible tant que ces cookies sont cohérents avec le schéma d’URI de la cible prévue.

On pourrait soutenir que l’utilisateur ne devrait tout simplement pas visiter des sites non approuvés, car visiter uniquement des sites de confiance permet de rester en sécurité en ligne. Il y a une certaine vérité à cela, mais malheureusement, ce conseil n’est pas toujours pratique. Peut-être que l’utilisateur « fait confiance » au site d’actualités local ConsolidatedMessenger.com et va visiter ce site à la place, mais ce site a une vulnérabilité XSS qui permet à un attaquant d’injecter le même extrait de code que celui qui s’exécutait sur fabrikam.com.

Vous pouvez vérifier que les demandes entrantes ont un en-tête De référence référençant votre domaine. Cela arrête les demandes envoyées involontairement à partir d’un domaine tiers. Toutefois, certaines personnes désactivent l’en-tête De référence de leur navigateur pour des raisons de confidentialité, et les attaquants peuvent parfois usurper cet en-tête si la victime a installé certains logiciels non sécurisés. La vérification de l’en-tête du référentiel n’est pas considérée comme une approche sécurisée pour empêcher les attaques XSRF.

Atténuations XSRF du runtime de pile web

Le ASP.NET Web Stack Runtime utilise une variante du modèle de jeton de synchronisateur pour se défendre contre les attaques XSRF. La forme générale du modèle de jeton de synchronisateur est que deux jetons anti-XSRF sont envoyés au serveur avec chaque HTTP POST (en plus du jeton d’authentification) : un jeton en tant que cookie et l’autre en tant que valeur de formulaire. Les valeurs de jeton générées par le runtime ASP.NET ne sont pas déterministes ou prévisibles par un attaquant. Lorsque les jetons sont envoyés, le serveur autorise la demande à se poursuivre uniquement si les deux jetons réussissent une comparaison case activée.

Le jeton de session de vérification de requête XSRF est stocké en tant que cookie HTTP et contient actuellement les informations suivantes dans sa charge utile :

  • Jeton de sécurité, constitué d’un identificateur 128 bits aléatoire.
    L’image suivante montre le jeton de session de vérification de requête XSRF affiché avec les outils de développement Internet Explorer F12 : (Notez qu’il s’agit de l’implémentation actuelle et est susceptible, même probablement, de changer.)

Capture d’écran montrant la page My A S P p dot NET M V C Application Index. L’onglet Réseau est ouvert.

Le jeton de champ est stocké en tant que <input type="hidden" /> et contient les informations suivantes dans sa charge utile :

Les charges utiles des jetons anti-XSRF étant chiffrées et signées, vous ne pouvez pas afficher le nom d’utilisateur lorsque vous utilisez des outils pour examiner les jetons. Lorsque l’application web cible ASP.NET 4.0, les services de chiffrement sont fournis par la routine MachineKey.Encode . Lorsque l’application web cible ASP.NET version 4.5 ou ultérieure, les services de chiffrement sont fournis par la routine MachineKey.Protect , qui offre de meilleures performances, extensibilité et sécurité. Pour plus d’informations, consultez les billets de blog suivants :

Génération des jetons

Pour générer les jetons anti-XSRF, appelez la méthode @Html.AntiForgeryToken à partir d’une vue MVC ou @AntiForgery.GetHtml() d’une page Razor. Le runtime effectue ensuite les étapes suivantes :

  1. Si la requête HTTP actuelle contient déjà un jeton de session anti-XSRF (le cookie anti-XSRF __RequestVerificationToken), le jeton de sécurité est extrait de celui-ci. Si la requête HTTP ne contient pas de jeton de session anti-XSRF ou si l’extraction du jeton de sécurité échoue, un nouveau jeton anti-XSRF aléatoire est généré.
  2. Un jeton de champ anti-XSRF est généré à l’aide du jeton de sécurité de l’étape (1) ci-dessus et de l’identité de l’utilisateur connecté actuel. (Pour plus d’informations sur la détermination de l’identité de l’utilisateur, consultez la section Scénarios avec support spécial ci-dessous.) En outre, si un IAntiForgeryAdditionalDataProvider est configuré, le runtime appelle sa méthode GetAdditionalData et inclut la chaîne retournée dans le jeton de champ. (Pour plus d’informations, consultez la section Configuration et extensibilité .)
  3. Si un nouveau jeton anti-XSRF a été généré à l’étape (1), un nouveau jeton de session est créé pour le contenir et est ajouté à la collection de cookies HTTP sortants. Le jeton de champ de l’étape (2) sera encapsulé dans un <input type="hidden" /> élément, et ce balisage HTML sera la valeur de retour de Html.AntiForgeryToken() ou AntiForgery.GetHtml().

Validation des jetons

Pour valider les jetons anti-XSRF entrants, le développeur inclut un attribut ValidateAntiForgeryToken sur son action ou son contrôleur MVC, ou il appelle @AntiForgery.Validate() à partir de sa page Razor. Le runtime effectue les étapes suivantes :

  1. Le jeton de session entrant et le jeton de champ sont lus et le jeton anti-XSRF extrait de chacun d’eux. Les jetons anti-XSRF doivent être identiques par étape (2) dans la routine de génération.
  2. Si l’utilisateur actuel est authentifié, son nom d’utilisateur est comparé au nom d’utilisateur stocké dans le jeton de champ. Les noms d’utilisateur doivent correspondre.
  3. Si un IAntiForgeryAdditionalDataProvider est configuré, le runtime appelle sa méthode ValidateAdditionalData . La méthode doit retourner la valeur booléenne true.

Si la validation réussit, la demande est autorisée à continuer. Si la validation échoue, l’infrastructure lève une exception HttpAntiForgeryException.

Conditions d’échec

À compter de La ASP.NET Web Stack Runtime v2, toute exception HttpAntiForgeryException levée pendant la validation contient des informations détaillées sur ce qui s’est produit. Les conditions d’échec actuellement définies sont les suivantes :

  • Le jeton de session ou le jeton de formulaire n’est pas présent dans la demande.
  • Le jeton de session ou le jeton de formulaire n’est pas illisible. La cause la plus probable est une batterie de serveurs exécutant des versions incompatibles de The ASP.NET Web Stack Runtime ou une batterie de serveurs où l’élément <machineKey> dans Web.config diffère entre les machines. Vous pouvez utiliser un outil tel que Fiddler pour forcer cette exception en falsifiant un jeton anti-XSRF.
  • Le jeton de session et le jeton de champ ont été échangés.
  • Le jeton de session et le jeton de champ contiennent des jetons de sécurité incompatibles.
  • Le nom d’utilisateur incorporé dans le jeton de champ ne correspond pas au nom d’utilisateur de l’utilisateur connecté actuel.
  • La méthode IAntiForgeryAdditionalDataProvider.ValidateAdditionalData a retourné false.

Les fonctionnalités anti-XSRF peuvent également effectuer des vérifications supplémentaires lors de la génération ou de la validation des jetons, et les défaillances au cours de ces vérifications peuvent entraîner la levée d’exceptions. Pour plus d’informations, consultez les sections WIF/ACS/Authentification basée sur les revendicationset Configuration et extensibilité .

Scénarios avec prise en charge spéciale

Authentification anonyme

Le système anti-XSRF contient une prise en charge spéciale pour les utilisateurs anonymes, où « anonyme » est défini comme un utilisateur où la propriété IIdentity.IsAuthenticated retourne false. Les scénarios incluent la fourniture d’une protection XSRF à la page de connexion (avant l’authentification de l’utilisateur) et des schémas d’authentification personnalisés dans lesquels l’application utilise un mécanisme autre que IIdentity pour identifier les utilisateurs.

Pour prendre en charge ces scénarios, rappelez-vous que les jetons de session et de champ sont joints par un jeton de sécurité, qui est un identificateur opaque généré de manière aléatoire 128 bits. Ce jeton de sécurité est utilisé pour suivre la session d’un utilisateur individuel à mesure qu’il navigue sur le site, de sorte qu’il sert efficacement l’objectif d’un identificateur anonyme. Une chaîne vide est utilisée à la place du nom d’utilisateur pour les routines de génération et de validation décrites ci-dessus.

WIF / ACS / authentification basée sur les revendications

Normalement, les classes IIdentity intégrées au .NET Framework ont la propriété qui IIdentity.Name suffit pour identifier de manière unique un utilisateur particulier au sein d’une application particulière. Par exemple, FormsIdentity.Name retourne le nom d’utilisateur stocké dans la base de données d’appartenances (qui est unique pour toutes les applications en fonction de cette base de données), WindowsIdentity.Name retourne l’identité qualifiée de domaine de l’utilisateur, etc. Ces systèmes fournissent non seulement l’authentification ; ils identifient également les utilisateurs d’une application.

En revanche, l’authentification basée sur les revendications ne nécessite pas nécessairement l’identification d’un utilisateur particulier. Au lieu de cela, les types ClaimsPrincipal et ClaimsIdentity sont associés à un ensemble d’instances Claim , où les revendications individuelles peuvent être « âgées de plus de 18 ans » ou « est un administrateur » à toute autre chose. Étant donné que l’utilisateur n’a pas nécessairement été identifié, le runtime ne peut pas utiliser la propriété ClaimsIdentity.Name comme identificateur unique pour cet utilisateur particulier. L’équipe a vu des exemples concrets où ClaimsIdentity.Name retourne null, retourne un nom convivial (d’affichage) ou retourne une chaîne qui n’est pas appropriée pour une utilisation comme identificateur unique pour l’utilisateur.

De nombreux déploiements qui utilisent l’authentification basée sur les revendications utilisent En particulier Azure Access Control Service (ACS). ACS permet au développeur de configurer des fournisseurs d’identité individuels (tels que ADFS, le fournisseur de compte Microsoft, les fournisseurs OpenID comme Yahoo!, etc.) et les fournisseurs d’identité retournent des identificateurs de nom. Ces identificateurs de nom peuvent contenir des informations d’identification personnelle (PII) telles qu’une adresse e-mail, ou ils peuvent être rendus anonymes comme un identificateur personnel privé (PPID). Quoi qu’il en soit, le tuple (fournisseur d’identité, identificateur de nom) sert suffisamment de jeton de suivi approprié pour un utilisateur particulier lorsqu’il navigue sur le site, de sorte que le runtime de la pile web ASP.NET peut utiliser le tuple à la place du nom d’utilisateur lors de la génération et de la validation des jetons de champ anti-XSRF. Les URI spécifiques pour le fournisseur d’identité et l’identificateur de nom sont :

  • https://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider
  • http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier

(pour plus d’informations, consultez cette page de documentation ACS .)

Lors de la génération ou de la validation d’un jeton, le runtime de la pile web ASP.NET essaiera de lier les types au moment de l’exécution :

  • Microsoft.IdentityModel.Claims.IClaimsIdentity, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 (Pour le Kit de développement logiciel (SDK) WIF.)
  • System.Security.Claims.ClaimsIdentity (Pour .NET 4.5).

Si ces types existent et si IIIIdentity de l’utilisateur actuel implémente ou sous-classe l’un de ces types, la fonctionnalité anti-XSRF utilise le tuple (fournisseur d’identité, identificateur de nom) à la place du nom d’utilisateur lors de la génération et de la validation des jetons. Si aucun tuple de ce type n’est présent, la demande échoue avec une erreur décrivant au développeur comment configurer le système anti-XSRF pour comprendre le mécanisme d’authentification basé sur les revendications particulier utilisé. Pour plus d’informations, consultez la section Configuration et extensibilité .

Authentification OAuth / OpenID

Enfin, la fonctionnalité anti-XSRF offre une prise en charge spéciale pour les applications qui utilisent l’authentification OAuth ou OpenID. Cette prise en charge est heuristique : si le IIdentity.Name actuel commence par http:// ou https://, les comparaisons de nom d’utilisateur sont effectuées à l’aide d’un comparateur Ordinal plutôt que du comparateur OrdinalIgnoreCase par défaut.

Configuration et extensibilité

Parfois, les développeurs peuvent souhaiter un contrôle plus strict sur les comportements de génération et de validation anti-XSRF. Par exemple, le comportement par défaut des helpers MVC et Web Pages, qui consiste à ajouter automatiquement des cookies HTTP à la réponse, n’est peut-être pas souhaitable, et le développeur souhaitera peut-être conserver les jetons ailleurs. Il existe deux API pour faciliter cette opération :

AntiForgery.GetTokens(string oldCookieToken, out string newCookieToken, out string formToken);
AntiForgery.Validate(string cookieToken, string formToken);

La méthode GetTokens prend en entrée un jeton de session de vérification de requête XSRF existant (qui peut être null) et produit en sortie un nouveau jeton de session de vérification de requête XSRF et un jeton de champ. Les jetons sont simplement des chaînes opaques sans décoration ; la valeur formToken pour instance pas être encapsulée dans une balise d’entrée<>. La valeur newCookieToken peut être null ; si cela se produit, la valeur oldCookieToken est toujours valide et aucun nouveau cookie de réponse n’a besoin d’être défini. L’appelant de GetTokens est responsable de la persistance des cookies de réponse nécessaires ou de la génération de tout balisage nécessaire ; La méthode GetTokens elle-même ne modifie pas la réponse en tant qu’effet secondaire. La méthode Validate prend les jetons de session et de champ entrants et exécute la logique de validation ci-dessus.

AntiForgeryConfig

Le développeur peut configurer le système anti-XSRF à partir de Application_Start. La configuration est programmatique. Les propriétés du type statique AntiForgeryConfig sont décrites ci-dessous. La plupart des utilisateurs qui utilisent des revendications souhaitent définir la propriété UniqueClaimTypeIdentifier.

Propriété Description
AdditionalDataProvider IAntiForgeryAdditionalDataProvider qui fournit des données supplémentaires pendant la génération du jeton et consomme des données supplémentaires lors de la validation du jeton. La valeur par défaut est null. Pour plus d’informations, consultez la section IAntiForgeryAdditionalDataProvider .
CookieName Chaîne qui fournit le nom du cookie HTTP utilisé pour stocker le jeton de session anti-XSRF. Si cette valeur n’est pas définie, un nom est généré automatiquement en fonction du chemin d’accès virtuel déployé de l’application. La valeur par défaut est null.
RequireSsl Boolean qui détermine si les jetons anti-XSRF doivent être envoyés sur un canal sécurisé SSL. Si cette valeur est true, tous les cookies générés automatiquement auront l’indicateur « sécurisé » défini, et les API anti-XSRF seront levées si elles sont appelées à partir d’une demande qui n’est pas envoyée via SSL. La valeur par défaut est false.
SuppressIdentityHeuristicChecks Boolean qui détermine si le système anti-XSRF doit désactiver sa prise en charge des identités basées sur les revendications. Si cette valeur est true, le système suppose que IIdentity.Name est approprié pour une utilisation en tant qu’identificateur unique par utilisateur et n’essaie pas de cas spécial IClaimsIdentity ou ClClaimsIdentity comme décrit dans la section WIF/ACS/authentification basée sur les revendications . La valeur par défaut est false.
UniqueClaimTypeIdentifier Chaîne qui indique le type de revendication approprié pour une utilisation en tant qu’identificateur unique par utilisateur. Si cette valeur est définie et que l’IIdentity actuel est basé sur des revendications, le système tente d’extraire une revendication du type spécifié par UniqueClaimTypeIdentifier, et la valeur correspondante est utilisée à la place du nom d’utilisateur de l’utilisateur lors de la génération du jeton de champ. Si le type de revendication est introuvable, le système échoue à la demande. La valeur par défaut est null, ce qui indique que le système doit utiliser le tuple (fournisseur d’identité, identificateur de nom) comme décrit précédemment à la place du nom d’utilisateur de l’utilisateur.

IAntiForgeryAdditionalDataProvider

Le type IAntiForgeryAdditionalDataProvider permet aux développeurs d’étendre le comportement du système anti-XSRF en effectuant des allers-retours de données supplémentaires dans chaque jeton. La méthode GetAdditionalData est appelée chaque fois qu’un jeton de champ est généré, et la valeur de retour est incorporée dans le jeton généré. Un implémenteur peut retourner un horodatage, un nonce ou toute autre valeur qu’il souhaite à partir de cette méthode.

De même, la méthode ValidateAdditionalData est appelée chaque fois qu’un jeton de champ est validé, et la chaîne « données supplémentaires » incorporée dans le jeton est passée à la méthode . La routine de validation peut implémenter un délai d’expiration (en vérifiant l’heure actuelle par rapport à l’heure stockée lors de la création du jeton), une routine de vérification nonce ou toute autre logique souhaitée.

Décisions de conception et considérations relatives à la sécurité

Le jeton de sécurité qui lie les jetons de session et de champ n’est techniquement nécessaire que lorsque vous essayez de protéger les utilisateurs anonymes/non authentifiés contre les attaques XSRF. Lorsque l’utilisateur est authentifié, le jeton d’authentification lui-même (probablement envoyé sous la forme d’un cookie) peut être utilisé comme la moitié d’une paire de jetons de synchronisateur. Toutefois, il existe des scénarios valides pour protéger les pages de connexion atteintes par des utilisateurs non authentifiés, et la logique anti-XSRF a été simplifiée en générant et validant toujours le jeton de sécurité, même pour les utilisateurs authentifiés. Il fournit également une protection supplémentaire dans le cas où un jeton de champ est compromis par un attaquant, car définir ou deviner le jeton de session serait un autre obstacle à surmonter pour l’attaquant.

Les développeurs doivent être prudents lorsque plusieurs applications sont hébergées dans un même domaine. Par exemple, même si example1.cloudapp.net et example2.cloudapp.net sont des hôtes différents, il existe une relation d’approbation implicite entre tous les hôtes sous le domaine *.cloudapp.net . Cette relation d’approbation implicite permet aux hôtes potentiellement non approuvés d’affecter les cookies de l’autre (les stratégies de même origine qui régissent les requêtes AJAX ne s’appliquent pas nécessairement aux cookies HTTP). L’ASP.NET Web Stack Runtime fournit une certaine atténuation en ce que le nom d’utilisateur est incorporé dans le jeton de champ. Ainsi, même si un sous-domaine malveillant est en mesure de remplacer un jeton de session, il ne peut pas générer un jeton de champ valide pour l’utilisateur. Toutefois, lorsqu’elles sont hébergées dans un tel environnement, les routines anti-XSRF intégrées ne peuvent toujours pas se défendre contre le détournement de session ou la connexion XSRF.

Les routines anti-XSRF ne protègent actuellement pas contre le clickjacking. Les applications qui souhaitent se défendre contre le détournement de clic peuvent facilement le faire en envoyant un en-tête X-Frame-Options: SAMEORIGIN avec chaque réponse. Cet en-tête est pris en charge par tous les navigateurs récents. Pour plus d’informations, consultez le blog IE, le blog SDL et OWASP. L’ASP.NET Web Stack Runtime peut, dans une version ultérieure, rendre les helpers MVC et Web Pages anti-XSRF définir automatiquement cet en-tête afin que les applications soient automatiquement protégées contre cette attaque.

Les développeurs web doivent continuer à s’assurer que leur site n’est pas vulnérable aux attaques XSS. Les attaques XSS sont très puissantes, et un exploit réussi briserait également les défenses ASP.NET Web Stack Runtime contre les attaques XSRF.

Accusé de réception

@LeviBroderick, qui a écrit la plupart des ASP.NET code de sécurité la majeure partie de ces informations.