flawopen.com/目录/226
●
CWE-367 / CWE-829 · 高危
安全研究 · FlawOpen
Marimo GHSL-2026-226:测试发布工作流中的 TOCTOU
同一秒内的时间戳竞争可使 Pull Request 更新绕过维护者的测试发布批准,并进入 TestPyPI 和 npm 发布作业。易受攻击的工作流已删除;未分配 CVE。
💡 通俗易懂的原理解析 (ELI5)
实验室负责人批准了一个样本,但实验室只核对时钟上的秒数。如果有人在同一秒更换样本,替换品也会被误认为已批准。Marimo 工作流对 Pull Request 提交犯了同样的错误,随后把构建出的包交给发布作业。
核心概念与专有名词
TOCTOU- Time-of-check to time-of-use:资源在安全检查后、实际使用前发生变化。
OIDC 发布- CI 作业将 GitHub Actions 身份换成包注册表的短期凭据,而不保存长期密码。
工作流工件- 在 GitHub Actions 作业间传递的文件集合。下游作业应将不可信工作流生成的工件视为不可信输入。
GHSL-2026-226- GitHub Security Lab 对此 Marimo 工作流缺陷的公告编号。该公告未分配 CVE。
CWE-367 / CWE-829- 公告指出了检查与使用之间的时间竞争,以及执行不可信控制域中的功能。
框架映射
攻击执行流程分解
步骤 1
维护者批准测试发布
维护者在 Pull Request 中发表评论,启动 Marimo 测试发布工作流。批准本应仅适用于维护者审查过的 Pull Request 状态。
步骤 2
Pull Request head 在同一秒发生变化
工作流将仓库的 pushed_at 与评论的 created_at 转换为整秒后进行比较。同一秒的 push 会得到相等值。
步骤 3
有漏洞的检查接受相等值
已删除的工作流只拒绝严格晚于评论时间的 pushed_at。相等值通过检查,更新后的提交被记录。
步骤 4
不可信代码构建软件包工件
作业检出该提交并运行由仓库控制的 actions 和构建脚本。生成物被上传为工件,随后交给下游发布作业,且没有从可信提交重新构建。
步骤 5
发布权限触及不可信输出
TestPyPI 和 npm 作业使用 OIDC 发布权限。公告说明工件可能进入发布流程;它没有报告实际软件包遭到入侵。
源代码对比:漏洞与安全实现
GHSL-2026-226 说明易受攻击的工作流已在 PR #10765 中删除;这并不表示项目应用了此处展示的比较符修改。旧检查使用 -gt,因此整秒相等值会通过。此处的 -ge 示例会拒绝相等值,但更好的做法是将批准绑定到准确的已审查提交。将不可信构建与 OIDC 作业分开,从获批 SHA 重新构建,并在发布前验证来源。本文不包含利用载荷。
存在漏洞的实现
# 已删除的 marimo-bot 工作流:整秒比较
if [[ $(date -d "$pushed_at" +%s) -gt $(date -d "$COMMENT_AT" +%s) ]]; then
exit 1
fi
# 记录的 head_sha 随后被检出,工件被交给
# 独立的发布作业。
加固后的安全修复
# 仅为说明性控制;GHSL 指出易受攻击的工作流
# 已删除,而不是在原处修补。
if [[ $(date -d "$pushed_at" +%s) -ge $(date -d "$COMMENT_AT" +%s) ]]; then
exit 1
fi
# 更强的设计:授权经过审查的不可变 SHA,在可信作业
# 中从该 SHA 重建,再发布经过验证的输出。
工程与系统安全加固清单
- ✓移除或替换已删除 marimo bot 工作流的受影响副本,并确认当前仓库不再启用有漏洞的发布路径。
- ✓将维护者批准绑定到确切且不可变的 Pull Request SHA;若 checkout 前 head 改变则失败关闭。
- ✓不要将整秒时间戳作为唯一证据,来证明可变分支就是获批版本。
- ✓在没有发布凭据的作业中构建不可信 Pull Request;仅在独立可信发布作业中授予 id-token: write。
- ✓在可信发布流程中从获批提交重新构建软件包,不要直接提升由不可信代码生成的工件。
- ✓发布到 TestPyPI 或 npm 前验证工件来源、源代码版本、软件包内容和发布目标。
- ✓将 GHSL 公告视为存在漏洞路径的证据,不要据此声称软件包已被入侵或实际遭到利用。