WordPress 核心的预认证 RCE PoC —— 将 CVE-2026-63030(REST /batch/v1 路由混淆反同步)与 CVE-2026-60137(author__not_in SQLi)串联为未认证 shell。仅限授权测试。
串联 CVE-2026-63030 + CVE-2026-60137 的未认证 WordPress 核心 RCE PoC。仅限授权测试。
wp2shell.py 是一个单文件、仅依赖标准库的概念验证(PoC),将 两个 WordPress 核心漏洞串联为未认证的远程代码执行:
| CVE | 组件 | 漏洞 |
|---|---|---|
| CVE-2026-63030 | REST /batch/v1 处理器 | 路由混淆 失步(desync)——一旦子请求路径解析失败,批量处理器就会在其 handler 表与 validation 表之间失去同步,导致某个子请求被分发到一个它从未被验证过的处理器。 |
| CVE-2026-60137 | WP_Query | 标量 SQL 注入——author__not_in 的值被直接拼接进 SQL,因此任何到达该值的字符串都是可注入的。 |
单独来看,两者都不是 RCE。失步是 投递手段(它将攻击者可控的字符串偷偷送入其本不该到达的查询);注入是 原语。二者结合,使预认证攻击者获得完整的 SQL 读/写 oracle,并由此获得可用的 shell。
⚠️ 仅限授权测试。 本工具针对运行中的 WordPress 站点进行利用。只能对您拥有或已获明确书面测试许可的系统使用。
受影响:6.8.0–6.8.5、6.9.0–6.9.4、7.0.0–7.0.1 已修复:6.8.6、6.9.5、7.0.2(及更高版本) 由 Adam Kues(Assetnote / Searchlight Cyber)报告;SQLi 同时致谢 TF1T、dtro、haongo。
只有 6.9.0–7.0.1 具备 RCE 能力。 在 6.8.x 系列上,失步仍会触发,但本应使 handler 错位的分支会命中 WP_Error::get_method() 致命错误——因此 6.8.x 是 拒绝服务 / 崩溃,而非 shell。该工具会检测这一点,并告知您当前可用的原语。
该利用按层次构建;每一层都是通往下一层的桥梁。
失步(CVE-2026-63030)。 一个批量请求中嵌套了第二个批量。其中一个子请求携带刻意构造的畸形路径(http://:,wp_parse_url() 会拒绝它)。在 6.9.0+ 上,解析失败会使 handler 表与 validation 表错位一项,因此 下一个 子请求会在一个它从未被验证过的处理器下运行——即公开文章集合。
注入(CVE-2026-60137)。 该错误分发的调用携带攻击者可控的 author__not_in 到达 WP_Query。这里的 UNION ALL SELECT 并不会 读取 行——而是 伪造 它们,让我们能在查询结果中伪造任意文章。
SQL → 写入桥梁。 伪造的文章携带 [embed] 标记。WordPress 会忠实地解析它们,并将其 缓存 为真实的 oembed_cache 文章元数据行——从而将只读注入转变为任意 写入 原语。
提权枢轴。 借助该写入桥梁,我们伪造 (a) 一个以真实管理员身份撰写的定制器 变更集(changeset) 与 (b) 一个自引用的 request 文章。二者共同迫使 WordPress 以该管理员的身份重新运行其自身的请求管线——这个时间窗口刚好足够一个排队的 POST /wp/v2/users 调用创建新的管理员。
Shell。 以新管理员身份登录,上传插件,执行命令。之后该账号、其元数据、oembed 行以及 webshell 都会被 清理——不会留下任何持久痕迹。
对于读取侧(--dump、前缀解析、缓存的 ID),工具会自动选择可用的最快 oracle,并在 首次命中时停止:
union — 带内 UNION:伪造行的 post_title 携带 |||HEX(value)|||,它会原样回显在 /wp/v2/posts 响应体中。仅需 一个请求 即可读取完整值(任意长度)。若 UNION 可回显,则永远不会探测布尔 oracle。bool — 布尔 X-WP-Total oracle:被混淆的子响应的 X-WP-Total 头在条件为真时为 count>0,为假时为 0。每个请求提取一位(二分法)。time — 基于时间的 SLEEP():当响应体被剥离 且 响应为 500(例如缓存型 mu-plugin 导致分发后致命错误)时的回退方案。即使目标每次 REST 调用都会崩溃,也能正常工作。当 UNION 可用时,--dump 使用 GROUP_CONCAT 一次性拉取整套凭据。
无需安装。仅依赖 Python 3.7+ 标准库——无需 pip install。
chmod +x wp2shell.py
# 1. Is it vulnerable? (no injection, no writes, no account created)
./wp2shell.py http://target --check
# 2. Read-only credential dump — FIRST administrator + option secrets
./wp2shell.py http://target --dump
# …every administrator
./wp2shell.py http://target --dump all
# 3. Full RCE — create a temp admin, run a command, clean up
./wp2shell.py http://target --exec "id; uname -a"
./wp2shell.py http://target --user-list # full user table (logins/emails/roles)
./wp2shell.py http://target --content # every post type at status=any (private/draft)
./wp2shell.py http://target --content full # …including each item's raw body
./wp2shell.py -f targets.txt --check --target-threads 8
$wp$2y$… / $P$… / $2y$…)可直接用于 hashcat(phpass 使用 -m 3200,或使用 WP bcrypt 模式)。WordPress 6.8+ 使用 $wp$… bcrypt。--dump 默认只导出第一个管理员。如需完整管理员列表,请使用 --dump all。--oracle time --dump 来恢复哈希。--exec 路径的设计目标是 不留任何痕迹:临时管理员、其用户元数据、伪造的 oembed_cache 行以及上传的 webshell 都会在工具退出前被全部移除。没有 --keep 标志——操作者绝不应在客户系统上留下一个存活且由攻击者创建的管理员账号。
wp2shell.py — 利用程序(单文件,仅标准库)。漏洞发现、披露以及最初的 "wp2shell" 分析文章均归功于 Searchlight Cyber / Assetnote 研究团队:
/batch/v1 路由混淆失步WP_Query author__not_in SQL 注入本仓库是他们所披露攻击链的独立概念验证实现;并不声称发现了底层漏洞。如果任何归属信息不完整或不正确,请提交 issue,我们将予以更正。
这是一个安全研究概念验证,用于授权的渗透测试与防御性验证。对您不拥有或未获书面测试许可的系统运行本工具属违法行为。作者对滥用行为不承担任何责任。
| 标志 | 用途 |
|---|
url / -f FILE | 单个目标,或一个目标文件(每行一个 URL) |
--check | 仅漏洞检查——无注入、无写入、不创建账号 |
--dump [all] | 导出 第一个 管理员的哈希 + 密钥;all = 所有管理员 |
--exec CMD | 创建临时管理员 → 执行 CMD → 清理 |
--user-list | 提权后的带内读取完整用户表(无需登录,绕过 2FA) |
--content [full] | 提权后以 status=any 读取所有文章类型的内容;full 附加正文 |
--oracle {auto,union,bool,time} | 强制指定提取 oracle(默认 auto) |
--prefix PFX | 强制指定表前缀,而非自动解析 |
--impersonate UID | 模拟指定用户 ID,而非第一个管理员 |
--threads N | 单个目标内的并发提取探测数(默认 5) |
--target-threads N | 从 -f 并发扫描 N 个目标(默认 1) |
--proxy URL | 通过代理转发(例如 Burp http://127.0.0.1:8080) |
--timeout SEC | 每个请求的超时时间(默认 30) |
--verbose / -v | 输出原始批量子响应(调试用) |
| 标志 | 用途 |
|---|
--form | 以 multipart/form-data 发送批量请求(URL 干净,无 ?rest_route=) |
--permalink [PREFIX] | 通过 /<PREFIX>/batch/v1 路由(默认 wp-json);让载荷避开 URL 以及 $_POST。提示:--permalink index.php/wp-json 通过 PATH_INFO 到达 REST,可绕过锚定在 /wp-json 上的规则 |
--dummy N | 在请求体中 前置 N 字节垃圾数据,以溢出 WAF 检查缓冲区 |
--donor {widgets,categories,tags} | 当某个混淆 donor 路由返回 500 时切换(默认 widgets) |
--per-page N | 伪造 per_page(-1 或较大的正数都会返回所有行;若目标对 -1 返回 500,请使用较大的正数) |
--users-route PATH | 为创建用户调用指定备选大小写/查询写法(/wp/v2/Users、…?_=1) |
--primer PATH | 若 WAF 对默认 http://: 进行指纹识别,可使用备选畸形失步路径 |
--user-agent UA | 固定 UA(默认:每次运行随机使用真实浏览器 UA) |
--embed-base URL | 覆盖 oembed URL 基址(避免自嵌入回环) |