Announcement

👇Official Account👇

Welcome to join the group & private message

Article first/tail QR code

Skip to content

wp2shell 深度拆解:WordPress 近十年首个核心 pre-auth RCE 利用链(CVE-2026-63030 + CVE-2026-60137)

WordPress 核心上一次出现无需插件配合的未认证 RCE 级别漏洞,要追溯到十年前。2026 年 7 月 17 日披露的 wp2shell 打破了这个纪录:两条 CVE(REST API 路由混淆 CVSS 9.8 + WP_Query SQL 注入 CVSS 5.9)串联成完整的 pre-auth 管理员创建链,默认配置、无需任何插件即可利用。披露数小时内开始大规模利用,Wordfence 防火墙累计拦截超过 1100 万次攻击。本文拆解利用链的每一环,以及它给所有 PHP 框架留下的教训。

漏洞速览

项目CVE-2026-63030CVE-2026-60137
类型REST API 批量请求路由混淆WP_Query SQL 注入
CVSS(CNA)9.8 Critical5.9 Medium(CISA-ADP 评 9.1!)
位置WP_REST_Server::serve_batch_request_v1()author__not_in 参数处理
影响子请求被分派给错误的 handler,绕过权限检查标量字符串绕过过滤直达 SQL 拼接
影响版本6.9.0–6.9.4 / 7.0.0–7.0.16.8.0–7.0.1
修复版本7.0.2 / 6.9.57.0.2 / 6.9.5 / 6.8.6

利用链成立的版本交集是 6.9.x 和 7.0.x;6.8.x 只受 SQL 注入影响(链不成立),但同样必须升级到 6.8.6。

发现者是 Assetnote / Searchlight Cyber 的安全研究员 Adam Kues——他使用 OpenAI 的 GPT-5.6 Sol 辅助代码审计,整个过程耗时约 10 小时、计算成本约 25 美元。AI 辅助漏洞发现从概念验证进入生产力的标志性案例(本站此前拆解过 GPT-5.6 Sol 自身沙箱机制的相关事件,见文末延伸阅读)。CVE-2026-60137 由 TF1T、dtro 和 haongo 独立报告。

第一环:CVE-2026-63030 路由混淆——两个数组的下标错位

WordPress 的 /wp-json/batch/v1 端点允许客户端把多个 REST 子请求打包进一次 HTTP 请求。serve_batch_request_v1() 的内部实现用了两个并行数组$validation(每个子请求的校验结果)和 $matches(每个子请求匹配到的路由与 handler)。

问题出在错误处理的不对称上:当某个子请求的 URL 解析失败(wp_parse_url() 出错)时,错误对象会写入 $validation 数组,$matches 数组里没有对应条目。校验循环和分派循环是分开跑的,两个数组从这一刻起发生下标错位——后面的子请求会拿到前面子请求的路由和 handler

一句话总结这个原语:请求 A 的数据跑在了请求 B 的处理器上

攻击者用它实现两个绕过:

  1. 绕过 batch schema 对内层 GET 的禁令:外层 batch 用一个"widget 形状"的请求去调用 batch 回调;
  2. 绕过路由级权限检查:内层用一个按 widget 路由校验通过的请求,去触发 posts 集合的 get_items() 处理器。

第二环:CVE-2026-60137 SQL 注入——字符串绕过类型检查

WP_Queryauthor__not_in 参数设计上接受数组(作者 ID 列表),WordPress 只在参数是数组时才做整数化过滤:

php
// 简化示意(漏洞逻辑)
if ( is_array( $q['author__not_in'] ) ) {
    // 数组路径:逐个 sanitize 整数化 —— 安全
    $q['author__not_in'] = array_map( 'absint', $q['author__not_in'] );
}
// 字符串路径:没有 else 分支的过滤!
// 直接拼进 SQL:
// AND wp_posts.post_author NOT IN (<攻击者控制的字符串>)

传入数组会被清洗,传入字符串则原样拼接——类型检查的漏网之鱼。攻击路径的巧妙之处在于怎么把字符串塞进去:

GET /wp/v2/categories?author_exclude=<SQL 载荷>

categories 路由没有注册 author_exclude 参数,所以它原样通过该路由的参数校验;然后靠第一环的路由混淆,让这个请求实际由 postsget_items() 处理器执行——而 posts 路由会把 author_exclude 映射到存在漏洞的 author__not_in 查询变量。SQL 注入的载荷就这样"借道"抵达了目标。

完整利用链:一个 HTTP 请求,五级跳

把两环串起来,攻击者用单个物理 HTTP 请求完成从匿名到管理员:

攻击者(无任何凭证)

   │ ① POST /wp-json/batch/v1
   │    外层 batch:widget 形状请求 → 触发 batch 回调(绕过内层 GET 禁令)
   │    内层 batch:widget 校验请求 → 实际由 posts get_items() 执行
   │    参数:author_exclude = "' UNION SELECT <23 列伪造 wp_posts 行>-- -"

