🎯 CVE-2026-63030 深度技术分析:漏洞根因 · PoC/EXP · 检测指纹
CVE-2026-63030 深度技术分析
摘要:CVE-2026-63030 是 WordPress Core 中一个因解释冲突(Interpretation Conflict)导致的严重漏洞,攻击者可在未认证的情况下通过 REST API 批量请求端点(/batch/v1)触发 SQL 注入,进而可能实现远程代码执行(RCE)。该漏洞影响 WordPress 6.9.0 至 6.9.4 以及 7.0.0 至 7.0.1 版本,已列入 CISA KEV 目录,且可被 CVE-2026-60137 链式利用。Searchlight Cyber 的研究团队最早披露了该漏洞(代号 wp2shell),随后多个独立 PoC 仓库公开了利用细节。本文将从漏洞根因、利用链、实际危害及修复缓解四个方面进行深度分析。
📌 漏洞概述
CVE-2026-63030 属于 解释冲突(Interpretation Conflict) 漏洞,CWE 分类可归为 CWE-436(Interpretation Conflict)或 CWE-843(Type Confusion)。漏洞核心位于 WordPress 的 REST 批量请求处理器 serve_batch_request_v1() 中,该函数在处理 /batch/v1 端点时存在数组索引错位问题,导致认证绕过和路由混淆(Route Confusion)。
- CVE 编号:CVE-2026-63030
- CVSS 评分:官方暂未公布最终分值,但鉴于未认证 SQLi 和 RCE 的潜在影响,预计 CVSS v3.1 评分在 9.8(Critical)级别
- 漏洞类型:SQL 注入(CWE-89)、远程代码执行(CWE-94),根因为解释冲突
- 影响版本:
- WordPress ≤ 6.8.5:不受影响
- WordPress 6.9.0 – 6.9.4:受影响
- WordPress 7.0.0 – 7.0.1:受影响
- 利用前提:无需认证,仅需目标站点启用 REST API(默认开启)
- 在野利用:已列入 CISA Known Exploited Vulnerabilities(KEV)目录,存在被积极利用的证据
根据 CISA 的说明,该漏洞可以与 CVE-2026-60137 链式组合,从 SQL 注入进一步提升为远程代码执行。Searchlight Cyber 发布的 wp2shell 公告中明确列出了受影响版本范围,与 CVE 数据一致。
🔬 漏洞根因分析
漏洞的根源是 WordPress 在批量 REST 请求处理中对“路由匹配数组”和“验证结果数组”的索引管理不当,造成一次 请求分发时的类型混淆。为了深入理解,我们需要分析 serve_batch_request_v1() 的实现逻辑。
该函数接收一个包含多个子请求(sub-requests)的批量请求体。对于每一个子请求,它需要做两件事:第一,通过路由解析找到对应的处理函数(handler),存入 $matches 数组;第二,对该子请求执行权限和参数验证(validation),将结果存入 $validation 数组。理想情况下,这两个数组应以相同的顺序、相同的键对应同一个子请求,后续分发时通过偏移量同时取到匹配的处理函数和验证结果。
然而,漏洞代码在某个分支中未能保持两个数组的同步。具体而言,当一个子请求的路径中包含无法通过 wp_parse_url() 解析的畸形 URL 时,该子请求会被追加到 $validation 数组(表示验证失败或需要跳过),但**不会**被追加到 $matches 数组(因为没有匹配到合法的路由)。这导致两个数组的长度和索引发生错位。
随后,分发逻辑使用某个循环变量(offset)同时访问两个数组。例如,假设第一个子请求是合法的 POST /wp/v2/posts,它被正常匹配且验证通过,两个数组索引均为 0;第二个子请求是一个畸形路径,它被加入验证但未加入匹配;第三个子请求是攻击者构造的恶意请求(如 GET /wp/v2/users)。此时,$matches 数组只有 [0] 和 [2] 两个元素(索引为 0 和 1),而 $validation 数组有 [0]、[1]、[2] 三个元素。当循环到偏移量 1 时,代码从 $validation[1] 取出的验证结果对应的是畸形请求(可能被标记为“已跳过验证”),但从 $matches[1] 取出的处理函数却是第三个子请求(恶意请求)的处理器!于是,一个本应被验证拦截的恶意请求,被错误地分发到了一个并未经过权限检查的处理器上。
PoC 中展示了如何利用这种“路由错位”嵌套两次,最终实现未认证的 SQL 注入。第一次错位:攻击者构造一个 POST /wp/v2/posts 请求,但其 body 携带了批量请求所需的 requests 字段。由于该请求先被验证为“文章创建”请求,WordPress 并未对其内部 requests 字段进行二次检查。此时,通过偏移错位,这个外层请求被分发到批量处理器自身,形成“批量中的批量”——内层请求列表脱离了原始验证上下文。第二次错位则是利用类似机制,使得某一个内层请求的处理器被替换为另一个可写的数据库操作处理器,从而在 SQL 语句中注入恶意参数。
更关键的是,这种注入并非传统的基于错误信息的注入,而是通过 参数预处理 与 SQL 语句拼接冲突 实现的。攻击者可以使用 UNION 或 OR 等语法读取任意表内容,包括 wp_users 表中的密码哈希和会话令牌。Searchlight 的公告将这一系列技术命名为“wp2shell”,其 PoC 工具中的 check、read、shell 三个命令分别对应了 SQLi 验证、数据读取和远程命令执行。
根因本质上属于 控制平面与数据平面分离不完整:批量请求本应在后台对每个子请求独立验证,但由于数组索引算法缺陷,验证结果与执行处理器之间产生了“多对多”的非法关联。这种漏洞在编程语言中常被称为“越界索引”或“结构体数组不对齐”,在安全领域则表现为一个典型的认证/授权绕过原语。
💥 影响与危害
CVE-2026-63030 的利用链可以完全破坏 WordPress 站点的机密性、完整性和可用性,具体危害包括:
- 未认证 SQL 注入:攻击者无需任何账号即可对 WordPress 数据库执行任意 SQL 查询,读取所有表的内容,包括用户凭据、私人文章、配置信息及敏感业务数据。
- 管理员权限接管:通过 SQL 注入读取管理员密码哈希后,攻击者可进一步利用 WordPress 的密码重置机制或直接修改数据库中的用户表,将自己提升为管理员。
- 远程代码执行(RCE):PoC 中的
shell命令演示了如何结合 SQL 注入与后台插件管理功能,上传恶意插件或修改插件代码,最终在目标服务器上执行操作系统命令。这相当于将 WordPress 完全变成攻击者的 WebShell。 - 供应链与传播风险:由于 WordPress 在全球市场份额巨大(约 43% 的网站),该漏洞可被用于大规模自动化攻击,安装后门、挖矿程序或发起横向渗透。CISA 将其列入 KEV 表明已出现真实世界利用,可能已导致数据泄露事件。
- 与 CVE-2026-60137 的链式利用:公告明确指出可被链接,意味着单独利用本漏洞或许只能实现部分目标,而串联另一个 CVE 可以绕过更多安全限制,形成一条完整的一击必杀链路。
- 检测与应急难度:REST 请求混入正常流量中,恶意载荷通常较长且编码复杂,常规 WAF 容易漏报。而一旦攻击者成功植入后门,基于文件完整性的告警可能滞后。
所有运行受影响版本且对外开放 REST API 的 WordPress 站点均处于高风险状态。互联网暴露资产扫描在 PoC 发布后数小时即可完成批量扫描,因此任何未更新的站点都已处于极大的被攻陷危险之中。
🛡️ 修复与缓解
WordPress 官方尚未在原始资料中给出具体的补丁版本号(如 6.9.5 或 7.0.2),但建议立即采取以下措施:
- 更新版本:将 WordPress 核心升级到最新稳定版,尤其是高于 7.0.1 的修复版本。若官方已发布 6.9.x 分支的更新,应优先升级。
- 禁用 REST 批量端点:在未升级之前,可以通过插件或
.htaccess/ Nginx 规则屏蔽/batch/v1路径,阻断利用入口。 - WAF 规则:部署 Web 应用防火墙,针对
requests参数内的嵌套批量请求特征进行拦截,尤其是包含两个以上requests的畸形结构。 - 最小化插件攻击面:由于 RCE 阶段需要注入插件或修改主题,建议移除不必要的插件,并限制
wp-content目录的写权限。 - 数据库监控:审计数据库日志,查找异常的
UNION查询或对wp_users表的非预期访问。 - 应急取证:若站点已疑似被攻击,参考 CISA 的取证缓解要求,保留内存转储、Web 日志和 SQL 审计日志,检查是否存在后门文件(如
wp-content/mu-plugins/下的小型 PHP shell)。 - 遵循 BOD 26-04:美国联邦机构务必在 CISA 规定的截止日期前完成补丁或缓解,其他组织也应参照该优先指导评估自身暴露风险。
鉴于该漏洞已在 KEV 中标记为“已知被利用”,任何在线站点都应当将本次修复视为最高优先级安全事件处理,不应等待例行更新窗口。
🧪 PoC 复现
从 GitHub 公开仓库抓取的实际 PoC 代码(仓库)。
📋 代码元数据语言md来源Icex0/wp2shell-poc针对性⚠️ 疑似通用代码(未检测到 CVE 引用,仅供参考)依赖见代码注释/README用法详见代码注释中的使用说明
# wp2shell-poc
Independent proof-of-concept for the unauthenticated WordPress REST batch route-confusion
SQL injection associated with Searchlight Cyber's wp2shell advisory.
This repository is not Searchlight Cyber's official checker. `check` confirms the SQLi path,`read` demonstrates database read,
and `shell` opens a plugin-backed command shell either with
supplied administrator credentials or by first exercising the SQLi-to-admin bridge.

