Git --end-of-options: 버전 관리 시스템에서의 인자 주입(Argument Injection) 방지
Git --end-of-options: 버전 관리 시스템에서의 인자 주입(Argument Injection) 방지
Git에서 --end-of-options의 역할
Git은 옵션 파싱을 중단하는 전용 마커를 제공하기 위해 2.24.0 버전(2019년 11월)에서 --end-of-options 플래그를 도입했습니다. 이는 Git이 리비전(revision)과 pathspec을 구분하기 위해 표준 POSIX 마커인 --를 재사용하면서, 대시(-)로 시작하는 리비전이 명령줄 옵션으로 오인될 수 있는 보안 공백이 발생했기 때문에 필요했습니다.
표준 Unix 도구에서 --는 옵션의 끝을 나타냅니다. 하지만 Git에서 git log main -- README.md와 같은 명령은 --를 사용하여 main이 리비전이고 README.md가 파일 경로임을 나타냅니다. 이 때문에 만약 스크립트가 git log "$rev"를 실행하고 $rev 변수가 대시로 시작한다면, Git은 해당 리비전을 참조(reference)가 아닌 플래그로 해석하려고 시도하게 됩니다.
신뢰할 수 없는 리비전과 경로를 안전하게 처리하기 위한 올바른 구문은 다음과 같습니다:
git log --end-of-options "$rev" -- "$path"
이 구조에서 --end-of-options는 리비전을 보호하고, --는 pathspec을 보호합니다.
버전별 지원 현황
--end-of-options 지원은 서브 명령(subcommands)에 따라 점진적으로 배포되어 파편화된 호환성 환경을 만들었습니다:
- Git 2.24.0: 플래그 최초 도입.
- Git 2.30.0: 커스텀 인자 파서를 사용하는
git rev-parse에 대한 지원 추가. - Git 2.43.1 (2024년 2월): 이전에
--를 내부적으로 처리하여 플래그를 거부했던git checkout및git reset에 대한 지원 추가.
인자 주입(Argument Injection) 및 CWE-88
인자 주입(CWE-88)은 프로그램이 신뢰할 수 없는 문자열을 인자 목록(argv 배열)으로 전달할 때, 해당 문자열이 대시로 시작한다는 이유로 수신 측 바이너리가 이를 옵션으로 파싱할 때 발생합니다. 이는 쉘을 필요로 하지 않기 때문에 커맨드 인젝션(command injection)과는 구별됩니다. 즉, 인자 배열을 사용하는 exec 호출을 사용할 때도 취약점이 존재합니다.
위험한 Git 옵션
공격자가 명령에 주입할 수 있다면 다음과 같은 여러 Git 옵션이 공격 프리미티브(attack primitives)로 사용될 수 있습니다:
--upload-pack=<cmd>:git clone에서 서버 측 바이너리를 지정하는 데 사용됨.-c core.sshCommand=<cmd>: 모든 Git 호출에 대해 SSH 연결 방법을 재정의함.
역사적 사례
이 취약점 클래스는 여러 버전 관리 시스템(VCS) 및 도구에 영향을 미쳤습니다:
- CVE-2019-13139 (Docker Build): git-context URL 조각(
#ref:dir)이git fetch로 전달되어--upload-pack주입이 가능해짐. - 2017년 8월 공개 사례: CVE-2017-1000117 (Git), CVE-2017-1000116 (Mercurial), CVE-2017-9800 (Subversion), CVE-2017-12836 (CVS) 모두 호스트 이름을 인자로
ssh에 전달하는 것과 관련이 있었습니다.-oProxyCommand=로 시작하는 호스트 이름은 임의의 명령 실행을 허용했습니다.
패키지 매니저에 미치는 영향
대부분의 패키지 매니저(예: Bundler, npm, pip, Cargo, Go)는 매니페스트(예: package.json 또는 go.mod)의 URL이나 ref를 Git 서브프로세스로 정기적으로 전달합니다. 19개의 패키지 매니저를 조사한 결과, 17개가 기본적으로 git 바이너리를 포크(fork)하여 사용하고 있었습니다.
완화 전략
패키지 매니저는 인자 주입을 방지하기 위해 다양한 전략을 사용합니다:
--사용: 많은 도구(예: Bundler)가 URL 앞에--를 추가합니다. 이는 POSIX 관례를 따르지만, Git 리비전에는 불충분합니다.- 입력 검증: 일부 도구는 대시로 시작하는 모든 브랜치 이름이나 URL을 거부합니다.
--end-of-options사용: 2026년 7월 기준으로, Go의cmd/go만이 전반적으로--end-of-options를 사용합니다 (CVE-2025-68119에 대한 수정으로 구현됨).- 라이브러리 사용: Cargo(libgit2를 통해) 및 Poetry(dulwich를 통해)와 같은 도구는 Git 라이브러리를 사용하여 argv 경계를 완전히 피하지만, 이는 라이브러리 구현에 대한 업스트림 보안 패치를 수동으로 추적해야 함을 의미합니다.
호환성 트레이드오프
많은 도구가 --end-of-options를 피하는 주요 이유는 최소 지원 Git 버전 때문입니다. 이 플래그에 의존하려면 대부분의 명령에 Git 2.24.0 이상, rev-parse에는 2.30.0 이상, checkout 및 reset에는 2.43.1 이상이 필요합니다. 이는 Ubuntu 18.04 (Git 2.17.0)와 같이 오래된 배포판 패키지 Git 버전을 사용하는 사용자를 제외하게 됩니다.
보안 권장 사항 요약
Git을 스크립트나 애플리케이션에서 래핑하여 사용하는 개발자를 위한 지침:
- 대시로 시작하는 신뢰할 수 없는 입력을 인자로 직접 전달하는 것을 피하십시오.
- 환경이 Git $\ge$ 2.30.0(또는 checkout/reset의 경우 2.43.1)임을 보장하는 경우
--end-of-options를 사용하십시오. - 마커 결합:
git <cmd> --end-of-options <rev> -- <path>를 사용하여 리비전과 pathspec 모두를 옵션 파서로부터 완전히 격리하십시오.