Java-XXE-总结

0x01 起因

同事问我研究过Java下的xxe漏洞嘛,为啥修复建议不起作用,emmmm然后这个问题我就回答不上来了,这个问题有两个关注点:

1.java下xxe产生的原理是啥。

2.修复建议的修复代码是啥。

0x02 深入分析

1.DocumentBuilder

原理分析

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.l1nk3r.xxe.javaxxe;
import org.w3c.dom.Document;
import javax.xml.parsers.*;
import java.io.ByteArrayInputStream;
import java.io.InputStream;

public class DocumentXXE {
public static void main(String[] args) throws Exception {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
String str = "<!DOCTYPE doc [ \n" +
"<!ENTITY xxe SYSTEM \"http://127.0.0.1:8888\">\n" +
"]><doc>&xxe;</doc>";
InputStream is = new ByteArrayInputStream(str.getBytes());
Document doc = db.parse(is);
}
}

db.parse处下个断点,代码来到这个Java.xml.parsers.DocumentBuilder#parse类中,跟进return parse

image-20191027173555753

DocumentBuilderImpl#parse中,调用了 DOMParser#parse

image-20191027173809315

跟进 DOMParser#parse 这个方法,调用 parse 方法来解析 xmlInputSource

image-20191027173944541

跟进 XMLParser#parse ,调用 fConfiguration.parse 方法。

image-20191027174210563

而这个 fConfiguration.parse 实际上是个继承接口,这里根据 debug 会进入XML11Configuration#parse中。

image-20191027174357821

image-20191027174629657

通过setInputSource(source)方法将Xml数据赋值给 fInputSource ,然后调用parse(boolean complete)构造方法进行处理。

image-20191027174934531

跟进parse(boolean complete)方法,然后就来到了XMLDocumentFragmentScannerImpl#scanDocument方法中。

image-20191027175604930

跟进XMLDocumentFragmentScannerImpl#scanDocument方法,这个方法首先会针对xml数据流中的document部分进行扫描。

image-20191027175916270

然后就会扫描xml数据流中的 DTD 部分。

image-20191027180015298

最后开始扫描 ELEMENT 部分。

image-20191027180538764

然后调用next方法。

image-20191027180713304

跟进 XMLDocumentFragmentScannerImpl#next ,这个方法会针对一些特殊字符进行标记。

image-20191027180954463

而我们刚刚payload:<doc>&xxe;</doc>里面存在&特殊字符,所以这里会调用 setScannerState 构造方法将fScannerState设置为 SCANNER_STATE_REFERENCE

image-20191027181026130

代码继续往下走,会来到 XMLDocumentFragmentScannerImpl#next 方法中的下图代码位置,我们已经知道这时候的fScannerStateSCANNER_STATE_REFERENCE ,所以这里的 case 应该要来到SCANNER_STATE_REFERENCE中。

image-20191027181402210

image-20191027181716689

SCANNER_STATE_REFERENCE这个case中,会调用 scanEntityReference 来处理 fContentBuffer中的数据。跟进 scanEntityReference 方法,首先会获取 scanName ,我们payload里面的name自然是xxe,所以这里debug的结果也是xxe。

image-20191027182011851

代码继续下行,来到下图位置,调用 XMLEntityManager#startEntity 进行处理。

image-20191027182137129

跟进 XMLEntityManager#startEntity 最后会调用startEntity(String name,XMLInputSource xmlInputSource,boolean literal, boolean isExternal)这个构造方法,并且可以看到已经解析了payload中的外部地址。

image-20191027182508481

startEntity 方法中调用了 setupCurrentEntity 方法。

image-20191027182737973

跟进 XMLEntityManager#setupCurrentEntity ,这里可以看到解析了我们payload中的地址,并且发起了http的请求。

image-20191027183020239

实际调用栈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
setupCurrentEntity:646, XMLEntityManager (com.sun.org.apache.xerces.internal.impl)
startEntity:1300, XMLEntityManager (com.sun.org.apache.xerces.internal.impl)
startEntity:1237, XMLEntityManager (com.sun.org.apache.xerces.internal.impl)
scanEntityReference:1908, XMLDocumentFragmentScannerImpl (com.sun.org.apache.xerces.internal.impl)
next:3067, XMLDocumentFragmentScannerImpl$FragmentContentDriver (com.sun.org.apache.xerces.internal.impl)
next:606, XMLDocumentScannerImpl (com.sun.org.apache.xerces.internal.impl)
scanDocument:510, XMLDocumentFragmentScannerImpl (com.sun.org.apache.xerces.internal.impl)
parse:848, XML11Configuration (com.sun.org.apache.xerces.internal.parsers)
parse:777, XML11Configuration (com.sun.org.apache.xerces.internal.parsers)
parse:141, XMLParser (com.sun.org.apache.xerces.internal.parsers)
parse:243, DOMParser (com.sun.org.apache.xerces.internal.parsers)
parse:348, DocumentBuilderImpl (com.sun.org.apache.xerces.internal.jaxp)
parse:121, DocumentBuilder (javax.xml.parsers)
main:19, DocumentXXE (com.l1nk3r.xxe.javaxxe)
代码流程图

