- Git의
--는 일반적인 옵션 종료자가 아니라 리비전과 경로 명세를 구분하므로, 신뢰할 수 없는 리비전을 안전하게 전달하려면 Git 2.24.0부터 지원된--end-of-options가 필요함 git log --end-of-options "$rev" -- "$path"에서 앞 표식은 옵션과 리비전, 뒤의--는 리비전과 경로를 구분하며 서로 대체할 수 없음- 셸 없이
argv배열을 직접 실행해도 대시로 시작하는 입력이--upload-pack,core.sshCommand,ProxyCommand같은 옵션으로 해석되면 CWE-88 인자 주입이 발생할 수 있음 - 조사한 패키지 관리자 19개 중 17개가 기본 또는 유일한 방식으로 Git 바이너리를 실행하지만,
--end-of-options를 사용하는 도구는 Go의cmd/go하나뿐이었음 - 근본 대응에는 서브명령에 따라 Git 최소 버전을 2.24.0·2.30.0·2.43.1로 높여야 하는 호환성 비용이 있으며, Git 라이브러리는 인자 주입 경계를 없애는 대신 upstream의 체크아웃 안전성 수정을 직접 추적해야 함
--와 --end-of-options의 차이
- 일반적인 Unix 도구에서
--는 옵션 해석의 끝을 표시하므로rm -- -f는-f를 강제 삭제 옵션이 아닌 파일명으로 처리함 - Git은 일찍부터
--를 리비전과 경로 명세(pathspec)의 구분자로 사용함git log foo는foo라는 브랜치와 파일 중 무엇을 뜻하는지 모호함git log main -- README.md는main의 커밋 중README.md를 건드린 커밋을 뜻함
- 이 설계 때문에 리비전 위치에는 옵션 종료 표식이 없었고,
git log "$rev"에서$rev가 대시로 시작하면 Git이 옵션으로 해석함 --end-of-options를 도입한 커밋은 기존--가 이미 리비전과 경로 명세를 나누고 있어 옵션과 리비전을 구분할 별도 표식이 필요하다고 밝힘--end-of-options는 gitcli(7)에 문서화됐으며, Git 2.24.0이 출시된 2019년 11월 추가됨
명령별 올바른 사용법
git clone -- "$url"은clone이 POSIX 관례를 따르므로 URL 앞의--가 옵션 해석을 종료함git checkout "$ref" --의 뒤쪽--는$ref를 파일명이 아닌 리비전으로 표시하지만,$ref가 먼저 옵션으로 해석되는 것은 막지 못함- 신뢰할 수 없는 리비전과 경로를 함께 안전하게 전달하려면
git log --end-of-options "$rev" -- "$path"처럼 두 표식을 모두 사용해야 함--end-of-options는 옵션과 리비전을 구분함--는 리비전과 경로를 나눔
- 두 표식을 서로 바꿔 쓸 수 있다고 취급하면 대시로 시작하는 입력을 차단하지 못함
서브명령마다 다른 지원 시점
--end-of-options지원은 모든 Git 명령에 한꺼번에 적용되지 않고 서브명령별로 추가됨git rev-parse는 자체 인자 파서를 사용해 최초 도입보다 1년 늦은 Git 2.30.0에서 지원하기 시작함git checkout과git reset은 자체적으로--를 해석하며, 초기 구현이--end-of-options를 인자 목록에 남겨 두는 바람에 이를 거부함- 이 문제는 Git 2.43.1이 출시된 2024년 2월에 해결됨
셸 없이도 발생하는 인자 주입
- Git, Mercurial, SSH에는 호출자가 지정한 명령을 실행하는 옵션이 공식 기능으로 존재함
git clone --upload-pack=<cmd>는 서버 측 바이너리를 지정함- 모든 Git 호출의
-c core.sshCommand=<cmd>는 연결 명령을 바꿈 - Mercurial의
--config=alias.<subcmd>=!<shell>은 실행할 서브명령을 임의의 셸 스크립트로 재정의함 - SSH의
-oProxyCommand=<cmd>는 프록시 명령을 지정함
- 래퍼 프로그램이 신뢰할 수 없는 문자열을 인자 목록에 넣으면 이러한 기능이 공격 수단으로 바뀔 수 있음
- 이 실패 유형은 CWE-88 인자 주입(argument injection) 에 해당하며 셸 명령 주입과 다름
- 프로그램이
system()대신argv배열과exec를 사용해도 발생함 - 배열은 손상 없이 Git에 전달되지만, Git이 대시로 시작하는 인자를 옵션으로 해석함
- 프로그램이
docker build의 CVE-2019-13139는 Go의os/exec와argv배열을 사용하고 셸을 거치지 않은 사례임- Git 컨텍스트 URL의
#ref:dir조각이git fetch origin <ref>에 전달되면서<ref>가--upload-pack=<cmd>로 해석됨
- Git 컨텍스트 URL의
여러 버전 관리 시스템에서 반복된 취약점
- 2017년 8월 같은 날 네 개 버전 관리 시스템에서 동일한 패턴이 공개됨
- Git의 CVE-2017-1000117
- Mercurial의 CVE-2017-1000116
- Subversion의 CVE-2017-9800
- CVS의 CVE-2017-12836
- 네 시스템 모두 URL의 호스트명을 SSH 인자로 전달했고,
-oProxyCommand=로 시작하는 호스트명이 SSH 옵션으로 처리됨 - Phabricator 사후 분석에 따르면 당시 활발히 유지되던 세 도구 중 Subversion만 호스트명 앞에
--를 추가함- Git과 Mercurial은 모든 SSH 구현이
--를 지원하지 않는다는 이유도 있어 호스트명 형식을 검증함
- Git과 Mercurial은 모든 SSH 구현이
--가 빠진 코드도 인자가 대시로 시작하기 전까지는 정상적으로 보이고 작동하므로, 이 메커니즘은 기본적으로 안전하지 않음
패키지 관리자가 노출되는 경로
- 패키지 관리자는 매니페스트, 잠금 파일, 전이 의존성 메타데이터에서 Git URL이나 ref를 받아 하위 프로세스에 전달함
- Gemfile의
gem 'foo', git: '...' package.json의github:user/repo#refpyproject.toml,Cargo.toml,mix.exs,Package.swift,pubspec.yaml,conanfile.py,go.mod의 동등한 설정
- Gemfile의
- 2026년 7월 HEAD 기준 조사한 19개 패키지 관리자 중 17개가 Git 바이너리를 기본 또는 유일한 경로로 실행함
- 나머지 두 개는 라이브러리를 기본으로 사용함
- Cargo는 libgit2를 사용하며
net.git-fetch-with-cli를 활성화하면 Git 프로세스를 실행함 - Poetry는 1.2.0부터 dulwich로 전환했고
system-git-client설정으로 시스템 Git을 사용할 수 있음
- Cargo는 libgit2를 사용하며
- Nix는 로컬 저장소를 읽을 때 libgit2를 쓰지만, libgit2가 git-credential 도우미를 지원하지 않아 fetch에는 Git 프로세스를 실행함
- 조사 대상은 Bundler, Cargo, CocoaPods, Composer, Conan, Go, Helm, Homebrew, Mix, Nix, npm, pip, pnpm, Poetry, Pub, SwiftPM, uv, vcpkg, Yarn임
패키지 관리자에서 확인된 CVE
- 이 유형으로 공개된 패키지 관리자 취약점에는 다음 사례가 있음
- Bundler의 CVE-2021-43809
- Composer의 CVE-2021-29472, CVE-2022-24828
- Poetry의 CVE-2022-36069
- pip의 CVE-2023-5752
- CocoaPods의 CVE-2022-21223, CVE-2022-24440
- Go의 CVE-2025-68119
- 여러 2022년 취약점을 발견한 Snyk는 Git과 Mercurial의 인자 주입 연구를 공개함
- Sonar는 바이너리별 위험한 옵션 목록을 관리함
실제 방어 현황과 Go의 수정
- Git 프로세스를 실행하는 17개 패키지 관리자 중
--end-of-options를 사용한 도구는 Go의cmd/go하나였음 - Go는 2019년 6월 일반적인 방어 강화 차원에서 저장소 URL 앞에
--를 추가함 - 2026년 1월
--만으로는 충분하지 않다는 사실이 드러나 CVE-2025-68119 수정으로--end-of-options를 전반적으로 추가함 - 같은 수정에는
HGPLAIN=+strictflags도 포함됨- 이 설정은 Mercurial 4.4.2가 출시된 2017년부터 Mercurial의 초기 옵션 해석을 제한함
- Go 수정 커밋은 같은 문제가 재도입되기 어렵도록 더 구조적인 변경이 필요할 수 있지만, 우선 현재 문제를 해결함
대부분의 방어는 취약점 공개 뒤 추가
- 나머지 패키지 관리자들은 인자 목록을 보호하더라도 주로
--또는 입력의 선행 대시 거부를 사용함 - Bundler의
git cloneURL 앞--는 CVE-2021-43809 패치로 추가됨 - cocoapods-downloader의 선행 대시 거부는 CVE-2022-21223 공개 시기와 맞물린 2022년 3월 열흘 동안 세 개 커밋으로 적용됨
- Poetry의 방어는 2021년 9월 추가됐고 1년 뒤 CVE가 배정됐으며, 6개월 뒤 dulwich로 전환함
- vcpkg는 예외적으로 Git 레지스트리 지원이 작성된 첫날부터
--를 사용함
최소 Git 버전이 만드는 호환성 제약
- Composer의 CVE-2022-24828 권고문은
--end-of-options를 올바른 수정으로 지목하지만, 더 오래된 Git도 지원해야 하므로 대시로 시작하는 브랜치명을 거부하는 방식을 택함 - vcpkg의 Git 통합은 최소 버전을 Git 2.7.4로 명시하며, Homebrew의 Linux용
HOMEBREW_MINIMUM_GIT_VERSION은 2018년에 설정된 2.7.0임 - Git 2.14.3을 제공한 Amazon Linux 2가 2026년 6월 수명을 종료하면서 이러한 하한선이 추적하던 배포판들이 이제야 지원 범위에서 빠지고 있음
- Ubuntu의 장기 지원 상태도 일괄적인 전환을 어렵게 함
- Ubuntu 18.04는 Git 2.17.0을 제공하며 2028년까지 연장 지원됨
- Ubuntu 20.04는 Git 2.25.1을 제공하며 2030년까지 연장 지원됨
- Git 2.25.1은
git fetch의--end-of-options를 받아들이지만git rev-parse에서는 거부함
--end-of-options에 의존하려면 대부분의 서브명령은 Git 2.24.0,rev-parse는 2.30.0,checkout과reset은 2.43.1을 최소 버전으로 요구해야 함- 최소 버전을 높이면 배포판에 포함된 오래된 Git을 사용하는 이용자를 지원할 수 없게 됨
프로세스 실행 대신 Git 라이브러리 사용
- libgit2, gitoxide, go-git, JGit, dulwich는 프로세스 내부에서 clone과 fetch에 필요한 Git 전송 프로토콜을 구현함
- 별도의
argv경계가 없어 인자 목록에 주입할 대상 자체가 없음 - Jujutsu는 Git 연동에 gitoxide를 사용하며 인자 주입 유형의 공개 CVE가 없음
- 현재까지의 권고 두 건은 경로 순회와 라이브러리에서 물려받은 SHA-1 충돌 검사 누락임
- go-git의 CVE-2025-21613은
file://전송에 한정됨- 이 경로는 go-git에서 Git 바이너리를 실행하는 유일한 코드 경로임
- 자체 Git 구현을 포함하면 upstream Git이 내놓는 모든 체크아웃 안전성 수정을 추적해야 하며, libgit2와 JGit 모두 이와 관련된 수정이 반복된 적이 있음
- 이 비용은 실제로 존재하지만, 각 호출 지점에서 영구적으로 인자 검사를 기억하는 대신 구체적인 upstream 패치 흐름을 적용하는 문제로 바뀜
Homebrew에 제안한 변경 범위
- Homebrew PR은 최소 Git 버전을 2.30.0으로 높이고 다음 위치에
--end-of-options를 추가함clone,remote set-url,ls-remote의 URL 앞rev-parse의 ref 앞
checkout과reset호출은 변경하지 않음- 두 명령까지 보호하려면 2024년 2월 출시된 Git 2.43.1이 필요함
- 이 버전은 현재 지원되는 여러 배포판이 제공하는 Git보다 최신임