Examen de spécialiste agréé en sécurité de l'information — Printemps 2024 (Reiwa 6), question 1 de l'après-midi expliquée — JWT alg=none, autorisation API et mesure provisoire par WAF

· · Spécialiste agréé en sécurité de l'information, Examen RISS, API, Sécurité des API, JWT, Authentification, Autorisation, WAF, Log4Shell, Sécurité de l'information, Vulnérabilité, IPA

« Comme on vérifie la signature du JWT, on peut faire confiance à l’identifiant d’utilisateur. »

Cette affirmation n’est vraie qu’à moitié.

La question 1 de l’après-midi de l’examen de spécialiste agréé en sécurité de l’information, session printemps 2024 (Reiwa 6), prend pour sujet une API appelée depuis un smartphone1. Une fois l’authentification réussie, un JWT est émis, et ce JWT accompagne les appels aux API de récupération et de mise à jour des informations de l’utilisateur. À première vue, c’est une configuration courante.

Pourtant, l’audit révèle les quatre problèmes suivants.

  1. En changeant alg dans l’en-tête du JWT pour none, un JWT sans signature est accepté
  2. En gardant un JWT valide mais en changeant mid vers un autre identifiant d’utilisateur, on peut récupérer et modifier les informations d’autrui
  3. En ajoutant status=paid, absent de la spécification, un utilisateur gratuit devient un utilisateur payant
  4. Le code d’authentification à 4 chiffres reçu par e-mail peut être attaqué par force brute sans aucune limite

Ces quatre problèmes ont tous l’air d’être des « vulnérabilités liées à l’authentification », mais leur cause n’est pas la même. Ce sont des frontières distinctes qui sont franchies : l’intégrité du jeton, l’autorisation au niveau de l’objet, l’autorisation au niveau de la propriété, et la limitation du nombre de tentatives.

Dans la seconde moitié, un autre sujet s’ajoute. Une vulnérabilité critique est publiée dans une bibliothèque open source largement utilisée, permettant d’exécuter du code depuis l’extérieur en exploitant JNDI Lookup. Ni version corrigée ni règle WAF finalisée n’existent encore. Pendant ce temps, il faut déterminer comment vérifier l’impact, où le WAF doit regarder, et pourquoi on commence par le mode « détection » plutôt que « blocage ».

Cet article s’appuie sur les exemples de réponses officiels2 et le commentaire de notation3 pour organiser, pour chaque question, non seulement la réponse, mais aussi pourquoi c’est cette réponse-là, et jusqu’à quel niveau de rigueur la concevoir en pratique.

Vue d'ensemble du problèmeMontre les frontières de confiance franchies à chaque étape : code d'authentification, JWT, autorisation API, vulnérabilité de bibliothèqueAucune limite de tentativesAutorise alg=noneFait confiance à midstatus=paidJNDI/LDAP/HTTPApplication de l'utilisateurCode d'authentification à 4 chiffresÉmission du JWTAPI utilisateurSortie de journalBibliothèque vulnérableExécution de code externeRécupération/modification des données d'autruiModification de l'état de facturation

Figure 1 : vue d’ensemble du problème. Une frontière de confiance différente est franchie à chaque étape.

1. La conclusion, d’abord

  • Le fait qu’une API RESTful n’ait pas de session s’appelle être sans état (stateless). Cela ne signifie toutefois pas que le serveur ne conserve absolument aucune base de données ni aucun état d’utilisateur
  • Un code d’authentification à 4 chiffres compte 10 000 combinaisons. À 10 tentatives par seconde, cela réussit en moyenne au bout de 5 000 tentatives, soit 500 secondes. C’est plus court que la durée de validité de 10 minutes, donc la seule durée d’expiration ne suffit pas à protéger
  • La mesure minimale contre alg=none consiste à vérifier que alg dans l’en-tête du JWT n’est pas NONE. En pratique, cependant, on fixe côté serveur les algorithmes autorisés
  • Même si le JWT est correct, il ne faut pas faire confiance au mid de la requête. Il faut soit comparer l’identifiant d’utilisateur du JWT avec mid, soit, de façon plus sûre, ne pas recevoir mid du tout et déterminer la cible à partir du JWT
  • L’ajout de status=paid est un problème de Mass Assignment, qui relie jusqu’à l’objet interne des propriétés hors spécification. Utiliser le DTO de mise à jour comme liste d’autorisation, et ne jamais laisser l’utilisateur modifier l’état de facturation
  • La réponse attendue contre la force brute est un traitement qui verrouille le compte lorsque le nombre d’échecs consécutifs dépasse un seuil. En pratique, on ajoute aussi un délai progressif et un contrôle par source
  • Pour vérifier l’impact d’une nouvelle vulnérabilité critique, on n’utilise pas de commande destructrice : on enregistre l’accès à index.html d’un serveur de test pour confirmer que l’exécution de code externe est bien atteinte
  • La chaîne d’attaque se trouve dans un en-tête HTTP, donc la cible d’inspection du WAF est Header. Comme expression régulière tolérant l’inversion majuscules/minuscules, on peut par exemple utiliser \W[jJ][nN][dD][iI]\W
  • L’avantage de mettre d’abord le WAF en mode « détection » est d’éviter de bloquer le trafic professionnel à cause d’un faux positif. À chaque alerte reçue, on examine s’il s’agit réellement d’une attaque, puis on passe au blocage après réglage
  • Le WAF est une mesure provisoire ; la mesure de fond est la mise à jour de la bibliothèque concernée vers une version corrigée

2. Le sujet et la correspondance des questions

Le contexte du problème est l’entreprise G, qui cherche à lancer un nouveau service de santé. Les utilisateurs saisissent depuis une application mobile des données comme leurs repas et leur poids, et reçoivent en retour une évaluation du risque pour la santé et des conseils de menus. Le système est construit sur le cloud, combinant une passerelle API, un traitement piloté par événements et une base de données managée.

Les noms de produits et de services de l’énoncé sont rendus abstraits. Cet article, lui aussi, ne reproduit pas les figures et le texte de l’IPA, et ne reformule que la structure nécessaire à la compréhension des questions.

Question Sujet Chapitre de cet article
Question 1 Nature d’une API RESTful Chapitre 4
Question 2(1) Temps de force brute sur le code à 4 chiffres Chapitre 5
Question 2(2) alg=none du JWT Chapitre 6
Question 2(3) Accès à autrui via mid Chapitre 7
Question 2(4) Faille acceptant un status hors spécification Chapitre 8
Question 2(5) Mesure contre la force brute Chapitre 9
Question 3(1) Confirmer l’existence de la vulnérabilité de façon sûre Chapitre 11
Question 3(2)(3) Emplacement à surveiller par le WAF et expression régulière Chapitre 12
Question 3(4) Avantage du mode détection et exploitation Chapitre 13

Le commentaire de notation indique que le taux de bonnes réponses global était dans la moyenne. En revanche, il relève que le taux de bonnes réponses était relativement faible pour la mesure contre la falsification du JWT (question 2(2)) et pour le mécanisme nécessaire au serveur de vérification (question 3(1)). Dans les deux cas, connaître seulement les termes ne suffit pas à résoudre la question. Il faut suivre quelle valeur l’attaquant a modifiée, vers quel traitement elle circule, et où elle a fini par être considérée comme digne de confiance.

3. Ce problème n’est pas « un simple problème d’authentification »

En alignant l’ensemble du problème par frontière de confiance, on obtient ce qui suit.

[Identifiant utilisateur / mot de passe]
          |
          v
[Vérification du code à 4 chiffres] ---- pas de limite de tentatives ----> force brute
          |
          v
[Émission du JWT]
          |
          v
[Bibliothèque JWT] ------- autorise alg=none ------> falsification de l'identifiant utilisateur
          |
          v
[API utilisateur]
    |             |
    |             +-- transmet status en bloc ----> défaut d'autorisation au niveau de la propriété
    |
    +-- fait confiance à mid ------------------------> défaut d'autorisation au niveau de l'objet