记录

漏洞修复

错误的修复方式

目前百度查询xxe的修复方式实际上有这么一段代码,但是这么一段代码实际上是不会生效。

image-20191029151623839

看看为啥不起作用,可以选择在dbf.setExpandEntityReferences(false);下一个断点。 DocumentBuilderFactoryImpl 这个类中的 expandEntityRef 变量的值默认是true,通过setExpandEntityReferences(false)之后将 expandEntityRef 变量的值设置为false。

image-20191029160157100

代码继续下行来到 dbf.newDocumentBuilder 中。

image-20191029160538484

该方法会返回一个实例化的 DocumentBuilderFactoryImpl 对象。

image-20191029160930515

而在 DocumentBuilderFactoryImpl 这个对象中,会根据刚刚的 expandEntityRef 的值取反之后赋值给CREATE_ENTITY_REF_NODES_FEATURE,也就是http://apache.org/xml/features/dom/create-entity-ref-nodes,对应的结果为 true

image-20191029161247273

最后这里处理完之后自然返回 DocumentBuilderFactoryImpl 对象,代码继续下行来到parse处。

image-20191029162217470

前面步骤省略,和之前一致,来到 XMLParser#parse 这里之后,这里有个 reset 方法。

image-20191029162432397

跟进这个 reset 方法,实际上来到了 AbstractDOMParser#reset 中,并且将 fCreateEntityRefNodes 的值设置为我们刚刚修改过,也就是 true 这个值。

image-20191029162643103

这是到这部分的调用栈

image-20191030210613938

之后代码继续下行,在XMLDocumentFragmentScannerImpl#scanDocument方法中,会先扫描 document 部分,再扫描 DTD 部分,最后扫描 ELEMENT 部分。 XMLDocumentFragmentScannerImpl#next 会针对&进行标记,调用 scanEntityReference 方法将fScannerState设置为 SCANNER_STATE_REFERENCE ,最后依然会调用 setupCurrentEntity 创建连接并发起请求,以获取外部实体的内容。

然后继续往下走来到 XMLEntityManager#endEntity ,经过一系列调用会来到AbstractDOMParser#endGeneralEntity中,会判断前面设置的 fCreateEntityRefNodes 的值,如果为 true则展开实体引用到生成的文档中替换掉&xxx的实体引用声明,设置为false则保留实体引用声明的DOM树在生成的文档中。

image-20191029170329567

此方法作用于XML解析后生成的文档。setExpandEntityReferences设置为true则展开实体引用到生成的文档中替换掉&xx的实体引用声明,setExpandEntityReferences设置为false则保留实体引用声明的DOM树在生成的文档中

image-20191030211647738

正确的修复方式
1
2
3
4
5
6
7
8
9
10
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();     
/*以下为修复代码*/ //https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Prevention_Cheat_Sheet#Java
//禁用DTDs (doctypes),几乎可以防御所有xml实体攻击
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); //首选
//如果不能禁用DTDs,可以使用下两项,必须两项同时存在
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); //防止外部实体POC
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); //防止参数实体POC
/*以上为修复代码*/
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(request.getInputStream());

当然如果还不放心的话,下面是owasp推荐的,其实也就是多了三个属性的设置,具体可以看看下面

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
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
String FEATURE = null;
try {
FEATURE = "http://apache.org/xml/features/disallow-doctype-decl";
dbf.setFeature(FEATURE, true);
// If you can't completely disable DTDs, then at least do the following:
// Xerces 1 - http://xerces.apache.org/xerces-j/features.html#external-general-entities
// Xerces 2 - http://xerces.apache.org/xerces2-j/features.html#external-general-entities
// JDK7+ - http://xml.org/sax/features/external-general-entities
FEATURE = "http://xml.org/sax/features/external-general-entities";
dbf.setFeature(FEATURE, false);
// Xerces 1 - http://xerces.apache.org/xerces-j/features.html#external-parameter-entities
// Xerces 2 - http://xerces.apache.org/xerces2-j/features.html#external-parameter-entities
// JDK7+ - http://xml.org/sax/features/external-parameter-entities
FEATURE = "http://xml.org/sax/features/external-parameter-entities";
dbf.setFeature(FEATURE, false);
// Disable external DTDs as well
FEATURE = "http://apache.org/xml/features/nonvalidating/load-external-dtd";
dbf.setFeature(FEATURE, false);
// and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and Entity Attacks"
dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false);
>..
// Load XML file or stream using a XXE agnostic configured parser...
DocumentBuilder safebuilder = dbf.newDocumentBuilder();

