flawopen.com/Incidents/gitlab-commits-api-path-traversal-september-2026
CVE-2026-85706: GitLab 서버 파일 노출
GitLab의 인증 없는 업로드 분석은 서버 파일을 선택할 수 있었습니다. 잘못된 이스케이프는 오류에 일부 내용을 드러냈지만, 선택만으로 파일이 공개되지는 않았습니다.[1], [3]
접수 담당자가 받침대에 놓인 서류 대신 방문자가 적어 낸 보관함 위치를 따른다고 생각해 보세요. 신분증을 확인하기 전에 보관함을 열고, 읽을 수 없는 줄이 있으면 오류를 설명하며 그 내용을 소리 내어 읽습니다. 서류를 여는 것과 내용을 되풀이하는 것은 서로 다른 두 잘못입니다.[1]
핵심 개념 및 용어
Upload metadata- 요청에서 온 file.path 또는 metadata.path가 신뢰하는 업로드 처리기가 등록한 버퍼를 대체해서는 안 됩니다.[1]
Percent decoding- 양식 분석은 % 뒤의 두 자리 16진수를 해독합니다. 잘못된 순서는 문제 부분이 포함된 오류를 낼 수 있습니다.[1]
CWE-22 / CVSS- CNA는 CVE-2026-85706을 경로 탐색, CVSS 10.0 치명적으로 분류했습니다. 요청자가 서버 파일을 선택한 것이 깨진 경계입니다.[3]
프레임워크 매핑
- 이 가이드에서 설명하는 약점의 CWE 참조: CWE-22
- CISA KEV — 추가일 2026-09-11 — CVE-2026-85706
사고 타임라인
GitLab이 수정 버전 19.1.8, 19.2.6, 19.3.2를 배포했습니다.[2]
공지에 이전 계열 수정 버전 18.11.12와 19.0.9가 추가됐습니다.[2]
단계별 공격 실행 흐름
인증 이전 선택
업로드 메타데이터가 인증 전에 예상한 임시 버퍼를 서버 경로로 바꿀 수 있었습니다.[1]
파일을 양식으로 해석
application/x-www-form-urlencoded에서 선택된 파일은 정상 커밋 응답이 아닌 양식 데이터로 해석됐습니다.[1]
오류에 비친 내용
잘못된 퍼센트 이스케이프가 해당 부분을 오류에 담을 수 있었습니다. & 와 = 가 범위를 결정하며, 읽기만으로 유출이 증명되지는 않습니다.[1]
조사
correlation_id로 Rails API와 Workhorse 항목을 연결하고 api_error와 written_bytes를 비교해 응답 바이트가 전송되었는지 판단하세요.[1]
소스 코드 비교: 취약한 구현 vs 보안 패치
이 Python 코드는 교육용 모델이며 GitLab 원본이나 실제 패치가 아닙니다. 취약 예제는 메모리 버퍼만 사용하고 모든 %를 오류로 간주하므로 실제 검증이나 부분 선택을 재현하지 않습니다. 개선 예제는 먼저 인증하고 경로 메타데이터를 거부하며 신뢰하는 업로드 시스템의 바이트만 받아 크기를 제한하고 일반 오류를 돌려줍니다. 공급자 업데이트도 적용하세요. 앞단에서 파일을 이미 읽었다면 경로 안에서 인증하는 것은 늦습니다.[1]
from urllib.parse import unquote_to_bytes
def parse_upload(authenticated, metadata, buffers):
# 안전한 메모리 내 fixture이며 파일 시스템에 접근하지 않습니다.
body = buffers.get(metadata.get("path"), b"")
if b"%" in body:
return 400, body.decode("ascii", errors="replace")
if not authenticated:
return 401, "Unauthorized"
return 200, unquote_to_bytes(body)
from urllib.parse import unquote_to_bytes
MAX_BYTES = 4096
HEX = frozenset(b"0123456789abcdefABCDEF")
def parse_upload(authenticated, metadata, registered_body):
# registered_body는 신뢰할 수 있는 업로드 미들웨어에서 오며,
# 요청 매개변수로 선택한 경로에서 오지 않습니다.
if not authenticated:
return 401, "Unauthorized"
if metadata or type(registered_body) is not bytes:
return 400, "Invalid upload"
if len(registered_body) > MAX_BYTES:
return 413, "Upload too large"
for i, byte in enumerate(registered_body):
if byte == 37 and (i + 2 >= len(registered_body) or
registered_body[i+1] not in HEX or
registered_body[i+2] not in HEX):
return 400, "Invalid upload"
return 200, unquote_to_bytes(registered_body)
엔지니어링 및 시스템 보안 강화 체크리스트
- ✓지원되는 수정 버전으로 올리세요. 계열별 최소 수정 버전은 18.11.12, 19.0.9, 19.1.8, 19.2.6, 19.3.2이며 영향은 18.7부터 시작합니다. 설치 버전을 공급자 공지와 비교하세요.[2], [3]
- ✓무해한 메모리 표시로 익명 요청을 시험해 인증 전 버퍼 조회가 없고 응답이나 일반 로그에 표시가 없는지 확인하세요. 분석 전 경로 메타데이터를 거부하세요.[1]
- ✓Rails API 및 Workhorse 로그를 보존하고
correlation_id로 항목을 연결한 뒤api_error와written_bytes를 비교하세요. 정상 업로드 경로가 함께 있다는 이유로 로그 줄 전체를 제외하지 말고 해독된 file.path와 metadata.path를 보세요. 파일을 읽었다는 사실만으로 유출이 입증되지는 않습니다. GitLab에 따르면 api_error에 반영된 조각을 Workhorse의 written_bytes로 확인해야 실제 전송된 바이트를 판단할 수 있으며, HTTP 상태 코드만으로는 결론을 내릴 수 없습니다.[1] - ✓교체는 확인되었거나 합리적으로 의심되는 노출에 포함된 자격 증명으로 제한하세요.
db_key_base또는otp_key_base가 관련될 수 있다면 교체 전에 GitLab 지원팀과 상의하세요. GitLab은 현재db_key_base에 대해 지원되는 교체 절차가 없다고 밝히며, 지원되지 않는 키 교체는 데이터 손실을 일으킬 수 있다고 경고합니다. 로그가 없다고 노출이 없었다는 뜻은 아닙니다.[1]