无需密码,一个请求就能拿下你的服务器,深度详解近几年 WordPress 最严重的漏洞「wp2shell」
昨天和大家说了「WordPress 发布紧急安全更新 7.0.2,高危漏洞“wp2shell”曝光,黑客无需密码即可控制网站」,可能大家还没有感觉到这个漏洞有多严重,这里首先还是提醒大家,还没升级的朋友,赶快去升级,再回来看这篇。
今天这篇就给大家详细分析一下这个漏洞,为什么安全圈都说它是近几年 WordPress 核心最严重的漏洞之一,它让匿名且完全未经身份验证的攻击者在未安装任何第三方插件的默认 WordPress 安装环境中运行任意代码。
换句话说,这是一个 WordPress 核心代码中的预身份验证远程代码执行(RCE)漏洞,黑客使用该漏洞,无需密码,一个请求就能控制你的服务器。
一把枪,一张通行证
wp2shell 其实不是一个漏洞,而是两个漏洞搭伙作案:
| CVE 编号 | 漏洞类型 | 评分 | 角色 |
|---|---|---|---|
| CVE-2026-60137 | WP_Query SQL 注入 | 9.1 | 武器 |
| CVE-2026-63030 | REST /batch/v1 路由混淆 | 7.5 | 通行证 |
如果把这两个漏洞单独拿出来,都成不了大事。
首先 CVE-2026-60137 ,WP_Query SQL 注入,9.1 分看着很吓人吧?但是触发它的路由原本要登录才能访问,这样攻击者连门都摸不到,可以理解为等于一把没开保险的枪。
而 CVE-2026-63030,REST /batch/v1 路由混淆,说白了就是个权限绕过,可是绕过去之后没有能打的靶子,也掀不起浪。
但是这俩一配合,路由混淆负责把恶意输入免认证地送到注入点门口,SQL 注入负责开火。枪和通行证都齐了。
攻击者无需任何前提条件,只需发送一个匿名 HTTP 请求就能控制服务器。
先说枪:WP_Query 里的 SQL 注入
CVE 编号:CVE-2026-60137
问题出在 WP_Query 的 author__not_in 参数上,这是个典型的 PHP 类型转换(type juggling)问题。
这个参数本应是一个 ID 数组,代码假定了你传的就是数组,并将这些值直接拼接到 SQL 查询中,因为它依赖于整数数组本质上是安全的这一事实。
那如果传的不是数组,而是一段精心构造的字符串呢?针对数组的那套清理操作逻辑会被绕过去,你传什么,SQL 里就拼什么,注入就这么成了。
// Expected: author__not_in = [1, 2, 3] -> "AND wp_posts.post_author NOT IN (1,2,3)"
// Supplied: author__not_in = "1) AND 1=0 UNION ALL SELECT ... -- -"
// the string is concatenated as-is into the SQL
$where .= " AND {$wpdb->posts}.post_author NOT IN ({$author__not_in})";
如果注入的 SQL 段是一个经典的 UNION 注入,比如下面的 SQL 段,通过 SQL 注释把 per_page=-1 禁用了,这样可以确保注入执行:
author__not_in=1) AND 1=0 UNION ALL SELECT <23 columns> -- -
per_page=-1
这种注入漏洞自 WordPress 6.8 版本就埋下了,但它本身的作用范围有限,因为暴露的参数 author__not_in 需要身份验证才能使用,所以一直没炸。
要匿名访问该漏洞,需要一个通行证,而这个通行证就是第二部分:
再说通行证:批处理接口的路由混淆
CVE 编号:CVE-2026-63030
WordPress 5.6 版本起,添加了一个批量操作的 REST 接口:/wp-json/batch/v1,它的作用是把多个 REST 请求打包成一个 HTTP 调用,服务器按顺序挨个处理。
6.9 版本引入的缺陷是这两个数组不同步。当一个子请求触发错误时,这两个数组会向右移动一位。结果是,只要某个子请求触发了错误,两个数组就错开一格,结果造成第 N 个子请求,跑在了第 N-1 个子请求的处理程序和权限上下文里。
听到这里你可能已经反应过来了:这不就是"挂羊头卖狗肉"吗?校验的时候看的是 A 请求,执行的时候跑的却是 B 请求。
实际利用更骚:攻击者构造嵌套的批处理请求(double batch),让本来需要权限的 GET /wp/v2/widgets,在公开的 posts::get_items() 处理程序底下执行。白名单检查形同虚设,恶意输入就这么在完全没登录的状态下,送到了 SQL 注入点。
简单说:枪,上膛了。
完整攻击链:从一个请求到 WebShell
整个攻击只需要一个匿名 HTTP 请求,我们一步步看:
第一步,绕过认证:
向 /wp-json/batch/v1 发一个嵌套的格式错误的批处理请求,靠路由混淆让公开路由给敏感路由打掩护。
给大家看看我捕捉到的、正在扫描我网站的这类请求:

第二步,触发注入:
恶意输入到达 author__not_in,从上图中可以看出,在 REST 批量请求中是 author_exclude,传到 WP_Query 里就是 author__not_in。
payload 是经典的 UNION 注入,注意后面有「--+-」这个小细节,URL 编码里 + 就是空格,所以它实际就是 SQL 注释符 -- -,它的作用是注释后面的 SQL 字段,保证整段注入完整执行。
第三步,伪造数据行,拿到写入能力:
到这一步还只是读库,攻击者想要的是写。于是他们往 wp_posts / wp_options 里伪造带 <site-url> 标记的行,骗 WordPress 自己去创建真实的 oEmbed 缓存条目和 transient。而且这些条目的标识符是可以算出来的(post_name = md5(url + attributes)),攻击者对写进去的东西了如指掌。
第四步,提权到管理员上下文:
伪造一条带着真实管理员 user_id 的 customize_changeset 记录,再配合 post_type=request 的行,触发在管理员上下文里执行的 parse_request。到这里,攻击者干的活已经是"以管理员身份"在干了。
第五步,创建管理员,植入 WebShell:
还是那同一个批处理请求,里面再塞一个 POST /wp/v2/users,直接建一个新管理员账号,然后自毁插件执行系统命令。
游戏结束,攻击者拿到 shell,这也就是 "wp2shell" 名字的由来。
为什么说它极度严重
把几个事实摆在一起,你就知道分量了:
- 不用登录:匿名攻击,不需要账号,不需要任何前置条件。
- 默认安装就中招:不挑插件不挑主题,刚装好的纯净 WordPress 就会被攻陷。
- 一个请求打完收工:写个脚本批量扫,成本约等于零。
- PoC 已经满天飞。7 月 18 日 GitHub 上就有可用的 PoC 和现成的 Docker 复现环境(wp2shell-lab),脚本小子都能直接用。
- 盘子太大:全世界超过 40% 的网站跑在 WordPress 上。
可能有人要问:我站点用了 Redis / Memcached 持久化对象缓存,是不是就没事了?
只能算半个没事。持久化对象缓存会改变 transient 的写入路径,可能让 RCE 这条链断掉。但它对底层的 SQL 注入一点办法都没有,攻击者照样能免认证读你的数据库。
再啰嗦一遍:WAF 只是创可贴
昨天文章除了提醒大家升级,也给了临时方案:封批处理路由、用 rest_pre_dispatch 禁用 Batch API。
add_filter('rest_pre_dispatch', function( $result, $server, $request){
if('/batch/v1' !== strtolower(untrailingslashit($request->get_route()))){
return $result;
}
return new WP_Error('rest_batch_disabled', '批量 API 已禁用', ['status'=>401]);
}, -1000, 3 );
但是这里必须再敲一次黑板:WAF 封锁是临时措施,不是修复。
编码一下、换换大小写、变个路由形式,WAF 规则就可能被绕过去。而且它根本管不了从 6.8 就存在的那个底层 SQL 注入。临时方案唯一的意义是给你争取升级的时间,真正关门的只有补丁。
升级完别急着走,顺手查一遍入侵痕迹:
- 用户列表里有没有不认识的管理员;
- 插件列表里有没有没装过的插件;
wp_posts/wp_options里有没有可疑的数据行;- 访问日志里有没有打
/batch/v1的异常请求。
最后
wp2shell 最让人后背发凉的地方,是它的"完美配合":一个 9.1 分的注入躺了一年多没人够得着,一个 7.5 分的路由混淆恰好给它递上了免认证的入口,最后拼成一条"无需登录、一个请求、直达 WebShell"的完整攻击链。该做的还是昨天那句话:马上升级,然后查一遍入侵痕迹,别指望 WAF 替你扛。
最后也多说一句:现在越来越多的人用 AI 写 WordPress 插件,方便是真方便,但某种程度上也是在给自己的站点埋雷。因为 AI 生成的代码追求的是"能跑",不是"安全":输入不校验、SQL 直接拼、权限检查和 nonce 验证这些防护措施,它经常会"忘记"写。更要命的是,攻击者也在用 AI——以前分析一个漏洞、写出一个 exploit 要好几天,现在几个小时就够了。像 wp2shell 这种核心漏洞你防不住,只能等官方补丁;但你装到自己站点上的每一行代码,责任都是自己的。AI 写的代码,上线前一定要过一遍,尤其是涉及数据库查询、文件上传和权限判断的地方。