当然还是需要看一下原理,选择在下面这个位置代码下个断点。

1
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true)

前面我们在错误修复方法里面,在dbf.setExpandEntityReferences(false);的时候,我们知道在 DocumentBuilderImpl 类中,调用domParser.setFeatureexpandEntityRef 取反之后赋值给CREATE_ENTITY_REF_NODES_FEATURE,之后调用 reset 方法的时候将 fCreateEntityRefNodes 的设置为 CREATE_ENTITY_REF_NODES_FEATURE 的结果。

回到现在这个设置,有点不太一样,在 DocumentBuilderImpl 类中找不到我们设置的这个disallow-doctype-decl的配置,因此会继续向下,下图是在 DocumentBuilderImpl 中第234行,调用 setFeatures 方法。

image-20191030101735383

这个方法会继续向下行,来到了 XMLDocumentScannerImpl#setFeature 中,并且将 fDisallowDoctype 设置为true,这是整个 setFeature 操作过程中的调用链。

image-20191030101926602

紧接着进入到 XMLDocumentFragmentScannerImpl#scanDocument ,我们知道首先会扫描document部分,然后调用 XMLDocumentScannerImpl$PrologDriver#next 方法。

image-20191030102952317

XMLDocumentScannerImpl$PrologDriver#next 方法中,调用 setScannerStatefScannerState 设置为24,也就是 SCANNER_STATE_DOCTYPE 属性。

image-20191030103115692

image-20191030103216727

紧接着代码继续下行,根据 fScannerState 进行选择,这里我们前面的 fScannerState 的状态设置为了 SCANNER_STATE_DOCTYPE ,所以自然进入这个case中,然后针对我们之前的 fDisallowDoctype 属性进行判断,如果是true的话,就抛出异常。

image-20191030103351564

这是到这个部分的调用栈。

image-20191030103955883

异常会被抛到XML11Configuration.parse()中处理。处理的结果是fParseInProgress变量被设置为了false,接着会调用cleanup()方法在完全解析XML文档之前终止解析,这个是到最后终止部分的调用栈。

image-20191030103836290

image-20191030104027146

2.SAXBuilder

原理分析

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.l1nk3r.xxe.javaxxe;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import org.jdom2.Document;d
import org.jdom2.input.SAXBuilder;

public class SAXBuilderXxe {
public static void main(String[] args) throws Exception {
String str = "<!DOCTYPE doc [ \n" +
"<!ENTITY xxe SYSTEM \"http://127.0.0.1:8888\">\n" +
"]><doc>&xxe;</doc>";
InputStream is = new ByteArrayInputStream(str.getBytes());
SAXBuilder sb = new SAXBuilder();
Document doc = sb.build(is);
}
}

下图是这个方法到触发xxe的调用栈,可以看到画圈的部分和我们前面分析的 DocumentBuilder 中造成xxe的调用栈基本相似,最后都是到 XMLEntityManager#setupCurrentEntity 中触发xxe。

image-20191031155244303

修复建议

修复建议可以参考下列代码,注意实例化 SAXBuilder 类和 build 方法解析之间的这些属性。

1
2
3
4
5
6
SAXBuilder sb = new SAXBuilder();
sb.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
sb.setFeature("http://xml.org/sax/features/external-general-entities", false);
sb.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
sb.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
Document doc = sb.build(is);

可以看到实际上将 disallow-doctype-decl 设置为true之后,就会抛出以下报错,具体原因和DocumentBuilder一致。

image-20191031155606057

3.SAXParserFactory

原理分析

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.l1nk3r.xxe.javaxxe;

import org.xml.sax.HandlerBase;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

public class SAXParseFactoryXxe {
public static void main(String[] args) throws Exception {
String str = "<!DOCTYPE doc [ \n" +
"<!ENTITY xxe SYSTEM \"http://127.0.0.1:8888\">\n" +
"]><doc>&xxe;</doc>";
InputStream is = new ByteArrayInputStream(str.getBytes());
SAXParserFactory spf = SAXParserFactory.newInstance();
SAXParser parser = spf.newSAXParser();
parser.parse(is, (HandlerBase) null);
}
}

