前段时间在搞应急,从一个啥都不会的小白学了一些XXE的简单poc构造和防御手段,网上攻击的poc很多,就不多说了,防御方法五花八门,而且有些防御根本就是无效的!!写篇文章分享一下,如何用Java彻底防御XXE。
一、简介
Java提供了一些解析xml的API,也有第三方写的,如果攻击者构造恶意的xml让Java应用去解析,可以造成任意文件读。在Java里是无法代码执行的,但在其他语言里可能会。
最全的链接是这个 https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Prevention_Cheat_Sheet#JAXB_Unmarshaller (但经过测试,大部分写的不错,漏了一些细节,也就有了这篇文章)
Github地址
二、各种写法
1、DocumentBuilderFactory
官方patch
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); String FEATURE = null; FEATURE = "http://apache.org/xml/features/disallow-doctype-decl"; dbf.setFeature(FEATURE, true); FEATURE = "http://xml.org/sax/features/external-parameter-entities"; dbf.setFeature(FEATURE, false); FEATURE = "http://xml.org/sax/features/external-general-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); DocumentBuilder builder = dbf.newDocumentBuilder(); builder.parse(new ByteArrayInputStream(s.getBytes()));
|
可以使用本文最后的poc1来验证。如果出现了网络上的卡顿、或者文件NotFound,就说明仍然可以被攻击,如果出现了抛出异常,就是成功防御。
另一种patch方式:
1 2 3 4
| DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); String FEATURE = null; FEATURE = "http://javax.xml.XMLConstants/feature/secure-processing"; dbf.setFeature(FEATURE, true);
|
使用这个Feature也是比较保险的方式,后面会讲我是如何找到这个Feature的,也是很巧合。
民间的垃圾patch
1
| dbf.setExpandEntityReferences(false);
|
只写这句没用的,照样可以被日。
2、SAXBuilder
官方patch
1 2 3 4 5
| 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); Document doc = builder.build(new File(fileName));
|
这个没什么好讲的,用poc1验证即可,应该也是第一句最重要,后面两句用处不是很大。
3、SAXParserFactory
官方patch
1 2 3 4 5
| SAXParserFactory spf = SAXParserFactory.newInstance(); spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); spf.setFeature("http://xml.org/sax/features/external-general-entities", false); SAXParser parser = spf.newSAXParser(); parser.parse(ResourceUtils.getPoc1(), (HandlerBase) null);
|
使用poc1来验证。
1 2
| spf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); spf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
|
民间流行的垃圾patch
1 2 3 4 5
| SAXParserFactory parserFactory = SAXParserFactory.newInstance(); parserFactory.setValidating(false); parserFactory.setXIncludeAware(false); parserFactory.setNamespaceAware(false); SAXParser parser = parserFactory.newSAXParser();
|
4、SAXReader
官方patch
1 2 3 4
| SAXReader saxReader = new SAXReader(); 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);
|
使用poc1来验证。
官方patch
1 2 3 4
| SAXTransformerFactory sf = SAXTransformerFactory.newInstance(); sf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); sf.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, ""); sf.newXMLFilter(Source);
|
使用poc1来验证。
6、SchemaFactory
官方patch
1 2 3 4
| SchemaFactory factory = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema"); factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); factory.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); Schema schema = factory.newSchema(Source);
|
使用poc1来验证。
官方patch
1 2 3
| TransformerFactory tf = TransformerFactory.newInstance(); tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
|
使用poc1来验证。
8、Validator
官方patch
1 2 3
| Validator validator = schema.newValidator(); validator.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); validator.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
|
使用poc1来验证。
官方patch
1 2
| xmlInputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, false); xmlInputFactory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
|
使用poc1来验证。
10、XMLReader
官方patch
1 2 3 4
| reader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); reader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); reader.setFeature("http://xml.org/sax/features/external-general-entities", false); reader.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
|
使用poc1来验证。
11、unmarshaller的操作
Unmarshaller的一般需要提供一个class,对其中的对象进行自动的编码和解码,所以没有通用的poc,需要和其他类进行配合。在实际使用场景中,为了防止自己写错,可以先marshall一份String,再将这个String unmarshall回去。 例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement public class Person { @XmlElement(name = "age") public int age; @XmlElement(name = "person") public String name; }
<person> <age>10</age> <person>Who</person> </person>
|
这个类和xmlString可以相互转化,那么poc构造时候,就要使用第二种,见末尾。
一般 unmarshall 是从上文的类里分出来的,只要工厂类设置好feature,这里就不会出事。
12、额外JAXBContext,自带防御。
1 2 3 4 5
| JAXBContext context = JAXBContext.newInstance(tClass); Unmarshaller um = context.createUnmarshaller(); StringReader sr = new StringReader(poc); Object o = um.unmarshal(sr); tClass.cast(o);
|
这个虽然没有设置过feature,但也是无法被日的,就是因为JAXBContext里在创建unmarshaller的时候,某句话,设置了secure-processing的Feature。 使用poc2来验证。
附:poc1
1 2 3 4 5 6 7
| <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE xdsec [ <!ELEMENT methodname ANY > <!ENTITY xxe SYSTEM "http://111.222.111.222:12345/test.txt" >]> <methodcall> <methodname>&xxe;</methodname> </methodcall>
|
附:poc2
1 2 3 4 5 6 7
| <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE ANY[ <!ENTITY poc SYSTEM "http://111.222.111.222:12345/test.txt">]> <person> <age>10</age> <name>&poc;</name> </person>
|
13、dom4j低版本自带的XXE(2018.11更新)
处理线上风险时,不小心看到了这个 API,我写的扫描器扫出来的,准备去官网报个洞,发现被人报了。
https://github.com/dom4j/dom4j/issues/28
很简单,DocumentHelper.parseText() 里直接用了 SaxReader,没有做保护。 方案有两个:一是升级版本,升级到2.1.1即可;二是重构代码,不要用这个 API。
注意,从1.x到2.x的过程,官方改名了! 1.x 是 dom4j:dom4j ;2.x是 org.dom4j:dom4j
三、Tips
1、如果是一些工厂类的话,一定要先设置Feature,再去生成数据。例如下面两种写法,后者是无效的。
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
| public void safe() throws ParserConfigurationException, IOException, SAXException { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); String FEATURE = null; FEATURE = "http://javax.xml.XMLConstants/feature/secure-processing"; dbf.setFeature(FEATURE, true); FEATURE = "http://apache.org/xml/features/disallow-doctype-decl"; dbf.setFeature(FEATURE, true); FEATURE = "http://xml.org/sax/features/external-parameter-entities"; dbf.setFeature(FEATURE, false); FEATURE = "http://xml.org/sax/features/external-general-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); DocumentBuilder builder = dbf.newDocumentBuilder(); builder.parse(ResourceUtils.getPoc1()); }
public void unsafe() throws ParserConfigurationException, IOException, SAXException { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = dbf.newDocumentBuilder(); String FEATURE = null; FEATURE = "http://javax.xml.XMLConstants/feature/secure-processing"; dbf.setFeature(FEATURE, true); FEATURE = "http://apache.org/xml/features/disallow-doctype-decl"; dbf.setFeature(FEATURE, true); FEATURE = "http://xml.org/sax/features/external-parameter-entities"; dbf.setFeature(FEATURE, false); FEATURE = "http://xml.org/sax/features/external-general-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); builder.parse(ResourceUtils.getPoc1()); }
|
2、觉得防御好后,记得测一下,不然很容易被网上的一些垃圾patch给坑了。
四、相关链接
https://blog.spoock.com/2018/10/23/java-xxe/ 后来的另一篇介绍xxe的,比我这篇更清晰。