XXE攻击的基本原理
XML外部实体注入(XXE)的核心是利用XML规范中的外部实体声明功能,攻击者在XML中自定义外部实体,指向本地文件或内网地址,当应用解析该XML时,会按照实体定义读取对应内容并返回,造成信息泄露或内网探测。比如恶意XML可以定义实体读取/etc/passwd文件,解析后该文件内容就会被攻击者获取。

Java中常见的XXE风险场景
Java生态中有多种XML解析方式,很多默认配置下都允许解析外部实体,常见风险场景包括:
- 使用
DocumentBuilderFactory解析用户上传的XML文件 - 使用
SAXParserFactory处理外部传入的XML数据 - 使用
XMLInputFactory(StAX)解析XML流 - 使用第三方库如Jackson、XStream解析包含XML格式的数据
防范XXE的核心方案
1. 禁用外部实体和DTD
最彻底的防护方式是禁用XML的外部实体解析和DTD(文档类型定义)功能,因为外部实体必须依赖DTD声明。不同解析工厂的配置方式略有差异,但核心逻辑都是关闭相关特性。
2. 限制XML解析的扩展功能
除了禁用外部实体,还可以限制XML解析的通用实体扩展、参数实体扩展等功能,进一步缩小攻击面。
3. 使用安全的解析配置模板
可以封装统一的XML解析安全配置方法,避免每次解析时重复配置,减少遗漏风险。
具体代码实现示例
DocumentBuilderFactory安全配置
DocumentBuilderFactory是Java最常用的DOM解析类,默认开启外部实体解析,需要手动关闭相关特性:
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import org.w3c.dom.Document;
import java.io.ByteArrayInputStream;
public class SafeXMLParser {
public Document parseXMLSafe(String xmlContent) throws Exception {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
// 禁用DTD,从根源上阻止外部实体声明
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);
// 禁用XInclude处理
dbf.setFeature("http://apache.org/xml/features/xinclude", false);
// 忽略XML命名空间依赖
dbf.setNamespaceAware(false);
DocumentBuilder db = dbf.newDocumentBuilder();
// 解析XML内容,这里使用字节流避免编码问题
return db.parse(new ByteArrayInputStream(xmlContent.getBytes("UTF-8")));
}
}
SAXParserFactory安全配置
SAX解析是事件驱动的流式解析,同样需要关闭外部实体特性:
import javax.xml.parsers.SAXParserFactory;
import javax.xml.parsers.SAXParser;
import org.xml.sax.helpers.DefaultHandler;
import java.io.ByteArrayInputStream;
public class SafeSAXParser {
public void parseXMLSafe(String xmlContent) throws Exception {
SAXParserFactory spf = SAXParserFactory.newInstance();
// 禁用DTD
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);
// 不验证XML
spf.setValidating(false);
SAXParser parser = spf.newSAXParser();
parser.parse(new ByteArrayInputStream(xmlContent.getBytes("UTF-8")), new DefaultHandler());
}
}
StAX(XMLInputFactory)安全配置
StAX是流式拉取解析,配置方式如下:
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamReader;
import java.io.ByteArrayInputStream;
public class SafeStAXParser {
public void parseXMLSafe(String xmlContent) throws Exception {
XMLInputFactory xif = XMLInputFactory.newInstance();
// 禁用外部实体
xif.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
// 禁用DTD
xif.setProperty(XMLInputFactory.SUPPORT_DTD, false);
XMLStreamReader reader = xif.createXMLStreamReader(new ByteArrayInputStream(xmlContent.getBytes("UTF-8")));
while (reader.hasNext()) {
reader.next();
}
reader.close();
}
}
第三方库的XXE防护
Jackson XML解析防护
Jackson解析XML时如果使用XmlMapper,需要关闭外部实体支持:
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.databind.DeserializationFeature;
public class SafeJacksonXML {
public XmlMapper getSafeXmlMapper() {
XmlMapper xmlMapper = new XmlMapper();
// 禁用外部实体
xmlMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// 关闭DTD支持,需要依赖jackson-dataformat-xml的版本支持,低版本可升级后使用
// xmlMapper.getFactory().setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
return xmlMapper;
}
}
XStream安全配置
XStream默认也支持外部实体,需要手动关闭:
import com.thoughtworks.xstream.XStream;
public class SafeXStream {
public XStream getSafeXStream() {
XStream xstream = new XStream();
// 禁用DTD
xstream.ignoreUnknownElements();
// 关闭外部实体解析,XStream底层依赖XML解析器,可设置解析器特性
// 建议升级到最新版本,新版本默认关闭了外部实体
return xstream;
}
}
防护注意事项
- 所有接收外部XML输入的场景都需要做安全配置,不能只处理部分接口
- 尽量使用白名单方式限制XML解析的允许内容,不要只依赖黑名单过滤
- 定期升级XML解析相关依赖库,修复已知的安全漏洞
- 对上传的XML文件做格式校验,拒绝不符合预期的XML结构
- 测试阶段可以使用恶意XXE payload验证防护是否生效,比如构造读取本地文件的XML进行测试