GitLab CVE-2026-85706 : lecture arbitraire de fichiers non authentifiée (CVSS 10.0)
CVE-2026-85706 : une faille CVSS 10 dans l'API des dépôts de GitLab laisse un anonyme lire n'importe quel fichier du serveur. Mécanisme, versions touchées, détection et correctif.

Une note maximale, et une seule requête
Le 10 septembre 2026, GitLab a publié son correctif du mois pour les éditions Community et Enterprise auto-hébergées. Dix-huit failles corrigées, et une qui écrase les autres : CVE-2026-85706, notée 10.0 sur l'échelle CVSS. On ne croise pas beaucoup de 10.0 dans une année. Celle-ci la mérite.
Le résumé tient en une phrase : sans compte, sans mot de passe, sans la moindre interaction, un inconnu envoie une requête HTTP et récupère le contenu d'un fichier quelconque du serveur. Le fichier de secrets, la configuration de la base, les jetons de vos runners CI/CD. Si le processus GitLab peut le lire, l'attaquant aussi.
Ce qui rend l'avis particulier, ce n'est pas la gravité, on en voit d'autres. C'est le tempo. Le patch sort le 10, les premières sondes tombent le 11 au matin, la CISA classe la faille parmi les vulnérabilités activement exploitées le jour même. Autrement dit, vous n'aviez pas jusqu'au prochain créneau de maintenance.
La suite détaille le mécanisme (il vaut le détour), pourquoi « lecture de fichier » sous-estime lourdement la casse, comment savoir si vous êtes touché et si on vous a déjà rendu visite, et quoi faire. Le patch, oui, mais pas seulement.
Les faits, tels que l'avis les pose
| Élément | Valeur |
|---|---|
| Identifiant | CVE-2026-85706 |
| Correctif | GitLab CE/EE 19.3.2, 19.2.6 et 19.1.8 (10 septembre 2026) |
| Classe | Traversée de chemin (CWE-22) + défaut d'authentification, lecture de fichier arbitraire |
| CVSS | 10.0 (v3.1), vecteur AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:N |
| Composant | API des dépôts (repository), via Workhorse |
| Versions affectées | 18.7 à 19.1.7, 19.2.0 à 19.2.5, 19.3.0 à 19.3.1 |
| Précondition | Instance joignable ; au moins un projet public sur le chemin observé ; aucune authentification |
| Périmètre | Auto-hébergé uniquement (GitLab.com et Dedicated non concernés) |
| Rapporteur | s3ntago, via le programme de bug bounty HackerOne de GitLab |
| État | Exploité dans la nature, inscrit au catalogue CISA KEV, PoC publics |
Deux choses valent d'être posées avant d'aller plus loin. La première : le 10.0 n'est pas gonflé. On imagine volontiers qu'une « lecture de fichier » plafonne côté confidentialité et laisse l'intégrité tranquille, mais le vecteur dit l'inverse. Le S:C signale que la faille sort de l'application pour atteindre le système de fichiers de l'hôte, et le I:H que l'intégrité est bel et bien touchée, parce que lire le bon fichier permet ensuite de forger des jetons de session valides. La lecture ouvre la porte à l'écriture.
La seconde tient à la précondition « au moins un projet public ». C'est la condition du chemin d'attaque observé dans les premiers PoC, pas nécessairement la seule voie : les chercheurs qui ont regardé la faille de près notent que d'autres mécanismes pourraient s'en passer. Ne comptez donc pas sur l'absence de projet public comme sur une protection.
Le mécanisme, ligne par ligne
Le bug ne vit dans aucun des deux composants pris séparément. Il vit dans l'écart entre eux.
Rappel d'architecture. Devant l'application Ruby (servie par Puma, avec le framework Grape pour l'API) se tient Workhorse, un proxy inverse écrit en Go. Workhorse n'est pas un simple relais : il intercepte certaines routes pour les traiter lui-même, en particulier tout ce qui touche aux envois de fichiers. Le déroulé normal d'un upload est le suivant. Le client envoie son fichier, Workhorse l'écrit sur le disque dans un emplacement temporaire, puis passe la main à Rails en lui fournissant des métadonnées de confiance : un champ file.path qui pointe vers ce fichier temporaire, un file.size, et le reste. Rails lit file.path sans se poser de question, puisque le chemin vient de Workhorse et non du client.
Reste à savoir comment Workhorse reconnaît les routes à intercepter. Il compare l'URL à ses expressions régulières, et il le fait sur la forme encodée du chemin (le EscapedPath() de Go). Puma, lui, décode les séquences %XX avant que Grape ne dispatche la requête. Les deux ne regardent pas la même chaîne. Tout est là.
L'attaquant encode une seule lettre d'un segment de chemin par ailleurs statique :
POST /api/v4/projects/1/repository/%66iles/quelconque?file.path=/etc/passwd&file.size=1 HTTP/1.1
Host: cible%66, c'est la lettre f. Pour Workhorse, le chemin est /repository/%66iles/…, qui ne correspond à aucune de ses routes d'upload : il ne l'intercepte pas, ne réécrit rien, laisse passer la requête telle quelle, avec les métadonnées fournies par le client. Pour Rails, une fois %66iles décodé en files, c'est une route d'API parfaitement valide, et elle atteint le gestionnaire de fichier. Ce gestionnaire trouve un file.path dans la requête, le croit posé par Workhorse comme d'habitude, et exécute l'équivalent d'un File.read(file.path). Sauf que cette fois, file.path vaut ce que l'attaquant a écrit. L'authentification, elle, saute par le même trou : le contrôle qui aurait dû s'appliquer était porté par le traitement de Workhorse, court-circuité en même temps.
La même astuce marche sur les autres segments : %63ommits pour commits, %72epository pour repository.

Un détail qui sert autant l'attaquant que le défenseur : les réponses ont une signature. Sur une instance vulnérable, une requête pointée vers un fichier inexistant renvoie un 400 avec « local file not present », la preuve que le contournement a fonctionné et que le gestionnaire a bien tenté de lire. Sur une instance corrigée, la même requête renvoie un 401 Unauthorized, parce que l'authentification s'exécute désormais avant tout. C'est sur cette différence que reposent les sondes non intrusives : le template Nuclei publié vise un chemin volontairement absent et lit le code de retour, sans jamais exfiltrer de fichier réel.
Pourquoi « lecture de fichier » est un euphémisme
Le fichier que tout le monde vise en premier s'appelle gitlab-secrets.json (ou son équivalent dans gitlab.rb). Il contient les clés maîtresses de l'instance, à commencer par secret_key_base. Cette clé signe les cookies de session de Rails et une bonne partie des jetons signés de l'application. La détenir, ce n'est pas seulement lire un secret de plus : c'est pouvoir fabriquer une session valide, administrateur comprise, sans connaître aucun mot de passe. Sur des versions plus anciennes de GitLab, la seule connaissance de secret_key_base a même suffi à obtenir l'exécution de code à distance, via des chaînes de désérialisation que cette clé permet de signer. Voilà le fameux I:H du vecteur : la lecture débouche sur la falsification.
Et le même fichier livre le reste. db_key_base et otp_key_base servent à chiffrer, en base, les variables CI/CD, les jetons d'accès, les secrets à deux facteurs. Les lire permet donc de déchiffrer ce que GitLab stockait pourtant chiffré. Ajoutez les cibles classiques d'un compte git sur disque : .gitlab_shell_secret, les jetons d'enregistrement des runners, database.yml, les clés SSH d'hôte, les jetons de déploiement. En quelques requêtes, un anonyme passe de « je vois un GitLab sur Internet » à « je tiens de quoi me faire passer pour un administrateur et déchiffrer vos secrets ».
De la requête anonyme à la compromission de la chaîne CI/CD
- 1
Instance auto-hébergée exposée
une interface GitLab joignable depuis Internet, avec au moins un projet public.
- 2
Requête d'API encodée
un POST sur /repository/%66iles/ avec un file.path pointant hors du dépôt.
- 3
Contournement de Workhorse
la route encodée n'est pas reconnue, l'authentification et le confinement sautent.
- 4
Lecture de gitlab-secrets.json
secret_key_base, db_key_base et les clés de chiffrement partent en clair.
- 5
Session administrateur forgée
la clé de signature permet de fabriquer un cookie valide, sans mot de passe.
Chaîne CI/CD compromise
déchiffrement des variables, jetons de runners, accès au code et aux déploiements.
Le calendrier, et ce qu'il impose

Le correctif sort le 10 septembre. Dès le 11 au matin, vers 6 h UTC, le réseau de leurres de watchTowr enregistre des sondes visant précisément ce point d'entrée : des acteurs avaient déjà rétro-analysé le patch. Le même jour, la CISA inscrit la faille à son catalogue des vulnérabilités activement exploitées, avec une échéance de remédiation au 14 septembre pour les agences fédérales américaines. Dans la foulée, des PoC publics apparaissent sur GitHub, en Go comme en Python, plus un template Nuclei de détection.
Ce tempo n'a rien de surprenant. Un correctif de sécurité désigne le fichier modifié, donc la fonction fautive, donc le raisonnement de l'attaque : sur une faille aussi facile à rejouer qu'un décalage d'encodage, quelques heures suffisent à passer du diff au PoC. La conséquence pratique est nette. Pour une vulnérabilité de ce profil, la fenêtre entre « le patch existe » et « on me scanne » se compte en heures.
Qui est touché
Vous êtes concerné si vous faites tourner une instance GitLab auto-hébergée (Omnibus, source, ou chart Helm) dans l'une de ces plages.
| Branche GitLab | Statut |
|---|---|
| 18.7 à 19.1.7 | Vulnérable, corriger vers 19.1.8 |
| 19.2.0 à 19.2.5 | Vulnérable, corriger vers 19.2.6 |
| 19.3.0 à 19.3.1 | Vulnérable, corriger vers 19.3.2 |
| 19.1.8 / 19.2.6 / 19.3.2+ | Corrigé |
| GitLab.com | Non concerné (déjà à jour) |
| GitLab Dedicated | Non concerné (aucune action requise) |
Si votre GitLab vit sur gitlab.com ou en offre Dedicated, GitLab a déjà déployé le correctif de son côté, vous n'avez rien à faire. Pour tout le reste, ce sont les machines qu'on oublie qui paient : un runner installé dans un coin, une instance interne « pas exposée » jusqu'au jour où un VPN mal segmenté la rend joignable, un serveur de test qui héberge un miroir de la production.
Suis-je vulnérable ? Ai-je été visité ?
Ma version. C'est le juge de paix. La bannière d'administration l'affiche, ou en ligne de commande sur l'hôte :
# Édition Omnibus
gitlab-rake gitlab:env:info | grep -i version
cat /opt/gitlab/version-manifest.txt | grep -i '^gitlab-ce\|^gitlab-ee'Si le numéro tombe dans une des plages ci-dessus, l'hôte est vulnérable jusqu'à preuve du contraire.
Ai-je été sondé, ou lu ? Le contournement laisse une trace reconnaissable : des requêtes vers l'API des dépôts avec un segment de chemin partiellement encodé (%66iles, %63ommits, %72epository) et un paramètre file.path.
# Requêtes suspectes vers l'API dépôt : segment encodé ou file.path
grep -Ei '/repository/%[0-9a-f]{2}|file\.path=' \
/var/log/gitlab/nginx/gitlab_access.log \
/var/log/gitlab/gitlab-rails/production_json.log \
/var/log/gitlab/gitlab-rails/api_json.logLà, un point compte plus que tout le reste : le code de retour et la taille de la réponse. Un mur de 400 (« local file not present ») ou de 401, c'est du balayage, du bruit. Un 200 accompagné d'un corps anormalement volumineux sur l'une de ces routes suggère qu'un fichier est réellement parti. À ce moment-là, on ne referme plus une porte, on considère les secrets comme compromis.
Mon exposition. Depuis l'extérieur de votre réseau, vérifiez si l'interface répond là où elle ne devrait pas. Une instance de test joignable en frontal est une urgence à part entière, indépendamment de la version.
Que faire maintenant
Le correctif ne suffit pas. Si votre instance a été exposée, ne serait-ce qu'un moment, en version vulnérable, partez du principe que des secrets ont pu fuir, et faites-les tourner.
- Appliquer le correctif, tout de suite. Montez vers
19.3.2,19.2.6ou19.1.8selon votre branche, ou plus récent. Il n'existe aucun contournement officiel : le patch est la seule vraie réponse. La mise à jour Omnibus est une opération de routine (apt/yumpuisgitlab-ctl reconfigure), et les déploiements multi-nœuds gèrent la bascule sans coupure. - Réduire l'exposition en attendant. Si vous ne pouvez pas patcher dans l'heure, sortez l'interface de l'accès direct à Internet : VPN, bastion, ou liste d'adresses autorisées au niveau du reverse proxy. Ça ne corrige pas la faille, ça gagne du temps.
- Faire tourner les secrets, si l'instance a été exposée non corrigée. C'est l'étape qu'on saute et qu'on regrette. Le contenu de
gitlab-secrets.json(secret_key_baseet les autres clés, avec ungitlab-ctl reconfigureaprès régénération), les identifiants de base, tous les jetons d'accès personnels, les clés et jetons de déploiement, les jetons d'enregistrement et d'authentification des runners, les secrets OAuth, chaque variable CI/CD contenant un identifiant. Et les clés SSH d'hôte. - Chasser les traces. Une fois les secrets réputés lisibles, ils ont pu servir. Passez au crible le journal d'audit : création de jetons inconnus, exports de projets, changements d'appartenance à des groupes, ajouts de clés SSH, connexions administrateur inattendues. C'est là que se voit une exploitation réussie, pas seulement tentée.
Les deux autres failles à ne pas perdre de vue
Le correctif du 10 septembre en referme dix-huit. Deux méritent qu'on les cite, parce qu'elles sont graves et qu'on les confond vite avec la vedette.
CVE-2026-87719 (CVSS 9.9) est une désérialisation non sécurisée dans le sérialiseur d'abonnements GraphQL, réservée à l'édition Enterprise. Un utilisateur authentifié disposant de l'accès à Duo Chat peut, via un argument GraphQL forgé, récupérer la configuration de la recherche avancée et des identifiants sensibles. Elle demande un compte, contrairement à la vedette, mais son score est presque aussi haut.
CVE-2026-88765 (CVSS 8.5) est un dépassement de tampon dans le wrapper de conversion Unicode de l'indexation de recherche (Enterprise également). Un utilisateur authentifié atteint l'exécution de code à distance en important un export de projet Git spécialement forgé.
Bref, ce correctif n'est pas « une critique et du remplissage ». Il s'applique en bloc.
Ce que ça dit de votre GitLab
Un serveur de forge concentre le code, les secrets de déploiement, les accès aux environnements, les jetons qui pilotent les runners. Une faille de lecture y vaut, en pratique, bien plus qu'un accès en lecture : elle donne les clés qui ouvrent le reste. Et c'est souvent la machine la moins surveillée du parc. On installe un GitLab « pour l'équipe », on ne le range jamais dans l'inventaire des actifs critiques, et c'est précisément celui-là qu'on retrouve exposé le jour de l'audit.
La bonne question, après une alerte comme celle-ci, n'est pas seulement « suis-je à jour ? ». C'est « qu'est-ce qui, chez moi, est joignable depuis Internet sans que je l'aie vraiment décidé ? ». Un GitLab de test resté en frontal, un runner qui expose une interface, un miroir oublié. C'est ce que révèle un inventaire de surface d'attaque externe, et ce qu'un scan de vulnérabilités bien ciblé recoupe avec les versions vulnérables connues, à condition que votre cartographie du système d'information contienne réellement ces machines. La faille du jour se corrige en une commande ; l'angle mort qui l'a laissée atteignable demande une méthode.
Questions fréquentes
Quelles versions de GitLab sont concernées ?
Les éditions auto-hébergées CE et EE, de 18.7 à 19.1.7, de 19.2.0 à 19.2.5, et de 19.3.0 à 19.3.1. Le correctif est dans 19.1.8, 19.2.6 et 19.3.2. GitLab.com et GitLab Dedicated ne sont pas concernés.
J'ai patché. C'est bon ?
Pas si votre instance a été exposée en version vulnérable, même brièvement. Le patch referme la porte, mais des secrets lus avant restent utilisables. Faites tourner secret_key_base, les identifiants de base, les jetons de runners, les variables CI/CD et les jetons d'accès.
Mon instance est-elle vulnérable sans aucun projet public ?
Le chemin d'attaque observé dans les premiers PoC exige au moins un projet public. Mais d'autres variantes pourraient s'en passer, alors ne traitez pas l'absence de projet public comme une protection. Patchez.
Comment distinguer une exploitation d'un simple scan ?
Dans les journaux, une réponse 400 ou 401 sur ces requêtes est une tentative ou un balayage. Un 200 avec un corps anormalement volumineux est le signe d'une lecture réussie : traitez-le comme un incident.
Pourquoi Workhorse et Rails ne voient-ils pas le même chemin ?
Workhorse compare l'URL à ses routes sur la forme encodée du chemin, tandis que Puma décode les %XX avant que l'application ne route la requête. En encodant une lettre d'un segment statique, on fait diverger les deux lectures : Workhorse ne reconnaît plus la route et la laisse passer, Rails la reconnaît une fois décodée. Le contournement vit dans cet écart.
Pour conclure
CVE-2026-85706 n'a rien d'un exploit sophistiqué : c'est un désaccord de décodage entre deux composants qui auraient dû lire la même URL de la même manière. Sa simplicité est justement ce qui la rend dangereuse. Une requête, aucun compte, et le fichier qui contient les clés de votre instance part sur Internet. Le score de 10.0 est mérité, l'exploitation a démarré en moins d'un jour, et le patch seul ne referme pas tout : reste à régénérer ce qui a pu fuir.
Deux gestes, dans l'ordre. Montez vers 19.3.2, 19.2.6 ou 19.1.8 sans attendre le prochain créneau de maintenance. Et si l'instance a été exposée, considérez vos secrets comme compromis et régénérez-les. Le reste est une affaire d'inventaire.
Faites analyser votre exposition par Flawfence : nous cartographions ce qui, depuis Internet, répond en votre nom, forges et interfaces d'administration comprises, et nous vous disons quoi fermer et quoi faire tourner en premier.
Sources
- GitLab, « Critical Patch Release: 19.3.2, 19.2.6, 19.1.8 »
- CISA, « CISA Adds One Known Exploited Vulnerability to Catalog » (11 septembre 2026)
- watchTowr, « Rapid Reaction: GitLab Critical Path Traversal Vulnerability (CVE-2026-85706) »
- Horizon3.ai, « CVE-2026-85706 »
- Rapid7, « CVE-2026-85706: Critical GitLab Path Traversal Exploited in the Wild »
- BleepingComputer, « GitLab urges users to patch max severity path traversal flaw »
- Projet Nuclei Templates, PR #17231 (template de détection CVE-2026-85706)
Discutons de votre exposition externe
Demandez une démo personnalisée de Flawfence et découvrez votre niveau d’exposition réel.
