← Retour au blog
9 novembre 2026 · 15 min de lecture

Semaphore UI et HFS : deux failles d'autorisation que nous avons signalées

GHSA-ffp6-w57q-758q et GHSA-c3w7-ff56-j345 : dans Semaphore UI, un Manager pouvait déplacer dans son projet le vault d'un autre projet ; dans HFS, un visiteur anonyme pouvait remplacer le fichier qu'un autre venait de déposer. Ce que nous avons trouvé et quelles versions installer.

cybersécuritéSemaphore UIHFSAnsibleWebDAVdivulgation de vulnérabilités
Schéma des deux vulnérabilités : dans Semaphore UI, un Manager du projet A déplace le vault du projet B ; dans HFS, un second visiteur anonyme remplace le fichier qu'un autre vient de déposer

Le 9 novembre 2026, en l'espace d'une heure, deux projets open source très différents ont publié un advisory de sécurité avec le même nom dans les crédits. Le premier est Semaphore UI, l'interface web avec laquelle de nombreuses équipes lancent Ansible et Terraform sur leurs serveurs. Le second est HFS, le serveur de fichiers qui, depuis 2003, permet de partager un dossier de son ordinateur avec un simple navigateur. Dans Semaphore, un utilisateur d'un projet pouvait retirer à un autre projet la configuration du vault d'un template. Dans HFS, un visiteur anonyme pouvait remplacer le fichier qu'un autre anonyme venait de déposer, dans un dossier où la suppression était interdite.

Il s'agit des advisories GHSA-ffp6-w57q-758q, corrigé dans Semaphore UI 2.19.20, et GHSA-c3w7-ff56-j345, corrigé dans HFS 3.4.1. C'est nous qui les avons signalés : les deux découvertes sont de Liam Giannini (Liam-G sur GitHub), hacker éthique et cofondateur de Webcracy. Nous remercions les mainteneurs de Semaphore et Massimo Melina, auteur de HFS, qui ont publié advisories et versions corrigées le même jour.

Les deux défauts semblent n'avoir rien en commun, mais ils ont la même racine. Dans les deux cas, le programme contrôlait une identité ; simplement, ce n'était pas la bonne.

Webcracy et la cybersécurité

En plus de développer des sites et des applications, Webcracy travaille sur tous les volets de la cybersécurité : nous protégeons réseaux et serveurs, les surveillons avec notre SOC jour et nuit, intervenons en cas d'incident, réalisons audits, conseil et durcissement des systèmes, et mettons les défenses à l'épreuve avec des tests d'intrusion et des revues de code. La recherche sur les logiciels open source que nous utilisons et qu'utilisent nos clients est une partie de ce travail. Liam cherche les failles avec les méthodes d'un attaquant, mais pour les faire corriger : il les signale en privé aux mainteneurs et ne les rend publiques qu'une fois la correction disponible. C'est la même divulgation responsable que pour ProjectSend, exarch et FreeScout.

Semaphore UI : le vault qui passait d'un projet à l'autre

Pourquoi Semaphore UI compte

Semaphore UI est une interface web et une API pour exécuter Ansible, Terraform, OpenTofu, Terragrunt, Pulumi et PowerShell sans passer par la ligne de commande. Il existe depuis 2014, quand il s'appelait encore Ansible Semaphore ; il est écrit en Go et distribué sous licence MIT. Sur GitHub, il compte plus de 14'400 étoiles, plus de 1'300 forks et plus de 180 contributeurs ; l'image Docker officielle semaphoreui/semaphore dépasse les 8,6 millions de téléchargements, auxquels s'ajoutent environ 600'000 pour l'ancienne image ansiblesemaphore/semaphore. Le site du projet parle de plus de 100'000 installations : c'est un chiffre de l'éditeur, mais l'ordre de grandeur est cohérent avec le reste. À côté de la version open source, il existe des éditions payantes Pro et Enterprise.

