Skip to content
KitploitKITPLOIT
工具漏洞利用博客
Log in
提交
工具漏洞利用博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

订阅源联系隐私© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
keycloak-cve-2026-18963-hunt — 在 Keycloak 数据库中搜寻 CVE-2026-18963 的利用痕迹(Keycloak 未认证账户接管) | Kitploit
工具/GitHubGitHub/kyos-public/keycloak-cve-2026-18963-hunt
漏洞分析数字取证身份验证事件响应数据库安全日志分析
GitHubkyos-public/keycloak-cve-2026-18963-hunt

keycloak-cve-2026-18963-hunt

在 Keycloak 数据库中搜寻 CVE-2026-18963 的利用痕迹(Keycloak 未认证账户接管)

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
查看仓库
161402个月前尚未审核
分享

CVE-2026-18963:Keycloak 账户接管,利用痕迹排查

一个 psql 脚本,用于在 Keycloak PostgreSQL 数据库中搜索 CVE-2026-18963 的利用痕迹(通过重置凭据流程实现的未认证账户接管)。

由 KYOS 发布。我们在为所运营的 Keycloak 部署打补丁时编写了它,用于验证在暴露窗口期内没有人被接管。

漏洞

CVE-2026-18963 是 keycloak-services 重置凭据流程中的一个缺陷(keycloak#51833)。未认证的攻击者无需点击邮件验证链接,即可为任意用户完成密码重置流程,然后在该账户上设置新密码。无需任何用户交互。

这对痕迹意味着什么

在一次性实例上针对 Keycloak 26.7.1 进行了验证。我们不公布复现步骤;以下细节是阅读这些查询所需的内容。

该流程以一种与设置它的步骤无关的方式来跟踪当前处于哪个认证器界面。这使得重置凭据流程可以在邮件验证步骤被重新进入,此时存在漏洞的 ResetCredentialEmail.action() 会在完全不检查 action token 的情况下返回成功。执行随后继续进入更新密码表单。

两个后果塑造了本仓库中的每一条查询:

  • 邮件步骤在绕过之前会真实执行。 受害者会收到一封真实的密码重置邮件,并且 Keycloak 会写入一条普通的 SEND_RESET_PASSWORD 事件。该事件的缺失并不是特征。
  • 存在漏洞的 action() 在返回之前会将账户的电子邮件地址标记为已验证。 合法路径根本不会到达 action(),因此这是只有绕过才会产生的状态变更。Q3 依赖于此。

修复(keycloak#51844)将界面状态与设置它的执行绑定,并且仅当存在针对该确切用户的 action token 时才允许 action() 成功。

严重性:严重,CVSS v3.1 9.1(AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N),依据 Red Hat 和 NVD。根本原因是认证流程中的状态验证不当,已在 keycloak#51844 中修复。

受影响 / 已修复版本

版本流修复于
26.7.x26.7.2(发行说明)
26.6.x26.6.6
26.4.x(LTS)26.4.15
26.826.8.0

受支持版本流中低于上述版本的所有版本均存在漏洞。已停止支持的版本(26.5.x、26.3 及更早版本)不会获得修复:请假定已暴露并升级到已修复的版本流。Red Hat 将旧版 RH-SSO 7 列为不受影响;Red Hat Build of Keycloak 26.4/26.6 已在 26.4.15-1 / 26.6.6-1 中修复。

如果无法立即打补丁的临时缓解措施

禁用自助密码重置可移除存在漏洞的入口点:管理控制台 > 领域设置 > 登录 > 关闭“忘记密码”,对每个领域执行。通过管理 API:PUT /admin/realms/{realm},请求体为 {"resetPasswordAllowed": false}。

这会阻止 login-actions/reset-credentials 端点,但也会阻止合法用户重置自己的密码,因此请将其视为升级前的临时措施。请注意,Red Hat 并未列出针对此 CVE 的官方认可的缓解措施;打补丁才是唯一真正的修复。

不能作为特征的情况

该脚本的第一个版本会将一次已完成的重置标记为可疑——如果该重置之前不久没有 SEND_RESET_PASSWORD。

这是错误的, vilmosnagy 指出了这一点, 他在自己的实例上复现了该 CVE,并观察到事件仍然出现。原因就是上面的第一个后果:邮件认证器正常运行,因此受害者会收到一封真实的密码重置邮件,Keycloak 也会记录一条普通的 SEND_RESET_PASSWORD。这个检查被写反了。它本会清除它检查过的每一个已失陷账户。现在它已被移除。

基于时序的方案存在一个较小但真实的问题。 thomasdelorge 的狩猎脚本 中的方法将 SEND_RESET_PASSWORD 与 UPDATE_PASSWORD 通过共享的 code_id 关联起来,并标记时间间隔过短的情况。关联键是正确的,作者也明确说明该阈值只是一种启发式判断。我们的测试结论相同:一次被绕过的重置和一次在同一浏览器中完成的合法重置会产生完全相同的事件类型、完全相同的详情键以及相同的 code_id。只有延迟不同,这就使得整个判定都压在了阈值上。不着急的人会一直低于该阈值。真实用户反而会触发它,因为只有当链接在发起流程的浏览器中打开时,code_id 才会保持不变,而这恰恰也是邮件很可能已经处于打开状态的情形。

我们没有发现任何事件、任何事件详情或任何错误能够区分这两种情况。不存在可供查找的“邮件步骤被跳过”事件。确实存在的是一个状态副作用,而这正是 Q3 所利用的。

脚本检查的内容

cve-2026-18963-keycloak-hunt.sql 运行六个只读查询:

查询它发现的内容
Q0realm 是否存储事件、其 TTL,以及事件详情是否被持久化。Q2 依赖 code_id 详情进行关联,因此如果详情缺失,Q2 就会失明,其沉默毫无意义。
Q1暴露窗口内(credential.created_date)设置的每一个密码凭据。这就是接管本身,并且它在事件过期后仍然存在。
Q2每一次已完成的凭据更新,与同一认证会话的重置邮件相关联,并进行分类。见下文。
Q3确定性的副作用:email_verified 为真但没有合法原因的用户。
Q4Admin-API 凭据重置和用户写入,用于解释 Q1 和 Q3 的行,并排除管理员驱动的路径。
Q5原始的重置/凭据事件时间线,用于手动阅读单个案例。

Q2 判定

判定含义
NO_MAIL_WAS_SENT凭据更新与一次失败的发送共享同一个 code_id:账户上没有地址,或 SMTP 拒绝了它。从未有链接离开服务器,因此不可能有人打开过链接,但流程仍然到达了密码表单。这在合法情况下不可能发生;应视为利用行为。
CLEARED_TOKEN_CLICKED重置完成时所用的 code_id 与该用户的任何 SEND_RESET_PASSWORD 都不同。只有在一个操作令牌于另一个浏览器或会话中被消费时才会产生这种情况,因此邮件链接确实被点击了。确定性地合法。
CANDIDATE与发送具有相同的 code_id。这既是绕过行为的样子,也是合法的同浏览器重置的样子。本身不构成发现。
NOT_RESET_FLOW该用户在窗口内没有 SEND_RESET_PASSWORD;密码通过其他方式更改(账户控制台、admin API、注册)。交叉核对 Q4。

secs_after_send 仅作为上下文打印。不要将其用作判定依据。

Q3,唯一确定性的数据库内痕迹

存在漏洞的 ResetCredentialEmail.action() 会在成功之前将账户的电子邮件地址标记为已验证。合法路径根本不会进入 action(),因为 authenticate() 会在操作令牌上短路返回,因此真正的重置不会改动 email_verified。我们在 26.7.1 上检查了这一点,针对在发起重置的浏览器中以及在不同浏览器中完成的合法重置。两者都让未验证用户保持未验证状态。两者中,被绕过的重置都翻转了该标志。

因此,一个密码在窗口期内被修改、当前为 email_verified、且没有 VERIFY_EMAIL/UPDATE_EMAIL 事件、也没有针对它的管理员写入操作的账户,对于其为何处于已验证状态没有任何无辜的解释。

这里有一个非常重要的限制:在窗口期之前就已经验证的账户对 Q3 是不可见的,因为该标志早已为 true。Q3 是精确的,而非穷尽的。它最强的一行是 verified_with_no_email = t,因为一个完全没有地址的账户既无法接收重置链接,也无法通过打开链接来完成验证。

用法

# defaults: realm 'master', window since 2026-06-01
psql -U keycloak -d keycloak -f cve-2026-18963-keycloak-hunt.sql

# explicit realm and window (run once per realm; plain values, psql quotes them)
psql -U keycloak -d keycloak \
  -v realm=myrealm -v since=2026-05-01 \
  -f cve-2026-18963-keycloak-hunt.sql

将 since 设置为你的易受攻击版本上线之前的时间点。在 Kubernetes 上:

kubectl exec -it my-postgres-pod -- \
  psql -U keycloak -d keycloak -v realm=myrealm -v since=2026-05-01 \
  -f - < cve-2026-18963-keycloak-hunt.sql

解读结果

先检查 Q0。如果 events_in_window 为 0,或者 details_with_code_id 为 0 而事件存在,那么 Q2 无法工作,它的沉默不能证明任何事情。Q1 和 Q3 读取表状态,不受事件保留影响。

然后依次向下处理:

  1. Q2 NO_MAIL_WAS_SENT。利用。据此采取行动。
  2. Q2 CLEARED_TOKEN_CLICKED。清除这些,不要再考虑它们。
  3. Q3 中 admin_ops_on_user 为空且 has_verify_email_event = f 的行 属于利用,但需注意:通过不写入管理员事件的导入路径被登记为已验证的账户也会落入此处。
  4. Q2 CANDIDATE 若 Q3 无法佐证,则未解决。数据库无法裁定这些,因此请查阅访问日志。

从访问日志裁定 CANDIDATE

只要日志仍然存在,这就是确定性的。打开邮件中的链接是一个 HTTP 请求,而这是绕过无法伪造的一件事。在你的反向代理或入口日志中,真正的重置总是显示

GET /realms/<realm>/login-actions/action-token?key=...

来自同一客户端,在邮件发出与密码设置之间。

grep -E "login-actions/action-token" access.log

对于 Q2 中的每个 CANDIDATE,在 SEND_RESET_PASSWORD 和 UPDATE_PASSWORD 之间查找该请求。如果找不到,说明邮件环节从未通过。

有两点需要注意。对 action-token 的 HEAD 请求来自邮件扫描设备,会被 Keycloak 忽略,因此只有 GET 才算数。另外,某些邮件安全产品会自动跟随链接,从而产生用户从未发起的真实 GET 请求;如果你的环境中有这类产品,请结合 user-agent 和源地址进行佐证,而不要仅凭请求本身判断。

最后,最省事的来源往往是用户自己。每位受害者都收到过一封自己从未请求过的密码重置邮件。在时间窗口较短、候选集较小的情况下,直接询问比推断更有效。

Q1 中的行并不自动意味着账号已被入侵。请将每一行与已知的合法原因(自助重置、服务台操作、新注册)进行匹配,并将任何无法解释的情况视为账号被接管的候选。

如果你发现账号被接管

  1. 禁用受影响的账号,或通过可信渠道强制重置密码。
  2. 撤销该账号的会话和离线令牌(管理控制台 > Sessions),并轮换其可能访问到的任何密钥。
  3. 扩大调查范围:查看 Q2 中时间戳和 IP 附近的反向代理/入口日志、该账号在凭据更改后执行的操作,以及通过 Keycloak 联合的下游应用。

注意事项

  • 只读操作,但建议对副本或备份运行。
  • 针对 PostgreSQL 上的 Keycloak 26.x 编写。表名在近期版本中保持稳定,但在其他数据库或旧版本上请先验证。
  • 没有发现并不等于没有被入侵的证明(参见 Q0),尤其是在事件保留期较短或日志被禁用的情况下。特别是 Q3 无法发现那些已经处于 email_verified 状态的受害者。
  • 如果你自己编写查询:在当前 Keycloak 26.x 中,事件详情存储在 event_entity.details_json_long_value 中,而 details_json 对每一行都是 NULL。在我们的 26.7.1 实验环境中,29 行中有 29 行如此。请同时读取这两列,否则你会静默地匹配不到任何内容。
  • 成功重置密码时不会写入 RESET_PASSWORD。完成事件是 UPDATE_PASSWORD 和 UPDATE_CREDENTIAL;失败时则会出现 RESET_PASSWORD_ERROR。

致谢

  • @vilmosnagy 复现了该 CVE,并报告 SEND_RESET_PASSWORD 确实会被记录,这促使我们回到源码,并移除了那个被反转的检查。
  • @thomasdelorge 提供了 code_id 关联和访问日志检查,本脚本正是基于这两者构建的。

许可证

MIT,参见 LICENSE。按原样提供,不附带任何担保。

下载工具