Java审计之XXE
写在前面
因为已经很久没有接触到XXE了,所以借此机会打算温习一遍XXE再来讲一下在Java中去审计XXE的一个思路和流程。
About XXE
基础知识
XXE(XML External Entity Injection) 全称为 XML 外部实体注入,与其他的注入漏洞类似,只不过XXE注入的是XML外部实体。
那么什么是XML?
XML是一种非常流行的标记语言,在1990年代后期首次标准化,并被无数的软件项目所采用。
XML用于标记电子文件使其具有结构性的标记语言,可以用来标记数据、定义数据类型,是一种允许用户对自己的标记语言进行定义的源语言。XML文档结构包括XML声明、DTD文档类型定义(可选)、文档元素。
XML被设计为传输和存储数据,其焦点是数据的内容,其把数据从HTML分离,是独立于软件和硬件的信息传输工具。它用于配置文件,文档格式(如OOXML,ODF,PDF,RSS,...),图像格式(SVG,EXIF标题)和网络协议(WebDAV,CalDAV,XMLRPC,SOAP,XMPP,SAML, XACML,...)。比如我们常见的web.xml,applicationContext.xml,Mybatis中用来写SQL的Mapper.xml等等
那什么是外部实体?
说到外部实体首先要说一下DTD(Document Type Definition) 即文档类型定义,用来为XML文档定义语义约束。可以嵌入在XML文档中(内部声明),也可以独立的放在一个文件中(外部引用)。在DTD中有一个实体 (ENTITY)概念,如果需要在XML文档种频繁的使用某一条数据,我们可以预先给这个数据起一个别名,也就是一个ENTITY,之后再在文档种调用它。
DTD实体的引用有内部声明实体和外部引用实体的区别。按类型分则分为:内置实体,字符实体,通用实体,参数实体。
而在XXE中最常见的就是通用实体和参数实体
内部实体
简单的内置实体格式如下:
<!DOCTYPE 文件名 [
<!ENTITY 实体名 "实体内容">
]>
内部实体可以通过如下格式声明:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE aaa[
<!ENTITY name "Zh1z3ven">
]>
<root>
<name>&name;</name>
上面提到的都是内部实体,而外部的DTD实体则需要通过URL来远程调用一个.dtd文件,也或者可以利用file协议引用一个本地的文件;
外部实体
下面以通用实体来举例子
通用实体:在DTD 中定义,在XML中以
&实体名;
引用
1.利用file协议
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE aaa [
<!ENTITY name SYSTEM "file:///c:/windows/win.ini" >
]>
<name>&name;</name>
2.利用http协议
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE aaa [
<!ENTITY name SYSTEM "http://127.0.0.1/123.txt" >
]>
<name>&name;</name>
参数实体:使用
% 实体名
在 DTD 中定义,并且只能在 DTD 中使用
%实体名;
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE a [
<!ENTITY % name SYSTEM "http://127.0.0.1/test.dtd">
%name;
]>
<d>&test;</d>
Test.dtd
<!ENTITY test SYSTEM "http://127.0.0.1/123.txt" >
当然不仅是file和http,也支持其他的协议,不过不同的语言支持的协议不一样,可以参考下图。
XXE相关更多的利用姿势也可以参考下面这篇文章
注意:libxml2.9.1及以后,默认不解析外部实体;
在java中netdoc可以做到类似于file的功能
Java中的XXE
其实不仅是Java,其他语言依旧是一样的思路,XML解析一般在导入配置、数据传输接口等场景可能会用到,涉及到XML文件处理的场景可查看XML解析器是否禁用外部实体,从而判断是否存在XXE。审计时首先需要定位危险函数,在Java中有如下的几个函数支持XML解析
javax.xml.parsers.DocumentBuilder
javax.xml.parsers.DocumentBuilderFactory
javax.xml.parsers.SAXParser
javax.xml.parsers.SAXParserFactory
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
之后就是判断是否参数可控且没有被禁用外部实体,例如如下代码会禁用外部实体
DocumentBuilderFactory dbf =DocumentBuilderFactory.newInstance();
dbf.setExpandEntityReferences(false);
.setFeature("http://apache.org/xml/features/disallow-doctype-decl",true);
.setFeature("http://xml.org/sax/features/external-general-entities",false)
.setFeature("http://xml.org/sax/features/external-parameter-entities",false);
下面以https://github.com/JoyChou93/java-sec-code/项目为例去做一个简单分析
DocumentBuilder
这是JDK自带的类,以此产生的XXE是存在回显的
WebUtils
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import com.google.common.base.Preconditions;
import org.springframework.web.util.HtmlUtils;
public class WebUtils {
// Get request body.
public static String getRequestBody(HttpServletRequest request) throws IOException {
InputStream in = request.getInputStream();
return convertStreamToString(in);
}
// https://stackoverflow.com/questions/309424/how-do-i-read-convert-an-inputstream-into-a-string-in-java
public static String convertStreamToString(java.io.InputStream is) {
java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A");
return s.hasNext() ? s.next() : "";
}
public static String getCookieValueByName(HttpServletRequest request, String cookieName) {
Cookie cookie = org.springframework.web.util.WebUtils.getCookie(request, cookieName);
return cookie == null ? null : cookie.getValue();
}
public static String json2Jsonp(String callback, String jsonStr) {
return HtmlUtils.htmlEscape(callback) + "(" + jsonStr + ")";
}
public static String getFileExtension(String fullName) {
Preconditions.checkNotNull(fullName);
String fileName = (new File(fullName)).getName();
int dotIndex = fileName.lastIndexOf('.');
return dotIndex == -1 ? "" : fileName.substring(dotIndex + 1);
}
public static String getNameWithoutExtension(String file) {
Preconditions.checkNotNull(file);
String fileName = (new File(file)).getName();
int dotIndex = fileName.lastIndexOf('.');
return dotIndex == -1 ? fileName : fileName.substring(0, dotIndex);
}
}
漏洞代码
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); // parse xml
// 遍历xml节点name和value
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;
}
SAXReader
SAXReader是第三方的库,该类是无回显的
public String SAXReaderVuln(HttpServletRequest request) {
try {
String body = WebUtils.getRequestBody(request);
logger.info(body);
SAXReader reader = new SAXReader();
// org.dom4j.Document document
reader.read(new InputSource(new StringReader(body))); // cause xxe
} catch (Exception e) {
logger.error(e.toString());
return EXCEPT;
}
SAXBuilder
public String SAXBuilderVuln(HttpServletRequest request) {
try {
String body = WebUtils.getRequestBody(request);
logger.info(body);
SAXBuilder builder = new SAXBuilder();
// org.jdom2.Document document
builder.build(new InputSource(new StringReader(body))); // cause xxe
return "SAXBuilder xxe vuln code";
} catch (Exception e) {
logger.error(e.toString());
return EXCEPT;
SAXParserFactory
该类也是JDK内置的类,但他不可回显内容,可借助dnslog平台
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()); // parse xml
return "SAXParser xxe vuln code";
} catch (Exception e) {
logger.error(e.toString());
return EXCEPT;
}
}
XMLReaderFactory
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;
}
Digester
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";
XMLReader
public String XMLReaderVuln(HttpServletRequest request) {
try {
String body = WebUtils.getRequestBody(request);
logger.info(body);
SAXParserFactory spf = SAXParserFactory.newInstance();
SAXParser saxParser = spf.newSAXParser();
XMLReader xmlReader = saxParser.getXMLReader();
xmlReader.parse(new InputSource(new StringReader(body)));
} catch (Exception e) {
logger.error(e.toString());
return EXCEPT;
}
return "XMLReader xxe vuln code";
}
小结
简单水一片文章,作为记录,后续遇到会没有的会继续补充。同时关于盲注外带XXE执行结果的利用姿势也可以参考先知的那篇文章
Reference
https://www.cnblogs.com/nice0e3/p/13746076.html
https://xz.aliyun.com/t/3357#toc-21
https://github.com/JoyChou93/java-sec-code
所有内容仅限于维护网络安全学习参考