【Java代码审计 | 第十三篇】XXE漏洞成因及防范
未经许可,不得转载。
文章目录
- XXE
- 漏洞成因
- 解析XML的Java方法
- DocumentBuilder(原生,可回显)
- SAXReader(DOM4J,第三方库)
- SAXBuilder(JDOM,第三方库)
- SAXParserFactory(原生,不可回显)
- XMLReaderFactory
- Digester(Apache Commons Digester)
- 支持XInclude的DocumentBuilder
- SAXParserFactory与XMLReader结合
- 漏洞代码
- 防范
XXE
XXE(XML External Entity,XML外部实体注入) 是一种安全漏洞,攻击者通过利用XML解析器处理外部实体的功能,读取本地文件、发起网络请求或导致拒绝服务攻击。
XXE的常见攻击场景:
1、读取本地文件:通过加载外部实体,读取服务器上的敏感文件(如/etc/passwd)。
2、发起SSRF攻击:通过外部实体发起网络请求,访问内网服务或云服务元数据。
3、拒绝服务攻击:通过构造恶意的XML实体,消耗服务器资源(如“Billion Laughs”攻击)。
漏洞成因
XXE通常发生在应用程序解析用户提供的XML输入时,未禁用外部实体解析的情况下。
解析XML的Java方法
Java中解析XML的方法有多种,常见的有以下四种:DOM、DOM4J、JDOM 和 SAX。每种方法各有特点,适用于不同的场景。
DocumentBuilder(原生,可回显)
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import org.w3c.dom.Document;
import java.io.StringReader;
import org.xml.sax.InputSource;
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
StringReader sr = new StringReader(xml_con); // xml_con为XML字符串
InputSource is = new InputSource(sr);
Document document = db.parse(is); // 解析XML并返回Document对象
特点:
- 基于DOM模型,将整个XML文档加载到内存中,生成一棵DOM树。
- 可回显:解析后的内容可以通过Document对象进行操作和访问。
- 适用于需要频繁访问和修改XML内容的场景。
风险:如果未禁用外部实体解析,可能导致XXE漏洞。
SAXReader(DOM4J,第三方库)
import org.dom4j.io.SAXReader;
import org.dom4j.Document;
import javax.servlet.http.HttpServletRequest;
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(request.getInputStream()); // 从请求流中读取并解析XML
特点:
- 基于DOM4J库,提供更简洁的API。
- 可回显:解析后的内容可以通过Document对象进行操作和访问。
- 适用于需要灵活处理XML的场景。
风险:默认情况下支持外部实体解析,需显式禁用以防止XXE漏洞。
SAXBuilder(JDOM,第三方库)
import org.jdom2.input.SAXBuilder;
import org.jdom2.Document;
import javax.servlet.http.HttpServletRequest;
SAXBuilder builder = new SAXBuilder();
Document document = builder.build(request.getInputStream()); // 从请求流中读取并解析XML
特点:
- 基于JDOM库,提供更直观的API。
- 可回显:解析后的内容可以通过Document对象进行操作和访问。
- 适用于需要快速解析和操作XML的场景。
风险:默认情况下支持外部实体解析,需显式禁用以防止XXE漏洞。
SAXParserFactory(原生,不可回显)
import javax.xml.parsers.SAXParserFactory;
import javax.xml.parsers.SAXParser;
import org.xml.sax.helpers.DefaultHandler;
import javax.servlet.http.HttpServletRequest;
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser saxParser = factory.newSAXParser();
DefaultHandler handler = new DefaultHandler(); // 自定义处理器
saxParser.parse(request.getInputStream(), handler); // 解析XML并触发处理器事件
特点:
- 基于SAX模型,采用事件驱动的方式解析XML。
- 不可回显:解析过程中通过事件回调处理数据,不生成完整的DOM树。
- 适用于处理大型XML文件或流式数据。
风险:默认情况下支持外部实体解析,需显式禁用以防止XXE漏洞。
其他解析方法如下。
XMLReaderFactory
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;
import org.xml.sax.InputSource;
import java.io.StringReader;
XMLReader xmlReader = XMLReaderFactory.createXMLReader();
xmlReader.parse(new InputSource(new StringReader(xml_con))); // 解析XML字符串
特点:
-
基于SAX模型,提供更底层的API。
-
不可回显:通过事件回调处理数据。
-
适用于需要自定义解析逻辑的场景。
Digester(Apache Commons Digester)
import org.apache.commons.digester3.Digester;
import java.io.StringReader;
Digester digester = new Digester();
digester.parse(new StringReader(xml_con)); // 解析XML字符串
特点:
-
基于规则驱动的解析方式,适合将XML映射为Java对象。
-
不可回显:解析过程中直接生成Java对象。
-
适用于配置文件解析或数据绑定场景。
支持XInclude的DocumentBuilder
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import org.w3c.dom.Document;
import java.io.StringReader;
import org.xml.sax.InputSource;
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setXIncludeAware(true); // 支持XInclude
dbf.setNamespaceAware(true); // 支持命名空间
DocumentBuilder db = dbf.newDocumentBuilder();
StringReader sr = new StringReader(xml_con);
InputSource is = new InputSource(sr);
Document document = db.parse(is); // 解析XML
特点:
-
支持XInclude和命名空间,适合处理复杂的XML文档。
-
可回显:解析后的内容可以通过Document对象进行操作和访问。
-
适用于需要高级XML特性的场景。
SAXParserFactory与XMLReader结合
import javax.xml.parsers.SAXParserFactory;
import javax.xml.parsers.SAXParser;
import org.xml.sax.XMLReader;
import org.xml.sax.InputSource;
import java.io.StringReader;
SAXParserFactory spf = SAXParserFactory.newInstance();
SAXParser saxParser = spf.newSAXParser();
XMLReader xmlReader = saxParser.getXMLReader();
xmlReader.parse(new InputSource(new StringReader(xml_con))); // 解析XML字符串
特点:
- 结合SAXParserFactory和XMLReader,提供更灵活的解析方式。
- 不可回显:通过事件回调处理数据。
- 适用于需要自定义解析逻辑的场景。
漏洞代码
以下漏洞代码未禁用外部实体解析:
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import java.io.ByteArrayInputStream;
import javax.servlet.http.HttpServletRequest;
public class VulnerableXXE {
public static void main(String[] args) {
HttpServletRequest request = getRequest();
// 从 GET 请求中获取 XML 内容
String xml = request.getParameter("xml");
if (xml == null || xml.isEmpty()) {
System.out.println("请提供 XML 内容作为参数");
return;
}
try {
// 创建 DocumentBuilderFactory 对象
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// 未禁用对外部实体的支持,导致 XXE 漏洞
DocumentBuilder builder = factory.newDocumentBuilder();
// 解析用户传递的 XML 内容
Document document = builder.parse(new ByteArrayInputStream(xml.getBytes()));
// 输出解析结果
System.out.println("解析结果:");
System.out.println(document.getDocumentElement().getTextContent());
} catch (Exception e) {
System.err.println("解析 XML 时发生错误:");
e.printStackTrace();
}
}
}
以上未禁用外部实体解析,攻击者可以通过<!ENTITY>标签加载外部文件(如file:///etc/passwd)。
<!ENTITY> 是 XML 中的一个声明,用于定义 实体。在 XML 中,实体可以用于以下目的:引用字符或者字符串;引用外部资源,例如文件或 URL;扩展某些 XML 元素或属性的内容。
payload:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]>
<foo>&xxe;</foo>
防范
禁用外部实体解析、对用户提供的XML输入进行严格的验证、选择默认禁用外部实体解析的库,或在初始化时显式禁用。
例如在使用DocumentBuilderFactory、SAXParserFactory等原生方法时,显式禁用外部实体解析:
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);