Java服务XXE漏洞防御方法

前段时间在搞应急,从一个啥都不会的小白学了一些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地址

https://github.com/LeadroyaL/java-xxe-defense-demo

本文原本写于2018年 8 月,很多不准确的地方,建议阅读2019 年 7 月的两篇文章,个人认为更加清晰和准确,链接为https://leadroyal.cn/p/930 https://leadroyal.cn/p/914

二、各种写法

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来验证。

5、SAXTransformFactory

官方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来验证。

7、TransformFactory

官方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来验证。

9、XMLInputFactory

官方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的,比我这篇更清晰。