Dans le monde Ansible, c'est l'alternative légère à AWX et à l'Ansible Automation Platform de Red Hat : un seul binaire, qui peut démarrer avec une base SQLite et ne demande pas de cluster Kubernetes comme AWX. C'est donc un choix fréquent pour qui veut une interface pour Ansible sans monter une infrastructure de plus.

Et là, une faille d'autorisation pèse plus qu'ailleurs. Un tel outil contient les clés SSH, les identifiants cloud, les mots de passe qui déverrouillent les secrets chiffrés : c'est le point de départ des commandes vers toute l'infrastructure. C'est pourquoi il est découpé en projets, et souvent la même installation sert plusieurs équipes, ou plusieurs clients. La frontière entre deux projets est, en pratique, la frontière entre qui peut toucher à quoi.

Ce que sont les vaults des templates

Dans Semaphore, un template décrit une tâche à exécuter : quel playbook, sur quel inventaire, avec quelles variables. Beaucoup de playbooks contiennent des secrets chiffrés avec Ansible Vault, et pour les déchiffrer il faut un mot de passe. Le template indique où le prendre au moyen d'une ou plusieurs liaisons de vault : une clé enregistrée dans le projet, ou un script qui le récupère dans un système externe. En base de données, chaque liaison est une ligne de la table project__template_vault, avec son propre identifiant numérique, le projet et le template auxquels elle appartient.

Où le contrôle manquait

Pour créer un template, on appelle POST /api/project/{project_id}/templates. Le middleware vérifie correctement que l'utilisateur est membre du projet indiqué dans l'URL et qu'il peut en gérer les ressources, ce que permet le rôle Manager. Le nouveau template est attribué à ce projet, mais la liste vaults arrive en base telle que le client l'a envoyée.

Le défaut se trouvait dans la fonction UpdateTemplateVaults. Pour chaque entrée avec un identifiant non nul, elle supposait qu'il s'agissait d'une liaison existante à mettre à jour, et exécutait :

UPDATE project__template_vault
SET project_id=?, template_id=?, vault_key_id=?, name=?, type=?, script=?
WHERE id=?

La ligne était choisie uniquement par id, sans vérifier à quel projet elle appartenait, et la mise à jour réécrivait justement project_id et template_id, en les attribuant au nouveau template de l'attaquant. La suppression ultérieure des liaisons en trop était filtrée par projet, mais elle arrivait quand la ligne avait déjà été déplacée.

Il suffisait donc à l'attaquant de créer un template dans son propre projet, en plaçant dans la liste vaults l'identifiant d'une liaison du projet victime. Avec le type script, le champ de la clé est remis à zéro : aucune autre information sur le projet d'autrui n'était nécessaire. Et les identifiants sont des numéros attribués en séquence par la base, pas des secrets. Le contrôle portait sur le projet de l'URL, mais l'objet modifié était choisi par un numéro dans le corps de la requête : le catalogue CWE appelle cela CWE-639, contournement de l'autorisation par une clé contrôlée par l'utilisateur, et CWE-863, autorisation incorrecte.

Quelle gravité

Après la requête, le template du projet victime n'a plus sa liaison de vault, et celui de l'attaquant l'a. La conséquence la plus probable est que les exécutions qui doivent déchiffrer des secrets échouent jusqu'à ce que quelqu'un recrée la configuration. Nous ne l'avons pas mesuré dans notre test : c'est une conséquence plausible, pas démontrée.

Nous sommes tout aussi précis sur ce que nous n'avons pas trouvé : aucune lecture de secrets, aucune utilisation de la clé de la victime, aucune exécution de code. Il faut en outre le rôle de Manager, ou des droits équivalents, dans au moins un projet. C'est pourquoi la gravité est modérée. Mais dans un outil qui orchestre l'infrastructure, « modérée » signifie quand même qu'un utilisateur pouvait modifier un projet où il n'avait aucun rôle, et que ceux qui gèrent les projets d'autres équipes ou clients ne l'auraient pas vu venir.

