Protégez vos applications Angular des attaques CSRF avec ASP.NET Web API

Magazine
Marque
MISC
Numéro
82
|
Mois de parution
novembre 2015
|
Domaines


Résumé
Les attaques Cross Site Request Forgery (CSRF) sont particulièrement redoutables. Si une équipe de développeurs n’est pas consciente du problème, les applications qu’elle produira seront très certainement vulnérables. Ces dernières années, de gros efforts ont été faits par les éditeurs pour équiper leurs frameworks d’outils de protection adéquats, allégeant ainsi la tâche du développeur. Leurs travaux ont été payants : la prévalence des vulnérabilités de ce type a chuté. Néanmoins, avec l’essor des applications SPA (Single Page Application), la mise en place d’une protection anti-CSRF n’est plus aussi directe. Dans cet article, nous proposons une approche pour défendre une application AngularJS reposant sur un backend ASP.NET Web API.

Body

Pour la suite de l’article, nous allons prendre l’exemple d’une application (ultra) simplifiée de gestion de documents. L’utilisateur peut lister les documents du système et supprimer ceux dont il est l’auteur.

1. Rappels sur le CSRF

Une attaque CSRF consiste à faire jouer à un utilisateur, authentifié sur un système, une action à effet de bord (création, modification, suppression…) qu’il a le droit d’effectuer. L’action se passe à l’insu et bien souvent contre la volonté de la victime.

Imaginons par exemple une application de gestion de documents ayant différents niveaux de confidentialité (publics, restreints, confidentiels, etc.). Avec un système vulnérable, une personne n’ayant pas accès à un document confidentiel pourrait faire exécuter à un administrateur une modification du statut, le passant de confidentiel à public. Ainsi, elle pourrait consulter l’information qui lui était jusqu’à présent inaccessible.

Dans le cas de notre application exemple, un utilisateur pourrait exploiter une faille CSRF pour  forcer un autre utilisateur à supprimer ses propres documents.

1.1 Mode opératoire

L’utilisateur malveillant remarque que son navigateur émet la requête suivante lorsqu’il supprime un document :

POST /api/documents/delete/a4ab6134-c20f-4f24-8ada-0f6b26e4416c HTTP/1.1

Host: myhost.com

Connection: keep-alive

Accept: application/json, text/plain, */*

Cookie: .AspNet.ApplicationCookie= L-7wYJg6yM52gr0tnil-TxsH77ymj...

Il souhaiterait supprimer un document pour nuire à un de ses collègues. N’ayant pas les droits nécessaires sur ce fichier, il lui faut trouver un moyen détourné pour arriver à ses fins. Il parvient à récupérer l’identifiant du document en question via l’application qui lui permet de les lister. Il met alors en place une page web trompeuse invitant le visiteur à cliquer sur un bouton pour gagner un smartphone.

En réalité, lors du clic sur le bouton, la page émet la requête de suppression avec l’identifiant du fichier à supprimer.

POST /api/documents/delete/4abcd580-8d7d-4571-9b3b-0c7f54cb81fe HTTP/1.1

Host: myhost.com

Connection: keep-alive

Accept: application/json, text/plain, */*

Cookie: .AspNet.ApplicationCookie= L-7wYJg6yM52gr0tnil-TxsH77ymj...

L’attaquant envoie ensuite à son collègue un e-mail contenant un lien sur la page piégée. Si la victime est authentifiée sur l’application de gestion de documents au moment où elle clique sur le lien de l’e-mail, le navigateur ouvrira un onglet sur le site frauduleux. Dès lors que l’utilisateur cliquera sur le bouton pour gagner son lot, son client web va envoyer la requête de suppression au système de gestion de documents accompagnée du cookie de session de l’utilisateur.

Du point de vue du serveur, la requête est parfaitement légitime :

- elle provient d’un utilisateur authentifié ;

- cet utilisateur a les droits de supprimer le fichier.

Elle est donc traitée tout à fait normalement, et l’utilisateur supprime le document sans s’en apercevoir.

1.2 Mécanismes de protection

On peut adopter plusieurs stratégies pour se protéger des attaques CSRF. Toutes cependant consistent à vérifier la légitimité de la requête reçue par l’application. Pour cela, il nous faut nous assurer que la requête a bien été émise par l’utilisateur que l’on pense, et qu’il veut effectivement procéder à l’action demandée. On peut donc :

- demander confirmation après contrôle côté serveur ;

- pour les opérations sensibles, demander confirmation à l’utilisateur avec saisie de mot de passe ;

- s’assurer que la requête provient bien de la page censée en être à l’origine dans le workflow (vérification du referer).

