OpenProject 16.6.6 安全更新深度解析:仓库 Diff 参数注入漏洞(CVE-2026-24685)与修复原理
【免费下载链接】openprojectOpenProject is the leading open source project management software for product, project and portfolio management. A powerful Jira alternative with agile planning, issue tracking, roadmaps, Gantt charts, time tracking, collaboration features, and more. Available on premises or in the cloud. ⭐ Star us on GitHub项目地址: https://gitcode.com/GitHub_Trending/op/openproject
OpenProject 16.6.6(2026-01-27 发布)是一次以安全修复为核心的补丁版本,重点修复了仓库 Diff 下载端点/projects/:project_id/repository/diff.diff中存在的**参数注入(Argument Injection)**漏洞 CVE-2026-24685。该漏洞允许具备:browse_repository权限的用户通过构造rev参数实现任意文件写入,并在具备仓库写权限时进一步升级为远程代码执行(RCE)。本文基于该版本发布说明,结合当前仓库源码(lib/open_project/scm/adapters/git.rb、app/controllers/repositories_controller.rb、config/routes.rb等)逐层拆解漏洞成因、攻击链、修复机制与版本升级要点,并介绍同版本附带的一项 git diff 修订号解析 bugfix。
版本信息与升级背景
OpenProject 16.6.6 的发布说明位于 docs/release-notes/16/16-6-6/README.md,官方社区版本页为community.openproject.org/versions/2261。该版本包含:
- 1 个安全漏洞修复:CVE-2026-24685(参数注入 → 任意文件写入 → 潜在 RCE);
- 1 个常规 Bugfix:修复 git diff 输出中的修订号(revision)解析问题(工作包 #71019);
- 发布说明明确强烈建议所有部署实例立即升级到最新版本。
对于安全敏感的部署方,这是一次"必修级"升级:漏洞影响面覆盖所有启用了仓库模块、且存在具备:browse_repository权限用户的实例,攻击无需高权限,利用成本低。
CVE-2026-24685:仓库 Diff 端点的参数注入漏洞
漏洞端点与触发条件
漏洞位于 OpenProject 的仓库 diff 下载端点:
/projects/:project_id/repository/diff.diff当渲染单个修订(single revision)时,OpenProject 底层通过git show命令生成 diff 内容。该端点在当前仓库的路由定义中位于 config/routes.rb:
get "(/revisions/:rev)/diff.:format", action: :diff get "(/revisions/:rev)/diff(/*repo_path)", action: :diff, format: "html", constraints: { rev: /[\w.-]+/, repo_path: /.*/ }值得注意的是,第一行diff.:format形态(即diff.diff、diff.html等带格式后缀的请求)没有对rev参数施加任何字符约束,而第二行 HTML 形态才带有rev: /[\w.-]+/的约束。这意味着通过diff.diff路径,rev参数可以携带任意字符——这是漏洞可利用性的路由基础。对应的路由测试同样覆盖了该端点形态,见 spec/routing/repositories_routing_spec.rb。
根因:用户输入被直接当作 git 命令行选项
当请求format为diff时,控制器进入 diff 下载分支(app/controllers/repositories_controller.rb):
def diff if params[:format] == "diff" @diff = @repository.diff(@path, @rev, @rev_to) unless @diff show_error_not_found return end filename = "changeset_r#{@rev}" filename += "_r#{@rev_to}" if @rev_to send_data @diff.join, filename: "#{filename}.diff", type: "text/x-patch", disposition: "attachment" # ...@rev来自 URL 参数,最终被透传给 Git 适配器的diff方法(lib/open_project/scm/adapters/git.rb):
def diff(path, identifier_from, identifier_to = nil) args = build_diff_range(identifier_from, identifier_to) args << "--" << scm_encode(@path_encoding, "UTF-8", path) unless path.empty? capture_git(args).lines rescue Exceptions::CommandFailed nil end单修订场景下,build_diff_range生成的核心命令为(lib/open_project/scm/adapters/git.rb):
["show", "--no-abbrev-commit", "--no-color", "--end-of-options", from_sha]from_sha直接由用户提供的rev派生。关键问题在于:git 会把它当作命令行选项来解释,而非修订号。由于 OpenProject 通过数组参数而非 shell 字符串拼接执行命令(见 lib/open_project/scm/adapters/local_client.rb 的Open3.capture3(client_command, *args, opts)),攻击者虽然无法做 shell 注入,却可以注入 git 自身的选项——这就是"参数注入"(argument injection)的含义。
发布说明给出的攻击示例为:
rev=--output=/tmp/poc.txt当git show收到--output=/tmp/poc.txt时,git 会把 diff 输出重定向到攻击者指定的路径,从而实现在 OpenProject 进程用户具备写权限的任意路径上创建/覆盖文件。
攻击链与影响范围
从发布说明与源码可以梳理出完整攻击链:
- 前置条件:攻击者在目标项目上具备
:browse_repository权限(仓库浏览权限,通常是项目成员的基础权限之一); - 构造请求:向
diff.diff端点发送精心构造的rev值,例如rev=--output=/tmp/poc.txt(同理可注入--output、-o等 git show 支持的输出重定向选项); - 参数注入:OpenProject 执行 SCM 命令时,git 将攻击者控制的
rev当作选项解析,把git show的输出(commit 元数据与补丁内容)写入攻击者选定的路径; - 落地影响:
- 任意写入文件的内容为
git show的输出(提交元数据 + patch),本身不可控,但覆盖应用文件或配置文件依然会造成数据丢失与拒绝服务(DoS),直接破坏系统的完整性与可用性; - 当攻击者对仓库具备写权限时,可以构造特定 commit,使
git show的输出内容满足攻击者意图(例如覆盖可被加载/执行的脚本或配置文件),从而升级为以 OpenProject 应用进程权限执行的远程代码执行(RCE)。
- 任意写入文件的内容为
漏洞披露信息
- 漏洞由安全研究员sam91281通过YesWeHack.com 的 OpenProject Bug Bounty 计划负责任地披露,该计划由欧盟委员会(European Commission)赞助;
- 官方安全公告编号为GitHub Advisory GHSA-74p5-9pr3-r6pw。
源码视角:修复机制如何封堵参数注入
虽然发布说明本身没有给出补丁 diff,但从当前仓库源码可以清晰看到针对此类注入的纵深防御措施,这些机制共同构成了对 CVE-2026-24685 的修复方案。
第一道防线:resolve_commit强制解析为合法 SHA
Git 适配器在构造任何基于用户输入的 git 命令前,先通过resolve_commit将用户提供的任意 commit-ish(分支名、标签、HEAD~1等)解析为经过验证的 commit SHA(lib/open_project/scm/adapters/git.rb):
# Resolve any user-provided commit-ish (branch, tag, HEAD~1, etc.) to a commit SHA, # so we can be sure to work with a valid commit identifier. def resolve_commit(rev) capture_git(["rev-parse", "--verify", "--quiet", "--end-of-options", "#{rev}^{commit}"]).strip end关键点有三:
rev-parse只做解析:git rev-parse不接受--output这类输出重定向选项,攻击者注入的--output=...在这里会被当作非法输入,--verify --quiet组合下解析失败即静默返回非零退出码,capture_git会抛出CommandFailed;^{commit}后缀:强制要求解析结果必须是一个 commit 对象,进一步收紧了输入语义;--end-of-options:确保#{rev}^{commit}即使以-开头也不会被rev-parse解释为选项。
随后build_diff_range使用解析得到的 SHA 构造命令:
def build_diff_range(identifier_from, identifier_to = nil) from_sha = resolve_commit(identifier_from) if identifier_to to_sha = resolve_commit(identifier_to) ["diff", "--no-abbrev-commit", "--no-color", "--end-of-options", to_sha, from_sha] else ["show", "--no-abbrev-commit", "--no-color", "--end-of-options", from_sha] end end第二道防线:--end-of-options全局收紧
--end-of-options是 git 提供的哨兵标记,它告诉 git:该标记之后的所有参数一律视为位置参数(修订号/路径),不得再当作选项解析。即使未来某处遗漏了resolve_commit校验,只要--end-of-options位于用户输入之前,注入的--output=...也会被 git 当作修订号字符串处理,从而失效。
当前仓库的 Git 适配器在所有涉及用户输入的 git 子命令中均使用了该标记,包括:
cat(文件内容读取):%w|show --no-color --end-of-options|(git.rb);diff/单修订展示:show --no-abbrev-commit --no-color --end-of-options(git.rb);- 双修订 diff:
diff --no-abbrev-commit --no-color --end-of-options(git.rb); rev-parse校验:rev-parse --verify --quiet --end-of-options(git.rb);- 分支创建/跟踪、
update-ref、ls-tree、log等其余 SCM 操作同样遵循该模式(git.rb 等)。
第三道防线:无 shell 拼接的进程执行
底层执行层(lib/open_project/scm/adapters/local_client.rb)使用Open3.capture3(client_command, *args, opts),参数以数组形式原样传给进程,不经过 shell 解释,因此不存在经典的;、|、$()等 shell 注入面。该 CVE 的本质是 git 客户端自身的选项解析面,而非 shell 注入,这也解释了为何需要--end-of-options与resolve_commit组合拳来根治。
修复机制的防御纵深小结
| 防线 | 位置 | 作用 |
|---|---|---|
resolve_commit+rev-parse --verify | lib/open_project/scm/adapters/git.rb#L602-L606 | 把用户输入收敛为合法 commit SHA,注入的选项无法通过校验 |
--end-of-options | git.rb 等多处 | 强制后续参数为位置参数,阻断选项注入语义 |
数组参数 +Open3.capture3 | local_client.rb | 消除 shell 注入面,命令参数不被重新解释 |
受影响权限与安全缓解建议
- 受影响权限:
:browse_repository(仓库浏览权限)。在 OpenProject 中,该权限通常是项目成员默认可获得的基础仓库权限,这意味着任何能访问项目仓库页面的普通成员都可能成为攻击者,并非只有管理员或仓库维护者; - 可利用性:任意文件写入的内容是
git show输出,直接内容可控性有限,但覆盖关键文件即可造成数据丢失与拒绝服务;叠加仓库写权限后可构造特定 commit 将可控内容写入目标文件,升级为 RCE,且 RCE 的执行权限为 OpenProject 应用进程用户; - 缓解措施:
- 立即升级到 16.6.6 或更新版本,这是发布说明明确要求的第一优先级动作;
- 升级前若无法立即停机,可临时收紧项目仓库权限,移除不可信成员的
:browse_repository权限; - 加固运行 OpenProject 进程的操作系统用户权限,遵循最小权限原则,限制应用进程用户对配置文件、应用目录的写权限,降低任意文件写与 RCE 的落地危害;
- 关注官方安全公告(GitHub Advisory GHSA-74p5-9pr3-r6pw)以获取完整的受影响版本范围说明。
随版本发布的 Bugfix:git diff 输出修订号解析
除安全修复外,16.6.6 还修复了一个功能性问题:"Fix revision parsing in git diff output"(工作包 #71019),即修复了 git diff 输出中修订号(revision)的解析错误。
从当前仓库代码看,diff 输出解析涉及两条链路:
- diff 表格渲染链路:前端/视图层对
git show/git diff输出的解析主要发生在 lib/redmine/diff_table.rb。add_line通过正则^@@ (\+|-)(\d+)(,\d+)? (\+|-)(\d+)(,\d+)? @@解析 hunk 头,将@@ -a,b +c,d @@中的左右起始行号提取为@line_num_l/@line_num_r,据此为每一行 diff 内容计算正确的行号。若git show输出的修订号/行号信息格式存在边界情况(如,count部分缺失、行号格式变化),解析就会出现偏差,导致 diff 视图中的行号与实际内容错位; - 修订对象构建链路:Git 适配器在
git log --raw输出解析中构建Git::Revision对象(lib/open_project/scm/adapters/git.rb),identifier与scmid取自解析出的 commit 哈希,作者、日期、描述分别从author、date、描述区段提取,路径变更列表则由--raw输出逐条解析。修订号解析修复即属于这一链路的健壮性改进。
该 bugfix 与 CVE 修复并不直接相关,但对依赖 diff 视图行号定位的日常使用体验有实际影响。如果你在 16.6.6 之前的版本上遇到过 diff 行号错位或修订号显示异常的问题,升级后即可获得修复。
升级到 16.6.6
发布说明对本次升级的要求非常明确:安全修复类发布,强烈建议所有实例立即升级。OpenProject 提供多种部署形态,升级路径取决于你的部署方式:
- Docker / Docker Compose 部署:更新镜像至
16.6.6对应 tag 并重建容器,仓库根目录提供了 docker-compose.yml 及 docker-compose.override.example.yml 可供参考配置; - 包管理 / 源码部署:按官方安装运维文档执行常规升级流程,相关文档位于 docs/installation-and-operations;
- 云端托管:由托管方自动完成,无需手动干预。
升级完成后,建议验证仓库模块的 diff 下载功能(/projects/:project_id/repository/diff.diff)正常工作,并确认 diff 视图的行号显示正确,以覆盖本次发布的两项变更。
总结
OpenProject 16.6.6 是一个典型的安全补丁版本,其核心价值在于修复 CVE-2026-24685:通过/projects/:project_id/repository/diff.diff端点向git show注入选项,实现任意文件写入乃至以应用进程权限执行的 RCE。从当前源码可以清晰看到修复采用的三层纵深防御——resolve_commit把用户输入收敛为合法 commit SHA、--end-of-options阻断 git 选项注入语义、数组参数执行消除 shell 注入面,这一组合对同类 SCM 参数注入问题具有普遍的参考价值。配合 #71019 的 diff 修订号解析修复,建议所有 16.6.6 之前版本的用户尽快完成升级。
【免费下载链接】openprojectOpenProject is the leading open source project management software for product, project and portfolio management. A powerful Jira alternative with agile planning, issue tracking, roadmaps, Gantt charts, time tracking, collaboration features, and more. Available on premises or in the cloud. ⭐ Star us on GitHub项目地址: https://gitcode.com/GitHub_Trending/op/openproject
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考