默认情况下也存在xxe,具体看下图调用栈就懂了。

image-20191031161618379

修复建议

1
2
3
4
5
6
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);
spf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
spf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
SAXParser parser = spf.newSAXParser();

4.SAXTransformerFactory

漏洞原理

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.l1nk3r.xxe.javaxxe;

import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.stream.StreamSource;
import java.io.ByteArrayInputStream;
import java.io.InputStream;

public class SAXTransformerFactoryXxe {
public static void main(String[] args) throws Exception {
String str = "<!DOCTYPE doc [ \n" +
"<!ENTITY xxe SYSTEM \"http://127.0.0.1:8888\">\n" +
"]><doc>&xxe;</doc>";
InputStream is = new ByteArrayInputStream(str.getBytes());
SAXTransformerFactory sf = (SAXTransformerFactory) SAXTransformerFactory.newInstance();
StreamSource source = new StreamSource(is);
sf.newTransformerHandler(source);
}
}

调用栈:

image-20191031165635649

修复建议

1
2
3
4
5
SAXTransformerFactory sf = (SAXTransformerFactory) SAXTransformerFactory.newInstance();
sf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
sf.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
StreamSource source = new StreamSource(is);
sf.newTransformerHandler(source);

5.SAXReader

漏洞原理

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.l1nk3r.xxe.javaxxe;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import org.dom4j.io.SAXReader;

public class SAXReaderXxe {
public static void main(String[] args) throws Exception {
String str = "<!DOCTYPE doc [ \n" +
"<!ENTITY xxe SYSTEM \"http://127.0.0.1:8888\">\n" +
"]><doc>&xxe;</doc>";
InputStream is = new ByteArrayInputStream(str.getBytes());
SAXReader saxReader = new SAXReader();
saxReader.read(is);
}
}

调用栈

image-20191031170101496

修复建议

1
2
3
4
5
6
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);
saxReader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
saxReader.read(is);

6.XMLReader

漏洞原理

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.l1nk3r.xxe.javaxxe;

import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;

import java.io.ByteArrayInputStream;
import java.io.InputStream;

public class XMLReaderXxe {
public static void main(String[] args) throws Exception {
String str = "<!DOCTYPE doc [ \n" +
"<!ENTITY xxe SYSTEM \"http://127.0.0.1:8888\">\n" +
"]><doc>&xxe;</doc>";
InputStream is = new ByteArrayInputStream(str.getBytes());
XMLReader reader = XMLReaderFactory.createXMLReader();
reader.parse(new InputSource(is));
}
}

调用栈:

image-20191031170539057

修复建议

1
2
3
4
5
6
XMLReader reader = XMLReaderFactory.createXMLReader();
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);
reader.parse(new InputSource(is));

7.SchemaFactory

漏洞原理

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.l1nk3r.xxe.javaxxe;

import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import java.io.ByteArrayInputStream;
import java.io.InputStream;

public class SchemaFactoryXxe {
public static void main(String[] args) throws Exception {
String str = "<!DOCTYPE doc [ \n" +
"<!ENTITY xxe SYSTEM \"http://127.0.0.1:8888\">\n" +
"]><doc>&xxe;</doc>";
InputStream is = new ByteArrayInputStream(str.getBytes());
SchemaFactory factory = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema");
StreamSource source = new StreamSource(is);
Schema schema = factory.newSchema(source);
}
}

调用栈:

image-20191031171101535

修复建议

1
2
3
4
5
SchemaFactory factory = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema");
factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
factory.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
StreamSource source = new StreamSource(is);
Schema schema = factory.newSchema(source);

8.XMLInputFactory

漏洞原理

测试代码:

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
package com.l1nk3r.xxe.javaxxe;

import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamReader;

