NOW Briefing
GitLab commits API 미인증 임의 파일 읽기 취약점 (CVE-2026-85706)
■ 개요
GitLab은 소스 코드 저장소와 CI/CD 파이프라인을 함께 제공하는 개발 플랫폼입니다. 2026년 9월 10일 긴급 패치 릴리스를 내고, 저장소 commits API에서 미인증 사용자가 서버의 임의 파일을 읽을 수 있는 취약점 CVE-2026-85706을 수정했습니다. 영향 범위는 자체 구축·운영(self-managed) 방식의 CE·EE 18.7부터 19.1.7까지, 19.2.0부터 19.2.5까지, 19.3.0부터 19.3.1까지입니다. GitLab은 GitLab.com이 이미 패치된 버전으로 운영 중이며 GitLab Dedicated 고객은 별도 조치가 필요하지 않다고 밝혔습니다.
공격에는 유효한 비밀번호나 토큰, 사용자 상호작용이 필요하지 않습니다. GitLab 웹 인터페이스에 네트워크로 도달할 수 있으면 로그인 이전 단계에서 요청이 처리됩니다. 읽어낼 수 있는 대상은 GitLab 서비스 계정 권한으로 접근 가능한 파일입니다. watchTowr 관측을 인용한 보도 기준, 공격자가 노린 파일은 GitLab 설정 파일, 로그, 시스템 SSH 설정과 그 안에 담긴 자격증명입니다. 파일 읽기 취약점이지만, 읽힌 대상이 서버 비밀값이면 계정 탈취와 내부 확산으로 이어질 수 있습니다.
보안 업체 watchTowr는 공개 다음 날인 9월 11일 06:00 UTC경부터 스캔을 관측했고, 단순 탐색을 넘어 민감 파일이 반출된 사례까지 확인했다고 밝혔습니다. 다만 해당 관측이 실제 피해 조직 환경인지 watchTowr 자체 관측 인프라인지는 공개 내용만으로 구분되지 않습니다. 같은 날 CISA는 이 취약점을 실제 악용이 확인된 취약점 목록(KEV)에 올리고 미국 연방기관에 9월 14일까지 조치를 지시했습니다. PoC 코드도 공개돼 있습니다. 국내에서도 폐쇄망 환경에 GitLab을 직접 구축해 운영하는 제조 기업 사례를 공식 파트너가 공개했습니다. 외부 인터넷과 분리된 인스턴스라면 즉시 악용 위험은 낮지만, 사내망에 접근 가능한 내부자나 이미 내부에 진입한 공격자에게는 인증 없이 서버 비밀값을 읽는 경로가 그대로 남습니다.
■ 요약
| 항목 | 내용 |
|---|---|
| CVE ID | CVE-2026-85706 (저장소 commits API 미인증 임의 파일 읽기) |
| CVSS 점수 | CVSS v3.1 10.0 |
| 취약점 유형 | CWE-22, 경로 탐색 및 인증 적용 누락 |
| 영향/위험 | • 인증·사용자 상호작용 없이 서버 파일 읽기 가능 • GitLab 서비스 계정이 읽을 수 있는 설정 파일·로그·키 노출 • 보안 업체 관측 기준, 민감 파일 반출 사례 보고 • CISA KEV 등재 및 PoC 코드 공개 |
| 취약 버전 | • CE/EE 18.7 ~ 19.1.7 • CE/EE 19.2.0 ~ 19.2.5 • CE/EE 19.3.0 ~ 19.3.1 |
| 패치 버전 | • 19.1.8 • 19.2.6 • 19.3.2 |
■ 기술 분석
GitLab 요청은 Ruby 웹 프레임워크 Rails로 만든 애플리케이션 앞단에 놓인 Go 기반 리버스 프록시 구성요소(Workhorse)를 먼저 지나갑니다. Workhorse는 요청 본문 업로드를 처리할 API 경로를 라우트 정규식으로 식별하는데, POST /api/v4/projects/[^/]+/repository/commits와 POST·PUT .../repository/files/[^/]+가 여기에 포함됩니다. Workhorse 소스 기준, 이 라우트 매칭은 퍼센트 인코딩이 그대로 남아 있는 경로 문자열(EscapedPath)을 대상으로 하지만 Rails는 인코딩을 디코딩한 뒤 라우팅합니다. PoC가 사용하는 우회 경로처럼 경로의 한 글자만 퍼센트 인코딩(%63ommits, %72epository, %66iles)으로 바꾸면 Workhorse는 업로드 경로로 인식하지 않고, Rails는 같은 요청을 정상적인 commits 엔드포인트로 연결합니다.
Workhorse가 요청을 가공하지 않고 통과시키면, 요청 파라미터에 담긴 파일 경로 값이 중간 검증 없이 Rails 핸들러까지 도달합니다. 해당 엔드포인트는 Workhorse 경유 여부만 확인하고 사용자 인증은 확인하지 않았습니다. 핸들러는 인증 절차가 실행되기 전에 공격자가 지정한 경로의 파일을 열어 읽었고, 파일 내용이 파라미터 파싱 오류 메시지에 그대로 실려 응답으로 되돌아왔습니다. 인증 검사는 파일을 읽은 뒤에야 동작했기 때문에 인증 실패 응답을 받더라도 읽기 자체는 이미 일어난 상태였습니다.
아래는 GitLab 공식 저장소의 수정 커밋 0d9ce3e7에서 lib/api/commits.rb의 post ':id/repository/commits' 블록이 바뀐 부분입니다. Workhorse 경유 여부를 확인하는 호출 바로 뒤에 인증 강제 호출을 추가했습니다.
@@ -349,6 +349,7 @@ def filter_commits_attrs!(attrs)
route_setting :authorization, permissions: :create_commit, boundary_type: :project
post ':id/repository/commits' do
require_gitlab_workhorse!
+ authenticate!
attrs = file_params_from_body_upload
같은 커밋은 lib/api/files.rb의 파일 생성·수정 엔드포인트에도 동일한 인증 호출을 추가했습니다. 업로드 파라미터를 다루는 헬퍼(lib/api/helpers/commits_body_uploader_helper.rb)가 요청에 담겨 온 file.path·file.size 값을 신뢰하지 않고 미들웨어가 확정한 업로드 객체만 사용하도록 재작성했습니다. 미인증 요청이 거부되는지, 오류 응답에 파일 내용이 섞여 나가지 않는지 검증하는 테스트도 함께 들어갔습니다. 인증 추가만으로는 경로 값을 신뢰하는 구조가 남기 때문에, 이번 패치는 인증 강제와 파라미터 신뢰 제거를 함께 적용했습니다. 연구자 s3ntago가 HackerOne 버그 바운티에 이 취약점을 제보했습니다.
■ PoC
GitHub에 올라온 PoC 코드는 대상 URL, 프로젝트 ID, 읽을 파일 경로를 인자로 받아, 인코딩을 이용한 여러 경로 변형을 차례로 시도하고 응답을 보고 어느 단계까지 도달했는지 판정합니다. 판정 상태에는 Workhorse가 요청을 가로챈 경우, 우회는 성공했으나 대상 파일이 없는 경우, 파일 내용이 오류 메시지로 유출된 경우, 파일은 읽혔지만 내용이 응답에 되돌아오지 않은 경우가 포함됩니다. 마지막 유형에서는 응답에 아무것도 노출되지 않아도 읽기가 이미 인증 이전에 일어났고, 해당 코드도 판정 메시지에 이를 명시합니다. 해당 PoC 기준, commits API 경로를 쓰려면 익명으로 읽을 수 있는 프로젝트 ID가 필요하고 그렇지 않은 경우에는 files API 경로를 사용합니다.
아래 코드는 공개 PoC의 classify() 함수 일부입니다. 코드는 수정하지 않고 그대로 옮겼습니다. 전체 코드는 참고 자료의 원문 링크에서 확인할 수 있습니다.
def classify(status, body):
"""Map a response onto the state of the exploit chain."""
if REWRITE_HAPPENED in body and "message" in body:
return ("rewrite", "Workhorse intercepted the request (path form unusable)")
if NOT_PRESENT in body:
return ("missing", "bypass OK, handler reached, but File.exist? failed")
m = re.search(r"Invalid parameter: invalid %-encoding \((.*)\)", body, re.S)
if m:
return ("leak", m.group(1))
m = re.search(r"Invalid parameter type: (.*)", body, re.S)
if m:
return ("leak-fragment", m.group(1))
if status == 401:
return ("read-noecho", "file exists and was parsed cleanly; no error echo "
"(content has no bad %-sequence). Read happened pre-auth.")
⚠️ WARN
공격자가 파일 내용을 그대로 받아내지 못한 요청도 실패한 공격이 아닙니다.
존재 여부만 확인되는 응답도 서버 내부 파일 구조를 파악하는 수단이 될 수 있습니다.
응답 본문에 유출 흔적이 없어도 침해 조사는 계속해야 합니다.
■ 탐지 및 점검
• 웹 서버·리버스 프록시 접근 로그에서 /api/v4/projects/ 아래 repository/commits 및 repository/files 경로로 향한 미인증 POST·PUT 요청을 조회합니다.
• 경로 문자열을 퍼센트 디코딩해 정규화한 뒤 매칭합니다. 공격이 경로 일부를 인코딩해 검사를 우회하므로, 원본 문자열만 비교하는 탐지 규칙은 요청을 놓칩니다.
• 쿼리 문자열에 file.path, file.size 파라미터가 포함된 요청을 먼저 조사합니다. 쿼리 문자열은 접근 로그에 기록되는 경우가 많지만, 요청 본문까지 확인하려면 프록시 본문 로깅이나 전체 패킷 캡처가 설정돼 있어야 합니다.
• HTTP 401·404 응답을 받은 요청도 함께 확인합니다. 인증 실패 응답이 반환됐더라도 파일 읽기는 그 전에 일어났기 때문입니다.
• 9월 10일 이전부터 패치 적용 시점까지 인터넷에 노출돼 있던 인스턴스는 침해 가능성을 전제로 우선 점검하고, 스캔이 관측된 9월 11일 전후 로그를 중점적으로 보존합니다.
✅ CHECK
조회 기준 시점은 패치 배포일이 아니라 인스턴스에 패치를 실제로 적용한 시각입니다.
패치가 늦어진 만큼 노출 구간이 길어집니다.
■ 대응 방안
• 자체 구축 인스턴스를 19.3.2, 19.2.6, 19.1.8 중 해당 계열 버전으로 즉시 업그레이드합니다. GitLab.com과 GitLab Dedicated는 별도 조치가 필요하지 않습니다.
• 즉시 업그레이드가 불가능하면 GitLab 인스턴스의 인터넷 직접 노출을 차단하고, VPN이나 신뢰 네트워크 경유로만 접근하도록 제한합니다.
• 노출 이력이 있는 서버는 패치 후 자격증명을 재발급합니다. GitLab 설정 파일에 담긴 시크릿, SSH 호스트 키, 배포 토큰, CI/CD 변수처럼 서비스 계정이 읽을 수 있었던 값이 대상입니다.
• 자격증명 재발급은 패치 적용을 끝낸 뒤에 진행합니다. 취약한 상태에서 새 값으로 바꾸면 같은 경로로 다시 읽힐 수 있습니다.
• 폐쇄망 운영 조직도 즉시 패치해야 합니다. 외부 차단은 인터넷 경유 공격만 막을 뿐, 사내망에서 인스턴스에 도달할 수 있는 경로에는 취약점이 그대로 남습니다.
💡 TIP
재발급 범위를 정할 때는 유출이 확인된 파일이 아니라 GitLab 서비스 계정이 읽을 수 있었던 파일 전체를 기준으로 잡습니다.
응답에 내용이 노출되지 않는 읽기 방식이 존재하므로, 로그만으로 유출 대상을 확정하기 어렵습니다.
[참고 자료]
• GitLab 긴급 패치 릴리스: 19.3.2, 19.2.6, 19.1.8
• GitLab 수정 커밋 0d9ce3e7 — commits API 인증 강제 추가
• GitLab Workhorse 라우트 정의 routes.go (v19.3.2-ee)
• GitLab Workhorse 요청 경로 처리 upstream.go (v19.3.2-ee)
• Rapid7 — CVE-2026-85706 실제 악용 분석
• The Hacker News — CVSS 10.0 GitLab 파일 읽기 취약점 스캔 관측
• watchTowr — GitLab 경로 탐색 취약점 관측 보고