Chacune de ces techniques comporte néanmoins son lot d’inconvénients. Soit elles ne sont pas fiables en étant utilisées seules (referer), soit elles perturbent l’utilisation (confirmations).

Toutes ces techniques peuvent être utilisées en complément d’un mécanisme plus fiable et moins invasif. Elles participent à la conception d’un système sûr. En les combinant, on applique le principe de défense en profondeur.

Une technique plus fiable et transparente pour l’utilisateur est communément utilisée. Elle consiste à utiliser un jeton de validation de requête, aussi appelé Token Anti-Forgery(anti-falsification).

Avec ce modèle, appelé Synchronizer Token Pattern, chaque réponse du serveur est accompagnée d’une séquence de caractères – nommée jeton – générée de manière aléatoire ou cryptographique. Selon les implémentations, ce jeton peut être à usage unique, à durée de vie limitée (expiration) ou peut être associé à la session de l’utilisateur (ou un subtil mélange). Lorsqu’une requête provient du client, elle doit être accompagnée de la séquence émise par le serveur pour être validée. Ainsi, il n’est plus possible de prédire le format des requêtes, l’introduction d’un paramètre non prédictible rendant celles-ci uniques.

Pour être valide, notre requête de suppression devra prendre la forme suivante :

POST /api/documents/delete/a4ab6134-c20f-4f24-8ada-0f6b26e4416c HTTP/1.1

Host: myhost.com

Connection: keep-alive

