FlawOpen

flawopen.com/Incidents/gitlab-commits-api-path-traversal-september-2026

● GitLab · CVE-2026-85706 · September 2026 · CVSS 10.0 · Critique
Recherche · FlawOpen

CVE-2026-85706 : divulgation de fichiers GitLab

Le parseur GitLab pouvait sélectionner des fichiers serveur sans authentification ; des échappements invalides révélaient des fragments dans l’erreur.[1], [3]

Vecteur CVSSCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:N[3]
💡 Explication en Langage Simple (ELI5)

Imaginez un agent d’accueil qui accepte le numéro d’une armoire écrit par un visiteur au lieu du document posé dans le bac prévu. Avant de vérifier son badge, il ouvre cette armoire ; si une ligne est illisible, il la répète à voix haute dans son message d’erreur. Ouvrir le document est une première faute, en réciter le contenu une seconde.[1]

Concepts Clés et Termes

Upload metadata
Les chemins file.path ou metadata.path fournis par la requête ne doivent pas remplacer un tampon enregistré par le système de téléversement fiable.[1]
Percent decoding
Dans un formulaire, % suivi de deux chiffres hexadécimaux est décodé ; une séquence invalide peut provoquer une erreur contenant le fragment concerné.[1]
CWE-22 / CVSS
Le CNA classe CVE-2026-85706 comme traversée de chemin, CVSS 10.0 Critique. La frontière franchie est la sélection d’un fichier serveur par le client.[3]

Correspondances de référentiels

Chronologie de l'Incident

2026-09-10

GitLab a publié les correctifs 19.1.8, 19.2.6 et 19.3.2.[2]

2026-09-23

L’avis a été complété par les rétroportages 18.11.12 et 19.0.9.[2]

Déroulement de l'Attaque Étape par Étape

Étape 1

Sélection avant authentification

Les métadonnées du téléversement pouvaient remplacer le tampon temporaire attendu par un chemin serveur avant le contrôle d’identité.[1]

Étape 2

Fichier traité comme formulaire

Avec application/x-www-form-urlencoded, le contenu sélectionné devenait des paramètres de formulaire, et non une réponse normale du commit.[1]

Étape 3

Fragment repris dans l’erreur

Un échappement pourcentage invalide pouvait insérer le fragment fautif dans une erreur. Les séparateurs & et = en limitent l’étendue ; lire ne signifie pas divulguer.[1]

Étape 4

Enquête

Reliez les entrées de l’API Rails et de Workhorse avec correlation_id ; comparez api_error à written_bytes pour déterminer si des octets ont été envoyés dans la réponse.[1]

Code Source : Vulnérable vs Sécurisé

Modèle pédagogique en Python, ni code source ni correctif GitLab. L’exemple vulnérable utilise uniquement des tampons en mémoire et traite tout % comme une erreur ; il ne reproduit ni la validation réelle ni le découpage exact des fragments. Le modèle corrigé authentifie en premier, refuse les chemins fournis, accepte seulement les octets du système de téléversement fiable, borne leur taille et rend des erreurs génériques. Appliquez le correctif du fournisseur : un contrôle dans la route arrive trop tard si un intermédiaire a déjà lu le fichier.[1]

IMPLÉMENTATION VULNÉRABLE
from urllib.parse import unquote_to_bytes

def parse_upload(authenticated, metadata, buffers):
    # Fixtures inoffensifs en mémoire ; aucun accès au système de fichiers.
    body = buffers.get(metadata.get("path"), b"")
    if b"%" in body:
        return 400, body.decode("ascii", errors="replace")
    if not authenticated:
        return 401, "Unauthorized"
    return 200, unquote_to_bytes(body)
PATCH SÉCURISÉ ET ROBUSTE
from urllib.parse import unquote_to_bytes

MAX_BYTES = 4096
HEX = frozenset(b"0123456789abcdefABCDEF")

def parse_upload(authenticated, metadata, registered_body):
    # registered_body provient du middleware de téléversement de confiance,
    # jamais d’un chemin choisi par les paramètres de la requête.
    if not authenticated:
        return 401, "Unauthorized"
    if metadata or type(registered_body) is not bytes:
        return 400, "Invalid upload"
    if len(registered_body) > MAX_BYTES:
        return 413, "Upload too large"
    for i, byte in enumerate(registered_body):
        if byte == 37 and (i + 2 >= len(registered_body) or
                registered_body[i+1] not in HEX or
                registered_body[i+2] not in HEX):
            return 400, "Invalid upload"
    return 200, unquote_to_bytes(registered_body)

Liste de Contrôle de Sécurité pour l'Ingénierie

Sources