Site icon Blog ARC Optimizer

Travailler avec des jetons d’accès sur un client dans une solution sans tête / Blogs / Perficient

Travailler avec des jetons d’accès sur un client dans une solution sans tête / Blogs / Perficient


Sitecore s’oriente vers une architecture composable avec un modèle de développement principalement sans tête, où toute la logique métier est déplacée vers une application principale, qui devient également un point d’intégration majeur, dont la majorité nécessite une sorte d’authentification.

TL;DR Ne travaillez pas avec un token d’authentification sur un client ! Mais que se passe-t-il si j’en ai besoin ? Ensuite, lisez le reste de cet article de blog pour comprendre quels sont vos risques et vos options.

Dans l’article, nous examinerons les raisons de la nécessité de travailler avec le jeton sur le client de l’application Web et découvrirons ce qui convient le mieux pour stocker le jeton : localStorage, sessionStorage ou cookie sans le HttpOnly drapeau (la réponse: aucun d’eux!). Nous examinons également les mesures à utiliser pour réduire le risque de fuites de jetons à travers diverses vulnérabilités.

Introduction

Les façons de faire correctement l’authentification et l’autorisation sont l’un des sujets les plus populaires dans le développement Web. Il existe diverses bonnes, mauvaises et mauvaises pratiques que je rencontre encore dans la vie de tous les jours lorsque je fouille certains sites Web avec des outils de développement. Avec cet article, nous allons en parler. Je veux prêter attention au problème, expliquer sa signification et également conseiller les approches pour améliorer la sécurité lors de l’utilisation de jetons sur le client.

Le problème

Une implémentation d’authentification défectueuse lors de l’utilisation de jetons sur un client augmente la vulnérabilité. Le scénario courant : avoir un jeton d’accès pour certaines ressources exposées côté client, suivi d’une attaque XSS, donne aux voleurs le même accès aux mêmes données que les scripts légitimes auraient.

On dirait :

… nous travaillons avec %framework% ou %library% où tous les problèmes doivent être pris en compte. Pourquoi m’en soucierais-je ?

Cela semble raisonnable, n’est-ce pas ? Cependant, la réalité n’est pas si simple et, dans la plupart des cas, il est presque impossible de garantir la sécurité. Il peut y avoir des situations où les menaces de sécurité proviennent d’endroits complètement différents. Imaginez, placer le jeton dans un cookie générique non HttpOnly et sécuriser l’application de XSS à l’absolu, n’empêche toujours pas une violation, simplement parce que l’un des sous-domaines détient un projet vulnérable qui néglige toutes ces mesures de sécurité.

Mais pourquoi un jeton d’accès volé est-il dangereux ? Avec lui, les attaquants pourront faire des demandes aux ressources API en notre nom ou simplement usurper l’identité de notre compte dans leur navigateur, obtenant le même niveau d’accès.

Cas maléfique n°1 : jeton d’accès dans localStorage

L’application reçoit un jeton d’accès du backend et l’enregistre dans le stockage local. Dans ce cas, s’il y a une vulnérabilité XSS, le jeton peut être obtenu par un attaquant. Disons que le jeton a une durée de vie de 15 minutes. Y a-t-il un risque que des attaquants utilisent le jeton en moins de 15 minutes ? Absolument! À mon avis, le risque est trop grand pour que cette approche soit envisagée.

Evil Case #2 : une paire de jetons d’accès et d’actualisation dans localStorage

Un cas avancé de l’exemple précédent. Un jeton d’accès a la même durée de vie, mais il existe également un jeton d’actualisation longue durée, une chaîne utilisée pour obtenir un nouveau jeton d’accès actualisé. Alors qu’un jeton d’accès est utilisé pour accéder à une ressource protégée, un jeton d’actualisation vous permet de demander un nouveau (ou supplémentaire) jeton d’accès. Comme dans l’exemple ci-dessus, un attaquant XSS peut accéder à la fois à un jeton d’accès et à un jeton d’actualisation de très longue durée.

Il existe un ensemble de mesures pour améliorer la sécurité des jetons d’actualisation : rotation des jetons, protection contre la réutilisation, bon (raisonnablement équilibré) sélection de la durée de vie, et autres. Cependant, ces mesures devraient compléter et ajouter plutôt que remplacer la protection contre les fuites de jetons XSS.

Cas maléfique n° 3 : jeton d’accès dans un cookie générique non HttpOnly

J’ai rencontré cette approche plusieurs fois et elle porte le plus grands risques de tous ceux ci-dessus. Je crois que l’intention d’utiliser un cookie générique est censée être pour une authentification « transparente » entre les sous-domaines, mais je voudrais mettre en garde contre l’utilisation négligente d’une telle approche.