**Detection / IoCs:** Elastic Security Labs and Eye Security published detection guidance and indicators for this chain. See the [Elastic write-up](https://www.elastic.co/security-labs/wp2shell-wordpress-rce-detection-elastic-defend) and the [Eye Security defenders guide](https://labs.eye.security/wp2shell-defenders-guide/).
## Affected versions
Searchlight Cyber's advisory lists these wp2shell RCE exposure ranges:
|
Version range |Status ||------------- |------ ||<= 6.8.5 |Not affected ||6.9.0 – 6.9.4 |Affected ||7.0.0 – 7.0.1 |Affected |## How it works
The REST batch endpoint (`/batch/v1`) is unauthenticated and runs several sub-requests in one
call,
relying on each sub-request being validated and permission-checked on its own.
`serve_batch_request_v1()` builds two parallel arrays — `$matches` (the matched handler per
sub-request) and `$validation` (the validation result per sub-request) — then indexes both by
the same offset when dispatching. A sub-request whose path fails `wp_parse_url()` is appended to
`$validation` but not to `$matches`,
so the arrays fall out of step and a sub-request is
dispatched under a **different** sub-request's handler. That is the route confusion.
The PoC nests the primitive twice:
1. A `POST /wp/v2/posts` request that carries a `requests` body is dispatched under the batch
handler itself. Having been validated as a posts request,its `requests` list is never checked
against the batch schema,
so its sub-requests may use `GET` — the method allow-list is
bypassed.
2. Inside that inner batch,a `GET /wp/v2/posts/999999` item-route request carries posts collection
query params such as `author_exclude`,`orderby`,and `per_page`. The `999999` ID does not need
to exist;it is just an unlikely post ID used to match the item route,
whose schema does not
validate those collection-only params. The desync then dispatches the same request under posts
`get_items()`,where `author_exclude` maps to the `WP_Query` `author__not_in` query var,
which
the vulnerable build interpolates into SQL as a string.
The result is a boolean- and time-based blind SQL injection reachable pre-authentication. This PoC
also includes the UNION fake-post primitive used by the SQLi-to-admin chain.
The RCE path implemented here is:
1. Use UNION fake `wp_posts` rows to render attacker-controlled content through a posts collection.
The render bridge uses the `/wp/v2/posts/999999` item-route source — the same route the SQLi read
uses to reach `get_items()`.
2. Use that render to make WordPress create real oEmbed cache posts.
3. Recover those real cache post IDs through the SQLi.
4. In one poisoned batch request,
recast those IDs as a customizer changeset,navigation item,and
request hook shape.
5. Let the same request reach `POST /wp/v2/users`,creating a generated administrator.
6. Log in as that generated administrator and use plugin upload behavior to run a command.
Steps 1–5 are pre-authentication;
the command-execution step is authenticated admin plugin upload.
## Requirements
Python 3.8+ and the standard library. No third-party dependencies.
## Usage
Run it from the repository directory:
```
./wp2shell.py <command><url>
[options]
```
Or `pip install .` to get a `wp2shell` command on your `PATH`.
### check — non-destructive vulnerability check
Prints passive WordPress markers and public version hints first,then sends a benign batch marker
probe. A vulnerable batch implementation returns HTTP 207 with the route-confusion marker pattern
`parse_path_failed`,`block_cannot_read`,
and `rest_batch_not_allowed`.
The marker probe is based on the WordPress core fix. The malformed `///` request creates
`parse_path_failed`;a `/wp/v2/posts` request acts as a batch-allowed spacer;the
`/wp/v2/block-renderer/...` route is not batch-allowed but returns `block_cannot_read` if its
handler is reached anonymously;
`/batch/v1` gives `rest_batch_not_allowed`. On vulnerable builds
the parse error shifts the batch handler arrays out of step,so the spacer request is dispatched
under the block-renderer handler. Fixed builds keep the arrays aligned,so this exact all-three
pattern should not appear for the crafted probe.
By default,
`check` stops there and does not send an SQLi payload. Use `--confirm-sqli` when you
also want an active SQLi confirmation. The confirmation tries the UNION read primitive first and
falls back to paired timing probes if UNION reflection is unavailable.
The signals are independent: a version hint is only a hint,the marker pattern shows route
confusion,
and `--confirm-sqli` shows a payload reached the database. A WAF can block the payload,
so a failed confirmation doesn't prove the bug is absent.
```
./wp2shell.py check http://target
./wp2shell.py check targets.txt # scan every URL in the file
```
### read — extract data through SQL injection
```
./wp2shell.py read http://target # server fingerprint
./wp2shell.py read http://target --preset users # user logins and password hashes
./wp2shell.py read http://target --query "SELECT @@version"
```
By default extraction is `--technique auto`,
which tries the available methods in this order:
1. **union** — forges a fake `WP_Post` row via `UNION` and reads its title back from the REST
response as `||HEX(value)||`. The payload uses the same `/wp/v2/posts/999999` source route with
`orderby=none` and `per_page=500` so the fake row survives as a rendered post. One request per
value.
2. **error** — `EXTRACTVALUE`/`UPDATEXML` leak ~15 bytes per request,
when the target reflects
MySQL errors (e.g. `WP_DEBUG_DISPLAY` on).
3. **blind** — boolean binary search,~8 requests per character;reads the posts collection
`X-WP-Total` header as the true/false signal and needs no reflected value.
Force one with `--technique union|error|blind`. These read paths do not write database rows.
### shell — command execution
With `--user` and `--password`,
`shell` logs in with supplied administrator credentials and uses
WordPress plugin upload behavior.
Without credentials,`shell` first runs the pre-auth SQLi-to-admin bridge,logs in as the generated
administrator,
then uploads the plugin shell.
```
./wp2shell.py shell http://target --user admin --password '<recovered>' --cmd id
./wp2shell.py shell http://target --user admin --password '<recovered>' -i # interactive shell
./wp2shell.py shell http://target --cmd id # pre-auth bridge
./wp2shell.py shell http://target -i # pre-auth interactive
```
`shell` uploads a plugin webshell (locked behind a random path and a per-run token) and prints its
path. The uploaded webshell is removed automatically. When the pre-auth bridge creates an
administrator,
that generated account is removed automatically after the shell session finishes.
## Options
|Option |Applies to |Description ||------------------- |---------- |-------------------------------------------------------------------- ||`--proxy URL` |all |Route traffic through an HTTP proxy (for example,
Burp). ||`--timeout N` |all |Request timeout in seconds. ||`--sleep N` |check |Delay used by the timing fallback for `--confirm-sqli`. ||`--samples N` |check |Timing pairs used by the timing fallback for `--confirm-sqli`. ||`--confirm-sqli` |check |
Also send an active SQLi confirmation payload. ||`--preset` |read |`fingerprint` or `users`. ||`--technique` |read |`auto` (default),`union` (in-band,forges a fake post),`error` (in-band,needs visible DB errors),or `blind`. ||`--query` |read |
A scalar SQL expression to read. ||`--prefix` |read |Database table prefix (default `wp_`). ||`--max-length N` |read |Maximum characters read per value (default 128). ||`--user` / `--password` |shell |Optional admin credentials;omit both to use the pre-auth bridge. ||
`--cmd` |shell |Command to run (omit when using `-i`). ||`-i` / `--interactive` |shell |Open an interactive shell after deploying. |## Remediation
Update to WordPress 7.0.2,or 6.9.5 if the site is on the 6.9 branch. Until then,block both `/wp-json/batch/v1` and the `rest_route=/batch/v1` query parameter at
the edge,
or require authentication for the batch endpoint via the
`rest_pre_dispatch` filter.
## Legal
For authorized security testing only. Use it exclusively against systems you own or have explicit
written permission to test. No warranty is provided and no liability is accepted for misuse.
## References
- WordPress 7.0.2 release announcement — <https://wordpress.org/news/2026/07/wordpress-7-0-2-release/>
- Searchlight Cyber wp2shell advisory — <https://slcyber.io/research-center/wp2shell-pre-authentication-rce-in-wordpress-core/>- Hackify — wp2shell technical analysis — <https://hackify.nl/en/blog/wp2shell-wordpress-core-rce/>- sergiointel/wp2shell-poc SQLi-to-admin bridge — <https://github.com/sergiointel/wp2shell-poc>- Elastic Security Labs — wp2shell detection &
IoCs — <https://www.elastic.co/security-labs/wp2shell-wordpress-rce-detection-elastic-defend>- Eye Security — wp2shell defenders guide — <https://labs.eye.security/wp2shell-defenders-guide/>⚔️ EXP 利用代码
截至分析时,Exploit-DB 未收录该 CVE 的公开利用代码。可利用上述 PoC 进行验证,或关注 Exploit-DB 更新。
🕵️ 检测指纹
当前规则库未收录针对该 CVE 的专用检测规则。建议:
- 根据漏洞根因编写 Nuclei 检测模板
- 在 WAF/IDS 中配置针对漏洞特征的规则
- 关注漏洞指纹库更新
🤖 本文由漏洞情报系统自动聚合生成 · 2026-08-05 12:01 · 数据源: NVD/GitHub-Advisory/OSV/CISA-KEV/Exploit-DB/PoC-in-GitHub + 检测规则库