← Retour au Grimoire[ Sécurité ]

Quatre failles trouvées sans lancer un seul outil : retour sur une divulgation responsable

Une prospection commerciale de trente minutes devenue un rapport de divulgation responsable de treize pages. La méthode, les quatre classes de vulnérabilités, le raisonnement juridique — et pourquoi cet article ne nomme personne.


· 15 min de lecture · Cyber & Sécurité

Des baies de serveurs dans l'obscurité, parcourues de câbles ambre et cyan et de diodes vertes

Je voulais comprendre la stack technique d'un prospect pour lui proposer une refonte. Trente minutes de DevTools. J'ai fini par écrire un rapport de divulgation responsable de treize pages, sur une plateforme qui équipe plusieurs dizaines d'entreprises. Sans lancer un seul outil offensif.

Ce qui devait être une analyse concurrentielle

Mai 2026. Un prospect, un site à analyser, une proposition de refonte à préparer. Le réflexe habituel : ouvrir les DevTools, regarder ce qui se passe dans l'onglet Réseau, comprendre ce qu'il y a sous le capot avant d'écrire quoi que ce soit.

Sauf que les appels API ne partaient pas vers le site. Ils partaient vers un domaine tiers, et les réponses JSON contenaient des structures que je ne m'attendais pas à voir traverser le navigateur d'un visiteur.

Ce n'était pas un WordPress. C'était le déploiement client d'une plateforme SaaS multi-tenant, éditée par une société tierce, qui fait tourner de la même façon plusieurs dizaines d'entreprises du même secteur.

À partir de là, l'analyse commerciale s'est arrêtée et autre chose a commencé.

Les quatre findings, en un coup d'œil

Si tu veux savoir avant de lire s'il y a matière :

Ce que j'ai vu OWASP API (2023) Sévérité
V1 Jetons d'API en clair dans le JavaScript et dans l'URL API2 + API8 Critique
V2 CORS wildcard accompagné de méthodes d'écriture API8 Élevée
V3 JWT de 30 jours contenant l'identité complète du porteur API2 Élevée
V4 Ces jetons journalisés par chaque couche d'infrastructure API8 Moyenne

Aucune n'a demandé autre chose que la touche F12.

La méthode — et surtout ce que je n'ai pas fait

Je cadre ça dès le départ, parce que c'est la partie qui protège juridiquement et la partie que la plupart des writeups escamotent.

Ce que j'ai fait : ouvrir les outils de développement de mon navigateur et lire ce que le site servait spontanément. Inspecter le HTML et le JavaScript rendus. Lire les en-têtes HTTP des réponses. Créer un compte utilisateur sur une interface publique d'inscription, et décoder mon propre jeton de session.

Ce que je n'ai pas fait : aucun fuzzing, aucun scan automatisé, aucune requête forgée, aucun proxy d'interception, aucune tentative d'accès à une ressource non publique, aucune consultation de données appartenant à un autre utilisateur. Pas de Burp, pas de script.

Autrement dit : rien qu'un visiteur curieux ne puisse observer en appuyant sur F12.

Cette distinction n'est pas cosmétique. C'est elle qui sépare l'observation légitime de l'accès frauduleux à un système de traitement automatisé de données au sens de l'article 323-1 du Code pénal. Et c'est elle qu'il faut pouvoir documenter, point par point, si la conversation tourne mal.

Les quatre observations

V1 — Des jetons d'API en clair côté client

Le premier finding est arrivé en regardant les requêtes réseau. Les appels vers l'API portaient un jeton d'authentification en clair, dans l'URL, sous forme de paramètre :

GET /<endpoint>?api_token=[REDACTED]

Un UUID v4, présent dans le JavaScript de la page, donc lisible par quiconque ouvre la console.

Jusque-là, ça pourrait passer pour une clé publique bénigne — il en existe. Mais ce jeton ne se contentait pas d'identifier le tenant : il ouvrait l'accès à des ressources de configuration. Et surtout, la même architecture se retrouvait sur tous les déploiements clients, chacun avec son propre jeton exposé de la même manière.

C'est la bascule mentale du multi-tenant : on ne regarde plus une erreur, on regarde un patron répliqué.

OWASP API Security Top 10 (2023) : API2 Broken Authentication + API8 Security Misconfiguration Sévérité : critique

V2 — Un CORS permissif au-delà de la lecture

Dans les en-têtes de réponse, une combinaison qui fait lever un sourcil :

Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE

Un wildcard sur l'origine, ça se défend sur une API strictement publique et en lecture seule. Accompagné de PUT, PATCH et DELETE, ça raconte autre chose : une configuration recopiée sans être décidée.

À noter, pour être exact : un wildcard CORS n'est pas exploitable seul. Le navigateur n'enverra pas les cookies avec Origin: *, et le contrôle d'autorisation côté serveur reste la vraie ligne de défense. Ce que ça signale, c'est une posture — et une posture permissive sur la couche visible invite à vérifier les autres.

OWASP API Security Top 10 (2023) : API8 Security Misconfiguration Sévérité : élevée

V3 — Un JWT de trente jours, avec l'identité complète dedans

C'est le finding le plus intéressant, et celui que je vois le plus souvent en vrai.

En créant un compte légitime, j'ai pu décoder le jeton de session que la plateforme m'avait émis. Trois problèmes qui se cumulent.

L'algorithme : HS256, donc un secret symétrique partagé. Ce n'est pas cassé en soi. Mais un secret symétrique compromis — un commit accidentel, un dump de configuration, une variable d'environnement qui fuit — permet de forger des jetons arbitraires, pour n'importe quel utilisateur. Avec RS256, une fuite de la clé publique ne permet rien.

La durée de vie : trente jours, l'écart entre les claims exp et iat étant constant. Sans mécanisme de révocation observable, un jeton volé reste valide un mois entier. Une déconnexion côté client ne déconnecte rien du tout : elle jette le jeton, elle ne l'invalide pas.

Le contenu : c'est là que ça devient un sujet RGPD et plus seulement un sujet d'authentification. Le payload d'un JWT n'est pas chiffré — il est encodé en base64url, ce qui veut dire lisible par n'importe qui, sans clé, en une commande.

Et il contenait l'identité complète du porteur. Voici sa forme, avec des noms de champs génériques — les noms réels identifieraient la plateforme, j'y reviens à la fin :

{
  "id": "[REDACTED]",
  "email": "[REDACTED]",
  "prenom": "[REDACTED]",
  "nom": "[REDACTED]",
  "telephone": "[REDACTED]",
  "date_naissance": "[REDACTED]",
  "code_postal": "[REDACTED]",
  "programme_fidelite": "[REDACTED]",
  "tenant_id": "[REDACTED]",
  "consentement_rgpd": true,
  "consentement_tiers": false
}

Nom, prénom, téléphone, date de naissance, code postal, statuts de consentement RGPD. Le jeton n'est plus une clé : c'est une fiche client, qui voyage à chaque requête.

Deux conséquences que je veux souligner, parce qu'elles ne sont pas évidentes du premier coup :

  1. Le JWT circule là où on ne l'attend pas. Stocké en localStorage, il est lisible par tout script tiers chargé sur la page — une balise analytics compromise suffit. Renvoyé dans un en-tête, il finit dans les logs applicatifs. Un jeton fuité, ce n'est pas « quelqu'un peut se connecter », c'est « quelqu'un a la fiche complète ».
  2. Le claim de tenant est un vecteur latéral. Le champ qui désigne l'entreprise d'affiliation, combiné à un algorithme symétrique, signifie qu'une compromission du secret n'ouvrirait pas un compte : elle ouvrirait le cross-tenant, par simple modification d'un entier dans un jeton forgé.

OWASP API Security Top 10 (2023) : API2 Broken Authentication Sévérité : élevée

V4 — Le jeton en query string, gravé dans les logs

Conséquence directe de V1, mais elle mérite son propre paragraphe parce qu'elle transforme une fuite en persistance.

Les en-têtes de réponse trahissaient un serveur web classique et un cache HTTP en frontal. Deux couches qui, l'une comme l'autre, indexent et journalisent par URL.

Un secret qui passe en paramètre d'URL se retrouve donc, sans que personne l'ait décidé :

  • dans les journaux d'accès du serveur web, conservés des semaines ;
  • dans les clés de cache du reverse proxy ;
  • dans l'historique du navigateur du visiteur ;
  • dans l'en-tête Referer envoyé à chaque tiers chargé par la page — analytics, polices, CDN ;
  • dans les logs de tout proxy d'entreprise sur le trajet.

C'est précisément pour ça que la RFC 6750 range la transmission d'un jeton par paramètre d'URL parmi les méthodes à éviter, au profit de l'en-tête Authorization: Bearer.

La correction est triviale à écrire. Elle est pénible à déployer : elle demande de coordonner tous les intégrateurs des sites clients. C'est le vrai coût, et c'est aussi pour ça que ce genre de dette dure.

OWASP API Security Top 10 (2023) : API8 Security Misconfiguration Sévérité : moyenne, mais elle amplifie V1

Pourquoi je n'ai rien facturé

C'est la question qu'on m'a posée le plus souvent. Quatre findings dont un critique, sur une plateforme qui fait tourner des dizaines d'entreprises : la tentation de transformer ça en mission est réelle.

Je ne l'ai pas fait, et le raisonnement tient en une phrase : conditionner la remise d'un rapport à un paiement, ça a un nom, et ce nom n'est pas « prestation ».

Même en étant certain de n'avoir enfreint aucune loi — observation passive, aucune donnée d'autrui consultée — le moment où j'écris « voici ce que j'ai trouvé, parlons de mes tarifs » est le moment où je me place dans une zone que je devrai défendre. Si l'échange tourne mal, l'article 323-1 ne s'appréciera plus sur ce que j'ai fait, mais sur ce que ma démarche donne à penser.

La voie bénévole, encadrée par l'article L.2321-4 du Code de la défense qui protège le signalement de bonne foi à l'ANSSI, était la seule qui me plaçait clairement du bon côté sans rien avoir à plaider.

La valeur commerciale, elle vient autrement : par la crédibilité, par le dialogue qui peut s'ouvrir ensuite, et par la capitalisation du cas sous forme anonymisée — ce que tu lis en ce moment.

Le rapport, et comment il est parti

Contact pris par le canal de support de l'éditeur, remontée jusqu'à un interlocuteur technique, puis transmission d'un document structuré :

  1. Une synthèse exécutive lisible par quelqu'un qui ne code pas.
  2. La méthodologie, avec une section explicite « ce qui n'a pas été fait ».
  3. Les quatre vulnérabilités : classification OWASP, sévérité, description, impact, preuves caviardées.
  4. Un relevé des catégories de données personnelles concernées — sans qualification juridique, ce n'est pas mon rôle et l'écrire m'aurait décrédibilisé.
  5. Des recommandations séquencées en court, moyen et long terme.
  6. Un engagement de confidentialité de quatre-vingt-dix jours, avec des jalons d'échange proposés.

Côté transmission : PDF chiffré en AES-128, mot de passe de 192 bits d'entropie envoyé par un service de partage à vue unique, sur instance européenne, avec expiration à 48 h.

Ça peut sembler théâtral pour un rapport bénévole. Ça ne l'est pas : le document contient assez de détail pour faciliter l'exploitation. Sécuriser la transmission fait partie du travail du chercheur, ce n'est pas une option de confort.

Ce que j'en retire, techniquement

Les failles les plus fréquentes ne demandent pas d'exploitation pour être vues. Les quatre sont sorties de DevTools, de la lecture d'en-têtes et d'un base64 -d. Pas une requête forgée. C'est rassurant — il n'y a pas besoin d'un OSCP pour apporter de la valeur — et c'est inquiétant, parce que les attaquants le savent aussi.

Le multi-tenant multiplie chaque erreur par le nombre de clients. Une mauvaise configuration sur un site vitrine expose un client. La même sur une plateforme SaaS expose tout le parc. La responsabilité d'un éditeur n'est pas celle d'un exploitant de site : elle est structurellement différente, et elle devrait se traduire dans son budget sécurité.

Un JWT n'est pas un cache de données personnelles. C'est une évidence écrite dans la RFC 8725, et je la vois enfreinte partout, pour une bonne raison : c'est pratique, ça évite un appel en base. Le principe de minimisation s'applique aux jetons exactement comme aux tables. Mets-y un identifiant, une expiration courte, des rôles. Le reste se lit côté serveur.

Ce que j'en retire, sur la divulgation

Un bon rapport, c'est un rapport qui retire. Le premier réflexe est de tout mettre — toutes les preuves, toutes les captures, toutes les observations. C'est l'inverse qu'il faut faire : le nécessaire et suffisant, les faits séparés des interprétations, et jamais un mot qui empiète sur le rôle du DPO ou du juriste d'en face.

Le ton est un outil. Trop technique, la direction ne lit pas et rien ne bouge. Trop vulgarisé, l'équipe technique se braque. Il faut les deux registres dans le même document, dans cet ordre.

La protection juridique n'est jamais automatique. L'article L.2321-4 protège le signalement de bonne foi à l'ANSSI. Il ne couvre pas tout, et la frontière avec l'article 323-1 est plus fine qu'on ne le croit. Documenter sa méthode et ce qu'on n'a pas fait est autant une protection pour soi qu'une information utile au destinataire.