Comment nous l'avons vérifié

Le problème est apparu en lisant le code de Semaphore UI 2.19.18 (commit b621d14) et en suivant la liste vaults du routeur jusqu'à la requête SQL. Pour le confirmer, Liam a écrit un test de régression en Go qui utilise la base SQLite en mémoire de la release et ses composants d'origine : middleware des projets, contrôle des droits, création des templates et couche SQL. Le test crée deux projets, A et B, donne à l'utilisateur le rôle de Manager dans A seulement, crée dans B un template avec un vault, puis demande à créer un template dans A avec l'identifiant de la liaison de B. L'API a répondu 201, le template de B est passé d'une liaison à zéro, et le nouveau template de A contenait celle d'origine de B.

La même requête figure dans le code de Semaphore au moins depuis la version 2.11. L'advisory n'indique que la version que nous avons vérifiée, la 2.19.18, et n'établit pas quelles versions antérieures sont exploitables de la même façon. Mieux vaut supposer qu'elles le sont.

La correction dans la 2.19.20

La version 2.19.20 contient la correction, dans le commit 9445ee9 de la pull request #4414. La mise à jour ne touche plus project_id et template_id, et ne trouve la ligne que si elle appartient vraiment au projet et au template autorisés :

UPDATE project__template_vault
SET vault_key_id=?, name=?, type=?, script=?
WHERE id=? AND project_id=? AND template_id=?

Un identifiant d'un autre projet n'a plus aucun effet. Les mainteneurs ont aussi ajouté un test, TestManagerCannotMoveForeignProjectVault, qui reproduit le scénario de l'advisory. Dans notre signalement, nous avions aussi suggéré de rejeter la requête quand la mise à jour ne trouve aucune ligne, et de créer le template et ses liaisons dans une seule transaction : la correction publiée ferme le chemin d'attaque, les autres mesures restent un choix des mainteneurs.

Que faire si vous utilisez Semaphore UI

Passez au moins à la 2.19.20, mieux encore à la 2.19.21, sortie quelques minutes plus tard, qui selon ses notes de version corrige un autre problème : un utilisateur pouvait changer son propre mot de passe via l'API sans connaître l'actuel. Avec Docker, fixez une version explicite plutôt que latest.

Après la mise à jour, vérifiez que les templates qui utilisent Ansible Vault ont toujours leurs liaisons : si l'un d'eux a perdu son vault sans raison apparente, vous connaissez maintenant une cause possible. Et revoyez qui a le rôle de Manager, et dans quels projets. Si la séparation entre deux groupes d'utilisateurs est vraiment critique, par exemple entre différents clients d'un prestataire, les projets d'une même installation sont une frontière utile mais pas absolue : des instances séparées, avec des bases séparées, limitent les dégâts de la prochaine erreur d'autorisation.

HFS : le fichier tout juste déposé qu'un autre anonyme pouvait remplacer

Pourquoi HFS compte

HFS, HTTP File Server, fait partie de ces programmes que quiconque travaille dans l'informatique depuis quelques années a presque certainement croisés. Massimo Melina, connu en ligne sous le nom de rejetto, le développe depuis le début des années 2000 : on le lance sur son ordinateur, on choisit les dossiers à partager, et toute personne qui a l'adresse peut télécharger ou déposer des fichiers avec un navigateur, sans service cloud au milieu. Sur SourceForge, le projet compte plus de 970'000 téléchargements depuis 2003, sans compter ceux du site de l'auteur. HFS 3, réécrit en TypeScript sur Node.js et distribué sous licence GPL-3.0, tourne sous Windows, Linux, macOS, FreeBSD et Android. Sur GitHub, il a presque 4'000 étoiles et plus de 370 forks ; les paquets de ses versions ont été téléchargés plus de 500'000 fois, l'image Docker rejetto/hfs plus de 116'000 fois, et le paquet npm environ 44'000 fois sur la dernière année.

