在Java应用中处理XML是极为常见的需求,无论是WebService报文、配置文件还是第三方数据交换,都离不开解析器。DocumentBuilderFactory作为JAXP规范提供的工厂类,负责创建DOM解析器实例。它的默认配置在不同历史版本中并不一致,早期版本会默认允许处理文档类型定义(DTD)中的外部实体,这就为XXE(XML External Entity)注入攻击打开了通道。攻击者只需在XML中嵌入指向本地敏感文件或内部服务的实体声明,就能在解析阶段触发任意文件读取或服务端请求伪造。

一、XXE攻击的基本原理与危害
XXE攻击的核心在于利用XML解析器对外部实体的支持。当一个XML文档包含<!DOCTYPE>声明,并在其中定义指向外部资源的实体时,若解析器被配置为展开该实体,就会主动去获取对应资源。例如定义实体指向file:///etc/passwd,解析后该实体的内容会被替换进DOM树,接口返回时便泄露了系统文件。更进一步,攻击者还能利用http或ftp协议探测内网端口,将解析器当作跳板机。
很多团队在代码审计时发现,自己只是简单地调用了DocumentBuilderFactory.newInstance().newDocumentBuilder(),完全没有意识到这套默认行为在旧JDK下是危险的根源。即便升级到较新JDK,由于部分应用服务器或框架会覆盖工厂属性,默认安全策略也可能失效。因此,不能依赖环境默认值,必须在代码层面对工厂做显式加固。
二、DocumentBuilderFactory关键安全配置项
要阻断XXE,最直接的方式是禁用DTD和外部实体处理。DocumentBuilderFactory提供setFeature方法,可对接底层解析器(如Xerces)的特性开关。最重要的两个特性是http://apache.org/xml/features/disallow-doctype-decl和http://xml.org/sax/features/external-general-entities。前者直接禁止文档中出现DOCTYPE声明,从源头消灭实体定义;后者专门控制是否展开外部通用实体。
除了Feature,还可以通过设置XMLConstants的安全属性来强化。例如XMLConstants.ACCESS_EXTERNAL_DTD和XMLConstants.ACCESS_EXTERNAL_SCHEMA,将它们的值设为空字符串,表示不允许通过DTD或Schema访问任何外部资源。下面是一段典型的加固代码:
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import org.w3c.dom.Document;
import javax.xml.XMLConstants;
public class SafeXmlParser {
public Document parseXml(byte[] xmlData) throws Exception {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
// 禁止DOCTYPE声明
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
// 禁用外部通用实体
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
// 禁用外部参数实体
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
// 不展开实体引用
dbf.setExpandEntityReferences(false);
// 限制外部DTD和Schema访问
dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
DocumentBuilder builder = dbf.newDocumentBuilder();
return builder.parse(new java.io.ByteArrayInputStream(xmlData));
}
}
上述代码中,disallow-doctype-decl设为true是最为彻底的方案,因为它让任何携带DOCTYPE的报文直接解析失败,根本不给实体生效的机会。但在某些必须支持合法DTD校验的业务场景下,无法完全禁止DOCTYPE,此时就应保证外部实体相关的两个feature均为false,并配合setExpandEntityReferences(false)避免实体被展开。
三、不同JDK版本下的行为差异与兼容处理
从JDK 7u15、JDK 8u20之后,Oracle官方逐渐将外部实体默认设为不展开,但不同厂商的JDK(如OpenJDK早期构建、Android运行时)表现并不统一。更麻烦的是,像Apache Tomcat或某些ESB产品会在全局系统属性里修改jaxp配置,导致你在本类中做的设置被间接覆盖。因此,在跨环境部署时,建议配合单元测试,用包含外部实体的恶意XML样本验证解析器是否抛异常。
若业务必须使用XML Schema校验而不是DTD,应当优先选用setSchema方式,并同样设置ACCESS_EXTERNAL_SCHEMA为空。此外,勿将DocumentBuilder实例作为静态变量长期复用,因为某些实现并非线程安全,且属性可能在复用中被其他代码篡改。每次解析新建工厂与builder虽然略有开销,但安全边界更清晰。
四、常见配置误区与排查建议
一个典型误区是只设置了external-general-entities为false,却遗漏了external-parameter-entities。参数实体常用于DTD内部被引用的片段,同样可指向外部文件,只堵一半仍会失血。另一个误区是以为调用了setValidating(true)就安全,实际上开启DTD校验反而可能扩大攻击面,除非你明确需要并已禁用外部加载。
排查时可临时在测试环境加入JVM参数-Djaxp.debug=1,观察解析器实际加载的特性列表。同时在网关层对Content-Type为text/xml的请求体做长度与结构扫描,拒绝明显包含<!ENTITY SYSTEM的负载。将解析逻辑封装在统一的安全工具类中,禁止业务代码直接new DocumentBuilderFactory,能从架构上减少遗漏配置的可能。
五、总结性实践清单
综合来看,安全的DocumentBuilderFactory配置应遵循最小权限原则:能不用DTD就不用,必须用时彻底关外部实体。推荐在应用启动阶段打印一次解析器特性,纳入安全基线巡检。下表列出核心开关与推荐值:
| 配置项 | 推荐值 | 作用 |
|---|---|---|
| disallow-doctype-decl | true | 禁止DOCTYPE,根防XXE |
| external-general-entities | false | 禁外部通用实体 |
| external-parameter-entities | false | 禁外部参数实体 |
| expandEntityReferences | false | 不展开实体引用 |
| ACCESS_EXTERNAL_DTD | 空字符串 | 禁DTD外部访问 |
把这张清单落地到代码规范里,结合自动化扫描,基本可以消除Java XML解析层面的外部实体风险。安全配置不是一次性的开关,而是需要随依赖升级持续验证的习惯。
DocumentBuilderFactoryXXEXML_security修改时间:2026-08-08 11:06:31