FlawOpen

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

● GitLab · CVE-2026-85706 · September 2026 · CVSS 10.0 · Crítica
Investigación · FlawOpen

CVE-2026-85706: exposición de archivos en GitLab

El parser de GitLab podía elegir archivos sin autenticar; escapes inválidos mostraban fragmentos en errores, pero elegir no divulgaba archivos.[1], [3]

Vector CVSSCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:N[3]
💡 Explicación en Lenguaje Sencillo (ELI5)

Imagina a un recepcionista que acepta la ubicación de un armario escrita por un visitante en lugar del documento de la bandeja de entrada. Antes de comprobar su identificación, abre ese armario; si no puede interpretar una línea, la repite en voz alta al explicar el error. Abrir el documento es un fallo; repetir su contenido es otro.[1]

Conceptos Clave y Términos

Upload metadata
Los parámetros file.path o metadata.path enviados por el cliente no deben sustituir un búfer registrado por el sistema de carga de confianza.[1]
Percent decoding
Al analizar formularios, % seguido de dos dígitos hexadecimales se decodifica; una secuencia inválida puede producir un error que incluya el fragmento problemático.[1]
CWE-22 / CVSS
La autoridad CNA clasifica CVE-2026-85706 como recorrido de rutas, CVSS 10.0 Crítico. El límite vulnerado es la selección de archivos del servidor controlada por el solicitante.[3]

Mapeos de marcos

Cronología del Incidente

2026-09-10

GitLab publicó 19.1.8, 19.2.6 y 19.3.2 con la corrección.[2]

2026-09-23

El aviso se actualizó para incluir las versiones corregidas 18.11.12 y 19.0.9.[2]

Flujo de Ataque Paso a Paso

Paso 1

Selección antes de autenticar

Los metadatos de carga podían sustituir el búfer temporal esperado por una ruta del servidor antes de autenticar.[1]

Paso 2

Contenido convertido en parámetros

Con application/x-www-form-urlencoded, el archivo elegido se interpretaba como formulario; no se devolvía como respuesta normal de una confirmación.[1]

Paso 3

Reflejo en el error

Un escape de porcentaje inválido podía incluir el fragmento defectuoso en el error. Los separadores & y = delimitaban su tamaño; leer no implica divulgar.[1]

Paso 4

Investigación

Correlacione los registros de la API de Rails y Workhorse mediante correlation_id; compare api_error con written_bytes para evaluar si se enviaron bytes en la respuesta.[1]

Código Fuente: Vulnerable vs. Seguro

Modelo ilustrativo en Python, no es código ni parche de GitLab. El ejemplo vulnerable solo usa búferes en memoria y trata cualquier % como error, sin reproducir la validación real ni la selección del fragmento. El modelo corregido autentica primero, rechaza metadatos de ruta, admite solo bytes registrados por un componente fiable, limita su tamaño y devuelve errores genéricos. Aplique la actualización del proveedor: autenticar dentro de la ruta llega demasiado tarde si un componente previo ya leyó el archivo.[1]

IMPLEMENTACIÓN VULNERABLE
from urllib.parse import unquote_to_bytes

def parse_upload(authenticated, metadata, buffers):
    # Fixtures inocuos en memoria; sin acceso al sistema de archivos.
    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)
PARCHE SEGURO Y ROBUSTO
from urllib.parse import unquote_to_bytes

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

def parse_upload(authenticated, metadata, registered_body):
    # registered_body proviene de middleware de carga confiable,
    # nunca de una ruta elegida por parámetros de la solicitud.
    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)

Lista de Verificación de Seguridad para Ingeniería

Fuentes