Il se prête à l'échange de fichiers sur un réseau local ou à la réception de documents de clients et de collaborateurs : des contextes où un dossier ouvert au dépôt anonyme mais protégé contre la suppression est une configuration tout à fait normale. Tout le monde peut déposer un fichier, personne ne peut en retirer.

Les dix secondes accordées à WebDAV

Depuis la version 3.1.0, publiée en avril 2026, HFS prend en charge WebDAV, le protocole qui permet de connecter un dossier partagé comme lecteur réseau dans l'Explorateur de fichiers de Windows ou le Finder du Mac. Certains clients WebDAV ont un comportement particulier : le Finder, par exemple, crée d'abord un fichier vide puis l'écrase aussitôt avec le vrai contenu. Mais écraser un fichier exige le droit de supprimer, et un dossier « dépôt seulement » ne l'accorde pas.

Pour ne pas casser ces clients, HFS accorde à qui vient de déposer un fichier avec un client WebDAV reconnu dix secondes pour le remplacer, même sans droit de suppression. C'est un compromis raisonnable. La question est de savoir comment HFS décidait qui était « celui qui vient de déposer ».

Tous les anonymes ont le même nom

L'autorisation temporaire était enregistrée sous une clé composée de deux éléments : le chemin du fichier et le nom de l'utilisateur. Pour les utilisateurs inscrits, cela fonctionne, et il y avait même un test automatique vérifiant que deux comptes différents ne pouvaient pas utiliser l'autorisation l'un de l'autre. Mais pour quelqu'un qui ne s'est pas connecté, HFS renvoie comme nom une chaîne vide. Tous les visiteurs anonymes avaient le même nom, et donc la même clé.

Un second visiteur anonyme, sans les cookies ni les identifiants du premier, pouvait donc envoyer dans les dix secondes une requête PUT sur le même chemin, avec un user agent contenant le mot WebDAV. HFS trouvait l'autorisation, supprimait le fichier existant avant même de traiter le nouveau dépôt, et contournait à la fois le contrôle can_delete et la protection qui lie les dépôts anonymes à la session qui les a faits. Si le nouveau dépôt réussissait, le fichier était remplacé ; s'il échouait ou était interrompu, l'original restait supprimé de toute façon. L'option force_webdav_login, active par défaut, ne bloquait pas cette requête, car un simple PUT sans en-têtes propres à WebDAV n'était pas traité comme une demande d'authentification WebDAV. Là encore, le catalogue parle de CWE-863, autorisation incorrecte.

Quelle gravité

Les conditions sont étroites. Il faut un dossier avec dépôt anonyme et suppression interdite, un fichier tout juste déposé par un client WebDAV, la connaissance de son chemin, et une seconde requête dans les dix secondes. Les fichiers plus anciens ne sont pas atteignables. La gravité est modérée.

Mais dans cette fenêtre, l'effet est concret : un fichier qui venait d'être publié, peut-être déjà partagé avec d'autres par son adresse, pouvait être remplacé par n'importe qui avec un contenu différent, à la même adresse. Dans un dossier de dépôt, cela signifie que le document reçu n'est peut-être pas celui que l'expéditeur a envoyé.

Comment nous l'avons vérifié

Liam a suivi le chemin dans le code de HFS 3.3.4 (commit bb6f718) et confirmé que le même code était toujours présent dans la branche principale. Il a ensuite compilé le serveur et l'a lancé dans un conteneur Docker sans accès réseau, avec un dossier uploads configuré pour autoriser le dépôt et interdire la suppression. Une première session anonyme a déposé un fichier contenant first avec le user agent du client WebDAV de Windows. Une seconde session, sans cookie, a tenté de le supprimer et HFS a correctement répondu 403. Cette même seconde session a ensuite envoyé un PUT sur le même chemin avec le contenu second : HFS a répondu 200, et le fichier à l'adresse d'origine contenait second.

