跳转到主要内容

CVE-2026-33322:OIDC JWT 算法混淆

旧版 OIDC 验证器混用了依赖方 client_secret 与 JWKS 公钥;我们在实现显式 HMAC 兼容分支后又主动将其删除,在 d24f449 中彻底恢复为纯 JWKS 非对称验签。

状态: 已发布
首个包含版本: RELEASE.2026-04-17T00-00-00Z
影响入口: AssumeRoleWithWebIdentity、AssumeRoleWithClientGrants
修复提交: d24f449
GitHub Issue: pgsty/silo#22

internal/config/identity/openid 的旧实现将 OIDC client_secret 放进了本地 JWT 验签密钥环(keyring),同时允许 HMAC 签名算法(HS256、HS384、HS512)。攻击者一旦获知 client_secret,即可自行使用 HMAC 签发伪造的 ID Token,并通过 STS(AssumeRoleWithWebIdentity 或 AssumeRoleWithClientGrants)换取临时 S3 凭据。

最终修复(d24f449)强制执行基于 JWKS 的非对称验签,宁可明确放弃 HS256/HS384/HS512 兼容性,也不保留一个会再次混淆密钥信任语义的配置开关。

为什么根因是密钥角色混淆而非漏掉验签

旧代码在签发 STS 凭据前确实执行了 JWT 签名验证。真正失效的边界在于:验证器接受了哪一种密钥,以及令牌头部的 alg 字段能否选择与该密钥原本职责完全不符的算法语义。

在 OIDC 协议中,client_secret 的职责是让依赖方(SILO)向身份提供商(IdP)的 Token Endpoint 证明客户端身份,它是依赖方与 IdP 之间的共享凭据,而不是 IdP 用来签署用户身份声明的密钥。旧代码把 client_secret 与 JWKS 公钥装进同一个本地 token.Parse 密钥环后,单次客户端密钥泄露就会直接升级为任意身份伪造:

  1. 攻击者通过配置泄露等途径获取 OIDC client_secret。
  2. 攻击者构造包含任意用户或角色声明的 JWT,并以 client_secret 作为 HMAC 密钥签发 HS256、HS384 或 HS512 令牌。
  3. 验证器在密钥环中找到 client_secret 并通过 HMAC 验签。
  4. 伪造令牌进入 AssumeRoleWithWebIdentity 或 AssumeRoleWithClientGrants 流程,成功换取临时 STS 凭据。

先写出兼容路径,再主动将其删除

在修复初期,我们曾实现过一版带有算法白名单与 allow_hmac 兼容开关的原型(fc47fa0)。表面上看,这种折中方案很稳妥:默认强制安全配置,同时允许确有 HMAC 需求的部署显式开启。

但在设计复核中,这个方案被推翻了。只要继续允许 client_secret 进入本地 JWT 验签密钥环,管理员一旦打开该开关,就会在不知情中把整个 STS 信任边界降级为“持有客户端密钥即可签发任意用户令牌”;同时,未来主验签路径与 JWKS 刷新重试路径的算法白名单只要发生任何漂移,漏洞就可能默认重现。

方案 收益 风险 结论
在密钥环中保留 client_secret,仅过滤部分算法 改动极小,保留 HMAC IdP 兼容 密钥环仍然混用 Token Endpoint 客户端认证密钥与本地 JWT 验签密钥 否决
增加 allow_hmac 显式配置项(fc47fa0) 将兼容性风险显式化 运维方极难评估开启后的信任边界扩张,且解析与重试分支的测试面翻倍 实现后删除
仅允许 JWKS 非对称公钥验签(d24f449) 密钥职责彻底隔离,首次验签与 JWKS 刷新重试共用同一解析策略 仍使用 HS256/384/512 的 IdP 必须先迁移 接受

主动删掉已经写完的兼容代码,换来了更清晰、更容易审计的信任边界。

最终不变量

提交 d24f449 在 internal/config/identity/openid 中确立了四条不变量:

  • 验签密钥仅从 IdP 的 JWKS 端点(jwks_uri)加载。
  • OIDC client_secret 绝不进入本地 JWT 验签密钥环。
  • 无条件拒绝 HS256、HS384 与 HS512。
  • 常规首次验签流程与 JWKS 缓存刷新重试流程强制共用同一套非对称算法白名单(RS256、RS384、RS512、ES256、ES384、ES512)。

同时,本次安全修复刻意保持了最小的算法支持面,未顺带扩展 PS256 或 EdDSA。

验证与发布

internal/config/identity/openid 的测试覆盖了 HS256 拒绝、RS256 放行以及密钥轮换时的 JWKS 刷新重试回归测试。原型阶段的临时兼容辅助函数、配置项与测试均已在最终提交 d24f449 中彻底移除,并随 RELEASE.2026-04-17T00-00-00Z 发布。

go test ./internal/config/identity/openid -count=1

兼容性代价

这是一项明确的不兼容变更(Breaking Change)。仍在签发 HS256、HS384 或 HS512 ID Token 的身份提供商必须先迁移至基于 JWKS 的 RSA 或 ECDSA 非对称密钥,再升级 SILO。若怀疑升级前 client_secret 曾发生泄露,请同步在 IdP 与 SILO 中轮换该密钥。