Le hic, c’est que le jeton placé dans un cookie devient disponible pour tous les sous-domaines d’un site donné. Même si un attaquant ne parvient pas à trouver un trou XSS dans l’application principale, un service vulnérable peut exister parmi les nombreux sous-domaines. En fait, la numérisation des sous-domaines est une technique typique utilisée dans les tests d’intrusion. les sous-domaines peuvent contenir beaucoup de choses sales : un ancien site, un projet de test, un bac à sable/terrain de jeu, des API non protégées ou tout service similaire potentiellement vulnérable. En plus de cela, il peut également y avoir une attaque de prise de contrôle de sous-domaine qui permet également à un attaquant de voler le jeton de la même manière.

Pourquoi du tout alors on a besoin de travailler avec un jeton sur un client ?

La question logique venant de ce qui précède est : pourquoi avons-nous besoin de rendre le jeton disponible sur un client et de travailler avec lui ? Pourquoi ne pas simplement le mettre dans un cookie renforcé par session ? Outre la mauvaise conception de l’application, je pourrais citer quelques raisons valables.

1. Utiliser des jetons sans état

Contrairement aux jetons « avec état » qui servent de clé aux données de session conservées sur un serveur, ce jeton est autosuffisant et contient toutes les informations requises pour l’autorisation. En règle générale, les jetons JWT sont utilisés à cette fin et ils ont une structure standard :

En raison du bloc de signature JWT, la validité pourrait être vérifiée lors de la réception d’une demande ayant un tel jeton (soit une clé privée pour le chiffrement symétrique, soit une clé publique pour le chiffrement asymétrique). Cela signifie que la validation JWT a lieu sans le serveur émetteur (c’est à dire. pas besoin d’accéder à une base de données pour chaque validation).

L’approche permet d’éviter de stocker des jetons sur un serveur en émettant un tel jeton JWT lors de l’authentification et en le conservant chez un client.

2. Utilisation d’OAuth 2.0/OIDC pour l’authentification dans votre propre application

Une chose assez populaire consiste à utiliser, par exemple, le flux d’octroi de code d’autorisation dans OAuth2.0 ou OpenID Connect (OIDC).

Certains confondent OpenID Connect avec OAuth 2.0 – en fait, OIDC étend OAuth 2.0 et est un protocole d’authentification, tandis qu’OAuth 2.0 est nativement un protocole d’autorisation. Cependant, vous pouvez souvent voir des implémentations d’OIDC en plus d’OAuth 2.0. La différence est que dans le cas du serveur d’autorisation OIDC, le serveur de ressources joue également un rôle, mais uniquement pour l’identité de l’utilisateur.

3. Utilisation de SPA sans backend

Sans s’appuyer sur un backend, les développeurs sont obligés de rechercher des moyens de travailler avec le jeton sur le client. Les litiges sur Internet étaient autrefois populaires là où il est préférable de stocker un jeton pour travailler avec : dans localStorage, sessionStorage ou cookies (bien sûr, sans HttpOnly). En fait, en termes de sécurité, il n’y a pratiquement aucune différence. Et c’est pourquoi:

stockage local

sessionStorage

Cookies sans indicateur HttpOnly

Disponible chez un client

Oui

Oui

Oui

Liaison de domaine

Oui

Oui

Oui, encore plus large que les cas génériques

Contexte

Synchronise entre les onglets

Limité dans un onglet

Synchronise entre les onglets

Persistance

Persiste à la fermeture d’un navigateur

Persiste lors de l’actualisation d’un onglet mais s’annule lors de la fermeture de l’onglet

Persiste à la fermeture d’un navigateur

Qu’en est-il d’IndexedDB ?

IndexedDB n’est pas différent de localStorage sur le plan technologique, en termes d’accès, il est très similaire, à une différence près : les services workers y ont également accès.

Considérations

Comme cela vient d’en haut, toutes les méthodes ci-dessus sont également vulnérables au code malveillant sur un client. Tout ce qui est disponible à partir du code du développeur est disponible pour le code de l’attaquant.

Mais que devons-nous faire alors ? Il n’y a pas de solution miracle, diverses approches ont leurs avantages et leurs inconvénients, sont spécifiques à des projets sur mesure et doivent être choisies lors d’une phase de conception.

Approche #1. Ne pas travailler du tout avec le jeton sur le client

Le premier et le plus simple moyen d’éviter les risques est de refuser de travailler avec les jetons sur le client. Une approche avec état de l’authentification pourrait être préférée à celle sans état. Dans ce cas, un cookie de session stockerait le jeton. Il est crucial de s’assurer qu’un cookie nécessitera le paramétrage correct du HttpOnly, Secure, SameSiteet Path les attributs. De plus, je vous déconseille d’utiliser sans réfléchir des cookies génériques (pour les raisons évoquées ci-dessus), vous devez donc également prêter attention à l’attribut Domain.

