🎯 CVE-2026-63030 深度技术分析:漏洞根因 · PoC/EXP · 检测指纹
CVE-2026-63030 深度技术分析
摘要:CVE-2026-63030 是 WordPress 核心中发现的一处高危 REST API 批处理端点路由混淆(Route Confusion)漏洞,CVSS 评分为 9.8(Critical)。该漏洞影响 WordPress 6.9.0 至 6.9.4 以及 7.0.0 至 7.0.1 版本,攻击者可在未认证的情况下,通过构造特制的批处理子请求,使 REST API 将某个子请求分派到错误的处理程序上,进而绕过权限检查。当此漏洞与另一个核心漏洞 CVE-2026-60137(author__not_in WP_Query SQL 注入)链接时,攻击者可实现从 SQL 注入到远程代码执行(RCE)的完整攻击链。CISA 已将该漏洞列入已知被利用漏洞目录(KEV),要求联邦机构限期修复,实际威胁严重。
📌 漏洞概述
CVE-2026-63030 是 WordPress Core 中的“解释冲突”(Interpretation Conflict)漏洞,由 REST API 批处理端点在路由匹配时产生数组索引错位导致。该漏洞允许未认证的远程攻击者构造恶意 HTTP 请求,使其在批处理上下文中以错误的处理程序执行子请求,从而绕过正常的验证与权限检查。漏洞基础数据如下:
- CVE 编号:CVE-2026-63030
- CVSS 评分:9.8(Critical)
- 漏洞类型:路由混淆 / 认证绕过 / 逻辑错误
- 受影响版本:WordPress 6.9.0 – 6.9.4、7.0.0 – 7.0.1
- 不受影响版本:WordPress 6.9.5、7.0.2 及更高版本;6.8.5 及更早版本不受影响
- 利用条件:无需认证,通过 HTTP 请求即可触发
- 关联漏洞:CVE-2026-60137(
author__not_inWP_Query SQL 注入)
该漏洞最初由 Searchlight Cyber 在 wp2shell 安全公告中披露,随后 CISA 将其纳入已知被利用漏洞(KEV)目录,表明野外已出现实际攻击活动。
🔬 漏洞根因分析
漏洞根因位于 WordPress REST API 的批处理端点实现中。WordPress 提供的 /batch/v1 端点允许客户端在一个请求中封装多个子请求(sub-requests),每个子请求都包含独立的方法、路径和请求体。按照设计,每个子请求会像普通 REST 请求一样被单独验证和授权。然而,serve_batch_request_v1() 函数在处理这些子请求时,其内部逻辑存在一个危险的数组索引管理缺陷。
具体而言,serve_batch_request_v1() 为批处理中的每个子请求构建了两个并行数组:$matches 和 $validation。$matches 数组保存每个子请求路由匹配到的处理程序(handler),$validation 数组保存每个子请求的验证结果(例如是否需要认证、参数是否合法等)。这两个数组本应通过相同的数组下标关联到同一个子请求。但在实现中,当某个子请求的路径解析失败(也就是 wp_parse_url() 返回 false)时,该子请求的结果被追加到了 $validation 数组,却没有追加到 $matches 数组中。这导致两个数组的长度不再相等,后续通过偏移量(offset)访问时,两个数组的对应关系发生错位——一个子请求的路由匹配结果被用于另一个子请求的验证结果之上,最终形成“路由混淆”。
更直接地说,攻击者可以构造这样一个批处理请求:第一个子请求是恶意构造的 /wp/v2/posts 请求,它携带了一个 requests 请求体(该请求体本身包含一个批处理操作)。当该请求被处理时,由于路由混淆,它被分派到了批处理端点自身的处理程序(即 serve_batch_request_v1),而此前对该子请求的验证(如权限检查)却基于原先的路由判断。换句话说,一个看似是普通文章列表(posts)请求,其内部却被当作新的批处理请求来解释。由于第一次验证认为它是正常的 posts 请求(不涉及高风险操作),所以权限检查被放行;但当它作为批处理程序执行时,其携带的 requests 数组会被再次迭代,攻击者便可以在嵌套的批处理中注入任意 REST 子请求,而这些子请求直接以批处理上下文执行,不再经过严格的权限验证,由此实现了未认证的任意 REST 调用。
在 wp2shell 攻击链中,该路由混淆被用来进一步触发 CVE-2026-60137。CVE-2026-60137 是 WP_Query 中 author__not_in 参数存在的 SQL 注入漏洞。由于 WordPress 的 REST API 在查询文章时允许传入一系列参数,攻击者通过批处理端点构造一个带有恶意 author__not_in 参数的请求,该参数被传递给 WP_Query,最终在 SQL 查询中造成注入。由于路由混淆的存在,原本需要高权限的 SQL 注入调用可以在未认证的情况下执行。攻击者利用 SQL 注入读取数据库中的管理员密钥并创建新的管理员账户,或者直接写入恶意插件文件,最终实现远程代码执行(RCE)。
值得注意的是,该漏洞利用并不需要公开的 EXP,已有独立 PoC 实现了从 SQL 注入检测到获取 shell 的完整流程。PoC 将这一原语“嵌套”两次,以最大程度地绕过现有验证:第一次嵌套实现路由混淆触发批处理逻辑;第二次嵌套则利用批处理逻辑执行恶意的 author__not_in SQL 注入载荷,从而到达 WP_Query 的脆弱路径。
💥 影响与危害
CVE-2026-63030 的影响范围极大,因为攻击链是 未认证 的,攻击者无需任何账户权限即可发起攻击。该漏洞与 CVE-2026-60137 链接后,攻击者能够实现以下危害:
- SQL 注入:未认证读取 WordPress 数据库中的任意数据,包括用户凭据哈希、选项值、文章内容等敏感信息。
- 远程代码执行(RCE):通过 SQL 注入升级为管理员账户,随后在 WordPress 后台利用“主题/插件编辑器”或直接写入 PHP 后门,从而在服务器上执行任意系统命令。
- 网站完全控制:攻击者一旦获得 shell,即可篡改网页内容、植入恶意跳转、挂马,或利用服务器资源进行进一步攻击(如发起 DDoS、发送钓鱼邮件)。
- 内网渗透:若目标服务器位于内网,攻击者可将其用作跳板,对内部网络横向移动。
- 供应链风险:WordPress 生态庞大,大量企业网站和内容系统依赖其核心,此漏洞可被批量扫描利用,造成大范围入侵事件。
- 已被野外利用:CISA KEV 目录确认该漏洞已被实际利用,修复的紧迫性极高。
根据 CISA 的 BOD 26-04 要求,联邦机构必须在规定时间内修补此漏洞;对于所有使用受影响版本 WordPress 的企业和个人站点,也应视其为“已处于被攻击风险中”,立即安排升级。
🛡️ 修复与缓解
WordPress 官方已在以下版本中修复该漏洞:
- WordPress 6.9.5(针对 6.9.x 分支)
- WordPress 7.0.2(针对 7.0.x 分支)
- WordPress 6.8.5 及更早版本不受影响,无需特殊处理
因此,最直接、最有效的缓解措施是 立即将 WordPress 升级到 6.9.5 或 7.0.2 及更高版本。同时,建议采取以下防御措施降低风险:
- 立即升级:若无法立即升级,应先禁用或屏蔽
/batch/v1REST 端点的外部访问(例如通过 Web 防火墙规则阻止path=/batch/*的请求),并同时修复 CVE-2026-60137(该漏洞在 6.9.5 和 7.0.2 中一并修复)。 - 监控异常请求:检查 Web 访问日志中是否存在大量对
/batch/v1的未授权请求、携带复杂requests参数的 POST 请求,以及异常的用户名枚举行为。 - 数据库加固:即使发生 SQL 注入,限制数据库账号的权限可降低影响;尽量为 WordPress 数据库使用最小权限账号,不要使用具有
FILE权限的账号。 - 文件完整性监控:对 WordPress 核心文件目录(如
wp-admin、wp-includes以及wp-content/plugins)进行监控,及时发现恶意 PHP 文件写入。 - 启用 WAF:部署 Web 应用防火墙,并订阅针对 wp2shell 攻击链的检测规则(Elastic Security 和 Eye Security 已发布相关检测指南)。
- 停用不需要的 REST API:如果业务不需要 REST 批处理功能,可通过插件或代码完全禁用 REST API,或者禁用特定路由。
鉴于该漏洞已被加入 CISA KEV 目录且利用链已公开,任何使用受影响版本 WordPress 的站点都存在被实时攻击的严重风险。管理员应停止迟疑,立即执行升级、确认修复,并着手排查是否已有入侵痕迹。
🧪 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-03 12:01 · 数据源: NVD/GitHub-Advisory/OSV/CISA-KEV/Exploit-DB/PoC-in-GitHub + 检测规则库