flawopen.com/Teardowns/cve-2026-87902-wordpress-page-template-path-traversal
CVE-2026-87902:WordPress 页面模板路径遍历
未验证的解码后页面名称进入模板解析,导致未经身份验证的本地 PHP 文件包含;RCE 需要特定主题和 PHP 环境条件。[1]
💡 通俗易懂的原理解析 (ELI5)
想象一位取书前会检查请求的图书管理员。WordPress 检查了一条模板路径,却漏掉了解码后名称对应的路径;特殊标记可能让其离开主题目录。修复会验证名称,并将模板限制在允许目录内。[1], [2], [3]
核心概念与专有名词
路径遍历- 若不验证输入,
..等路径组件可能使文件访问越出预期目录。[1] 本地 PHP 文件包含- 加载服务器上已有的 PHP 文件;RCE 还需要其他条件。[1]
`validate_file()`- WordPress 函数,在使用路径前拒绝不允许的路径组件。[3]
框架映射
- 本指南所述弱点的 CWE 参考: CWE-98
- CISA KEV — 收录日期 2026-09-25 — CVE-2026-87902
根本原因剖析 (Root Cause)
在 get_page_template() 中,WordPress 解码 pagename 并加入 page-{name}.php,但未调用 validate_file();相邻分支则有此检查。因此路径可能越出主题目录。前提包括顶层 page-* 目录和可读本地 PHP 文件;RCE 还需额外 PHP/服务器条件。7.1.2 验证解码值,并在 locate_template() 限制解析路径。 WordPress 公告将此弱点归类为 CWE-98(未正确控制 PHP include/require 使用的文件名),并将发现及负责任披露归功于 Robert Ressl。[1], [2], [3], [7]
CVE-2026-87902 影响范围:暴露条件与有条件的 RCE
| 部署条件 | 一手来源确认的事实 | 运维操作 |
|---|---|---|
| 受影响的 WordPress Core 分支 | GHSA 按分支列出了受影响版本,范围从 4.7.0–4.7.36 到 7.1.0–7.1.1。对应修复版本从 4.7.37(最早的回移版本)到 7.1.2(当前分支),每个列出的分支都有单独的修复版本。 | 核对已安装的 Core 版本并应用对应分支的修复。低于 4.7 的版本应升级到受支持版本。 |
| 当前主题的目录结构 | 当前父主题或子主题必须包含一个名称以 page- 开头的顶层目录(例如 page-templates)。 | 检查当前启用的父主题和子主题目录。主题变更后重新核实这一条件。 |
| 可读取的本地 PHP 文件 | 所选 .php 文件必须存在于当前主题目录之外,并且 Web 服务器账户能够读取。 | 检查 Web 服务器可读取哪些本地 PHP 文件;仅凭 CVE 版本范围不能证明发生了文件包含。 |
| 有条件 RCE 的运行环境 | 公告说明只有满足额外 PHP/服务器条件时才可能导致 RCE。示例包括 register_argc_argv=On 时的 pearcmd.php、官方 PHP Docker 镜像,以及运行 PHP 8.5 之前版本的默认 cPanel 配置。 | 评估实际 PHP 运行环境和配置。WordPress 版本受影响本身不能证明 RCE 或系统已被攻陷。 |
攻击执行流程分解
源代码对比:漏洞与安全实现
此代码片段对比官方 7.1.1 和 7.1.2 标签。补丁在解码后验证值;同一版本还在 locate_template() 限制解析路径。这是上游源码改动,不是完整独立补丁,也不是漏洞利用演示。[2], [3]
源码摘录: wp-includes/template.php — get_page_template()
存在漏洞的实现
if ( $pagename ) {
$pagename_decoded = urldecode( $pagename );
if ( $pagename_decoded !== $pagename ) {
$templates[] = "page-{$pagename_decoded}.php";
}
}
加固后的安全修复
if ( $pagename ) {
$pagename_decoded = urldecode( $pagename );
if ( $pagename_decoded !== $pagename && 0 === validate_file( $pagename_decoded ) ) {
$templates[] = "page-{$pagename_decoded}.php";
}
}