WordPress 核心
   │ ② 路由混淆生效,SQL 注入触发
   │ ③ UNION 注入返回伪造的 wp_posts 行(按物理 23 列顺序构造)
   │ ④ WordPress 把伪造行转成 WP_Post 对象并写入缓存
   │ ⑤ Core 更新逻辑消费伪造对象:
   │    以真实管理员的 user_id 发布一个 Customizer changeset
   │    → 短暂"借用"管理员身份

⑥ 攻击者创建自己的管理员账号


⑦ 上传恶意插件 / web shell → 服务器 RCE

这里有个反直觉的设计点:SQL 注入本身是 SELECT-only 的——它不能直接写数据库。攻击者的解法是注入伪造的 post 行,让 WordPress 自己的对象缓存和更新逻辑把伪造数据当成真数据消费,再借 Customizer changeset 的发布流程冒用管理员身份。这把"只读注入"升级成了"身份冒用",也是整条链最值得安全研究者细品的一环。

时间线与在野利用

  • 2026-07-17:漏洞披露,6.8.6 / 6.9.5 / 7.0.2 同步发布修复(7.1 Beta2 同样修复);
  • 披露当天:攻击量较前一日暴涨 277 倍
  • 数小时内:公开 PoC 出现在 GitHub,德国联邦信息安全局(BSI)发布橙色级别警告;
  • 累计:约 195 万次攻击记录,Wordfence 单防火墙拦截超 1100 万次;
  • 2026-08-04:日本 Cyber Security Cloud 发布在野利用报告,WordPress 6.8.0–7.1 Beta 全版本范围受影响;
  • 2026-09:PoC 仍在流传,vulhub 提供了 Docker 复现环境,未打补丁站点仍在被持续扫描。

评分分歧:同一个 CVE,5.9 与 9.1 之间差着什么

这次事件里有个值得所有做漏洞管理的人记住的插曲:CVE-2026-60137 的 CNA(WPScan)给的 CVSS 是 5.9 Medium,而 CISA-ADP 对同一个 CVE 评了 9.1 Critical——差 3.2 分、跨两个等级。

分歧的根源在于视角:单看"SQL 注入 + 只读"确实是 Medium 量级;但放在"可被路由混淆无认证触发 + 可组成 pre-auth RCE 链"的上下文里就是 Critical。更危险的是 6.8.x 系列只受 5.9 分的 SQL 注入影响、不受路由混淆影响——如果你的补丁优先级流程按 CVSS 分数排序,6.8.x 站点的这条注入可能被排到低优先级,而这正是分数驱动 triage 的经典盲区。CVSS 评的是"漏洞在真空中的最大危害",不是"它在你的攻击面里的实际位置"。

防御清单

立即动作(所有 WordPress 站点):

  1. 升级到 7.0.2 / 6.9.5 / 6.8.6 及以上(8.x 分支用户等后续正常版本,不受影响);
  2. 无法立即升级的站点:WAF 规则拦截含 author_exclude 参数的 /wp/v2/categories 请求与异常的 /batch/v1 嵌套请求;限制 /wp-json/batch/v1 的访问来源;
  3. 排查 IOC:核对管理员账号列表、检查异常 REST API 访问日志、留意陌生登录尝试与新增插件(BSI 警告的三项检查)。

给 PHP 框架与插件开发者的工程教训:

  1. 批量/嵌套请求端点是高危区:校验数组和分派数组必须同源同构——要么用单一结构体数组(每项自带 validation + match),要么在错误路径上同步两个数组。任何"两个并行数组靠下标对齐"的设计都是错位漏洞的温床;
  2. 参数过滤不要依赖类型假设is_array() 才过滤的写法意味着"传字符串就能绕过"。对外部输入统一走入口清洗(WordPress 的 sanitize_* / Laravel 的 FormRequest),而不是按预期类型分派;
  3. 未注册参数要显式拒绝:categories 路由不认识 author_exclude 却放行了它——未知参数静默透传,加上参数映射的间接层(author_exclude → author__not_in),让污染数据流穿过了两道"看起来存在"的校验;
  4. 对象反序列化/ hydrate 边界要验证数据来源:WP_Post 从查询结果构造时信任了行数据——任何"把数据库行变成领域对象再驱动业务逻辑"的路径,都要意识到注入者可以伪造行。

小结

wp2shell 的技术栈没有超出"路由混淆 + SQL 注入"这两个经典类别的组合,但它的每个环节都踩在工程实践的真实软肋上:并行数组的下标耦合、按类型分派的过滤逻辑、未注册参数的静默透传、以及"SELECT-only 注入危害有限"的误判。加上 AI 辅助审计把发现成本压到 25 美元量级——这类漏洞的发现速度只会越来越快。如果你的业务还挂在 6.x 旧版本上,今天就是升级日。

参考资料

上次更新于: