Confidentialité. Le nom du client, son domaine et les volumes cités dans cet article ont été modifiés. Les défauts de configuration décrits, eux, sont ceux qu’on a réellement trouvés pendant la mission.
Une commande, et la production apparaît
Une seule commande. C’est tout ce qu’il nous a fallu pour trouver une copie complète de la production d’une plateforme de santé française, posée sur Internet, avec de vrais dossiers patients dedans. On n’avait même pas encore ouvert leur application.
Le client : Curalis, une HealthTech française dont la plateforme de téléconsultation et de dossier patient équipe des cabinets de ville et des centres de santé. Une application où chaque ligne de la base relève du secret médical.
Pour la mission, on nous fournit un compte praticien et un périmètre : l’application web, son API, et le front React qui va avec. Un pentest en boîte grise classique.
Sauf qu’avant d’ouvrir la moindre page, on commence toujours par regarder ce que l’entreprise expose sur Internet sans le savoir. Et pour ça, il existe une source publique que beaucoup d’équipes ignorent, les journaux de Certificate Transparency.
curl -s "https://crt.sh/?q=%25.curalis.fr&output=json" | jq -r '.[].name_value' | sort -u
api.curalis.fr
app.curalis.fr
curalis.fr
grafana.curalis.fr
preprod.curalis.fr
www.curalis.fr
C’est la cinquième ligne qui nous intéresse. preprod.curalis.fr n’était pas dans le périmètre, personne ne nous en avait parlé, et pourtant il répond depuis n’importe quelle connexion Internet.
Ce qu’on va y trouver porte un nom : A02:2025 Security Misconfiguration, le défaut de configuration. C’est la deuxième vulnérabilité du Top 10 OWASP dans l’édition 2025, contre la cinquième place en 2021. Sa particularité : elle ne vient pas du code, mais de la façon dont l’application est déployée. Un environnement laissé accessible, une option de développement restée active, un identifiant par défaut jamais changé.
Autrement dit, on n’a trouvé aucune faille dans le code de Curalis. Leur production est correctement protégée. C’est sa copie qui ne l’était pas.
Le cas en bref
- Secteur : HealthTech française, téléconsultation et dossier patient
- Catégorie OWASP : A02:2025 Security Misconfiguration
- Point d’entrée : un sous-domaine de préproduction découvert dans les journaux Certificate Transparency
- Défauts constatés : mode debug Django actif en ligne, compte d’administration au mot de passe par défaut, API sans contrôle d’authentification
- Donnée exposée : copie intégrale de la base de production, 48 273 dossiers patients
- Faille dans le code applicatif : aucune
- Correctif : isoler l’environnement du réseau public, durcir sa configuration, et n’y charger que des données anonymisées
Comment on trouve une préprod que personne n’a signalée
Certificate Transparency est un mécanisme public, et volontairement transparent. Chaque fois qu’une autorité de certification émet un certificat TLS, elle le publie dans des journaux publics que n’importe qui peut consulter. Ce n’est pas une fuite, c’est le fonctionnement prévu : c’est ce qui permet de détecter qu’un certificat frauduleux a été émis pour son domaine, et Chrome refuse les certificats qui n’y figurent pas.
La conséquence échappe à beaucoup de monde. Le jour où quelqu’un chez Curalis a demandé un certificat Let’s Encrypt pour preprod.curalis.fr, ce nom est devenu public, et il l’est resté. Aucun robots.txt, aucune absence de lien ne peut revenir là-dessus.
crt.sh n’est qu’une interface de recherche par-dessus ces journaux, et ce n’est pas le seul chemin.
# Agrégation d'une trentaine de sources publiques
subfinder -d curalis.fr -silent
# Ou du test de noms, à partir d'une simple liste
ffuf -w wordlist.txt -u https://FUZZ.curalis.fr -mc 200
Et dans n’importe quelle liste de sous-domaines qui circule publiquement, preprod, staging, dev, test et demo figurent dans les cent premières lignes. Bref, on parle des dix premières minutes d’une reconnaissance externe, pas d’une technique avancée.
L’analogie de la clinique-école
Imaginez une clinique qui construit un bâtiment de formation, copie conforme du vrai. Mêmes couloirs, mêmes salles, mêmes armoires à dossiers. L’idée est excellente, on y forme le personnel dans des conditions réelles sans perturber les vrais patients.
Deux détails, pourtant. À l’entrée, il n’y a ni badge, ni accueil, ni caméra, parce que ce n’est “qu’un bâtiment d’entraînement”. Et pour que la formation soit réaliste, on a rempli les armoires avec les dossiers des vrais patients.
Voilà ce qu’est une préprod mal configurée. Prise séparément, chaque décision se défend : copier la production pour tester sur des cas réalistes répond à un vrai besoin, et alléger la sécurité d’un environnement de test pour avancer vite, ça s’entend. C’est leur combinaison qui crée la faille, et comme personne ne regarde les deux en même temps, personne ne la voit.
Préprod, staging, recette, environnement de test ? Selon les équipes, ça porte des noms différents, mais c’est toujours la même chose, une copie de votre application pour vérifier que tout fonctionne avant la mise en ligne. Si vous en avez plusieurs, la question de cet article se pose autant de fois.
Trois défauts, dans l’ordre où on les trouve
preprod.curalis.fr affiche la même page de connexion que la production. Même logo, même formulaire, même front React.
Le mode debug est actif. On appelle une route qui n’existe pas, et au lieu d’une page d’erreur générique, on reçoit la page jaune de Django. On y lit la version du framework, les chemins absolus des fichiers, la liste des applications installées, la requête SQL qui vient d’échouer et l’intégralité de la configuration. Django masque bien les valeurs dont le nom contient PASS, SECRET, KEY, TOKEN ou API, mais le nom de la base, son hôte et l’utilisateur qui s’y connecte s’affichent en clair. En une requête mal formée, on obtient la cartographie interne de l’application.
Le compte d’administration a gardé son mot de passe par défaut. Django expose son interface sur /admin/, elle est bien là. On tente quelques identifiants de service classiques, du type de ceux qu’un script de provisionnement crée en montant un environnement, et l’un d’eux passe.
L’API ne demande pas de jeton. En production, elle exige un jeton valide sur chaque appel et le vérifie correctement. Sur la préprod, la contrainte a été retirée pour faciliter les tests automatisés.
# En production, sans jeton, l'API refuse
$ curl -s https://api.curalis.fr/api/patients/
{"detail":"Informations d'authentification non fournies."}
# Sur la préprod, exactement la même requête passe
$ curl -s https://preprod.curalis.fr/api/patients/ | jq '.count'
48273
48 273 dossiers, et ce ne sont pas des jeux de démonstration : de vrais noms, de vraies dates de naissance, de vrais numéros de sécurité sociale, de vrais comptes rendus de consultation. La base de la préprod est une copie intégrale de celle de production.
Aucune exploitation compliquée là-dedans. On a listé des sous-domaines, lu une page d’erreur, essayé un mot de passe par défaut et appelé une API sans jeton. À aucun moment on n’a eu besoin du compte praticien qu’on nous avait fourni.
Comment y remédier
Tout ne se règle pas au même endroit, ni avec le même effort. Certains points tiennent en une ligne de configuration, d’autres demandent de reprendre le code ou la chaîne de déploiement.
- Couper le mode debug. Une erreur en ligne doit renvoyer une page générique, jamais la configuration du serveur.
- Aligner l’authentification sur celle de la production. Pas une version allégée le temps des tests, la même.
- Changer les identifiants créés à l’installation. Les comptes de service générés au montage d’un environnement gardent leur mot de passe d’origine bien plus longtemps qu’on ne le croit.
- Sortir l’environnement d’Internet. VPN d’entreprise, liste d’adresses IP autorisées, ou authentification au niveau du reverse proxy. C’est le point le plus important, parce que c’est le seul qui ferme la porte au lieu de la rendre plus étroite.
- N’y charger que des données anonymisées. Jamais un export brut de la production.
- Tenir l’inventaire des environnements. Et supprimer ceux qui ne servent plus, ça coûte moins cher que de les surveiller.
Et la règle qui les résume tous, plus simple à retenir que n’importe quelle liste de réglages : un environnement de test ne doit jamais avoir une configuration de sécurité plus permissive que la production. Chaque écart entre les deux est une vulnérabilité en puissance.
Reste celui qui demande le plus de travail, l’anonymisation. Une plateforme de santé ne peut pas tester sérieusement sur des patients fictifs nommés “Test Test”. Il lui faut du volume, des dates cohérentes, des cas limites. D’où le réflexe de copier la production : le besoin est légitime, c’est le raccourci qui ne l’est pas. Des outils comme PostgreSQL Anonymizer existent justement pour insérer une étape d’anonymisation entre la production et l’environnement de test.
Autant être honnête, cette étape a un coût. Anonymiser une base médicale, ce n’est pas remplacer les noms par des chaînes aléatoires, il faut conserver l’intégrité référentielle, la cohérence des dates et la distribution des valeurs, sinon le jeu de données ne sert plus à tester. C’est un vrai chantier, et c’est la seule chose qui garantit qu’une préprod oubliée ne devient pas une fuite.
« Personne ne connaît l’adresse de notre préprod »
C’est la réponse qu’on entend le plus souvent en restitution, et elle est sincère. On vient de voir pourquoi elle ne tient pas : le nom d’un sous-domaine qui possède un certificat TLS est public par construction, et il le reste indéfiniment. Ce n’est pas une négligence de votre hébergeur, c’est le fonctionnement normal de Certificate Transparency.
La seconde objection arrive juste derrière : « de toute façon, il n’y a pas de vraies données dedans ». Celle-là ne se tranche pas en réunion, elle se vérifie. Deux questions à poser à votre équipe technique :
- Comment cet environnement a-t-il été peuplé, et par qui ?
- À quand remonte le dernier rafraîchissement, et qu’est-ce qui en a été retiré à cette occasion ?
La réponse honnête est presque toujours la même. Quelqu’un a restauré un export de production, il y a longtemps, parce que c’était le plus rapide. Et cette personne n’est souvent plus dans l’entreprise.
Ce que ça coûte quand il s’agit de données de santé
Un dossier médical n’est pas une donnée personnelle comme une autre, ce qui change l’ordre de grandeur des sanctions.
- Article 9 du RGPD. Les données de santé relèvent des catégories particulières, dont le traitement est interdit par principe, sauf exceptions encadrées (définition des données sensibles par la CNIL).
- Article L1111-8 du code de la santé publique. Héberger des données de santé impose de passer par un prestataire certifié HDS, Hébergeur de Données de Santé (texte sur Légifrance, la certification expliquée par l’Agence du Numérique en Santé). S’en passer n’est pas qu’un manquement administratif, c’est un délit puni de trois ans d’emprisonnement et 45 000 euros d’amende (article L1115-1), et ces peines s’ajoutent aux sanctions RGPD.
- Et votre préprod, elle est hébergée où ? Si elle contient une copie de la production, elle héberge des données de santé, donc elle relève exactement de la même obligation. Dans la pratique, ces environnements sont posés sur un compte cloud générique, en dehors du périmètre certifié.
- Dedalus Biologie, 1,5 million d’euros, avril 2022. Près de 500 000 personnes touchées, avec des données sensibles (statuts sérologiques, pathologies, grossesses, données génétiques). Les faits retenus sont presque les nôtres : des données extraites au-delà du périmètre demandé lors de migrations, conservées sans chiffrement, sur un serveur dont une zone était accessible sans authentification depuis Internet (communiqué CNIL, délibération SAN-2022-009).
- Nexpublica France, 1,7 million d’euros, décembre 2025. Un outil utilisé par des maisons départementales des personnes handicapées, où des utilisateurs accédaient aux documents de tiers (communiqué CNIL).
- Cegedim Santé, 800 000 euros, septembre 2024. Traitement de données de santé non anonymes sans autorisation (communiqué CNIL).
Et l’amende n’est jamais le coût principal. Pour une HealthTech qui vend à des cabinets et à des centres de santé, une sanction publique associant son nom à une fuite de dossiers médicaux se paie au renouvellement des contrats, puis dans chaque appel d’offres suivant.
Pourquoi cette faille survit
A02 est passée de la cinquième à la deuxième place du Top 10 OWASP entre 2021 et 2025. Quatre raisons l’expliquent.
La préprod n’appartient à personne. La production a un responsable, une astreinte, de la supervision. La préprod, c’est “l’environnement des devs”, donc personne ne la traite comme un actif exposé.
La configuration ne passe pas en revue de code. On relit une pull request avec attention, mais le fichier de configuration d’un environnement de test, le groupe de sécurité qui l’expose ou le script qui l’a peuplé, personne ne les ouvre jamais.
L’inventaire ne suit pas. Chaque environnement de démonstration, chaque déploiement de branche en aperçu, chaque instance montée pour un POC ajoute une surface exposée. Créer un environnement prend quelques minutes, le supprimer ne figure sur la feuille de route de personne.
Le “temporaire” qui reste. Chez Curalis, la vérification d’authentification de l’API avait été désactivée avec ce commentaire : « retiré temporairement pour faciliter les tests automatisés ». Toute la catégorie tient là-dedans, une contrainte de sécurité levée pour débloquer une situation, avec l’intention sincère de la remettre, puis le sprint suivant arrive.
Comment savoir si vous êtes exposé
Commencez par ce que vous pouvez faire vous-même, aujourd’hui, sans outil et sans budget. La commande du début de cet article fonctionne sur n’importe quel domaine, y compris le vôtre :
curl -s "https://crt.sh/?q=%25.votre-domaine.fr&output=json" | jq -r '.[].name_value' | sort -u
Prenez la liste qui en sort, et passez chaque nom au filtre de ces trois questions :
- Est-ce que je reconnais ce nom ?
- Est-ce que je sais qui en est responsable aujourd’hui ?
- Est-ce que je sais ce qu’il y a dedans ?
Dans la plupart des organisations qu’on audite, il y a au moins un nom qui récolte un non. C’est par celui-là qu’il faut commencer.
Ce que cette liste ne dit pas, c’est lesquels répondent encore et ce qu’il y a dans leur base. Un scanner de vulnérabilités ne vous aidera pas non plus, parce qu’il teste ce qu’on lui donne : si preprod.curalis.fr ne figure pas dans sa configuration, il ne le verra jamais. Le problème n’est pas la détection, c’est la découverte.
Côté référentiels, le Web Security Testing Guide de l’OWASP consacre un chapitre aux tests de configuration et de déploiement, et l’ASVS donne les exigences correspondantes. Les CIS Benchmarks couvrent l’infrastructure, l’ANSSI publie son guide d’hygiène informatique. Ce sont les référentiels sur lesquels s’appuie notre test d’intrusion web et API. Côté classification, ce qu’on a trouvé chez Curalis relève de la CWE-16 pour la configuration et de la CWE-489 pour le mode de débogage laissé actif en ligne.
La méthode qui fonctionne, c’est un pentest qui démarre par une reconnaissance externe, sans se limiter au périmètre défini par le client. Parce que ce périmètre, c’est la liste de ce que vous savez avoir. Un attaquant ne s’y arrête pas.
Conclusion
On aurait pu passer trois jours à chercher une faille dans l’application de Curalis. Elle est propre, l’équipe a fait son travail. Mais pendant que leur production était protégée dans les règles, sa copie intégrale attendait sur Internet.
C’est ce qui rend le défaut de configuration si difficile à traiter en interne. Il ne naît dans aucun fichier source, il n’apparaît dans aucune revue de code, et aucun développeur ne l’a introduit en écrivant une fonction. Il naît de décisions raisonnables prises séparément, à des mois d’intervalle, par des personnes qui ne se sont jamais parlé.
Que vous soyez dans la santé, la finance ou n’importe quel secteur qui manipule des données clients, la question n’est pas de savoir si votre application est bien écrite. Elle est plus simple : combien d’environnements avez-vous exactement, qu’est-ce qu’ils contiennent, et à quand remonte la dernière fois que quelqu’un a vérifié ?
Savoir y répondre est un bon début, mais ce n’est jamais un acquis. Votre infrastructure bouge à chaque déploiement, les techniques de reconnaissance progressent, et un environnement qui n’exposait rien il y a six mois peut être devenu un point d’entrée. C’est précisément pour ça qu’on existe : regarder ce que vous exposez avec les outils et les méthodes du moment, puis recommencer, parce qu’un audit décrit une situation à une date donnée et pas un état permanent.