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

现在百度”XXE漏洞修复”,搜索到的Java语言修复方案大部分如下:

1
2
DocumentBuilderFactory dbf =DocumentBuilderFactory.newInstance();
dbf.setExpandEntityReferences(false);

经过实际的测试发现setExpandEntityReferences(false)根本无法防御XXE漏洞!不禁思考到两个问题:

  1. setExpandEntityReferences为何无法防御XXE?
  2. 为何一个无法防御的方案,却广为流传?

上一周我们深入Java内置XML解析器中,研究XXE漏洞的深层原理。这周我们在这个基础上,进一步弄清以上两个问题。

0x01 测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
import javax.xml.parsers.*;
import java.io.ByteArrayInputStream;
import java.io.InputStream;

public class DOMXXETest02 {
public static void main(String[] args) throws ParserConfigurationException,SAXException,Exception{
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setExpandEntityReferences(false);
DocumentBuilder db = dbf.newDocumentBuilder();
String str = "<!DOCTYPE doc [ \n" +
"<!ENTITY xxe SYSTEM \"http://127.0.0.1:1664/test.dtd\">\n" +
"]><doc>&xxe;</doc>";
InputStream is = new ByteArrayInputStream(str.getBytes());
Document doc = db.parse(is);
System.out.println(doc.getElementsByTagName("doc"));
}
}

test.dtd

1
test by c0ny1

0x02 原理分析

我们在dbf.setExpandEntityReferences(false);打断点开始分析!

setExpandEntityReferences(false)会将DocumentBuilderFactory对象中的expandEntityRef属性默认值true修改为false。

在newDocumentBuilder()会方法返回一个DocumentBuilderImpl对象前,会在DocumentBuilderImpl对象初始化时,调用setFeature()方法对DOM解析器的CREATE_ENTITY_REF_NODES_FEATURE(http://apache.org/xml/features/dom/create-entity-ref-nodes) 配置项设置为上一步的expandEntityRef变量的相反值true。

domParser.setFeature()最终会调用解析器配置对象设置目标配置项的值。

在XMLParser对象调用reset()方法重置状态时,AbstractDOMParser对象中通过解析器的配置对象获取到CREATE_ENTITY_REF_NODEShttp://apache.org/xml/features/dom/create-entity-ref-nodes) 配置项的值true,并将fCreateEntityRefNodes属性设置为true。

在XMLDocumentFragmentScannerImpl.scanDocument()进入START_ELEMENT阶段后,next()方法会对XML中的元素进行扫描。当扫描到文本中的&字符时(识别一般实体),解析器会调用scanEntityReference() 扫描实体引用。最后会调用setupCurrentEntity()创建连接并发起请求,以获取外部实体的内容,这时XXE漏洞将会触发!可以发现程序运行流程,依然会执行到XXE漏洞触发的位置。

继续跟踪,AbstractDOMParser.endGeneralEntity()在判断fCreateEntityRefNodesfalse时,实体引用&xxe将会被从DOM树删除,引用的具体内容Test by c0ny1将会在DOM树中展开,替换掉&xxe。此时为true,实体引用节点将保留在DOM树中。这是setExpandEntityReferences方法对XML解析器处理XML最终产生影响的位置。

最终调用链如下:

经过以上分析,我们大致了解了setExpandEntityReferences()方法的功能是对解析XML生成的Document文档进行设置,设置为 true则展开实体引用到生成的文档中替换掉&xxx的实体引用声明,设置为false则保留实体引用声明的DOM树在生成的文档中。

由于setExpandEntityReferences(false)对Java内置XML解析器的设置起作用前,解析器就已经发起了对外部实体的请求了,故无法防御XXE漏洞!

0x03 思考原因

为何setExpandEntityReferences明明无法防御XXE漏洞,但却很多人在使用呢?当我看了官方JDK API文档之后,发现描述过于简单,从字面上理解很容易与方法的实际功能存在偏差。

初步判断有两个原因:

  1. 官方文档的描述太过于模糊,很容易让人产生歧义。如果没有跟踪该方法底层实现很容易对它的实际功能理解错误,从而导致错误使用。

  2. 第一批修复的人应该是看了官方JDK文档来编写修复代码的,之后更多的人是直接百度到了一批人的编写的错误修复代码,直接复制粘贴。导致这个错误的修复方案进一步蔓延。

0x04 参考文章