
Keycloak 26.8.1 SAML broker 漏洞的环回 PoC:经过百分号编码的重复 SAMLResponse 名称可绕过重定向签名验证,从而允许以其他用户身份登录。
没有 Keycloak 账户的人可以以另一个人的身份完成公司 SAML 登录。他们先在上游身份提供者处登录一次,保留该已签名的重定向,然后在一个百分号编码的名称下,把第二个 SAML 文档放在它前面。Keycloak 校验了真实的签名,然后以伪造文档中的名称将其登录。这需要一个会签名 HTTP-Redirect 响应的 SAML broker,开启签名校验并关闭断言签名,这正是元数据导入所配置的方式。这不是 issue #53354 中的分号拆分。
类别: OAuth 遗留问题 可达性: 远程(对 Keycloak 未认证;需要一个会签名重定向的 SAML broker)
Keycloak 26.8.1 (24ed1dad) - Red Hat
| ID | 暂无 CVE |
| 类别 | OAuth 遗留问题 |
| 可达性 | 远程(无 Keycloak 账户;需要来自 IdP 的一个真实 HTTP-Redirect 签名) |
| CWE | CWE-287 / CWE-347 / CWE-116 |
| CVSS | 高危:7.4 CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N |
| 产品 | Keycloak |
| 受影响版本 | 26.8.1 (24ed1dadf3f246a555adb6d3d4a99e9b3003fb3b);Quarkus 3.40.1;镜像 quay.io/keycloak/keycloak:26.8.1 |
| 认证 | Keycloak 上无需认证。需要一个 SAML 身份提供者 broker,且 validateSignature=true、wantAssertionsSigned 缺失或为 false。 |
| 许可证 | GNU Affero GPL v3.0 |
| 实验环境 | 仅 127.0.0.1:18084 |
需要受害者 NameID(通常是电子邮件)以及来自该 IdP 的一个真实已签名 HTTP-Redirect。不需要 Keycloak 密码。不需要邮箱。仅支持 POST 的 IdP 不会交出该签名。即使元数据导入偏好 POST,GET ACS 仍然保持挂载。
| 权限 | 方法 / 汇聚点 | 侦察 / 完整性 / 可用性 | 实验验证或推断 |
|---|---|---|---|
| 在真实签名仍能通过校验的情况下绑定伪造的 SAML 文档 | 使用 GET broker ACS,在已签名的 SAMLResponse 之前放置 SAML%52esponse=<forged> | 完整性(认证) | 实验验证。对照邮箱 [email protected]。攻击邮箱 [email protected] |
| 通过首次 broker 登录创建受害者用户 | 对伪造的 NameID 触发 callback.authenticated | 完整性(账户创建) | 实验验证。拆分查询前受害者数量为 0,之后为 1 |
| 以现有 broker 用户身份建立会话,包括使用同一 IdP 的管理员 | 同一 ACS,该用户的 NameID | 完整性(账户接管) | 推断。实验在首次登录时创建了受害者 |
| 保留 RelayState 和认证会话 | RelayState 保持为已签名值 | 完整性 | 源码。签名覆盖 RelayState |
| 阴性对照 | 真实已签名重定向,无额外参数 | 不适用 | 实验验证。对照以攻击者邮箱完成 |
在此遗留问题上仍然关闭
| 维度 | 方法 / 汇聚点 | 为何关闭 |
|---|---|---|
| 完整性 | 分号拆分 SAMLResponse=...;SAMLResponse=... | issue #53354。quarkus.http.use-semicolon-as-query-param-delimiter=false |
| 完整性 | wantAssertionsSigned=true 时的未签名断言 | 此时 handleLoginResponse 要求断言签名 |
| 完整性 | 无 SAML broker,或仅支持 POST 的 IdP 无重定向签名 | 攻击者永远无法在 GET 上获得 SigAlg + Signature |
| 完整性 | 主机 RCE、未认证 Admin REST | 超出类别 |
| 完整性 | 默认 VERIFY_PROFILE 名称表单 | 实验验证为完成步骤,而非反证。该表单已持有 broker 邮箱 |
我固定使用 Keycloak 26.8.1。RESTEasy Reactive 从 Vert.x 获取 @QueryParam("SAMLResponse"),而 Vert.x 会对查询名称进行百分号解码,因此 SAML%52esponse 会折叠为 SAMLResponse,并且第一个值生效。verifyRedirectSignature 会基于原始查询中的 getQueryParameters(false) 重建签名字符串,而在那里该名称仍是另一个不同的键。
已记录的弯路:将 #53354 视为这一遗留问题(百分号编码的名称,而非分号);在实验客户端中跟随 ACS 302 并丢弃 AUTH_SESSION_ID;将 VERIFY_PROFILE 上空的 firstName/lastName 视为未命中(电子邮件已经是代理的 NameID);一个反向 shell。纯属表演。
Python 3.11+ 及 cryptography。需要 Docker。
python3 keycloak-saml-redirect-param-split-Abraxas-Labs.py
或:
cd lab
./run.sh
仅以 http://127.0.0.1:18084 为目标。镜像为 quay.io/keycloak/keycloak:26.8.1。启动器会 chdir 进入 lab/ 并执行 run.sh。实验环境中的 IdP 是进程内运行的。它从不与真实身份提供方通信。
IOC image=quay.io/keycloak/keycloak:26.8.1 label-version=26.8.1
IOC serverinfo-version=26.8.1
IOC validate-signature=true want-assertions-signed=absent
IOC review-profile=DISABLED
IOC [email protected]
IOC victim-before=0
IOC [email protected]
IOC victim-after=1
SUCCESS KC-H04-SAML-REDIRECT kcs04b8n1
验证端点解析的同一查询文档。在重建签名之前对参数名进行百分号解码,或者在解码后拒绝包含多个 SAMLResponse 的查询。为重定向代理(包括元数据导入路径)开启 wantAssertionsSigned。
24ed1dadf3f246a555adb6d3d4a99e9b3003fb3b)SAMLEndpoint.redirectBinding(SAMLEndpoint.java:182-183、:779-813、:896-914)SamlProtocolUtils.verifyRedirectSignature(SamlProtocolUtils.java:216-249)KeycloakUriInfo.getQueryParameters(KeycloakUriInfo.java:147-163)GNU Affero GPL v3.0。参见 LICENSE。仅限回环实验环境。不提供任何担保。
Abraxas Labs [email protected]
请将 CVE 归功于 Abraxas Labs。