XXE

XXE防御笔记

Posted by kingkk on 2019-07-21

官方防御手册

1
https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html

DocumentBuilder

javax.xml.parsers.DocumentBuilderFactory

禁用外部实体

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
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
String FEATURE = null;
try {
// This is the PRIMARY defense. If DTDs (doctypes) are disallowed, almost all
// XML entity attacks are prevented
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
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);

// 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);
} catch (Exception e) {
...
}
DocumentBuilder safebuilder = dbf.newDocumentBuilder();

以上是官方推荐的修复方式,最主要的是第一条feature

1
"http://apache.org/xml/features/disallow-doctype-decl";

也就是XXE攻击失败时经常跳出来的一条语句

以下为个人理解

如果没有特殊需求其实只要开启http://apache.org/xml/features/disallow-doctype-decl这一条,就可以禁止外部实体加载。

如果还是有加载外部实体的需求,就可以开启如下几条

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
> // 禁止加载外部实体
> 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);
>
> // 禁用外部dtd
> FEATURE = "http://apache.org/xml/features/nonvalidating/load-external-dtd";
> dbf.setFeature(FEATURE, false);
>
> dbf.setXIncludeAware(false);
> dbf.setExpandEntityReferences(false);
>

还看到有一种feature,貌似是一种安全的XML加载方式,禁止了http、file等一些协议的加载,具体没深究

1
2
FEATURE = "http://javax.xml.XMLConstants/feature/secure-processing";
dbf.setFeature(FEATURE, true);

需要注意的是,设置feature是需要在DocumentBuilder safebuilder = dbf.newDocumentBuilder();之前

如果在这之后进行操作,还是会存在XXE漏洞。错误示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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());

这样的操作是无效的。

SAXParserFactory & DOM4J

这两个的防御方式也是和DocumentBuilder一样的

1
2
3
4
5
6
7
8
9
10
SAXParserFactory spf = SAXParserFactory.newInstance();
// 如果没有特殊需求之开启下面这一句就够了
spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);

// 如果不想完全禁用外部实体,可以设置如下,同理于DocumentBuilder
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();
parser.parse(ResourceUtils.getPoc1(), (HandlerBase) null);

调用方式也是和之前一样,不能颠倒,先setFeaturenewSAXParser

XMLInputFactory

防御方式

1
2
3
4
5
6
7
8
9
XMLInputFactory xmlInputFactory = XMLInputFactory.newFactory();
// XMLInputFactory.SUPPORT_DTD = "javax.xml.stream.supportDTD"
xmlInputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, false);
// XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES = "javax.xml.stream.isSupportingExternalEntities"
xmlInputFactory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
XMLStreamReader parse = xmlInputFactory.createXMLStreamReader(ResourceUtils.getPoc1());
while (parse.hasNext()) {
parse.next();
}

当设置了防御参数之后,就不会去解析外部实体的内容

TransformerFactory & Validator & SchemaFactory & SAXTransformerFactory

修复方式都类似,都是设置XMLConstants.ACCESS_EXTERNAL_DTDXMLConstants.ACCESS_EXTERNAL_SCHEMA

1
2
3
public static final String ACCESS_EXTERNAL_DTD = "http://javax.xml.XMLConstants/property/accessExternalDTD";
public static final String ACCESS_EXTERNAL_STYLESHEET = "http://javax.xml.XMLConstants/property/accessExternalStylesheet"
public static final String ACCESS_EXTERNAL_SCHEMA = "http://javax.xml.XMLConstants/property/accessExternalSchema";

修复后的错误提示也都是一致的

每种修复方式都有设置XMLConstants.ACCESS_EXTERNAL_DTD

XMLConstants.ACCESS_EXTERNAL_STYLESHEETXMLConstants.ACCESS_EXTERNAL_SCHEMA根据不同场景开启

TransformerFactory

javax.xml.transform.TransformerFactory

这里是ACCESS_EXTERNAL_STYLESHEET

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(ResourceUtils.getPoc1());
tf.newTransformer().transform(source, new DOMResult());

Validator

javax.xml.validation.Validator

这里是ACCESS_EXTERNAL_SCHEMA

1
2
3
4
5
6
7
SchemaFactory factory = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema");
Schema schema = factory.newSchema();
Validator validator = schema.newValidator();
validator.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
validator.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
StreamSource source = new StreamSource(ResourceUtils.getPoc1());
validator.validate(source);

SchemaFactory

javax.xml.validation.SchemaFactory

这里是ACCESS_EXTERNAL_SCHEMA

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(ResourceUtils.getPoc1());
Schema schema = factory.newSchema(source);

SAXTransformerFactory

javax.xml.transform.sax.SAXTransformerFactory

