Mise en œuvre dun jeton anti-CSRF en Java

Objectif de cet article

:

attaques CSRF (Cross Site Request Forgery). L’implémentation proposée est un filtre Java plus quelques classes auxiliaires et elle convient (évidemment) aux projets utilisant le langage Java comme technologie de backend.

Dans

le cas d’une attaque CSRF, le navigateur est amené à faire des requêtes non autorisées au nom de la victime, à l’insu de celle-ci. Le scénario d’attaque général contient les étapes suivantes :

  1. La victime se connecte au site Web vulnérable, de sorte qu’il dispose d’une session réelle et authentifiée.
  2. Le pirate force la victime (généralement à l’aide d’un spam/e-mail de phishing) à naviguer vers un autre site Web (malveillant) contenant l’attaque CSRF.
  3. Lorsque le navigateur de la victime exécute la page Web (malveillante), le navigateur exécute une requête (frauduleuse) à l’intention des personnes vulnérables à l’aide de la session authentifiée par l’utilisateur. L’utilisateur n’est pas du tout conscient du fait que la navigation sur le site web (maléfique) déclenchera une action sur le site web vulnérable.

Afin d’atténuer les attaques CSRF, les techniques suivantes peuvent être utilisées :

  • Vérification de la même origine avec des en-têtes standard —  Cette technique consiste à déterminer l’origine de la requête (origine source) et à déterminer l’origine de la requête (origine cible).
  • Modèle de jeton de synchronisation : un jeton anti-CSRF est créé et stocké dans la session utilisateur et dans un champ masqué lors des soumissions de formulaires suivantes. À chaque envoi, le serveur vérifie que le jeton de la session correspond à celui soumis à partir du formulaire. Tomcat 6+ implémente ce modèle (pour plus d’informations, veuillez consulter le filtre de protection CSRF) et l’OWASP CSRFGuard propose une autre mise en œuvre.
  • Schéma défi/réponse – La solution consiste à forcer l’utilisateur à saisir une valeur connue de lui seul afin de réaliser l’action.
  • Modèle de jeton  chiffré : lorsqu’un utilisateur s’authentifie sur un site, celui-ci doit générer un jeton unique composé de l’ID de l’utilisateur, d’une valeur d’horodatage et d’un nonce, à l’aide d’une clé unique disponible uniquement sur le serveur. Le jeton est renvoyé au client et intégré dans un champ masqué. Lorsque le formulaire est publié sur le serveur, celui-ci déchiffre le jeton et vérifie que l’ID d’utilisation récupéré à partir du jeton correspond à celui de la session.
  • – Cela sera défini dans le paragraphe suivant.

Pour des explications plus approfondies, je vous recommande  vivement de lire le chapitre 5 du livre Iron-Clad Java : Building Secure Applications et/ou l’aide-mémoire de prévention de l’OWASP Cross-Site Request Forgery (CSRF).

Le serveur n’a pas besoin d’enregistrer cette valeur de quelque manière que ce soit, c’est pourquoi ce modèle est également appelé Stateless CSRF Defense .

Le site exige ensuite que chaque demande de transaction inclue cette valeur aléatoire en tant que valeur de formulaire masquée (ou autre paramètre de demande).

  
Le flux de travail

du
serveur

AngularJS implémente le flux de travail du client prêt à l’emploi, voir Protection contre la falsification de requête intersite (XSRF).

L’implémentation proposée se présente sous la forme d’un filtre Servlet (Java) et peut être trouvée ici : GenericCSRFFilter GitHub.

Pour utiliser le filtre, vous devez doit le définir dans votre fichier web.xml :

Le filtre peut avoir deux paramètres d’initialisation facultatifs : csrfHeaderName , qui représente le nom de l’en-tête HTTP qui contiendra également le jeton CSRF.

Les valeurs par défaut de ces paramètres sont « XSRF-TOKEN » pour le et « X-XSRF-TOKEN » pour le csrhHeaderName, les deux étant les valeurs par défaut qu’AngularJS s’attend à avoir pour implémenter la protection CSRF.

Par défaut, le filtre possède les fonctionnalités suivantes :

  • Fonctionne avec AngularJS.
  • Le jeton CSRF sera un UUID aléatoire.
  • Toutes les ressources qui ne sont PAS accessibles via une méthode de requête GET seront protégées par CSRF.

Comment ça marche Sous le capot

Le la plupart des fonctionnalités se trouvent dans la méthode GenericCSRFStatelessFilter#doFilter, voici le diagramme de séquence qui explique ce qui se passe dans cette méthode :

Diagramme de séquence de la méthode doFilter

La méthode doFilter est exécutée sur chaque requête HTTP :

  1. Le filtre crée une instance de ExecutionContext est présent) et l’implémentation de ResourceCheckerHook   , TokenBuilderHook et ResponseBuilderHook . (Voir la section suivante pour la signification de ces classes).
  2. Le filtre vérifie l’état de la ressource HTTP, ce statut peut être :  (voir l’énumération ResourceStatus) à l’aide d’une instance de ResourceCheckerHook.
  3. Si l’état de la ressource est ResourceStatus#MUST_NOT_BE_PROTECTED TokenBuilderHook .
  4. Si l’état de la ressource est calculé, calculez le CSRFStatus de la ressource, puis utilisez une instance de ResponseBuilderHook pour renvoyer la réponse au client.

Comment étendre le comportement par défaut

Il est possible d’étendre ou d’écraser le comportement par défaut en implémentant les interfaces hooks. Toutes les implémentations de hooks doivent être sécurisées par thread.

  1. Le ResourceCheckerHook est utilisé pour vérifier l’état d’une ressource demandée. L’implémentation par défaut est DefaultResourceCheckerHookImpl et elle renverra ResourceStatus#MUST_NOT_BE_PROTECTED pour toute méthode HTTP GET, pour tous les autres types de requêtes, elle renverra {@link   sinon. La signature de l’interface est la suivante :
  2. The  TokenBuilderHook L’implémentation  par défaut est DefaultTokenBuilderHookImpl et utilise un appel à UUID.randomUUID pour récupérer un jeton. La signature de l’interface est la suivante :
  3. Le  ResponseBuilderHook est utilisé pour générer la réponse au client en fonction du CSRFStatus de la ressource. L’implémentation par défaut est DefaultResponseBuilderHookImpl et elle lève une SecurityException  si l’état CSRF est , CSRFStatus#HEADER_TOKEN_NOT_PRESENT, ou . Si l’état CSRF est  , la signature de l’interface est la suivante :

Les hooks sont instanciés à l’intérieur de la méthode GenericCSRFStatelessFilter#init à l’aide de la fonction de chargement Java 6. Donc, si vous voulez utiliser votre implémentation de l’un des hooks, vous devez créer un  répertoire qui contient un fichier texte dont le nom correspond au nom de classe d’interface complet du hook que vous souhaitez remplacer.

Voici le diagramme de séquence représentant les initialisations des hooks :