夜雨聆风学习资料网

ARTICLE · 1089359

WordPress 页面模板存在文件包含漏洞(POC已公开)CVE-2026-87902

WordPress 页面模板存在文件包含漏洞(POC已公开)CVE-2026-87902

一、漏洞描述

WordPress 的页面模板从一组候选名里挑,主题目录下命中第一个存在的就直接用。

CVE-2026-87902 出在这组候选名的拼接上。匿名请求送进来的 pagename 值先过清洗,之后才被解码,解码出来的 ../ 段把候选名带到了主题目录之外。

触发要凑几样东西。主题目录根下得有一个名字以 page- 开头的顶层目录,存在即可,不要求可写。页面由数字 page_id 选中,且没有指定自定义模板。目标 php 文件存在、Web 账号读得到。

官方点名了自带这类目录的老主题,Twenty Twelve 与 Twenty Fourteen,还有 Neve、Hestia、Sydney 几个第三方主题。

走到代码执行还要再凑三样,服务器上有可读的 pearcmd.php,PHP 的 register_argc_argv 这个开关是打开的,另外还得有一个能写的目录,三样缺一不可。

公告提到官方 PHP 镜像与 PHP 8.5 之前的 cPanel 默认配置落在这几条里。

公开验证脚本在 7.0.2 加 PHP 8.3.33 的容器里跑通了整条链,两次匿名 POST,写出的文件 1219 字节,标记在响应里出现 12 次。

CVE 编号
CVE-2026-87902
漏洞类型
页面模板路径穿越导致的本地 PHP 文件包含,CWE-98
影响组件
WordPress 核心,wp-includes/template.php 的 get_page_template 与 locate_template
受影响版本
4.7.0 起每个分支的全部小版本,7.1.0 ~ 7.1.1、7.0.0 ~ 7.0.5、6.9.0 ~ 6.9.8 等,公告共列 25 个区间
修复版本
7.1.2、7.0.6、6.9.9、6.8.10、6.7.9,更早分支各有对应版本,最低 4.7.37
触发前提
主题根目录存在以 page- 开头的顶层目录,页面由 page_id 选中且未指定自定义模板,目标 php 文件可读
利用条件
匿名即可,无需用户交互,POST 请求就能送出路由参数
危害面
Web 账号可读的本地 php 文件会被包含进来执行,凑齐环境侧条件时可发展为代码执行
CVSS
公告定为 critical 且未附向量,公开验证脚本记 8.1 (v3.1) 与 9.2 (v4.0)

二、漏洞原理

请求从 parse_request 进来。pagename 和 page_id 两个都是公开查询变量,表单体里带的值会被读进查询对象,匿名就能送。

清洗先走一遍。class-wp-query.php 把 pagename 交给 wp_basename 处理,本意是砍掉路径里的目录部分。

能穿过清洗是因为编码套了两层。请求体里的 %252f 被 PHP 解一层成 %2f,wp_basename 又 urlencode 了一遍,替换斜杠用的 %2F 就对不上了。

// wp-includes/formatting.php 7.0.2
function wp_basename( $path, $suffix = '' ) {
    return urldecode( basename( str_replace( array( '%2F', '%5C' ), '/', urlencode( $path ) ), $suffix ) );
}

// wp-includes/formatting.php 7.0.2 sanitize_title_with_dashes 开头三步
$title = preg_replace( '|%([a-fA-F0-9][a-fA-F0-9])|', '---$1---', $title );
$title = str_replace( '%', '', $title );
$title = preg_replace( '|---([a-fA-F0-9][a-fA-F0-9])---|', '%$1', $title );

sanitize_title_for_query 落到 sanitize_title_with_dashes,开头三步把百分号加两位十六进制的写法保护起来,只清掉落单的百分号,其余字符过一遍白名单。

%2f 与 %2e 就整体留了下来,句点替换碰不到。

本机按 7.0.2 的源码把这两个函数抄出来跑了一遍,下面是从请求体一路走到候选名的结果。