Une chose à garder à l’esprit : étant donné que ce cookie sera envoyé au serveur dans le cadre des requêtes adressées au Domain et Pathil est nécessaire de se protéger des attaques CSRF (selon OWASP recommandations).

Cette approche supprime les avantages des jetons sans état, mais rend les risques transparents et est plus facile à mettre en œuvre.

Approche #2. Utilisation du backend de proxy

Mais que se passe-t-il si nous devons conserver la possibilité de travailler avec des jetons sans état ? Dans ce cas, vous devez utiliser une couche middleware qui assurera la sécurité du stockage du jeton.

Cela peut être visualisé dans un diagramme. Tout d’abord, voyez à quoi ressemble un processus simplifié d’authentification et d’accès à une ressource sans backend de proxy :

Le diagramme ci-dessus reprend tous les problèmes précédents. Ajoutons un proxy backend intermédiaire :

Dans ce schéma, le proxy principal n’agit pas comme un publicmais en tant que confidentiel client (RFC 6749). Il doit demander un jeton avec son ID client et son secret client.

Lors de l’authentification et de la réception d’un jeton, nous n’appelons pas directement le serveur d’autorisation, mais nous nous appuyons sur le proxy Backend [1]. Par conséquent, le proxy Backend est celui qui reçoit le jeton d’accès [4]. Ensuite, le serveur génère un cookie avec HttpOnly drapeau à envoyer à un client [6].

Ensuite, le client appelle la ressource API, mais pas directement, car elle ne possède pas de jeton, mais via un proxy [7]. L’authentification s’y fait ainsi que l’échange d’un cookie vers un access_token valide [8] à utiliser pour la requête Serveur de ressources [9] afin que la réponse reçue soit renvoyée par proxy au client [12].

La question se pose : quel cookie le proxy Backen peut-il créer et renvoyer ?

  • Un équivalent de cookie de session ordinaire au jeton reçu. Dans ce cas, nous devrons implémenter le travail avec des sessions et les stocker. Cependant, cela nous permet de gérer l’invalidation d’accès.
  • Chiffrement des valeurs reçues du serveur d’autorisation. Dans ce cas, nous évitons d’avoir à stocker la session. Lors d’une demande à une ressource, nous déchiffrons la valeur du cookie (comme nous avons la clé pour cela) et transmettre la requête plus loin.

Vous devez vous soucier de la date d’expiration du cookie et être également responsable de la définition d’autres attributs. Séparément, vous devez vous soucier de sélectionner la durée de vie d’un access_token, de le mettre en cache sur le proxy Backend, de mettre à jour un access_token, etc.

Cette approche pourrait également être appliquée à SPA, car avec SPA, il n’est pas obligatoire d’abandonner entièrement le backend. Dans une architecture de microservices, le proxy Backend pourrait être fait par API gateway ou Backend-for-frontend (BFF).

Ainsi, cette approche nous donne la possibilité de nous protéger contre le vol de jetons via XSS, cependant, il est vulnérable aux attaques CSRF, vous devez donc vous en protéger.

Approche #3 : Utiliser un service worker

Employé de service – un script qui s’exécute en arrière-plan dans un navigateur qui agit comme un serveur proxy pour les communications entre l’application, le navigateur et le réseau. Le service worker s’exécute dans un contexte séparé, s’exécute dans un thread séparé, n’a pas accès au DOM et, par conséquent, le client n’a pas non plus accès au service worker et aux données qui s’y trouvent.. Nous utiliserons cette fonctionnalité pour protéger le jeton d’accès contre les fuites pendant XSS.

Dans ce cas, le service worker est chargé d’obtenir le jeton du serveur d’autorisation et d’adresser les requêtes au serveur de requête. Dans ce cas, les requêtes du client sont mandatées par le service worker, et il semble les intercepter. Par conséquent, l’appel à la méthode getter et le jeton lui-même sont complètement isolécar le contexte du service worker n’est pas accessible aux autres contextes JavaScript.

Cela rappelle le schéma discuté précédemment avec un backend proxy, n’est-ce pas ? La différence est que la couche intermédiaire, agissant comme un proxy, est implémentée en toute sécurité directement sur un client, et non sur le serveur.

Dans ce cas, l’attaque CSRF n’est pas possible, car le jeton n’est disponible que pour le service worker, la possibilité de voler le jeton via XSS est également inexistante. Cela se fait au prix d’une mise en œuvre plus compliquée. Et bien sûr, travailleur de service est pris en charge par tous les navigateurs modernes.

Les références






Source link
Quitter la version mobile