CVE-2026-41145:Unsigned-Trailer 查询认证绕过

query-string SigV4 credential 进入 unsigned-trailer 流后没有被验签,最终在共享 reader 边界统一关闭。

状态: 已发布
首个包含版本: RELEASE.2026-04-17T00-00-00Z
GitHub Advisory: GHSA-hv4r-mvr4-25vw

query-string SigV4 credential 可以进入 STREAMING-UNSIGNED-PAYLOAD-TRAILER 数据流,但旧代码只在存在 Authorization header 时验证签名。结果是:请求只要提供有效 access key 标识,即使没有正确 signature,也可能完成写入。

最终修复把 presigned rejection 与 SigV4 verification 放进 newUnsignedV4ChunkedReader(),让所有消费该数据流的 caller 共用同一认证边界。

编号说明

修复时正式编号尚未分配,commit subject 临时写为 fake CVE-2026-40027。正式编号后来确定为 CVE-2026-41145。历史 commit 保留原样,公开材料统一使用正式编号。

根因:把认证绑定到传输形式

受影响入口包括 PutObjectPutObjectPart。请求使用 STREAMING-UNSIGNED-PAYLOAD-TRAILER,credential 与 signature 放在 query string,而不是 Authorization header。

旧 handler 用“header 是否存在”决定要不要验签。body reader 却仍然正常读取数据,于是 query auth 被静默降级成近似匿名写入。攻击者只需要知道一个有效 access key 标识,并不需要构造正确 signature。

问题不是 query 参数没有解析,而是认证决定依赖凭据的传输形态,而不是真正消费数据流的信任边界。

为什么不逐 handler 打补丁

方案风险结论
PutObjectPutObjectPart 各补一段 header/query 判断当前入口能闭合,但新 caller 很容易再次遗漏否决
发明 presigned unsigned-trailer 兼容协议协议与测试面显著扩大,又没有既有支持契约否决
newUnsignedV4ChunkedReader() 统一拒绝并验签所有消费者强制经过同一边界接受

匿名 unsigned-trailer 并没有被一刀切禁用。如果 bucket policy 明确允许 anonymous write,它仍然可以按匿名授权路径工作。真正被禁止的是“带 query credential,却没有验证 credential”的混合状态。

实现与验证

修复在 cmd/streaming-v4-unsigned.go 的 reader 入口完成 presigned rejection 与 SigV4 verification,同时移除 PutObject / multipart handler 中依赖 header presence 的 gate。

新增测试覆盖 forged query PUT、multipart、mixed auth 与 anonymous policy。历史记录还包括 vulnerable parent 上写入成功、patched tree 上失败,以及 header-authenticated 与合法 anonymous flow 继续工作的 live server before/after smoke。

公开修复提交为 fa7c579。本次博客整理没有重新执行 live exploit。

兼容性与残余风险

  • presigned/query unsigned-trailer 现在明确不受支持,这是有意的 breaking change;
  • 修复下沉到 reader,显著降低 sibling handler 再次漏检的风险;
  • 其他 streaming auth mode 仍然需要独立审计,不能因为这一条 reader 修复就宣称所有 SigV4 streaming 组合安全。

这次修复的形状比 payload 本身更重要:当多个 handler 共享同一种认证数据流时,认证应该属于 reader,而不是每个 caller 的可选判断。

最后修改 August 2, 2026: init commit (8338d5b)