public class XMLInputFactoryXxe {
public static void main(String[] args) throws Exception {
XMLInputFactory xmlInputFactory = XMLInputFactory.newFactory();
XMLStreamReader reader = xmlInputFactory.createXMLStreamReader(ResourceUtils.getPoc1());
try {
while (reader.hasNext()) {
int type = reader.next();
if (type == XMLStreamConstants.START_ELEMENT) {//开始节点
System.out.print(reader.getName());
} else if (type == XMLStreamConstants.CHARACTERS) {//表示事件字符
System.out.println("type" + type);
} else if (type == XMLStreamConstants.END_ELEMENT) {//结束节点
System.out.println(reader.getName());
}
}
reader.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
1
2
3
4
5
6
7
8
9
package com.l1nk3r.xxe.javaxxe;

import java.io.InputStream;

public class ResourceUtils {
public static InputStream getPoc1() {
return ResourceUtils.class.getClassLoader().getResourceAsStream("poc1.xml");
}
}

调用栈:

image-20191031174014342

修复建议

1
2
3
4
XMLInputFactory xmlInputFactory = XMLInputFactory.newFactory();
xmlInputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, false);
xmlInputFactory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
XMLStreamReader reader = xmlInputFactory.createXMLStreamReader(ResourceUtils.getPoc1());

9.TransformerFactory

漏洞原理

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.l1nk3r.xxe.javaxxe;

import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.stream.StreamSource;
import java.io.ByteArrayInputStream;
import java.io.InputStream;

public class TransformerFactoryXxe {
public static void main(String[] args) throws Exception {
String str = "<!DOCTYPE doc [ \n" +
"<!ENTITY xxe SYSTEM \"http://127.0.0.1:8888\">\n" +
"]><doc>&xxe;</doc>";
InputStream is = new ByteArrayInputStream(str.getBytes());
TransformerFactory tf = TransformerFactory.newInstance();
StreamSource source = new StreamSource(is);
tf.newTransformer().transform(source, new DOMResult());
}
}

调用栈:

image-20191031203948815

修复建议

1
2
3
4
5
TransformerFactory tf = TransformerFactory.newInstance();
tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
StreamSource source = new StreamSource(is);
tf.newTransformer().transform(source, new DOMResult());

10.Validator

漏洞原理

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.l1nk3r.xxe.javaxxe;

import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import java.io.ByteArrayInputStream;
import java.io.InputStream;

public class ValidatorSampleXxe {
public static void main(String[] args) throws Exception {
String str = "<!DOCTYPE doc [ \n" +
"<!ENTITY xxe SYSTEM \"http://127.0.0.1:8888\">\n" +
"]><doc>&xxe;</doc>";
InputStream is = new ByteArrayInputStream(str.getBytes());
SchemaFactory factory = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema");
Schema schema = factory.newSchema();
Validator validator = schema.newValidator();
StreamSource source = new StreamSource(is);
validator.validate(source);
}
}

调用栈:

image-20191031203310291

修复建议

1
2
3
4
5
6
Schema schema = factory.newSchema();
Validator validator = schema.newValidator();
validator.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
validator.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
StreamSource source = new StreamSource(is);
validator.validate(source);

11.Unmarshaller

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.l1nk3r.xxe.javaxxe;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;

public class UnmarshallerXxe {
public static void main(String[] args) throws Exception {
Class tClass = UnmarshallerXxe.class;
JAXBContext context = JAXBContext.newInstance(tClass);
Unmarshaller um = context.createUnmarshaller();
Object o = um.unmarshal(ResourceUtils.getPoc1());
tClass.cast(o);
}
}

使用默认的解析方法不会存在XXE问题,这也是唯一一个使用默认的解析方法不会存在XXE的一个库。

0x03 小结

通过上面的总结,默认情况下用 Unmarshaller 来处理xml不会发生xxe的问题。我们可以看到调用栈的过程中,存在xxe问题的库或者类实际上最后底层调用都是jdk自身处理xml的类,最后的核心触发流程都会来到 XMLEntityManager#setupCurrentEntity 当中。

针对修复方式实际上也是两种:

其一是通过setfeature的方式来防御XXE,主要几个配置项如下所示:

1
2
3
4
"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

还有一种是通过修改 XMLConstants 这个类的一些配置来进行修复:

1
2
XMLConstants.ACCESS_EXTERNAL_DTD, ""
XMLConstants.ACCESS_EXTERNAL_STYLESHEET, ""

实际上通常禁用DTD,就ok了。但是由于一些实际业务情况,无法禁用DTD,这时候建议禁用通用实体和参数实体一起配置,因为禁用了通用实体就不会被回显,禁用了参数实体就不会被外带查询。

当然xml配置中还有很多冗余部分,具体可以看看前文 DocumentBuilder 中正确修复方式中的一些官方给的配置。

最后当然再提一个小建议,修复XXE建议采取最小化原则。如果一股脑将这些参数全部禁用或者限制,可能会出现一些奇怪的bug。

Reference

Java XXE注入修复问题填坑实录

修不好的洞,JDK的坑——从WxJava XXE注入漏洞中发现了一个对JDK的误会

XML_External_Entity_Prevention_Cheat_Sheet

一个被广泛流传的XXE漏洞错误修复方案

JAVA常见的XXE漏洞写法和防御