Pourquoi cet article ne nomme personne — et ne le fera pas

Le délai de quatre-vingt-dix jours est écoulé. La pratique de divulgation coordonnée, telle que la décrit l'ISO/IEC 29147, m'autoriserait à publier davantage.

Je ne le fais pas, et je préfère l'écrire plutôt que de laisser croire à de la prudence molle.

Tant que je n'ai pas confirmation que les correctifs sont déployés, tout détail reproductible est une aide à l'attaquant. Pas au chercheur : à l'attaquant. Un nom de champ, un segment de chemin, une version de composant d'infrastructure — pris isolément ce sont des miettes, recoupés c'est une empreinte qui désigne la plateforme en quelques minutes.

C'est pour ça que dans cet article :

  • les noms de champs du JWT sont réécrits en générique ;
  • les chemins d'API sont remplacés par des <endpoint> ;
  • les versions et les composants d'infrastructure ne sont pas cités ;
  • le secteur d'activité et la zone géographique de l'éditeur ne sont pas mentionnés.

Ce qui reste — la méthode, les quatre classes de faille, le raisonnement — est ce qui a de la valeur pour toi si tu fais ce métier. Le nom de l'éditeur, lui, n'en a aucune, sauf pour celui qui voudrait s'en servir.

Dix minutes sur ta propre application

Le plus utile que je puisse te laisser, ce n'est pas mon histoire. C'est la procédure. Ouvre ton app en production, appuie sur F12, et réponds à ces cinq questions.

1. Qu'est-ce qui part dans l'URL ? Onglet Réseau, filtre XHR. Cherche token, key, secret, api dans les query strings. Tout ce qui est là est dans tes logs serveur, dans le cache de ton proxy et dans l'historique de tes visiteurs.

2. Que contient ton jeton de session ? Connecte-toi, récupère le JWT, colle sa partie centrale dans base64 -d. Si tu y lis autre chose qu'un identifiant, une expiration et des rôles, tu as un problème de minimisation.

3. Combien de temps vit-il ? Compare exp et iat. Au-delà de quelques heures sans mécanisme de révocation, une déconnexion côté client ne déconnecte personne.

4. Que disent tes en-têtes ? curl -sI https://ton-site.fr. Chaque X-Powered-By, chaque numéro de version est une information offerte à qui cartographie avant d'attaquer.

5. Qu'est-ce que ton JavaScript embarque ? Cherche dans le bundle servi. Une clé d'API destinée au serveur qui se retrouve côté client est l'erreur la plus courante et la plus coûteuse.

Cinq questions, aucun outil à installer. Si l'une d'elles te met mal à l'aise, tu sais déjà par où commencer — et c'est aussi le point de départ de mon offre d'audit.

Et maintenant

Cette démarche a accéléré un mouvement que j'avais en tête depuis un moment : basculer du développement web vers l'audit de sécurité applicative.

Deux choses le rendent évident. La réglementation d'abord : NIS2 et DORA élargissent le périmètre des entreprises soumises à des obligations de sécurité, et les assureurs cyber posent des questions qu'ils ne posaient pas il y a trois ans. Mon expérience de développeur ensuite : quelqu'un qui a écrit du code comprend mieux ce qu'il audite qu'un auditeur qui n'en a jamais écrit. Je sais pourquoi ce JWT contient trente champs — parce que ça évitait une jointure, et parce que personne n'a repris la décision depuis.

Si tu édites un SaaS, si tu tiens une boutique en ligne, ou si tu exploites une application métier, la question à te poser n'est pas « suis-je attaquable ». C'est : qu'est-ce que mon application donne à lire, spontanément, à quelqu'un qui ouvre les DevTools.

Souvent, la réponse tient en trente minutes. C'est ce que raconte cet article.

Pour aller plus loin :


Tu veux savoir ce que ton application expose sans que tu le saches ? Envoie-moi l'URL et le contexte : je te dis honnêtement s'il y a matière à un audit, y compris si la réponse est « pas maintenant ». Parlons-en.


Image de couverture : photographie libre de droits (licence Unsplash).


Robin Lebon

Développeur web et sécurité applicative à Saint-Joseph, La Réunion. Vous parlez à celui qui construit.

Le studio ↗
  • divulgation responsable
  • OWASP API Security
  • JWT
  • SaaS multi-tenant
  • RGPD
  • audit sécurité
  • sécurité applicative

Une question sur ce sujet ?

Écrivez-moi. Un article ne remplace pas un échange sur votre cas.

En parler ↗