dinosn/wp2shell-lab
GitHub: dinosn/wp2shell-lab
针对 WordPress 核心 wp2shell 漏洞链的非破坏性检测工具与 Docker 实验环境,通过时间盲注延迟比对确认注入点存在与否。
Stars: 34 | Forks: 9
# wp2shell 实验环境与检测工具
一个小巧、独立的实验环境,外加一个针对 **wp2shell** 的**非破坏性检测工具**——这是 WordPress 核心中的认证前漏洞链:
| CVE | 组件 | 类型 | CVSS |
|-----|-----------|-------|------|
| **CVE-2026-60137** | `WP_Query::author__not_in` | SQL injection (CWE-89) | 9.1 |
| **CVE-2026-63030** | REST `/batch/v1` 路由混淆 | 解释冲突 (CWE-436) → 链接至 RCE | 7.5 |
**受影响版本:** WordPress 核心 **6.9.0–6.9.4** 和 **7.0.0–7.0.1**(仅 SQLi 触发点也会影响
6.8.0–6.8.5)。**已在 6.8.6 / 6.9.5 / 7.0.2 中修复。** 由 Adam Kues (Assetnote / Searchlight
Cyber) 报告;SQLi 的发现也归功于 TF1T、dtro、haongo。
## 它的本质(在称其为“RCE”之前请先阅读此内容)
这个**恒真**(always-true)的原语是一个**无需认证、无需插件、原生核心的 SQL injection**,允许进行完整的
**数据库读取**(管理员密码哈希,以及 `wp_options`/`wp_users` 中的所有内容)。仅凭这一点,
它就足以达到 9.1 的评分并需要立即修补。
**“RCE”是真实存在的,但有条件。** 公开展示的唯一仅针对核心的提权方式是
SQLi → `INTO OUTFILE` webshell,这还需要满足**所有**以下条件:
1. WordPress 的数据库用户拥有全局 **FILE** 权限——*这并非默认设置*
(由 cPanel/托管主机颁发的 `GRANT ALL ON wordpressdb.*` 并不包含 FILE;FILE 权限主要出现在
自管 VPS 的 `GRANT ALL ON *.*` 和开发环境中),
2. 一个**可被 Web 访问的 `secure_file_priv`** 目录,并且
3. 落地的文件**可被 Web 用户读取**(MySQL 写入的 `OUTFILE` 权限为 `0640 mysql:mysql`)。
在普通/托管主机上,这些条件均不成立,因此 wp2shell 是一个**只读的 SQL injection**,而不是 RCE。
本实验环境的配置类似于普通主机(没有 FILE 权限),并且此检测工具刻意
在证实了 SQLi 后即停止——它绝不会尝试执行代码。
## 漏洞链的工作原理
`serve_batch_request_v1()`(位于 `wp-includes/rest-api/class-wp-rest-server.php`)在迭代子请求时会构建两个平行的
数组:如果子请求的路径未通过 `wp_parse_url()` 解析(例如 `http://`),
它会被附加到 `$validation` 中,但**不会**附加到 `$matches`。随后,分发循环(dispatch loop)对两者使用
相同的偏移量进行索引,因此在注入解析错误之后的每个子请求,都会在**下一个**
子请求的 handler 下执行。通过两次嵌套,这会 (a) 自调用 batch handler 以绕过方法
白名单,然后 (b) 在 posts `get_items` 下运行 `POST /wp/v2/categories?author_exclude=`——
并且因为 `categories` 从未注册 `author_exclude`,该值跳过了清理并到达了
`WP_Query::author__not_in`,被插入到 `NOT IN (...)` 中。直接请求
`?rest_route=/wp/v2/posts&author_exclude=` 会返回 HTTP 400(已被清理);因此必须制造混淆。
## 快速开始
环境要求:Docker + Docker Compose v2、Python 3.8+(仅限标准库)、`make`、`curl`。
```
make up # WordPress 6.9.4 (vulnerable) + MySQL 8.0, auto-installed on :8093
make check # -> [VULNERABLE] http://localhost:8093 (WordPress 6.9.4 ...)
make proof # -> also reads @@version and current_user() as read-only evidence
make patched # rebuild on the fixed image and re-check -> [not vulnerable]
make down # tear down (removes volumes)
```
使用 `WP_PORT=8100 make up` 更改端口。
### 预期输出
```
$ make check
[VULNERABLE] http://localhost:8093 (WordPress 6.9.4, affected-range) [fast=0.02s slow=4.03s delta=4.01s]
$ make patched
[not vulnerable] http://localhost:8093 (WordPress 7.0.2, outside-affected-range) [fast=0.02s slow=0.03s delta=0.01s]
```
## 检测工具 (`wp2shell_check.py`)
仅使用标准库。**非破坏性:** 通过基于时间的差异比对(快速请求对比注入了 `SLEEP` 的请求)
在不读取数据或更改状态的情况下确认注入。`--proof` 仅通过有限的盲读读取 `@@version` 和
`current_user()`,作为提交工单的硬证据。它**不会**
尝试执行代码或提取敏感数据。
```
# 你自己的单个 asset(remote targets 需要 authorization assertion)
python3 wp2shell_check.py https://your-site.example --authorized
# 你拥有的 asset 列表,JSON out
python3 wp2shell_check.py -f assets.txt --authorized --json
# 通过 Burp
python3 wp2shell_check.py http://127.0.0.1:8093 --proxy http://127.0.0.1:8080
```
选项:`--sleep N`(注入延迟,默认为 4)、`--rounds N`(在 N 次探测中取中位数)、
`--route auto|rest-route|wp-json`、`--timeout N`、`--proof`、`--json`、`--authorized`。
退出代码:`0` 表示存在漏洞,`1` 表示无漏洞,`2` 表示结果不明/发生错误。
## 修复方案
- **修补**至 WordPress **6.9.5 / 7.0.2**(或者 6.8 分支上的 **6.8.6**)。
- 如果无法立即修补,请**同时**封锁 `/wp-json/batch/v1` **和** `?rest_route=/batch/v1`
——仅针对美观路径(pretty path)的规则会使查询字符串路由保持开放——或者通过
`rest_pre_dispatch` 过滤器要求对 batch 路由进行认证。
- 纵深防御:确保 WordPress 的数据库用户**没有全局 FILE 权限**。
## 参考资料
- Searchlight Cyber / Assetnote — https://slcyber.io/research-center/wp2shell-pre-authentication-rce-in-wordpress-core/
- WordPress 7.0.2 release — https://wordpress.org/news/2026/07/wordpress-7-0-2-release/
- 安全公告:GHSA-ff9f-jf42-662q, GHSA-fpp7-x2x2-2mjf
## 许可证
MIT — 查阅 [LICENSE](LICENSE)。
标签:CISA项目, Docker, Maven, WordPress, 安全防御评估, 文件完整性监控, 漏洞验证, 版权保护, 请求拦截, 逆向工具, 靶场环境