请求体里的 pagename templates%252f%252e%252e%252f...%252fpearcmd
PHP 解一层之后 templates%2f%2e%2e%2f...%2fpearcmd
清洗之后 templates%2f%2e%2e%2f...%2fpearcmd
模板处 urldecode templates/../../../../../../../usr/local/lib/php/pearcmd
拼上前缀与后缀 page-templates/../../../../../../../usr/local/lib/php/pearcmd.php

页面本身也得查得出来。page_id 那一段给 where 用的是赋值,前面按 pagename 查不到留下的条件被整段覆盖,查询照旧落在真实的已发布页面上,pagename 的值原样留在查询对象里。

// wp-includes/class-wp-query.php 7.0.2 2266 行起
if ( $query_vars['page_id'] ) {
    if ( ( 'page' !== get_option( 'show_on_front' ) ) || ( get_option( 'page_for_posts' ) != $query_vars['page_id'] ) ) {
        $query_vars['p'] = $query_vars['page_id'];
        $where = " AND {$wpdb->posts}.ID = " . $query_vars['page_id'];
    }
}

到拼模板名那一步,get_page_template 对 pagename 做了一次 urldecode,解码前后不一样就把解码后的值也加进候选。

// wp-includes/template.php 7.0.2 get_page_template()
if ( $pagename ) {
    $pagename_decoded = urldecode( $pagename );
    if ( $pagename_decoded !== $pagename ) {
        $templates[] = "page-{$pagename_decoded}.php";
    }
    $templates[] = "page-{$pagename}.php";
}

// wp-includes/template.php 7.0.2 locate_template()
if ( file_exists( $wp_stylesheet_path . '/' . $template_name ) ) {
    $located = $wp_stylesheet_path . '/' . $template_name;
    break;
}

// wp-includes/template-loader.php 7.0.2 116 行起
$template = $is_stringy ? realpath( (string) $template ) : null;
if (
    is_string( $template ) &&
    ( str_ends_with( $template, '.php' ) || str_ends_with( $template, '.html' ) ) &&
    is_file( $template ) &&
    is_readable( $template )
) {
    include $template;
}

候选名拼到主题根目录后面,locate_template 只问文件在不在,找到就停下。

template-loader 拿到后做一次 realpath,接下去查后缀、是不是文件、读不读得到。路径是否还在主题目录内这一步被跳过了。

本机复算这一段,拼出来的路径在 file_exists 里返回真,realpath 落在主题目录之外,带 .. 的路径被原样接受,include 就此发生。

用 POST 也是必需的。redirect_canonical 开头就对非 GET 与 HEAD 的请求直接返回,规范化跳转不做,查询串得以原样留给 PEAR 拆参数。

代码执行那一跳借的是 PHP 自带的老包管理组件 PEAR。

pearcmd.php 被包含进来后,config-create 把查询串里带的参数写进一份配置文件,写入内容里可以带 PHP 开始标记,第二次请求再把这份文件包含进来。

载荷里引号全部省掉。Web 层的转义会把引号变成带反斜杠的形式,脚本改用 chr 拼字符串绕开这一步。

PEAR 要求 config-create 的根路径是绝对路径,脚本索性把要执行的 PHP 整体塞进这个路径,PEAR 序列化配置时把它原样写进文件。

补在 7.0.6 与 7.1.2 里的改动分两处。

// wp-includes/template.php 7.0.6 get_page_template()
if ( $pagename_decoded !== $pagename && 0 === validate_file( $pagename_decoded ) ) {
    $templates[] = "page-{$pagename_decoded}.php";
}

// 7.0.6 新增 _wp_is_template_path_allowed(),先查 .. 再比真实路径
if ( 0 === preg_match( '#(?:^|/)\.\.[. ]*(?:/|$)#', wp_normalize_path( $path ) ) ) {
    return true;
}

// 命中 .. 时改用 realpath 与主题目录比对,两侧都带尾斜杠
if ( str_starts_with( $real_path, trailingslashit( wp_normalize_path( $real_directory ) ) ) ) {
    return true;
}

validate_file 在这里起作用,路径里出现两次以上的 ../ 返回 1,解码后的候选名直接被丢掉。

第二处落在 locate_template。原来的写法命中第一个存在的文件就 break,改完之后先收集候选,再用新函数逐个判一次,不通过的继续往下找。

三、修复建议

按所在分支升到对应版本,7.1 线升 7.1.2,7.0 线升 7.0.6,6.9 线升 6.9.9,6.8 线升 6.8.10。更早的分支查公告里对应的那一行,最低是 4.7.37。

近期值得重点关注的漏洞预警

【紧急漏洞预警】nacos爆出权限绕过漏洞,可以创建管理员账户,经过分析有点出处(已复现,exp脚本已编写)
WordPress插件Forminator未认证短代码执行漏洞(poc已公开)CVE-2026-92229
WordPress插件Authorizer存在权限提升漏洞(POC已公开)CVE-2026-81294
WordPress插件MStoreAPI存在认证绕过漏洞(POC已公开)CVE-2026-13447
CakePHP爆出SQL注入漏洞(POC已公开)CVE-2026-77635
Node.js模块proxy-addr爆出严重漏洞CVE-2026-90711
Langflow爆出代码执行漏洞CVE-2026-17633  (POC已公开)
Apache CVE-2026-42536,补它只用一行,洞龄十四年
【紧急】搜狗输入法爆出远程代码执行漏洞CVE-2026-51990  ,已被在野利用
【紧急】无需登录就能获取 GitLab 的任意文件数据(附POC)
【紧急】JumpServer堡垒机被公布Access Key泄露漏洞,可以实现提权
Spring Cloud Azure公布严重漏洞 无需任何权限即可取得登录会话
【漏洞POC已公开】Linux RDS 爆出提权漏洞 一块内存放两次,root就到手了
Tenda光猫爆出严重漏洞,目前无补丁(POC已公布
13 行补丁,堵住Linux能拿 root 的内核洞(EXP已公开)
48 字节,换走一台 Windows 的 SYSTEM(CVE-2026-62735漏洞POC已公开)
【漏洞预警】linux删个网桥就能提权root?目前没有补丁(POC已公开)
【漏洞预警】Linux三年前补上的洞,又一次被挖开了
【情报】支付宝8.2亿数据泄露?给同事说一声,别自己吓自己
【漏洞预警】自己注册个号,就能搬空别人的 Magento 订单和支付卡(POC已公开)
【漏洞预警】Next.js 爆出新漏洞,不用登录实现命令执行(POC已公开)
【漏洞补丁绕过预警】Redis CVE-2026-23479变种高危漏洞,EXP已公开
【漏洞补丁绕过】超 30 万 WordPress 站点受影响,Forminator 文件上传漏洞(附POC)
【漏洞预警】Exchange爆出RCE漏洞,利用Exchange 帮另一台 Exchange 开门(POC已公开)
【漏洞预警】不用登录,一个请求就能让 GeoServer 乖乖执行命令(POC已公开)
【漏洞预警】Keycloak不登录就能接管任意账户,双重验证也拦不住
【漏洞预警】 8月WebLogic公布了9个漏洞,有一个需要重点关注
【漏洞预警】近期MongoDB 公布了5个漏洞,最严重的一个可以不认证、远程就能拿下你电脑
【0DAY漏洞】 全球5G手机芯片厂商之一‌的紫光展锐SOC存在RCE漏洞,影响数十家手机厂商,还未发布补丁
【漏洞告警】安卓 14 到 16 全中招,通讯录 SQL 注入漏洞正在抄家(POC已公开)
【漏洞预警】紧急排查,SMB服务再曝新漏洞,利用脚本已公开
【漏洞预警】不用输密码,对方已经接管你的Mac:官方补丁公布macOS存在未鉴权RCE漏洞
【0day漏洞预警】MariaDB 13 爆 9.8 分 RCE 链:最低 USAGE 权限就能远程执行命令
VMware 爆 5 个洞,两个 9.8 不用密码就能打穿 vCente

相关学习资料