Les deux requêtes venaient de la même adresse IP, mais la clé ne contient ni la session ni l'IP : le code indique que le résultat serait identique depuis des adresses différentes, même si nous n'avons pas testé ce cas séparément. L'advisory indique comme concernées toutes les versions de la 3.1.0 à la 3.4.0.

La correction dans la 3.4.1

La version 3.4.1, sortie le 9 novembre, mentionne une seule modification dans ses notes de version, dans le commit 9a33df2 : l'autorisation temporaire est désormais liée au nom d'utilisateur ou, pour les visiteurs anonymes, à l'identifiant de leur session. S'il n'y a ni l'un ni l'autre, aucune autorisation n'est accordée. C'est exactement la solution que nous avions proposée. Le nouveau test automatique vérifie les deux côtés : une autre session anonyme reçoit 403, tandis que celui qui a déposé le fichier peut toujours l'écraser comme le fait le Finder.

Que faire si vous utilisez HFS

Passez à la 3.4.1 ou ultérieure. HFS dispose d'une procédure de mise à jour automatique, disponible même lorsqu'il tourne en tant que service, l'opération est donc généralement rapide. Si pour une raison quelconque vous ne pouvez pas mettre à jour tout de suite et que vous avez des dossiers avec dépôt anonyme, envisagez d'exiger un compte pour déposer, au moins jusqu'à l'installation de la correction.

Plus généralement, un dossier ouvert au dépôt anonyme est une porte ouverte par définition. Cela a du sens dans beaucoup de cas, mais il vaut mieux s'en servir pour recevoir des fichiers que pour les publier à d'autres, et en contrôler régulièrement le contenu.

La même erreur, deux fois

Semaphore vérifiait que l'utilisateur pouvait travailler dans le projet de l'URL, puis modifiait un objet choisi par un numéro dans la requête. HFS vérifiait que le fichier avait été déposé par le même utilisateur, mais utilisait un nom identique pour tous les visiteurs anonymes. Dans les deux cas, le contrôle existait, il était écrit avec soin, et dans un cas il était même couvert par un test. Il manquait la bonne question : l'identité que je contrôle est-elle vraiment celle de la personne, et de l'objet, sur lesquels je m'apprête à agir ?

Ces erreurs ne se voient pas en utilisant l'application normalement. Elles se voient en suivant une valeur de la requête jusqu'à l'endroit où elle est utilisée, en se demandant chaque fois « qui a décidé de cette valeur ? ». C'est une partie du travail que nous faisons.

Pourquoi nous faisons de la recherche sur les logiciels open source

Des outils comme Semaphore et HFS sont la raison pour laquelle une petite équipe peut gérer des dizaines de serveurs ou échanger des fichiers sans dépendre d'un service cloud : open source, gratuits, faciles à installer. Mais plus une application est répandue, plus ses contrôles internes comptent. Lire le code des projets que nous utilisons et recommandons, et signaler ce que nous trouvons, c'est notre façon de rendre quelque chose à ces projets et de savoir ce que nous installons chez nos clients.

Si vous utilisez des outils d'automatisation, des serveurs de fichiers ou des applications avec plusieurs utilisateurs et rôles, et que vous voulez savoir si les permissions tiennent vraiment, parlons-en volontiers. Notre travail de cybersécurité comprend la protection et la surveillance des réseaux, la réponse aux incidents, les audits, le durcissement et les tests d'intrusion, et il part toujours de là : lire le code et la configuration, comprendre où un contrôle manque, et vous dire précisément ce que nous avons vérifié et ce que nous n'avons pas vérifié.

Parlons de la sécurité de votre entreprise

VOUS AVEZ UN PROJET ?

Voyons ensemble comment nous pouvons aider votre entreprise avec des solutions numériques sur mesure.

CONTACTEZ-NOUS