这里是ACCESS_EXTERNAL_STYLESHEET

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(ResourceUtils.getPoc1());
sf.newTransformerHandler(source);

XMLReader

org.xml.sax.XMLReader

1
2
3
4
5
6
7
XMLReader reader = XMLReaderFactory.createXMLReader();
reader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
// This may not be strictly required as DTDs shouldn't be allowed at all, per previous line.
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(ResourceUtils.getPoc1()));

和之前的DocumentBuilder是一样的,假如开启了第一句之后,后面的其实是可以省去的。

SAXReader

org.dom4j.io.SAXReader

这里比较特殊,需要将三个防御全部开启

1
2
3
4
5
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.read(ResourceUtils.getPoc1());

在文档中特地标注了这样一句话

Based on testing, if you are missing one of these, you can still be vulnerable to an XXE attack.

如果少了其中一句,还是会存在XXE攻击,至于具体的利用方法,emmm,目前暂未深究。

SAXBuilder

org.jdom2.input.SAXBuilder

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(ResourceUtils.getPoc2());

关于这个,测试的话,仅开启后面两个是没用的。需要将三个选项全部开启

JAXB Unmarshaller & XPathExpression

这两个操作都不支持直接的设置禁用外部实体,它不能单独安全地配置,因此必须首先通过另一个安全的XML解析器解析不受信任的数据。

JAXB Unmarshaller

javax.xml.bind.Unmarshaller先生成安全的SAXParserFactory,再通过它来生成安全的Unmarshaller

1
2
3
4
5
6
7
8
9
10
11
12
//Disable XXE
SAXParserFactory spf = SAXParserFactory.newInstance();
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);

//Do unmarshall operation
Source xmlSource = new SAXSource(spf.newSAXParser().getXMLReader(),
new InputSource(new StringReader(xml)));
JAXBContext jc = JAXBContext.newInstance(Object.class);
Unmarshaller um = jc.createUnmarshaller();
um.unmarshal(xmlSource);

或者其实这样也是可以的

1
2
3
4
5
6
7
8
9
10
11
SAXParserFactory spf = SAXParserFactory.newInstance();
Source xmlSource = new SAXSource(spf.newSAXParser().getXMLReader(),
new InputSource(new StringReader(xml)));
xmlReader.setFeature("http://javax.xml.XMLConstants/feature/secure-processing", true);
xmlReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
xmlReader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
xmlReader.setFeature("http://xml.org/sax/features/external-general-entities", false);
xmlReader.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
JAXBContext jc = JAXBContext.newInstance(Object.class);
Unmarshaller um = jc.createUnmarshaller();
um.unmarshal(xmlSource);

然而其实还存在一种默认的写法,但是不会被日,无需开启防御选项(感觉正常人应该都是用这种写法的吧)

1
2
3
4
5
Class tClass = Some.class;
JAXBContext context = JAXBContext.newInstance(tClass);
Unmarshaller um = context.createUnmarshaller();
Object o = um.unmarshal(ResourceUtils.getPoc1());
tClass.cast(o);

XPathExpression

javax.xml.xpath.XPathExpression,与Unmarshaller类似

1
2
3
4
5
6
7
8
9
DocumentBuilderFactory df = DocumentBuilderFactory.newInstance();
df.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
df.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");

XPathFactory xpathFactory = XPathFactory.newInstance();
XPath xpath = xpathFactory.newXPath();
XPathExpression expr = xpath.compile("/person");
DocumentBuilder builder = df.newDocumentBuilder();
String result = expr.evaluate( builder.parse(ResourceUtils.getPoc1()));

XMLDecoder

java.beans.XMLDecoder

无解,等死吧,有解的话weblogic也就没那么多事了。

最后

不同的xml解析器的防御方式不尽相同,但大致可以分为如下几种。

也可以根据当前XXE的报错来知道大概是使用了哪种防御方式。

setFeature

1
2
3
4
5
xmlReader.setFeature("http://javax.xml.XMLConstants/feature/secure-processing", true);
xmlReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
xmlReader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
xmlReader.setFeature("http://xml.org/sax/features/external-general-entities", false);
xmlReader.setFeature("http://xml.org/sax/features/external-parameter-entities", false);

XMLConstants

1
2
3
df.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
df.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
df.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");

XMLInputFactory

这个比较小众

1
2
xmlInputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, false);
xmlInputFactory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);

防御策略

由于一些需要开启全部选项才能防御的,而有些只要开启其中一部分选项就能进行防御。

面对这种情况,作为防御方,我们可以采取security default原则,让开发者尽可能的开启全部防御选项。

至于其他一些第三方库,比如org.apache.poi这种,则也是在内部调用了SAXReader这些库,并且没有设置禁用DTD。对于这种只需要升级到最新的版本即可解决。

Reference