[Enregistrement de l'entrée externe dans le journal]
          |
          v
[Bibliothèque vulnérable] ---- JNDI/LDAP/HTTP ------> exécution de code externe

Ici, la distinction la plus importante est la suivante.

Vérification Ce qu’elle questionne Exemple franchi dans ce problème
Authentification Qui êtes-vous ? Force brute sur le code à 4 chiffres
Validation du jeton Ces informations d’identité ont-elles été falsifiées ? alg=none
Autorisation au niveau de l’objet Êtes-vous autorisé à accéder aux données de cet utilisateur ? Substitution de mid
Autorisation au niveau de la propriété Êtes-vous autorisé à modifier ce champ ? status=paid
Frontière entrée-exécution Une entrée externe est-elle interprétée comme une instruction ? JNDI Lookup

Réussir la vérification précédente ne dispense jamais d’effectuer la suivante. Un utilisateur qui possède un JWT valide n’est pas forcément autorisé à lire les données d’autrui. Un utilisateur autorisé à mettre à jour ses propres données n’est pas forcément autorisé à modifier son état de facturation.

Une fois cette décomposition par étapes acquise, la réponse à chaque question cesse d’être un exercice de mémorisation.

Différence entre authentification et autorisationL'authentification confirme le sujet, l'autorisation confirme les opérations que ce sujet est habilité à effectuerAuthentificationqui est-ceAutorisationque peut-il faire

Figure 2 : différence entre authentification et autorisation. L’authentification vient en premier, l’autorisation est une vérification distincte.

4. Question 1 — Qu’est-ce que « sans état » ?

La question 1 interroge sur l’un des principes de conception d’une API RESTful : la caractéristique de ne pas gérer de session.

La réponse est sans état (stateless).

Être sans état signifie que chaque requête contient à elle seule toutes les informations nécessaires au traitement, sans que le serveur ait besoin de se souvenir de l’état de la conversation issu de la requête précédente. Dans ce problème, l’application mobile joint un JWT à l’en-tête Authorization de chaque requête. Le serveur valide ce JWT et identifie l’utilisateur de cette requête.

Une erreur d’interprétation fréquente consiste à lire « sans état » comme « le serveur ne conserve absolument aucun état ». En réalité, il conserve normalement les états suivants :

  • La base de données stockant les informations d’utilisateur et les données de santé
  • L’état de facturation
  • La valeur du code d’authentification, sa date d’expiration, le nombre d’échecs
  • La clé de signature du JWT
  • Pour une conception utilisant une liste de révocation, ces informations de révocation
  • Les journaux et les enregistrements d’audit

Ce que le serveur ne conserve pas, c’est un état de session côté serveur destiné uniquement à faire perdurer une conversation, qui servirait de prérequis à chaque appel d’API.

De plus, être sans état n’améliore pas automatiquement la sécurité. Envoyer le JWT à chaque fois facilite la répartition horizontale de charge, mais si la validation du JWT est erronée, cette erreur se propage uniformément à tous les nœuds. La propriété architecturale et la justesse sécuritaire sont deux choses distinctes.

5. Question 2(1) — Le code à 4 chiffres tombe en moyenne au bout de 500 secondes

L’API d’authentification envoie par e-mail un nombre à 4 chiffres si l’identifiant et le mot de passe correspondent. Ensuite, si l’identifiant d’utilisateur et le code à 4 chiffres correspondent, un JWT est émis. Le code est valide pendant 10 minutes à partir de sa génération.

Lors de l’audit, 10 tentatives par seconde étaient possibles. Il faut déterminer, en moyenne, en combien de temps on parvient à le franchir.

Le calcul, c’est « la moitié du nombre de candidats »

Un nombre à 4 chiffres, en incluant les zéros de tête, donne les 10 000 possibilités suivantes.

0000, 0001, 0002, ... , 9999

Si la bonne réponse est choisie uniformément, le nombre moyen de tentatives qu’un attaquant essayant dans l’ordre, sans répétition, doit effectuer pour l’atteindre est la moitié du nombre de candidats.

Nombre moyen de tentatives = 10 000 ÷ 2 = 5 000
Temps moyen                = 5 000 ÷ 10 tentatives/s = 500 secondes

Le blanc b est donc 500.

Dans le pire des cas, cela prend 1 000 secondes, mais la question porte sur la moyenne. Et comme la durée de validité du code est de 600 secondes, elle est plus longue que le temps moyen de 500 secondes nécessaire pour le franchir. C’est la raison pour laquelle on juge le risque de franchissement élevé.

Perception du temps pour le code d'authentification à 4 chiffresEssayer 10 000 possibilités à 10 tentatives par seconde réussit en moyenne au bout de 5 000 tentatives, soit 500 secondes, ce qui est plus court que la durée de validité de 600 secondesMoyenne 10 000 / 2 = 5 000 tentatives500 s < 600 s10 000 candidats possiblesTemps moyen de franchissement : 500 secondesDurée de validité : 600 secondesFranchissable dans la durée de validité

Figure 9 : perception du temps pour le code d’authentification à 4 chiffres. Essayer en moyenne la moitié du nombre de candidats permet de réussir dans la durée de validité.

Raccourcir seulement la durée d’expiration ne suffit pas si les candidats sont peu nombreux

La robustesse d’un code d’authentification ne se détermine ni par le nombre de chiffres seul, ni par la durée de validité seule.

Nombre de tentatives possibles pendant la durée de validité
= tentatives par seconde × durée de validité
= 10 × 600
= 6 000

En essayant dans l’ordre des valeurs non répétées, on peut vérifier 60 % des 10 000 possibilités dans la durée de validité. Même avec une durée d’expiration en place, cela ne suffit pas si le nombre de tentatives n’est pas limité.

La norme actuelle NIST SP 800-63B exige au moins 6 chiffres pour un secret de courte durée utilisé en authentification hors bande, et impose une limitation du nombre de tentatives en dessous de 64 bits. Elle demande également de ne pas utiliser le courrier électronique pour l’authentification hors bande4. L’examen répond dans le cadre de la spécification donnée (4 chiffres, envoi par e-mail), mais pour une conception nouvelle en pratique, il faut aussi remettre en question ce postulat lui-même.

6. Question 2(2) — alg=none : le problème d’avoir « laissé l’attaquant choisir la méthode de vérification »

Le JWT de ce problème se compose de trois parties : l’en-tête, la charge utile et la signature.

base64url(header).base64url(payload).base64url(signature)

L’en-tête indiquait RS256 comme algorithme de signature. La charge utile contenait l’identifiant d’utilisateur, l’heure d’émission et la date d’expiration.

L’auditeur a modifié les deux éléments suivants :

  1. Changer alg dans l’en-tête, de RS256 à NONE
  2. Changer l’identifiant d’utilisateur dans la charge utile pour un autre utilisateur

En envoyant ce JWT, la validation réussissait et permettait d’usurper l’identité d’autrui.

Déroulement de l'attaque JWT alg=noneÀ partir d'un JWT valide, changer alg en none et réécrire l'identifiant d'utilisateur pour le faire accepterChange alg dans l'en-tête vers noneContourne la validation de signatureJWT validealg=RS256user=user01JWT falsifiéalg=noneuser=user02Le serveur acceptel'identité user02

Figure 3 : déroulement de l’attaque JWT alg=none. C’est l’attaquant qui choisit l’algorithme de vérification.

none n’est pas une faute de frappe

La RFC 7519 définit un « Unsecured JWT », c’est-à-dire un JWT dont alg vaut none, sans signature ni chiffrement5. La valeur none n’est donc pas totalement absente de la spécification.

Le problème, c’est que l’API, qui aurait dû n’accepter que des JWT signés, a accepté le none spécifié par l’attaquant.

Écrit de façon conceptuelle, le traitement vulnérable ressemble à ceci :

1. Lire l'en-tête du JWT
2. Regarder l'alg indiqué dans l'en-tête, et choisir la méthode de vérification
3. Si alg vaut none, ne pas effectuer la vérification de signature
4. Faire confiance à l'identifiant d'utilisateur de la charge utile

C’est une entrée contrôlée par l’attaquant qui décide de la robustesse même de la sécurité.

La réponse attendue à l’examen

La question demande, pour la bibliothèque Q corrigée, « sur quelles données » et « quelle vérification » elle effectue, chacune en 20 caractères maximum.

L’exemple de réponse est le suivant.

Élément Point clé de la réponse
Données à vérifier La valeur indiquée dans alg de l’en-tête du JWT
Contenu de la vérification Vérifier qu’elle n’est pas NONE

En tant que correction directe de la vulnérabilité décrite dans l’énoncé, c’est la bonne réponse.

En pratique, ne pas se contenter de « tout sauf NONE »

Il faut ici distinguer la réponse attendue à l’examen de la recommandation en pratique.

La RFC 8725 exige que la bibliothèque JWT fasse spécifier par l’appelant un ensemble d’algorithmes autorisés, et n’utilise jamais rien en dehors de cet ensemble6. Autrement dit :

Mauvaise approche :
  accepter si token.header.alg != "none"

Bonne approche :
  n'accepter que si la valeur figure dans serverConfig.allowedAlgorithms
  exemple : allowedAlgorithms = ["RS256"]

Rejeter seulement none peut laisser subsister un autre algorithme faible, ou une confusion d’algorithme entre un schéma à clé publique et un schéma à clé symétrique. Le principe n’est pas d’accumuler des conditions négatives pour ce qu’on accepte, mais de fixer étroitement les conditions de ce qu’on autorise.

Pour la validation d’un JWT, il faut vérifier, selon l’usage, au moins les points suivants, pas seulement l’algorithme.

Élément À vérifier
Signature Peut-elle être validée avec la clé et l’algorithme attendus ?
iss S’agit-il d’un émetteur de confiance ?
aud Le jeton a-t-il été émis pour cette API ?
exp Sommes-nous dans la période de validité ?
nbf Ne sommes-nous pas avant l’heure de début d’utilisation ?
sub ou identifiant d’utilisateur S’agit-il d’un sujet valide dans l’application ?
Type de jeton N’y a-t-il pas de confusion entre jeton d’identité, jeton d’accès, etc. ?

Dans ce problème, la clé de la charge utile s’appelle user, mais en pratique, il vaut mieux utiliser le sub standard, ou définir clairement le sens d’une revendication (claim) personnalisée.

Validation JWT sûre et validation JWT dangereuseLa validation dangereuse dépend de alg, la validation sûre utilise une liste d'autorisation côté serveurValidation sûreAlgorithme autorisé en configuration serveurex. RS256Vérifie que alg de l'en-tête du JWTfigure dans la liste d'autorisationValide signature, iss, aud, expValidation dangereuseLit alg dans l'en-tête du JWTAccepte si alg vaut none

Figure 4 : validation sûre et validation dangereuse. En pratique, on fixe étroitement les algorithmes autorisés.

Le Base64url n’est pas du chiffrement

Il existe un autre malentendu fréquent au sujet des JWT. L’en-tête et la charge utile sont représentés en base64url, mais ce n’est pas du chiffrement. N’importe qui peut les décoder et les lire.

Ce que garantit la signature, c’est que, lorsqu’elle peut être validée correctement, le contenu n’a pas été falsifié depuis son émission. Cela ne signifie pas qu’il est permis de placer des informations personnelles que l’on veut garder secrètes dans la charge utile d’un JWT signé.

7. Question 2(3) — Même avec un JWT valide, changer mid permettait de lire les données d’autrui

Voici maintenant une attaque qui ne falsifie pas le JWT lui-même.

L’API utilisateur reçoit, en GET ou en PUT, un identifiant d’utilisateur nommé mid. Le module commun P récupère et met à jour, depuis la base de données, les informations d’utilisateur liées à ce mid.

La structure de l’attaque est simple.

Identifiant d'utilisateur du JWT : user01    ← JWT correctement signé
mid de la requête : user02                   ← modifié par l'attaquant

Comme la signature du JWT est correcte, l’authentification réussit. Mais l’API fait confiance à mid=user02 tel quel, et renvoie les informations de user02.

C’est un exemple typique de ce que l’OWASP API Security Top 10 2023 appelle Broken Object Level Authorization (BOLA). Lorsqu’on accède à des données à l’aide d’un identifiant d’objet spécifié par l’utilisateur, il faut vérifier l’autorisation pour cet objet à chaque fois7.

Attaque BOLAUtilise un JWT valide tout en changeant le mid de la requête vers un autre identifiant d'utilisateurJWT : user01mid : user02Fait confiance à midAttaquantAPI utilisateurRenvoie les informations de user02 depuis la BDD

Figure 5 : attaque BOLA. L’authentification passe, mais l’autorisation n’est pas vérifiée.

La réponse attendue

Le trait souligné ② du tableau 5 demande, en 40 caractères maximum, le traitement à ajouter au processus d’appel du module commun P.

L’exemple de réponse est le suivant.

Un traitement qui vérifie si l’identifiant d’utilisateur contenu dans le JWT correspond à la valeur de mid

L’avantage de vérifier cela dans le module commun P est de pouvoir appliquer facilement la même autorisation à la fois à GET et à PUT, et à toute autre API qui utiliserait P à l’avenir. Si l’on copie le même traitement de comparaison dans chaque écran ou chaque point de terminaison, il finira par manquer quelque part.

Une conception plus sûre consiste à ne pas recevoir mid

Pour une API qui ne fait que récupérer et mettre à jour les informations de l’appelant lui-même, il n’est pas nécessaire de recevoir l’identifiant d’utilisateur depuis le client.

GET /users/me
Authorization: Bearer <JWT>

Côté serveur, on extrait le sujet du JWT validé.

principal = validateJwt(request.authorization)
userId = principal.subject
return repository.getUser(userId)

La mise à jour se fait de la même façon.

principal = validateJwt(request.authorization)
input = validateProfileUpdate(request.body)
repository.updateProfile(
    userId = principal.subject,
    name = input.name,
    age = input.age
)

Un traitement de comparaison protège, à condition d’être écrit. Mais une conception qui ne reçoit jamais d’identifiant cible depuis l’extérieur réduit le risque même d’oublier d’écrire ce traitement de comparaison.

Si un administrateur a besoin d’agir sur les informations d’un autre utilisateur, il faut séparer les points de terminaison ainsi :

PUT /users/me                  pour l'utilisateur ordinaire
PUT /admin/users/{userId}      pour l'administrateur

Le point de terminaison administrateur exige des permissions distinctes, un journal d’audit, et si nécessaire une réauthentification. Cela rend la frontière de la politique d’autorisation plus visible que d’« ajouter une exception à l’API utilisateur ordinaire uniquement pour le cas administrateur ».

Comment se prémunir de BOLANe pas utiliser le mid de la requête ; déterminer ou vérifier la cible à partir du sujet du JWTGET /users/me + JWTRécupère le sub du JWTCorrespondNe correspond pasPas de midUtilisateurAPISi mid fourni,correspond-il au sub ?Renvoie ses propres donnéesRefus 403Recherche en BDD via le sub du JWT

Figure 6 : comment se prémunir de BOLA. Ne pas recevoir mid, ou le comparer au sujet du JWT.

Distinguer authentification et autorisation en une phrase

À l’examen comme en pratique, la formulation suivante est utile.

  • Authentification : qui est-ce ?
  • Autorisation : que cette personne est-elle autorisée à faire ?

Réussir la validation de la signature du JWT ne signifie que « le sujet représenté par ce jeton mérite confiance ». Que « ce sujet soit autorisé à lire user02 » doit être vérifié séparément.

8. Question 2(4) — status=paid est un défaut d’autorisation au niveau de la propriété

La spécification de l’API utilisateur définit les paramètres de mise à jour suivants.

mid   identifiant d'utilisateur
name  nom
age   âge

Or l’auditeur a ajouté la valeur suivante, absente de la spécification.

status=paid

Le statut d’un utilisateur gratuit est alors devenu celui d’un utilisateur payant.

Selon l’énoncé, le service L ne validait pas les paramètres reçus et les transmettait tous au module commun P. P était conçu pour mettre à jour directement la base de données.

La réponse au blanc c est le module commun P.

Mass AssignmentUn status=paid hors spécification est ajouté et se répercute intégralement sur l'objet interneL'attaquant ajoute status=paidLiaison automatiqueEnregistrement en BDDSpécification de l'APImid / name / ageCorps de la requêteModule commun PL'état de facturation devient paid

Figure 7 : Mass Assignment. Une propriété hors spécification se répercute intégralement sur l’objet interne.

La différence avec BOLA

La substitution de mid au chapitre précédent et l’ajout de status cette fois-ci se ressemblent, mais la granularité protégée diffère.

Vulnérabilité Ce que l’attaquant modifie Ce qu’il fallait vérifier
Substitution de mid L’objet cible Cet utilisateur est-il autorisé à accéder à cet enregistrement d’utilisateur ?
Ajout de status Une propriété au sein de l’objet Cet utilisateur est-il autorisé à modifier ce champ ?

Dans l’OWASP API Security Top 10 2023, ce second cas est traité sous le nom de Broken Object Property Level Authorization, qui inclut désormais l’ancien Mass Assignment dans cette catégorie8.

« Injecter le JSON directement dans l’entité » est dangereux

L’implémentation vulnérable ressemble, conceptuellement, à ceci :

entity = repository.find(body.mid)
bindAllProperties(entity, body)
repository.save(entity)

Même si l’écran ne comporte de champs de saisie que pour name et age, un attaquant peut construire directement une requête HTTP. Ce qui n’apparaît pas dans l’interface utilisateur n’est pas une frontière de sécurité.

Une implémentation sûre explicite les champs modifiables.

input = parseExactSchema(body, fields = ["name", "age"])

entity = repository.find(authenticatedUserId)
entity.name = input.name
entity.age  = input.age
repository.save(entity)

Deux points sont importants ici :

  1. Ne placer, dans le type de saisie de mise à jour, que les champs que l’utilisateur est autorisé à modifier
  2. Ne pas ignorer silencieusement un champ inconnu hors spécification, mais le rejeter comme une erreur si possible

Ignorer silencieusement un champ inconnu permet de dissimuler l’échec d’une attaque, mais aussi de passer à côté d’erreurs d’implémentation du client ou de signes d’une attaque. En l’absence de contrainte de compatibilité, rejeter avec un schéma strict facilite les investigations ultérieures.

Autorisation au niveau du champLe DTO de mise à jour ne contient qu'une liste d'autorisation, et rejette les propriétés inconnuesValidation du schémaOuiNonVoie dédiéeDTO de mise à jour (liste d'autorisation)nameageCorps de la requêteUniquement deschamps autorisés ?Met à jour name/age de l'entitéRenvoie une erreurService de paiementnotification vérifiéeMet à jour status=paid

Figure 8 : autorisation au niveau du champ. Les champs modifiables sont limités par une liste d’autorisation, et l’état de facturation est modifié par une voie distincte.

status ne doit changer qu’à partir du résultat du paiement

status=paid ne fait pas partie du profil de l’utilisateur. C’est un état qui découle d’un fait constaté côté serveur : la réussite du paiement.

Mise à jour du profil de l'utilisateur
  -> seuls name / age sont modifiables

Notification vérifiée du service de paiement
  -> vérifie paymentId
  -> empêche un traitement en double
  -> change status vers paid

Même stockées dans la même colonne de base de données, le droit de modifier et la voie pour le faire sont deux choses distinctes. Si l’on utilise directement l’entité interne comme type de saisie pour une API externe, cette frontière disparaît.

9. Question 2(5) — La mesure contre la force brute consiste à conserver le nombre d’échecs comme un état

Contre la force brute sur le code à 4 chiffres, il faut répondre, en 30 caractères maximum, au traitement qui va dans le blanc d du tableau 5. Le seuil est fixé à 10.

L’exemple de réponse est le suivant.

Un traitement qui verrouille le compte lorsque le nombre d’échecs consécutifs dépasse le seuil

Ce point ne contredit pas le caractère sans état de la question 1. Ne pas conserver l’état de la conversation d’un appel d’API comme une session serveur, et rendre persistant le nombre d’échecs nécessaire à une décision de sécurité, sont deux choses distinctes.

Présence ou absence de limitation du nombre de tentativesSans limite, l'attaque réussit en moyenne en 500 secondes ; avec une limite du nombre d'échecs, la vitesse d'attaque peut être freinéeAvec limiteChute brutale de la vitesse d'attaqueCompte verrouilléVerrouillage après 10 échecsDélai progressifSans limiteenviron 500 secondesAuthentification réussie10 tentatives par seconde

Figure 10 : présence ou absence de limitation du nombre de tentatives. La limitation du nombre d’échecs permet d’arrêter la force brute de façon pratique.

En pratique, ne pas se limiter à « le verrouillage permanent, et rien d’autre »

La limitation par compte est nécessaire, mais si l’attaquant connaît l’identifiant d’utilisateur d’une autre personne, il peut délibérément provoquer 10 échecs pour exclure l’utilisateur légitime. C’est pourquoi, en pratique, on combine les éléments suivants.

Contrôle Rôle
Nombre d’échecs par compte Arrête la force brute sur un seul compte
Délai d’attente progressif Tolère les erreurs de saisie de l’utilisateur légitime tout en ralentissant la vitesse d’attaque
Contrôle par adresse IP source, appareil, ASN, etc. Freine une attaque qui tente peu de fois sur un grand nombre de comptes
Évaluation basée sur le risque Restreint fortement une région, un appareil ou une vitesse inhabituels
Notification à l’utilisateur Permet de remarquer une attaque ou une erreur de manipulation
Procédure de récupération sûre Empêche que le guichet de déverrouillage devienne lui-même une voie d’attaque

De plus, il ne faut jamais remettre le nombre d’échecs à zéro lors du renvoi d’un code. Sinon, l’attaquant peut faire renaître son quota de tentatives à chaque appel de l’API de renvoi. La norme actuelle NIST SP 800-63B exige elle aussi de ne pas réinitialiser le nombre d’échecs même en générant un nouveau secret d’authentification4.

Faire en sorte que le code d’authentification ne soit utilisable qu’une seule fois

L’énoncé se concentre sur la durée de validité, mais en pratique, il faut aussi :

  • Invalider immédiatement un code ayant réussi
  • Refuser la réutilisation d’un même code
  • Ne jamais conserver le code lui-même dans les journaux
  • Répondre de façon à ce que le succès ou l’échec de la comparaison du code ne permette pas de déduire l’existence de l’utilisateur
  • Limiter également le nombre d’appels de l’API d’envoi du code

Dès lors qu’on utilise un secret court, on ne peut pas confier toute la sécurité à la seule génération aléatoire.

Mesures pour le code d'authentificationSe protéger en combinant nombre de chiffres et durée d'expiration avec la limitation des tentatives, le refus de réutilisation, les notifications, etc.Code d'authentificationAugmenter le nombre de chiffresRaccourcir la durée de validitéLimiter le nombre de tentativesInvalider après succèsNe pas réinitialiser le nombre d'échecs au renvoiNe pas conserver le code dans les journauxContrôle par source

Figure 11 : mesures pour le code d’authentification. Au-delà du nombre de chiffres et de la durée d’expiration, combiner contrôle des tentatives et exploitation.

10. Distinguer d’un seul coup d’œil les quatre points de la question 2

Voici, organisés par la valeur que l’attaquant contrôlait, les points souvent confondus dans la question 2.

Attaque Valeur modifiée par l’attaquant Ce qu’il ne fallait pas croire Mesure de fond
Falsification du JWT alg de l’en-tête du JWT, identifiant d’utilisateur de la charge utile L’algorithme de vérification déclaré par le jeton lui-même Fixer côté serveur les algorithmes autorisés
Récupération des informations d’autrui mid de la requête L’identifiant cible spécifié par le client Comparer avec le sujet du JWT, ou déterminer l’identifiant cible à partir du JWT
Passage à utilisateur payant status hors spécification Toutes les propriétés liées automatiquement Établir une liste d’autorisation pour les propriétés modifiables
Franchissement du code à 4 chiffres Le candidat otp Des tentatives d’authentification illimitées Introduire une limitation du nombre d’échecs, un délai, une évaluation du risque

Il est important de ne pas tout regrouper sous « valider les valeurs saisies ».

  • alg relève d’une politique de traitement cryptographique
  • mid relève de l’autorisation au niveau de l’objet
  • status relève de l’autorisation au niveau de la propriété
  • otp relève de la résistance à la devinette en ligne

Même à l’intérieur d’une seule et même requête HTTP, la raison de les protéger diffère.

11. Question 3(1) — Confirmer l’exécution de code externe sans rien détruire

Après le lancement du service, une vulnérabilité critique V est publiée dans la bibliothèque open source H, largement utilisée. Le déroulement décrit dans l’énoncé est le suivant.

  1. L’attaquant place une chaîne contenant un JNDI Lookup dans un en-tête HTTP et l’envoie
  2. Le serveur ciblé écrit cette valeur dans les journaux
  3. La bibliothèque vulnérable évalue le JNDI Lookup et interroge un serveur LDAP contrôlé par l’attaquant
  4. La réponse LDAP renvoie l’URL d’un serveur HTTP contrôlé par l’attaquant
  5. Le serveur ciblé récupère un fichier de classe et exécute une commande

On peut lire cela comme une attaque de type Log4Shell (CVE-2021-44228), dont le nom propre est tu. La documentation d’Apache décrit elle aussi cette vulnérabilité comme permettant, dès lors qu’un attaquant contrôle un message de journal ou un paramètre, l’exécution d’un code arbitraire chargé depuis un serveur LDAP9.

Flux de vérification d'une vulnérabilité de type Log4ShellVérifie, par un rappel inoffensif, si la chaîne allant du JNDI jusqu'à l'exécution de code externe fonctionneInjecte jndi:ldap://...dans x-api-versionJNDI LookupRéponse URL HTTPEnregistre le GETConfirmation d'atteinteAttaquantServeur vulnérableTraitement du journalServeur LDAP malveillantServeur HTTP malveillantindex.htmlJournal d'accèsdu serveur de testVulnérabilité confirmée

Figure 12 : flux de vérification d’une vulnérabilité de type Log4Shell. On confirme l’atteinte par l’enregistrement d’un accès HTTP, et non par une commande destructrice.

Le code de vérification ne provoque qu’un accès HTTP inoffensif

L’entreprise G exécute un code de vérification sans impact sur le système, pour confirmer si la vulnérabilité V peut être exploitée depuis l’extérieur. La seule commande que ce code de vérification exécute est de récupérer index.html sur un serveur de test.

La question 3(1) demande ce qu’il faut implémenter sur le serveur de test pour confirmer que la commande a été exécutée.

L’exemple de réponse est le suivant.

Un mécanisme qui enregistre et permet de confirmer l’accès à index.html du serveur de test

Si le journal d’accès du serveur web conserve une requête GET provenant du serveur ciblé, cela confirme au moins que la chaîne suivante a fonctionné.

Requête HTTP externe
  -> traitement du journal
  -> JNDI Lookup
  -> réponse LDAP
  -> récupération de classe
  -> exécution de la commande de vérification
  -> accès HTTP vers le serveur de test

Pourquoi « afficher du texte à l’écran » ne suffit-il pas ?

La cible de l’attaque est le serveur. Rien ne garantit qu’un changement apparaisse à l’écran du navigateur de l’utilisateur. De plus, même si la vulnérabilité existe, la communication sortante intermédiaire peut être bloquée par un pare-feu.

Enregistrer l’accès du côté du serveur de test constitue une preuve observable que le serveur ciblé a bien atteint l’extérieur.

Lorsqu’on effectue une vérification du même type en pratique, il faut impérativement respecter les points suivants.

  • Obtenir l’accord explicite du propriétaire du système cible
  • Choisir une méthode de vérification sans impact sur la production, ou dont l’impact est acceptable
  • Ne jamais utiliser de commande destructrice (écriture, suppression, changement de configuration, etc.)
  • Gérer soi-même le domaine et le serveur de vérification
  • Consigner l’heure de vérification, la source, la cible, et le rappel attendu
  • Retirer, après la vérification, les serveurs LDAP/HTTP temporaires et les identifiants utilisés

« Confirmer l’exécution de code arbitraire » et « exécuter un code arbitraire dangereux » ne sont pas la même chose. Il faut viser l’effet de bord minimal qui atteint l’objectif.

12. Questions 3(2) et 3(3) — Le WAF inspecte les en-têtes HTTP

Le WAF du service N permet de choisir GET, POST, PUT, ANY, Header, COOKIE ou Multipart comme cible d’inspection.

Le code d’attaque se trouve dans la valeur de l’en-tête HTTP nommé x-api-version. Les blancs e et f du tableau 6 valent donc tous les deux Header.

Faire correspondre directement l’emplacement décrit dans l’énoncé à la cible d’inspection du WAF

Il s’agit ici moins de connaissances générales que de lire le flux de données de l’énoncé.

Emplacement de la chaîne d'attaque :
  en-tête x-api-version
          |
          v
Cible d'inspection du WAF :
  Header

Ce n’est ni un paramètre GET ni un corps POST. Plutôt que de regarder la liste des fonctionnalités du WAF et de choisir « ANY parce que ça ressemble à une attaque », il faut répondre en fonction de l’emplacement où l’attaquant a placé la valeur dans l’énoncé.

Gérer l’inversion des majuscules et des minuscules

La première version, conceptuellement, suivait les règles suivantes.

Header  \Wjndi\W  bloquer
Header  \Wldap\W  bloquer

Mais en inversant la casse, comme dans jNdI, on peut contourner un motif ne comportant que des minuscules.

L’exemple de réponse à la question 3(3) est l’une des expressions suivantes.

\W[jJ][nN][dD][iI]\W
\W(j|J)(n|N)(d|D)(i|I)\W

Dans le fascicule d’examen, la barre oblique inverse peut apparaître sous la forme d’un signe yen dans le rendu de caractères de l’environnement japonais, mais il s’agit bien de \W en tant qu’expression régulière. \W correspond à tout caractère autre qu’alphanumérique ou trait de soulignement. Dans la syntaxe du JNDI Lookup, des caractères non alphanumériques comme ${ ou : apparaissent avant et après jndi, d’où leur inclusion dans le motif.

Selon la même logique, on peut aussi rendre ldap insensible à la casse.

\W[lL][dD][aA][pP]\W

Ne pas considérer cette expression régulière comme une « protection complète contre Log4Shell »

L’examen demande de répondre par l’expression régulière correspondant à la méthode de contournement indiquée dans l’énoncé. Dans une attaque réelle, il peut exister des variantes difficiles à couvrir par une simple signature : découpage de la chaîne, autre type de Lookup, encodage, autre protocole, etc.

En pratique, ce dispositif se positionne donc ainsi :

  1. Bloquer provisoirement, via le WAF, les motifs d’attaque déjà identifiés
  2. Vérifier si la bibliothèque concernée est réellement présente
  3. Restreindre les communications sortantes LDAP, RMI, ou HTTP inutiles
  4. Mettre à jour vers la version corrigée
  5. Continuer à vérifier les journaux après la mise à jour pour rechercher toute compromission

Le WAF est une couche qui fait gagner du temps jusqu’à la sortie de la version corrigée.

Positionnement du WAFLe WAF est une couche d'atténuation provisoire ; la mesure de fond est la mise à jour de la bibliothèque vers une version corrigéeRègle WAFdétection/blocageRestriction des communications sortantesPublication d'une vulnérabilité critiqueVérification de l'impactAtténuation provisoireBloque temporairement le motif d'attaqueFerme la voie d'exploitationMise à jour vers la bibliothèque corrigéeVérification a posteriori et prévention de la récidive

Figure 13 : positionnement du WAF. Le WAF fait gagner du temps jusqu’à la sortie de la version corrigée ; la mesure de fond est la mise à jour.

13. Question 3(4) — Pourquoi commencer par le mode « détection »

Au sujet de la règle WAF modifiée, M. Z, spécialiste agréé en sécurité de l’information (RISS), conseille de régler le comportement sur « détection » plutôt que sur « blocage » pendant une certaine période après le lancement en production.

La question demande, chacun en 25 caractères maximum, l’avantage du mode détection et ce qu’il faut faire pour minimiser les dégâts.

L’exemple de réponse est le suivant.

Élément Point clé de la réponse
Avantage Peut éviter un blocage causé par un faux positif
Ce qu’il faut faire Examiner, à réception d’une alerte, s’il s’agit d’une attaque

Le mode détection n’est pas un « mode où l’on ne fait rien »

En mode détection, le trafic correspondant aux règles est laissé passer tout en étant enregistré dans les journaux et en déclenchant des alertes. Même si les chaînes jndi ou ldap apparaissent par hasard dans un appel d’API normal, l’activité n’est pas interrompue immédiatement.

En contrepartie, l’exploitation doit prévoir ce qui suit.

Réception de l'alerte
   |
   v
Examen de la requête concernée
   |
   +-- trafic normal -> restreindre la règle, envisager une exception
   |
   +-- attaque       -> isoler la cible, préserver les journaux, enquêter sur l'impact, passer au blocage

Si personne ne consulte jamais les alertes, le mode détection n’a aucun effet protecteur. La détection va toujours de pair avec une exploitation qui observe et décide.

Passer de la détection au blocage

La procédure de déploiement habituelle est la suivante.

  1. Appliquer le mode détection au trafic réel
  2. Classer les faux positifs et les vrais positifs
  3. Ajuster les en-têtes ciblés, les chemins, les API, les limites de caractères, etc.
  4. Confirmer que l’impact sur le trafic normal est acceptable
  5. Passer en mode blocage
  6. Surveiller le nombre de blocages et l’impact sur l’activité

Cela dit, ce sont les principes valables en temps normal. Si la vulnérabilité est critique, effectivement exploitée, et sans solution de contournement, on peut juger que les dégâts d’une compromission dépassent ceux d’un arrêt causé par un faux positif, et bloquer d’emblée. Dans la situation de l’examen, on choisit d’abord la détection afin de vérifier si le service reste utilisable comme avant.

Du mode détection au mode blocage du WAFObserver les alertes en mode détection, ajuster les faux positifs, puis passer en mode blocageTrafic réelFaux positifAttaqueMode détectionGénération d'alertesAttaque oufaux positif ?Ajuster la règlePasser en mode blocageSurveiller le nombre de blocages et l'impact sur l'activité

Figure 14 : du mode détection au mode blocage. On observe et on ajuste d’abord, puis on passe au blocage une fois l’impact confirmé acceptable.

14. Le WAF est une mesure provisoire ; la mise à jour est la mesure de fond

Dans l’énoncé, le site officiel de la bibliothèque H ne disposait encore ni de version corrigée ni de mesure provisoire, et les règles WAF exhaustives du fournisseur cloud pouvaient prendre jusqu’à 72 heures. C’est pourquoi l’entreprise G vérifie elle-même l’impact et bloque provisoirement, au moins, les motifs déjà identifiés.

Cet ordre est la forme de base de la réponse à incident.

Étape Objectif Réponse dans ce problème
Vérification de l’impact Déterminer si l’on est réellement en danger Confirmer, par un rappel inoffensif, la possibilité d’exploitation externe
Atténuation provisoire Gagner du temps jusqu’à la correction Règle WAF, détection/blocage, restriction des communications sortantes
Correction de fond Éliminer la cause vulnérable Mise à jour vers la bibliothèque corrigée
Vérification a posteriori Rechercher une exploitation déjà survenue Examen des journaux WAF, application, DNS, proxy, etc.
Prévention de la récidive Accélérer la prochaine décision Inventaire des dépendances, SBOM, procédure de mise à jour, canal de communication

« Ne pas savoir si on l’utilise » constitue le plus grand facteur de retard

D’après l’énoncé, même lorsque l’entreprise G interroge l’entreprise F pour savoir si elle utilise la bibliothèque H, la réponse prend du temps car elle nécessite une analyse détaillée de la configuration.

En pratique, si l’on commence à chercher des fichiers JAR seulement après la publication d’une vulnérabilité critique, la réponse prend du retard. Il faut disposer, dès le temps normal, au moins des éléments suivants :

  • La liste des dépendances directes et transitives
  • Les composants et versions réellement inclus dans les livrables
  • Les services, conteneurs et appareils sur lesquels ils sont déployés
  • La procédure pour mettre à jour une bibliothèque dépendante, reconstruire et redéployer
  • Le canal de communication pour approuver un changement d’urgence
  • Les destinations autorisées pour les communications sortantes, et l’impact si on les coupe
  • L’emplacement et la méthode de recherche des journaux

Un SBOM n’est pas une fin en soi. C’est un index permettant de répondre rapidement à la question « quels systèmes en cours d’exécution cette vulnérabilité affecte-t-elle ? »

Ne pas clore l’enquête une fois la mise à jour effectuée

Il est possible qu’une attaque ait déjà eu lieu avant ou après la publication de la vulnérabilité. Mettre à jour vers la version corrigée arrête les exploitations futures, mais ne fait pas disparaître des identifiants déjà compromis ou une porte dérobée déjà installée.

Pour un type Log4Shell, il faut examiner au moins les points suivants.

  • Requêtes HTTP contenant des chaînes suspectes évoquant JNDI ou LDAP
  • Communications du serveur d’application vers un LDAP, un RMI ou un HTTP externe
  • Démarrage de processus enfants inhabituels
  • Création de fichiers JAR, class, scripts ou exécutables suspects
  • Accès aux identifiants cloud ou aux variables d’environnement
  • Modifications d’authentification, changements de permissions, envois externes avant et après la mise à jour

Il est important de ne pas conclure « pas d’attaque » sur la seule base des journaux du WAF. Il existe des voies internes qui ne passent pas par le WAF, ainsi que des journaux qui n’ont pas été conservés dans le passé.

15. Une méthode de lecture pour marquer des points plus facilement à l’examen

Ce problème est moins un exercice de connaissances qu’un exercice de lecture de l’écart entre la spécification et l’implémentation.

15.1 Séparer, dans les tableaux, la « spécification » et l’« implémentation »

Dans le problème de status, une valeur absente de la spécification de l’API passe malgré tout dans l’implémentation.

Spécification :
  mid / name / age

Implémentation :
  tous les paramètres reçus sont envoyés à P

En repérant cet écart, on comprend que le blanc c est le module commun P.

15.2 Tracer une ligne sous chaque valeur modifiée par l’attaquant

Voici les valeurs modifiées dans chaque attaque.

  • alg dans l’en-tête du JWT
  • L’identifiant d’utilisateur dans la charge utile du JWT
  • Le mid d’un paramètre d’API
  • status, hors spécification
  • otp de l’API d’authentification
  • L’en-tête HTTP x-api-version

Presque toutes les questions demandent essentiellement « où cette valeur aurait-elle dû être vérifiée ? »

15.3 Ramener la réponse au vocabulaire de l’énoncé

En pratique, on peut parler de « BOLA », de « Mass Assignment », de « rate limiting ». Mais ce que demande la question, c’est un traitement concret, formulé selon la structure de l’énoncé.

Mauvais exemple :

Effectuer correctement l’autorisation.

Bon exemple :

Vérifier que l’identifiant d’utilisateur contenu dans le JWT correspond à la valeur de mid.

Mauvais exemple :

Mettre en place une mesure contre la force brute.

Bon exemple :

Verrouiller le compte lorsque le nombre d’échecs consécutifs dépasse le seuil.

Connaître seulement le nom abstrait ne suffit pas à produire une réponse notable dans la limite de caractères imposée.

15.4 Le WAF : suivre « où la valeur a été insérée »

La cible d’inspection du WAF ne se déduit pas du type d’attaque, mais de l’emplacement où la chaîne d’attaque est stockée.

insérée dans l'en-tête x-api-version
        ↓
la cible d'inspection est Header

Si le taux de bonnes réponses à la question 3(1) était relativement faible dans le commentaire de notation, c’est aussi parce que beaucoup de réponses ne correspondaient pas au déroulement de l’attaque de la figure 6. Il suffit de réécrire la procédure d’attaque sous forme de flèches pour voir clairement ce qu’il faut observer.

16. Une checklist utilisable pour les revues d’API en pratique

Voici une checklist pour ramener ce problème dans une revue de conception ou de code réelle.

Validation du JWT

  • Les algorithmes de signature autorisés sont fixés dans la configuration serveur
  • none et les algorithmes non prévus sont rejetés
  • La signature, iss, aud, exp, nbf sont vérifiés selon l’usage
  • Le jeton d’identité, le jeton d’accès et le jeton de rafraîchissement ne sont jamais confondus
  • Une procédure de rotation des clés et de révocation existe
  • Aucune information à garder secrète n’est placée dans la charge utile du JWT

Autorisation au niveau de l’objet

  • Changer l’identifiant à l’intérieur de la requête ne permet pas d’atteindre les données d’autrui
  • L’autorisation est appliquée pour la liste, le détail, la mise à jour, la suppression et le téléchargement
  • L’autorisation est mise en œuvre non pas dans l’écran, mais dans une couche commune d’accès aux données
  • Pour une API réservée à soi-même, on a envisagé de dériver l’identifiant cible du jeton
  • Les opérations administrateur sont séparées, en API et en politique, de celles de l’utilisateur ordinaire

Autorisation au niveau de la propriété

  • Le type de saisie externe est distinct de l’entité de base de données
  • Les champs modifiables sont énumérés via une liste d’autorisation
  • Les propriétés hors spécification sont rejetées ou auditées
  • Les états tels que les permissions, la facturation, l’approbation, le propriétaire ne peuvent pas être modifiés par une saisie de l’utilisateur
  • La réponse ne contient pas non plus de propriétés sensibles inutiles

Tentatives d’authentification

  • Une limite du nombre d’échecs par compte existe
  • Un délai progressif ou un contrôle par source existent
  • Le nombre d’échecs n’est pas réinitialisé lors du renvoi d’un code
  • Le code d’authentification n’est utilisable qu’une seule fois
  • Le code d’authentification et le mot de passe ne sont jamais conservés dans les journaux
  • La procédure de déverrouillage/récupération ne constitue pas une autre voie d’authentification plus faible

Vulnérabilité critique d’une bibliothèque dépendante

  • Les services en cours d’exécution peuvent être associés à leurs versions de dépendances
  • Une procédure permet de vérifier l’impact par une méthode inoffensive
  • Des mesures provisoires (WAF, restriction des communications sortantes, etc.) peuvent être appliquées
  • Une exploitation existe pour que les alertes de détection soient vérifiées par un responsable
  • Un canal de publication d’urgence permet de passer à la version corrigée
  • Les journaux sont examinés avant la mise à jour pour rechercher une exploitation possible

17. Ce que ce problème révèle sur les deux visages des composants communs

Ce problème met en scène deux composants communs : la bibliothèque de gestion des JWT, Q, et le module commun P.

Un composant commun présente de grands avantages.

  • Corriger un seul endroit répercute la correction à toutes les API qui l’utilisent
  • Évite de dupliquer l’implémentation de l’autorisation et de la validation dans chaque fonctionnalité
  • Permet de concentrer les cibles de test
  • Permet d’uniformiser le format des journaux et de l’audit

Mais, en contrepartie, une erreur s’y propage à tout l’ensemble.

  • Si la bibliothèque Q accepte alg=none, toutes les API utilisant le JWT deviennent dangereuses
  • Si le module commun P accepte n’importe quel mid ou status, à la fois GET et PUT deviennent dangereux
  • Si la bibliothèque vulnérable H est utilisée à la base, plusieurs voies qui écrivent des en-têtes HTTP dans les journaux deviennent une surface d’attaque

Ce qu’il faut donc mutualiser n’est pas un simple accès aux données. Il faut mutualiser les invariants de sécurité, et valider rigoureusement ce composant commun de façon isolée.

Par exemple, on peut définir le contrat de P ainsi :

P.getOwnUser(authenticatedSubject)
P.updateOwnProfile(authenticatedSubject, ProfileUpdate{name, age})

Il est plus sûr de ne pas exposer directement aux appelants ordinaires une API de bas niveau comme celle-ci :

P.getUser(arbitraryMid)
P.updateUser(arbitraryMap)

Cette dernière n’est nécessaire que sur des voies limitées, comme les traitements administratifs. Répandre une liberté de bas niveau à toutes les API revient à concevoir un système qui suppose que chaque appelant l’utilisera correctement, à chaque fois.

18. Conclusion

La question 1 de l’après-midi, session printemps 2024 (Reiwa 6), est un problème qui fait lire les points de sécurité des API un par un, séparément.

Utiliser un JWT ne signifie pas une authentification sûre. Laisser l’attaquant choisir l’algorithme de signature permet de réécrire l’identifiant d’utilisateur.

Que la signature du JWT soit correcte ne signifie pas que l’autorisation est correcte. Faire confiance au mid de la requête permet à un utilisateur légitime d’accéder aux informations d’autrui.

Pouvoir mettre à jour son propre objet ne signifie pas être autorisé à modifier toutes ses propriétés. Lier automatiquement un état interne comme status permet de réécrire les permissions ou l’état de facturation.

Qu’un code d’authentification ait une date d’expiration ne signifie pas qu’il résiste à la force brute. Il faut calculer le nombre de candidats et la vitesse de tentative, et limiter le nombre d’échecs.

Introduire une règle dans le WAF ne signifie pas avoir corrigé la vulnérabilité. On gagne du temps par la détection et le blocage, on vérifie l’impact, et on finit par mettre à jour la bibliothèque.

Tableau de correspondance entre vulnérabilités et mesuresAssocie chaque vulnérabilité à la frontière de confiance concernée et à sa mesureValidation du jetonAutorisation au niveau de l'objetAutorisation au niveau de la propriétéContrôle des tentatives d'authentificationEntrée vers exécutionFalsification du JWTFixer les algorithmes autorisésSubstitution de midComparer avec le sujet du JWT / se passer de midstatus=paidÉtablir une liste d'autorisation pour le DTO de mise à jourForce brute sur le code à 4 chiffresLimitation des échecs / délaiVulnérabilité de type Log4ShellMise à jour de la bibliothèque / WAF

Figure 15 : tableau de correspondance entre vulnérabilités et mesures. Une mesure distincte pour chaque frontière franchie.

Un seul principe traverse tout ce problème.

Réussir la vérification précédente ne dispense jamais d’omettre la frontière de confiance suivante.

Dans les articles précédents de la série, nous avons expliqué le XSS stocké de la question 1 de l’automne 2023 (Reiwa 5) et l’exfiltration d’informations par le Wi-Fi invité de la question 2 de l’automne 2023 (Reiwa 5). Pour les points de contrôle de l’ensemble d’un site web, voir aussi Utiliser le guide « Comment concevoir un site web sûr » de l’IPA comme checklist.

Conclusion finaleMontre que réussir la vérification précédente ne dispense jamais d'omettre la frontière de confiance suivanteAuthentification réussieValidation de la signature du JWTAutorisation au niveau de l'objetAutorisation au niveau de la propriétéLimitation du nombre de tentativesFrontière entre l'entrée et l'exécutionWAF / mise à jour de la bibliothèque

Figure 16 : conclusion finale. Les frontières de confiance se vérifient par étapes ; aucune ne doit être omise.

Références

  1. IPA, Examen de spécialiste agréé en sécurité de l’information, session printemps 2024 (Reiwa 6) — Cahier de questions de l’après-midi. L’énoncé traité par cet article. 

  2. IPA, Examen de spécialiste agréé en sécurité de l’information, session printemps 2024 (Reiwa 6) — Exemples de réponses de l’après-midi. Les exemples de réponses officiels pour chaque question. 

  3. IPA, Examen de spécialiste agréé en sécurité de l’information, session printemps 2024 (Reiwa 6) — Commentaire de notation de l’après-midi. L’explication des taux de bonnes réponses et des tendances d’erreurs. 

  4. NIST, SP 800-63B: Authentication and Authenticator Management. Indique le nombre de chiffres pour un secret de courte durée, la limitation du nombre de tentatives, le nombre d’échecs lors d’une nouvelle émission, et le fait de ne pas utiliser le courrier électronique pour l’authentification hors bande.  2

  5. RFC Editor, RFC 7519: JSON Web Token (JWT). La spécification du JWT, incluant l’Unsecured JWT et alg=none

  6. RFC Editor, RFC 8725: JSON Web Token Best Current Practices. Une BCP qui définit la fixation des algorithmes autorisés, la validation de l’émetteur, du sujet et de l’audience, etc. 

  7. OWASP, API1:2023 Broken Object Level Authorization. Explique la nécessité de vérifier l’autorisation pour chaque identifiant d’objet spécifié par l’utilisateur. 

  8. OWASP, API3:2023 Broken Object Property Level Authorization. Explique le défaut d’autorisation au niveau de la propriété, y compris le Mass Assignment, et les mesures associées. 

  9. Apache Logging Services, Security. Explique l’impact de la CVE-2021-44228, l’exécution de code via JNDI et LDAP, et la version corrigée. 

Articles récents partageant les mêmes étiquettes, pour approfondir des sujets proches.

Ces pages replacent le sujet dans un contexte plus large de services et de décisions.

Cet article est directement lié aux services suivants.

Création de sites web

Dans les API de membres et les intégrations avec des applications mobiles, la validation des JWT, l'autorisation au niveau de l'objet et la limitation des propriétés modifiables déterminent directement la sécurité du système web.

Questions fréquentes

Questions souvent posées lors d’une consultation sur le sujet de cet article.

La vérification de la signature du JWT a réussi, alors pourquoi peut-on quand même usurper l'identité d'autrui ?
Dans ce problème, la bibliothèque de gestion des JWT acceptait la valeur de alg dans l'en-tête du JWT exactement telle que l'attaquant l'avait spécifiée, et traitait un JWT avec alg=none comme valide, sans signature. Par conséquent, même en réécrivant l'identifiant d'utilisateur dans la charge utile, la validation réussissait. La réponse de l'examen consiste à vérifier la valeur alg de l'en-tête du JWT et à confirmer qu'elle n'est pas NONE. En pratique, cependant, rejeter uniquement NONE ne suffit pas. Il faut fixer, dans la configuration côté serveur, les algorithmes autorisés (par exemple RS256), sans jamais utiliser directement l'algorithme déclaré par le jeton pour effectuer ce choix. Il faut aussi vérifier, selon l'usage, l'émetteur (issuer), l'audience, la date d'expiration, le sujet (subject), etc.
Comparer l'identifiant d'utilisateur contenu dans le JWT avec le mid de la requête suffit-il comme mesure d'autorisation ?
Pour la réponse attendue à cet examen, cela suffit. En vérifiant, dans le module commun P, que l'identifiant d'utilisateur contenu dans le JWT correspond au mid, on arrête une attaque qui spécifie le mid d'un autre utilisateur. Cependant, pour une API qui ne traite que les informations propres à l'appelant, il est plus sûr, en pratique, de ne jamais recevoir le mid du client et de déterminer l'identifiant d'utilisateur à partir du sujet (subject) du JWT validé. En utilisant par exemple GET /users/me ou PUT /users/me, on réduit le risque même d'oublier d'implémenter la comparaison. Une API où un administrateur agit sur un autre utilisateur doit être séparée en un point de terminaison distinct, avec sa propre politique d'autorisation.
Pourquoi une validation ordinaire des valeurs saisies ne suffit-elle pas à empêcher l'attaque qui ajoute status ?
Parce que même en validant la longueur de name ou la plage de age, cela ne protège rien si status — un champ qui n'aurait jamais dû être accepté — est automatiquement lié et transmis à l'objet interne. Le problème n'est pas le format de la valeur, mais l'autorisation au niveau de la propriété : l'utilisateur est-il autorisé à modifier cette propriété ? Le type de saisie utilisé pour la mise à jour ne doit définir que name et age, et rejeter toute propriété inconnue. L'état de facturation ne doit être modifié qu'à partir d'événements dignes de confiance côté serveur, comme le résultat réussi du service de paiement.
Le code d'authentification à 4 chiffres expire au bout de 10 minutes ; pourquoi reste-t-il dangereux ?
Parce qu'il n'existe que 10 000 candidats possibles, de 0000 à 9999, et qu'à raison de 10 tentatives par seconde, un attaquant réussit en moyenne au bout de 5 000 tentatives, soit 500 secondes. Or la durée de validité de 10 minutes équivaut à 600 secondes : en essayant des candidats non répétés dans l'ordre, il est possible d'en vérifier 6 000 dans ce laps de temps. La durée d'expiration seule ne suffit pas à arrêter une attaque par force brute. Il faut concevoir ensemble le nombre de candidats, la vitesse de tentative et le plafond du nombre de tentatives.
La mesure attendue par la question est le verrouillage du compte ; un simple verrouillage immédiat suffit-il aussi en pratique ?
Non. Le blanc du problème appelle un traitement qui verrouille le compte lorsque le nombre d'échecs consécutifs dépasse un seuil, mais un verrouillage permanent et figé permet à lui seul à un attaquant de verrouiller délibérément le compte d'autrui, créant un déni de service. En pratique, on combine un compteur d'échecs par compte, des délais d'attente progressifs, une évaluation du risque liée à la source ou à l'appareil, des notifications, et une procédure de récupération. Il est également important que l'émission d'un nouveau code ne remette pas le compteur d'échecs à zéro.
Quel est l'intérêt de configurer le WAF en mode détection plutôt qu'en mode blocage ?
Cela permet de ne pas interrompre le trafic professionnel légitime, même lorsqu'une chaîne de caractères normale est faussement jugée comme une attaque. Dans la réponse attendue, l'avantage est de pouvoir éviter un blocage causé par un faux positif, et ce qu'il faut faire est d'examiner, à chaque alerte reçue, s'il s'agit réellement d'une attaque. Le mode détection n'est pas un réglage que l'on peut laisser sans surveillance. Il sert de période d'observation pendant laquelle on consulte les journaux pour écarter les faux positifs, ajuster les règles, puis passer au blocage. En cas d'urgence, lorsqu'une vulnérabilité critique connue est effectivement exploitée, il peut être justifié de bloquer d'emblée, en mettant ce choix en balance avec le risque pour la disponibilité.
La bibliothèque H de ce problème est-elle Log4j ?
L'énoncé tait le nom du produit, mais la séquence d'attaque — JNDI Lookup, serveur LDAP, récupération de classe depuis un serveur HTTP, chaîne intégrée dans un en-tête HTTP, et score de base CVSS v3.1 élevé — se lit naturellement comme une abstraction de la CVE-2021-44228, connue sous le nom de Log4Shell. Cet article explique cette correspondance, mais l'examen ne demande pas de citer le nom du produit. Il peut être résolu uniquement à partir de la procédure d'attaque donnée et de la spécification du WAF.
Que faut-il retenir de ce problème pour sa propre pratique ?
Que réussir l'authentification, que le JWT n'a pas été falsifié, qu'on est autorisé à accéder à l'objet cible et qu'on est autorisé à modifier la propriété cible sont quatre vérifications entièrement distinctes. De plus, un code d'authentification court exige une limitation du nombre de tentatives, et une vulnérabilité critique dans une bibliothèque impose de mener de front la vérification de l'impact, la protection provisoire et la correction de fond. L'essentiel, en pratique, tient à centraliser l'autorisation dans des composants communs, à faire du schéma de saisie une liste d'autorisation, à fixer côté serveur les conditions de validation du JWT, et à connaître ses bibliothèques dépendantes de façon à pouvoir les mettre à jour.

Profil de l’auteur

Page de présentation de l’auteur de l’article.

Go Komura

Représentant de KomuraSoft LLC

Spécialisé dans le développement de logiciels Windows, le conseil technique et l’analyse de pannes, notamment pour les systèmes existants et les incidents difficiles à reproduire.

Retour au blog