FlawOpen

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

● GitLab · CVE-2026-85706 · September 2026 · CVSS 10.0 · Критический
Исследования · FlawOpen

CVE-2026-85706: раскрытие файлов через GitLab

Парсер загрузок GitLab позволял без аутентификации выбирать файлы сервера; неверные escape-последовательности раскрывали фрагменты в ошибках.[1], [3]

Вектор CVSSCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:N[3]
💡 Простыми словами (ELI5)

Представьте сотрудника приёмной: вместо документа из лотка он принимает номер шкафа, написанный посетителем. Не проверив пропуск, сотрудник открывает шкаф; если строка не читается, он произносит её вслух в объяснении ошибки. Открыть документ — одна ошибка, повторить его содержимое — другая.[1]

Ключевые понятия и термины

Upload metadata
Переданные клиентом file.path и metadata.path не должны подменять буфер, зарегистрированный доверенным обработчиком загрузок.[1]
Percent decoding
При разборе формы знак % с двумя шестнадцатеричными цифрами декодируется; неверная последовательность может вызвать ошибку с проблемным фрагментом.[1]
CWE-22 / CVSS
Регистратор CNA относит CVE-2026-85706 к обходу пути, CVSS 10.0, критическая тяжесть. Нарушенная граница — выбор серверного файла по воле клиента.[3]

Связи со стандартами

Хронология инцидента

2026-09-10

GitLab выпустил исправления 19.1.8, 19.2.6 и 19.3.2.[2]

2026-09-23

В бюллетень добавили обратные порты 18.11.12 и 19.0.9.[2]

Пошаговый сценарий атаки

Шаг 1

Выбор до проверки доступа

Метаданные загрузки могли заменить ожидаемый временный буфер путём к файлу ещё до аутентификации.[1]

Шаг 2

Файл как параметры формы

При application/x-www-form-urlencoded содержимое выбранного файла разбиралось как форма, а не возвращалось обычным ответом на commit.[1]

Шаг 3

Фрагмент в ошибке

Неверное процентное кодирование могло попасть в текст ошибки. Разделители & и = ограничивали фрагмент; само чтение не доказывает раскрытие.[1]

Шаг 4

Расследование

Свяжите записи Rails API и Workhorse по correlation_id; сравните api_error с written_bytes, чтобы определить, были ли байты отправлены в ответе.[1]

Исходный код: Уязвимый vs Защищённый вариант

Учебная модель на Python, не исходный код и не патч GitLab. Уязвимый пример использует только буферы в памяти и считает любой % ошибкой; точная проверка и выбор фрагмента здесь не воспроизводятся. Исправленная модель сначала аутентифицирует, отклоняет метаданные пути, принимает только байты доверенной загрузки, ограничивает размер и выдаёт общие ошибки. Установите обновление производителя: проверка внутри маршрута запоздала, если файл уже прочитан промежуточным компонентом.[1]

УЯЗВИМАЯ РЕАЛИЗАЦИЯ
from urllib.parse import unquote_to_bytes

def parse_upload(authenticated, metadata, buffers):
    # Безопасные фикстуры в памяти; доступа к файловой системе нет.
    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)
БЕЗОПАСНЫЙ ИСПРАВЛЕННЫЙ ВАРИАНТ
from urllib.parse import unquote_to_bytes

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

def parse_upload(authenticated, metadata, registered_body):
    # registered_body поступает из доверенного middleware загрузки,
    # а не из пути, выбранного параметрами запроса.
    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)

Чек-лист по защите системы для инженеров

Источники