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 checkoutgit 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.jsongo.mod)的 URL 或參考傳遞給 Git 子行程。對 19 個套件管理器的調查顯示,有 17 個預設會 fork git 二進位檔。

緩解策略

Package managers employ different strategies to prevent argument injection:

  1. 使用 --:許多工具(例如 Bundler)會在 URL 前加上 --。雖然符合 POSIX 規範,但對 Git 修訂仍不足以防護。
  2. 輸入驗證:部分工具會拒絕任何以破折號開頭的分支名稱或 URL。
  3. 使用 --end-of-options:截至 2026 年 7 月,僅有 Go 的 cmd/go 在所有情況下使用 --end-of-options(作為 CVE-2025-68119 的修補實作)。
  4. 使用函式庫:像 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(checkoutreset)。這會排除使用較舊發行版內建 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:防止版本控制中的參數注入

Sources