Accept: application/json, text/plain, */*

Cookie: .AspNet.ApplicationCookie= L-7wYJg6yM52gr0tnil-TxsH77ymj...__RequestVerificationToken=km52SnEBO2NhoZcbgnSOqOZNKmh8Bqk_luGqFIQ6KWMQh6…

__RequestVerificationToken=H1AUE3_MkZpz4np3U7wL5IOkfsiB3vax6_pvw1E1vNZz19Y...

2. Protection Anti-Forgery d'ASP.NET

L’implémentation de la protection CSRF du framework ASP.NET repose sur une variante du Synchronizer Token Pattern, appelée Double Submit Cookies,  qui consiste à valider les requêtes après une double-vérification.

Lorsqu’un utilisateur demande une page permettant d’exécuter des actions à effets de bord, un jeton est inséré dans un champ de type hidden :

<input name="__RequestVerificationToken" type="hidden" value="Mj33sWdAnGfqKX2CKsl_U0KbnSSX_vphRKsjYEUk-lCBsDurOMsaozMUJmlDleyLtz26pMYEvFjk4WyrnW44XGkjtAdS1S3NMcZbc0v3wlk1" />

En plus de ce jeton de formulaire, ASP.NET envoie un cookie de validation au navigateur. Les 2 jetons vont de pair. Leurs valeurs sont liées et les deux sont nécessaires à la validation des requêtes.

fig1_cookie_validation_aspnet

Fig. 1 : Cookie de validation installé par ASP.NET.

3. Protection d’une ASP .NET MVC classique

Protéger une application ASP .NET MVC est très simple pour le développeur : il lui suffit d’invoquer la méthode helper @Html.AntiforgeryToken dans le code de la vue Razor – le moteur de templating du framework .Net – pour que le serveur insère le champ caché contenant le jeton dans le formulaire et affecte le cookie de validation à la réponse.

@using (Html.BeginForm("Delete", "Documents"))

{

    @Html.AntiForgeryToken()

}

Pour la validation, il ne reste qu’à décorer l’action « cible » du formulaire avec l’attribut ValidateAntiForgeryToken.

[HttpPost]

[ValidateAntiForgeryToken]

public ActionResult DeleteDocument(Guid id)

{

    Document document = db.Documents.Find(id);

    if (IsActionAuthorized(DocumentAction.Delete, User, document))

    {

        db.Documents.Remove(document);

    }

    else

    {

        return NotAuthorized();

    }

    return View();

}

Lors de la soumission du formulaire, le navigateur enverra le cookie __RequestVerificationToken et le contenu du champ hidden __RequestVerificationToken. L’attribut ValidateAntiForgeryToken s’assure de la validation côté serveur, contrôlant la correspondance entre les 2 valeurs incluses dans la requête.

4. Protection d’une application SPA

4.1 Génération des jetons

Les applications de type SPA sont plus complexes à défendre. Les vues sont générées côté client, et il n’est pas rare que plusieurs requêtes soient nécessaires au chargement d’un formulaire et de ses données (listes de sélections, options, etc.). Comme il n’est plus possible d’affecter un jeton de validation aux formulaires, nous devons trouver un moyen pour mettre à disposition du navigateur les 2 jetons pour les requêtes XHR.

AngularJS fournit, pour la gestion des requêtes AJAX , les services $http et $resource qui sont équipés nativement d’un système de protection CSRF. Par défaut, le service $http – et par extension le $resource s'appuyant dessus –  cherche un cookie nommé XSRF-TOKEN puis en extrait le contenu pour l’affecter à un header « custom » X-XSRF-TOKEN.

Côté Web API, nous avons donc 3 choses à faire :

- Générer des jetons pour les requêtes et les passer dans un cookie nommé XSRF-TOKEN ;

- Créer un système de validation des requêtes similaire à l’attribut ValidateAntiForgeryToken du framework MVC. Seulement, nous utiliserons une liste blanche plutôt qu'une liste noire : au lieu de dire explicitement quelles actions protéger, nous les protégerons par défaut et dirons explicitement quelles actions ne pas protéger* ;

- Prévoir un système d’exclusion, permettant de marquer les actions à ne pas protéger.

* Procéder ainsi respecte le principe de moindre privilège. L’utilisation d’un attribut décorant les actions à protéger risque d’introduire des vulnérabilités en cas d’oubli.

4.1.1 Création d'un filtre d'actions global

Nous voulons envoyer un couple de jetons avec chaque réponse du serveur, afin d’être sûr que les requêtes futures disposeront de tokens valides.

ASP.NET offre un mécanisme simple permettant d’ajouter un comportement après l’exécution de toutes les actions : les filtres globaux (Global Filters). Nous allons ici implémenter un filtre qui générera le cookie attendu par le service $http d’Angular et l’ajoutera à la réponse.

Les filtres peuvent être vus comme des décorateurs ou des triggers SQL. Ils permettent d’ajouter du comportement avant ou après l’exécution d’une méthode. Pour implémenter un filtre, nous pouvons étendre la classe de base ActionFilterAttribute. Dans notre cas, nous devons redéfinir la méthode OnActionExecuted, dont l’exécution suit celle de chaque action.

public class XsrfCookieGeneratorFilter : ActionFilterAttribute

{

    public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)

    {

        var xsrfTokenCookie = new HttpCookie("XSRF-TOKEN");

        xsrfTokenCookie.Value = ComputeXsrfTokenValue();

        xsrfTokenCookie.Secure = true;  

        HttpContext.Current.Response.AppendCookie(xsrfTokenCookie);

    }

    private string ComputeXsrfTokenValue()

    {

        string cookieToken, formToken;

        AntiForgery.GetTokens(null, out cookieToken, out formToken);

        return cookieToken + ":" + formToken;

    }

}

Dans le code ci-dessus, nous créons un cookie nommé XSRF-TOKEN, comme l’attend le service $http d’Angular. La valeur du cookie est générée à l’aide du la méthode AntiForgery.GetTokens du framework.Net, qui nous fournit les valeurs des 2 jetons corrélés dont nous avons besoin. C’est cette même méthode qui est utilisée par la méthode Html.AntiForgeryToken vue précédemment. Ces valeurs sont concaténées avec un séparateur – qui doit être différent du séparateur de cookies – entre elles. Le résultat est affecté au cookie, ajouté à la réponse pour envoi au navigateur.

Notons que le cookie doit être créé avec l’attribut secure afin de garantir son transit uniquement par canal sécurisé HTTPS. Il est important de ne pas ajouter de date d’expiration pour que sa durée de vie soit limitée à la durée de la session. Enfin,  n’utilisez pas l’attribut HttpOnly : Angular a besoin du cookie et HttpOnly aurait pour effet d’empêcher la récupération des tokens par le code JavaScript.

4.1.2 Enregistrement du filtre

L’instruction suivante permet d’indiquer à Web API de charger le filtre pour l’exécuter après chaque action. On la place dans la méthode Register de la classe WebApiConfig située dans le répertoire App_Start de l’application :

config.Filters.Add(new XsrfCookieGeneratorFilter());

Une fois l’instruction ajoutée, le système d’affectation systématique du cookie est opérationnel.

Il peut arriver que dans une application, certains controllers n’aient pas accès aux informations de session. Il faudra dans ce cas utiliser un mécanisme d’exclusion comme présenté dans la suite de l’article pour éviter tout problème de validation de tokens.

4.2 Logique de validation

La première étape terminée, il nous faut maintenant implémenter le mécanisme de validation des jetons. Comme précédemment, nous allons utiliser un filtre. Par défaut, le contrôle s’effectuera pour toutes les actions Web API, assurant ainsi une protection systématique.

public class XsrfTokensValidationFilter : ActionFilterAttribute

{

    public override void OnActionExecuting(HttpActionContext actionContext)

    {

        var cookieToken = "";

        var formToken = "";


        IEnumerable<string> headerValues;

        actionContext.Request.Headers.TryGetValues("X-XSRF-TOKEN", out headerValues);

        if (headerValues != null)

        {

            var xsrfTokensValue = headerValues.FirstOrDefault();

            // Count(char) est une méthode d'extension "maison"

            if (!string.IsNullOrEmpty(xsrfTokensValue) && xsrfTokensValue.Count(':') == 1)  

            {

                var values = xsrfTokensValue.Split(':');

                cookieToken = values[0];

                formToken = values[1];

            }

        }

        AntiForgery.Validate(cookieToken, formToken);

    }

}

Nous redéfinissons cette fois  la méthode OnActionExecuting, qui s’exécute juste avant l’exécution de l’action à protéger. La valeur du header X-XSRF-TOKEN ajouté par Angular est utilisée pour extraire les 2 tokens de validation. Ceux-ci sont contrôlés par la méthode AntiForgery.Validate du framework, qui lève une exception dans le cas où ils sont invalides. Dans un tel cas, l’action protégée n’est donc pas exécutée. S’ils sont valides, l’action protégée s’exécute normalement.

Là encore, nous devons donner une portée globale au filtre pour protéger par défaut toutes nos actions.

config.Filters.Add(new XsrfTokensValidationFilter());

4.3 Système d’exclusion

Il peut arriver que nos actions n’aient pas besoin d’être protégées. C’est le cas pour les opérations publiques, accessibles avant authentification, ou sans effet de bord (GET/HEAD). Nous devons donc maintenant ajouter des exceptions, ce qui peut être fait de 2 manières. Le choix de l’approche à adopter dépendra de votre projet.

4.3.1 Exception systématique sur méthodes http

Un simple test préalable à la validation des jetons suffit pour désactiver le contrôle des requêtes utilisant une des méthodes http sans effet de bord. Ainsi, nous pouvons modifier la méthode OnActionExecuting de la classe XsrfTokensValidationFilter précédemment créée de la manière suivante :

if (actionContext.Request.Method != HttpMethod.Get &&

    actionContext.Request.Method != HttpMethod.Head)

{

    AntiForgery.Validate(cookieToken, formToken);

}

La validation ne se fait plus alors que si les requêtes n’utilisent ni GET ni HEAD.

4.3.2 Exception explicite par attribut

Pour une granularité plus fine de la gestion de validation de tokens, on peut demander explicitement que le contrôle soit omis pour une méthode donnée. Un système de décoration convient tout à fait à ce type de cas. On définit donc un simple attribut vide qui servira uniquement à marquer les actions à ne pas contrôler.

public class SkipXsrfValidationAttribute : Attribute

{

}

Nous allons à nouveau ajouter quelques lignes de code à notre méthode OnActionExecuting de la classe XsrfTokensValidationFilter pour éviter la validation lorsque l’action est décorée.

var isDecorated = actionContext.ActionDescriptor

       .GetCustomAttributes<SkipXsrfValidationAttribute>(false)

       .Any();

if (actionContext.Request.Method != HttpMethod.Get &&

    actionContext.Request.Method != HttpMethod.Head &&

    !isDecorated)

{

    AntiForgery.Validate(cookieToken, formToken);

}

Il ne reste plus qu’à décorer les méthodes que nous ne voulons pas protéger.

[HttpPost]

[SkipXsrfValidation]

public void SomeMethod()

{

    // Code ne nécessitant pas de protection

}

Conclusion

Bien qu’il soit moins aisé de protéger une application SPA qu’une application ordinaire, les frameworks récents tels qu’ASP.NET offrent de nombreux mécanismes permettant une défense efficace. Sans surprise, c’est une fois encore côté serveur que les choses se passent. C’est aussi dû au fait que l’équipe d’AngularJS ait pensé à proposer un mécanisme « out-of-the-box » pour la protection de nos applications. Les efforts des éditeurs nous poussent vers un Web plus sûr. À nous maintenant de faire le reste.


Sur le même sujet

Zero Trust : anti-SOC, tu perds ton sang froid ?

Magazine
Marque
MISC
Numéro
110
|
Mois de parution
juillet 2020
|
Domaines
Résumé

Les security operation centers, au sens large, sont aujourd’hui au cœur des systèmes d’information des entreprises. En revanche, beaucoup adoptent encore et toujours une approche traditionnelle de la sécurité du SI. Comment le paradigme Zero Trust va-t-il impacter nos supervisions ? Repensons un peu à toutes ces années de service pour voir ce que Zero Trust peut apporter au SOC, et réciproquement comment ces derniers peuvent accompagner la transition.

Anti-leurrage et anti-brouillage de GPS par réseau d’antennes

Magazine
Marque
MISC
Numéro
110
|
Mois de parution
juillet 2020
|
Domaines
Résumé

La localisation, la navigation et le transfert de temps (PNT) par constellation de satellites, et notamment le Système de Positionnement Global (GPS), sont devenus omniprésents dans notre quotidien. Le brouillage – volontaire ou non – et le leurrage de ces signaux très faibles sont désormais accessibles à tout le monde, mais les subir n’est pas une fatalité : nous allons aborder les méthodes pour se protéger de tels désagréments afin de retrouver les services d’origine en annulant ces interférants par une approche multi-antennes.

Découverte et scan de réseaux

Magazine
Marque
MISC
HS n°
Numéro
21
|
Mois de parution
juillet 2020
|
Domaines
Résumé

Ce réseau est trop vaste à surveiller, l’administrateur ne s’y retrouvait plus entre toutes ces plages d’adresses, ces serveurs, imprimantes et autres webcams éparpillés dans tous les coins des locaux ; sans parler du Cloud situé on ne sait où. Et s’il avait quelques outils et méthodes sous la main pour avoir une cartographie à peu près complète de son réseau et des différents services disponibles ? Essayons de l’aider dans sa tâche en complétant sa panoplie.

Exploitations de collisions MD5

Magazine
Marque
MISC
HS n°
Numéro
21
|
Mois de parution
juillet 2020
|
Domaines
Résumé

Vous trouvez un système indexant des fichiers via MD5. À quel point est-il vulnérable ? Depuis l'attaque [1] en 2008 qui a généré un faux certificat SSL, la fonction d'empreinte MD5 est considérée comme inutilisable par les experts, car vulnérable en pratique. Cela dit, elle est encore souvent utilisée, et parfois à bon escient. Par exemple en juillet 2019, on apprenait que le système de censure de WeChat [2] était basé sur une liste noire indexée avec MD5. Cet article a pour but de clarifier les attaques par collisions contre MD5.

Introduction au dossier : Zero Trust : avenir de la sécurité ou chimère marketing ?

Magazine
Marque
MISC
Numéro
110
|
Mois de parution
juillet 2020
|
Domaines
Résumé

Derrière ce titre modéré, il aurait été trop facile d’être provocateur, se cache une référence que nos lecteurs historiques auront relevée. Il y a plus de dix ans maintenant, au SSTIC 2008, Cédric Blancher donnait une conférence dont le titre était : « Dépérimétrisation : Futur de la sécurité ou pis aller passager ? ». Les constats de l’époque, qui ne doivent pas être loin pour de nombreuses entreprises encore aujourd’hui, sont que les paradigmes de sécurité des réseaux informatiques ont des limites importantes : difficulté à mettre en place du contrôle d’accès ainsi qu’une segmentation et un filtrage réseau efficace.

Sécurité de l'implémentation standard de VXLAN

Magazine
Marque
MISC
Numéro
110
|
Mois de parution
juillet 2020
|
Domaines
Résumé

Cet article de sensibilisation met en avant une faiblesse de la RFC 7348 permettant de réaliser une attaque du type « homme du milieu ». Il décrit d'abord le fonctionnement de VXLAN, explique ensuite le mécanisme exploité et les dangers associés, puis propose des recommandations ainsi qu'une alternative.

Par le même auteur

Protégez vos applications Angular des attaques CSRF avec ASP.NET Web API

Magazine
Marque
MISC
Numéro
82
|
Mois de parution
novembre 2015
|
Domaines
Résumé
Les attaques Cross Site Request Forgery (CSRF) sont particulièrement redoutables. Si une équipe de développeurs n’est pas consciente du problème, les applications qu’elle produira seront très certainement vulnérables. Ces dernières années, de gros efforts ont été faits par les éditeurs pour équiper leurs frameworks d’outils de protection adéquats, allégeant ainsi la tâche du développeur. Leurs travaux ont été payants : la prévalence des vulnérabilités de ce type a chuté. Néanmoins, avec l’essor des applications SPA (Single Page Application), la mise en place d’une protection anti-CSRF n’est plus aussi directe. Dans cet article, nous proposons une approche pour défendre une application AngularJS reposant sur un backend ASP.NET Web API.