Git --end-of-options:防止版本控制中的參數注入
Git --end-of-options:防止版本控制中的參數注入
在 Git 中 --end-of-options 的作用
Git 在 2.24.0 版(2019 年 11 月)引入了 --end-of-options 旗標,以提供一個專用的標記來停止選項解析。之所以需要此旗標,是因為 Git 重新利用了標準 POSIX -- 標記來分隔修訂與路徑規格,導致當修訂名稱以破折號開頭時,可能被誤判為命令列選項,形成安全漏洞。
在標準的 Unix 工具中,-- 代表選項的結束。然而在 Git 中,像 git log main -- README.md 這樣的指令會使用 -- 來表示 main 為修訂,README.md 為檔案路徑。因此,如果腳本執行 git log "$rev",且變數 $rev 以破折號開頭,Git 會嘗試將該修訂解析為旗標而非參考。
為了安全地處理不受信任的修訂與路徑,正確的語法是:
git log --end-of-options "$rev" -- "$path"
在此寫法中,--end-of-options 保護修訂,-- 則保護路徑規格。
版本特定支援
Support for --end-of-options was rolled out incrementally across subcommands, creating a fragmented compatibility landscape:
- Git 2.24.0:旗標的首次引入。
- Git 2.30.0:由於
git rev-parse使用自訂參數解析器,加入了對--end-of-options的支援。 - Git 2.43.1(2024 年 2 月):為
git checkout與git reset加入支援,這兩者先前因內部處理--而拒絕此旗標。
參數注入與 CWE-88
Argument injection (CWE-88) occurs when a program passes untrusted strings into an argument list (argv array) and the receiving binary parses those strings as options because they start with a dash. This is distinct from command injection because it does not require a shell; the vulnerability exists even when using exec calls with an array of arguments.
參數注入(CWE-88)發生於程式將不受信任的字串傳入參數清單(argv 陣列)時,接收的二進位檔因字串以破折號開頭而將其解析為選項。這與指令注入不同,因為它不需要 shell;即使使用帶有參數陣列的 exec 呼叫,也會出現此漏洞。
危險的 Git 選項
Several Git options can be used as attack primitives if an attacker can inject them into a command:
--upload-pack=<cmd>:git clone用於指定伺服器端的二進位檔。-c core.sshCommand=<cmd>:覆寫任何 Git 呼叫的 SSH 連線方式。
歷史先例
This vulnerability class has affected multiple version control systems (VCS) and tools:
- 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=開頭的主機名稱允許任意指令執行。
對套件管理器的影響
Most package managers (e.g., Bundler, npm, pip, Cargo, Go) routinely pass URLs or refs from manifests (like package.json or go.mod) to a Git subprocess. A survey of 19 package managers found that 17 fork the git binary by default.
大多數套件管理器(例如 Bundler、npm、pip、Cargo、Go)通常會將來自清單(如 package.json 或 go.mod)的 URL 或參考傳遞給 Git 子行程。對 19 個套件管理器的調查顯示,有 17 個預設會 fork git 二進位檔。
緩解策略
Package managers employ different strategies to prevent argument injection:
- 使用
--:許多工具(例如 Bundler)會在 URL 前加上--。雖然符合 POSIX 規範,但對 Git 修訂仍不足以防護。 - 輸入驗證:部分工具會拒絕任何以破折號開頭的分支名稱或 URL。
- 使用
--end-of-options:截至 2026 年 7 月,僅有 Go 的cmd/go在所有情況下使用--end-of-options(作為 CVE-2025-68119 的修補實作)。 - 使用函式庫:像 Cargo(透過 libgit2)與 Poetry(透過 dulwich)完全避免了 argv 邊界,改以 Git 函式庫操作,然而這需要它們自行追蹤函式庫的上游安全修補。
相容性取捨
The primary reason many tools avoid --end-of-options is the minimum supported Git version. Relying on this flag requires a minimum of Git 2.24.0 for most commands, 2.30.0 for rev-parse, and 2.43.1 for checkout and reset. This would exclude users on older distribution-packaged Git versions, such as Ubuntu 18.04 (Git 2.17.0).
許多工具避免使用 --end-of-options 的主要原因是最低支援的 Git 版本。依賴此旗標需要至少 Git 2.24.0(大多數指令)、2.30.0(rev-parse)以及 2.43.1(checkout 與 reset)。這會排除使用較舊發行版內建 Git 版本的使用者,例如 Ubuntu 18.04(Git 2.17.0)。
安全建議摘要
For developers wrapping Git in scripts or applications:
- 避免直接傳遞以破折號開頭的不受信任輸入 作為參數。
- 使用
--end-of-options,前提是環境保證 Git 版本 $\ge$ 2.30.0(或 checkout/reset 需要 2.43.1)。 - 結合標記:使用
git <cmd> --end-of-options <rev> -- <path>以徹底將修訂與路徑規格從選項解析器中隔離。
SUMMARY:
--end-of-options 旗標在 Git 中解決了一個關鍵的安全漏洞,該漏洞會因不受信任且以破折號開頭的輸入被解析為命令列選項,從而導致參數注入漏洞。
TITLE: Git --end-of-options:防止版本控制中的參數注入