Git --end-of-options:防止版本控制中的参数注入
Git --end-of-options:防止版本控制中的参数注入
--end-of-options 在 Git 中的作用
Git 在 2.24.0 版本(2019 年 11 月)引入了 --end-of-options 标志,提供了一个专门用于停止选项解析的标记。这是必要的,因为 Git 重用了标准的 POSIX -- 标记来区分修订版本(revisions)和路径规范(pathspecs),这留下了一个安全漏洞,即以连字符开头的修订版本可能会被误认为是命令行选项。
在标准的 Unix 工具中,-- 标志着选项的结束。然而,在 Git 中,像 git log main -- README.md 这样的命令使用 -- 来表示 main 是修订版本,而 README.md 是文件路径。因此,如果脚本执行 git log "$rev" 且变量 $rev 以连字符开头,Git 会尝试将该修订版本解析为标志(flag)而非引用(reference)。
为了安全地处理不受信任的修订版本和路径,正确的语法是:
git log --end-of-options "$rev" -- "$path"
在这种结构中,--end-of-options 保护修订版本,而 -- 保护路径规范。
特定版本的支持
对 --end-of-options 的支持是在各个子命令中逐步推出的,这导致了碎片化的兼容性局面:
- Git 2.24.0:最初引入该标志。
- Git 2.30.0:由于其自定义参数解析器,增加了对
git rev-parse的支持。 - Git 2.43.1 (2024 年 2 月):增加了对
git checkout和git reset的支持,这两个命令之前因为在内部处理--而拒绝该标志。
参数注入与 CWE-88
参数注入 (CWE-88) 发生在程序将不受信任的字符串传递到参数列表 (argv 数组) 中,且接收端二进制文件因为这些字符串以连字符开头而将其解析为选项时。这与命令注入不同,因为它不需要 shell;即使在使用带有参数数组的 exec 调用时,该漏洞依然存在。
危险的 Git 选项
如果攻击者可以将以下几个 Git 选项注入到命令中,它们可以被用作攻击原语:
--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 或引用传递给 Git 子进程。对 19 个包管理器的调查发现,其中 17 个默认调用 git 二进制文件。
缓解策略
包管理器采用不同的策略来防止参数注入:
- 使用
--:许多工具(例如 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。这将排除使用旧版发行版打包的 Git 版本的用户,例如 Ubuntu 18.04 (Git 2.17.0)。
安全建议摘要
对于在脚本或应用程序中封装 Git 的开发者:
- 避免直接传递不受信任的输入作为参数,如果它们以连字符开头。
- 使用
--end-of-options,如果您的环境保证 Git $\ge$ 2.30.0(对于 checkout/reset 则需要 2.43.1)。 - 组合标记:使用
git <cmd> --end-of-options <rev> -- <path>来将修订版本和路径规范从选项解析器中完全隔离。