XML 相关漏洞

XML基础

基础

https://www.cnblogs.com/l0nmar/p/13337996.html

一个XML示例:

<?xml version="1.0" encoding="utf-8"?>
<!--这里是注释-->
<books>
    <book id="b01">
        <name>Python黑客编程从入门到入狱</name>
        <author>张三</author>
        <price>$20.00</price>
    </book>
</books>

如上代码,第一行是XML文档的声明,由“”结尾,其中的内容是对本xml文档所使用的版本 “version”和编码“encoding”的声明,version一般情况下都是1.0,因为目前为止,xml只有这一个版本。

第二行是注释,不多做解释。

从第三行开始,就是XML文档的主要内容了,如代码中所示的“”,是本文档的根元素,“”是“”的子元素,而“”也都是子元素,但是是“”的子元素。

那么我们可以将这个XML文档,视为是一个描述图书的文档,它所描述的内容,包括了图书的名字、作者和价格,如果使用程序对这个文档进行解析后,那么这些信息就可以更好的显示在web页面或者是应用程序中,方便用户查看。

XML的格式

a. 声明信息,用于描述xml的版本及编码格式。

b. xml有且仅有一个根元素(可以理解为顶级的元素、没有被其他元素包起来的元素)。个人理解:像是数据库的表名

c. xml中大小写敏感

d. 标签是成对出现的,所有元素都必须有一个关闭标签,而且要正确嵌套。

e. 属性值要使用双引号

f. 注释的写法。

<!--这是注释 -->
g. 一个格式良好的xml文件

<?xml version="1.0" encoding="utf-8"?>
<!--这里是注释-->
<books>
    <book id="b01">
        <name>Python黑客编程从入门到入狱</name>
        <author>张三</author>
        <price>$20.00</price>
    </book>
</books>

h. XML并不是让用户直接打开的,而是让别的语言来从文件中读取信息的。至于为什么可以直接用浏览器浏览,只是浏览器可以识别而已。

XML的属性

拥有正确语法的 XML 被称为"形式良好"的 XML。而判断XML的语法是否合法,叫做XML验证,是通过 DTD进行验证的。
DTD:Document TypeDefinition 文档类型定义。用于约束xml的文档格式,保证xml是一个有效的xml,DTD分为内部和外部两种。DTD定义在xml文件中视为内部DTD;DTD定义在外部的dtd文件中,视为外部DTD。
说的简单一点,DTD就是对当前的XML文档做一个约束,DTD中定义了这个文档中的根元素是什么,有几个子元素,每个子元素能出现几次,哪些元素有属性,属性的类型是什么,属性的默认值是什么等等,如果后面的XML内容中,与DTD中的定义不符,如元素个数不符、元素名称大小写不符等,那么XML文件解析时就会报错。

1)内部DTD的使用

内部DTD的定义

<!DOCTYPE 根元素 [元素声明]>

元素声明语法

[
<!ELEMENT 根元素 (子元素)>
<!ELEMENT 根元素的子元素 (子元素的子元素,子元素的子元素)>
<!ELEMENT 子元素 (数据类型)>
<!ELEMENT 子元素 (数据类型)>
]

元素声明中的数量词

"+" 表示出现一次或者多次
"?"表示出现0次或多次
"*"表示出现任意次。

属性声明语法

<!ATTLIST 元素名称 属性名称 属性类型 默认值>

示例:

<?xml version="1.0" encoding="utf-8"?>
<!--这里是注释-->
<!DOCTYPE books [
    <!ELEMENT books (book+)>
    <!ELEMENT book (name,author,price)>
    <!ATTLIST book id CDATA #REQUIRED>
    <!ELEMENT name (#PCDATA)>
    <!ELEMENT author (#PCDATA)>
    <!ELEMENT price (#PCDATA)>
    
]>
<books>
    <book id="b01">
        <name>Python黑客编程从入门到入狱</name>
        <author>张三</author>
        <price>$20.00</price>
    </book>
</books>

如上,就是一个内部DTD的引用示例,在DTD定义中,要求根元素books的子元素book出现一次及以上,子元素book又有三个子元素,分别为name,author和price,然后声明了元素book的id属性,其类型是CDATA,并且是必须的(#REQUIRED),最后定义了book的三个子元素的数据类型为#PCDATA,这表示这三个元素标签中的内容必须是文本,并能再出现子标签。

2)外部DTD的使用:

首先需要创建一个外部的dtd文件。内容中不需要包括<!DOCTYPE...>,直接<!ELEMENT...>,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<!ELEMENT books (book+)>
<!ELEMENT book (name,author,price)>
<!ATTLIST book id CDATA #REQUIRED>
<!ELEMENT name (#PCDATA)>
<!ELEMENT author (#PCDATA)>
<!ELEMENT price (#PCDATA)>

然后在XML文档中引入外部的DTD:
<!DOCTYPEbooks SYSTEM "xxx.dtd">
注意外部实体引用时的关键字“SYSTEM”,同时也可以使用“PUBLIC”这个关键字,这两者的区别在于,SYSTEM表示私有的DTD,PUBLIC表示共有的DTD。

DTD实体

实体就像是变量,可以用于存储数据,以便后续的使用。但它的功能又不仅仅是存储,比如外部实体,除了可以存储数据,还可以从远程文件或远程网络中读取内容或调用数据。

从实体被定义的位置来看,实体可以分为内部实体和外部实体,就像内部DTD和外部DTD一样,内部实体,就是在XML文档内部的DTD进行定义的实体,外部实体就是定义在外部DTD文件中然后被引用到当前XML中的实体。

(1) 内部实体声明

声明语法:<!ENTITY 实体名称 "实体的值">

内部实体引用示例:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE books [
	<!ENTITY test "Hello World">
]>

<books>&test;</books>

(2) 外部实体声明

声明语法:

<!ENTITY 
实体名称 SYSTEM "URI/URL"
>

声明一个外部实体的关键在于“SYSTEM”这个关键字。SYSTEM在此意图让xml解析器知道,现在声明的是一个外部实体,需要从后面的外部资源中获取内容并存储在内部实体,如果后面的外部资源的语法,存在特殊符号,那么xml解析器会报错。

外部实体引用可支持http,file等协议,不同的语言支持的协议不同,但存在一些通用的协议,比如http、file、ftp等,具体内容如下所示:

另外,从实体的引用方式来区分,实体又可以分为:一般实体、参数实体、预定义实体。

一般实体:General Entities,就是我们上面的示例中的实体,使用&进行引用

预定义实体:PredefinedEntities,就是xml本身对一些特殊字符进行了预定义,方便用户直接引用,比如小于号,如果直接在xml文档中使用小于号,会被xml解析器视为标签,从而引起解析错误。那么此时就需要调用小于号所对应的预定义实体来引用:&#x3C。

参数实体:Parameter Entities,这也是XXE学习中的重点,在XXE利用中经常被使用。

(3) 参数实体

参数实体声明:

内部:<!ENTITY % 实体名称 "实体值">
外部:<!ENTITY % 实体名称 SYSTEM  "URI">

参数实体应注意以下几点:

(1)使用 % 实体名(这里面空格不能少) 在 DTD 中定义,并且只能在 DTD 中使用 “%实体名;” 引用

(2)只有在 DTD 文件中,参数实体的声明才能引用其他实体

(3)和通用实体一样,参数实体也可以外部引用

就是参数实体不能像普通实体那样在xml文档内容中进行引用,它的引用范围只在当前xml文件的DTD声明中,或者是当前的DTD文件中

参数实体引用示例:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE books [
	<!ENTITY % xxe "hello">
	%xxe;

]>

<books></books>

XML注入

原理

比如:一个 web 应用,在进行用户注册时,选择以 xml 来存储数据到 xmldb 数据库中,当用户填写用户名,密码和邮箱时,后台存储的文件格式及内容如下

那么攻击者就可以在注册的时候构造恶意的数据,假设他在用户名与密码的输入框中输入正常的文本,在最后的邮箱输入框中输入如下内容:

那么就会多注册一个名为admin的用户

防御

能够进行XML注入攻击的前提是,用户能够控制数据的输入,程序没有对输入的内容进行过滤且拼接了数据

那么相应的,破坏掉其中一个前提就可以进行防御了,既然我们无法限制用户的输入,那么就可以对数据进行过滤,将XML语言本身的“保留字符”进行过滤或者转义即可。

XXE注入漏洞

介绍

XXE注入也是XML注入的一部分,但相较于普通的XML注入,XXE注入的攻击面更广,危害更大。

XXE注入(XML External Entity Injection) 全称为 XML 外部实体注入

注入的对象 : XML外部实体

当遇见能够解析XML内容的页面时,如果能注入外部实体并且成功解析的话,这就会大大拓宽XML 注入的攻击面

XXE类型

XXE的攻击形式主要分为:带内数据实体注入、基于错误的实体注入和带外数据实体注入

带内数据实体注入:in-band ,XML解析后的数据会直接显示在屏幕上

基于错误:error-based,解析结果只有一大堆的错误

带外数据:out-of-band,也叫XXE盲注,注入的XML解析后无任何输出响应,必须执行一些带外请求把数据提取出来。

PHP的XXE注入产生的条件:

a. Libxml的版本尽可能的低,libxml是PHP的xml解析库,因为从2.8.0版本开始,libxml默认是不加载外部实体的,如果要使用较高版本的libxml的话,需要在编写代码的时候对参数做设置。
b. 目标主机没有禁用外部实体的引用。
c. 用户可以控制xml的输入内容

有回显的本地文件读取

读取文件

测试代码:

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

?>

payload:

<?xml version="1.0" encoding="UTF-8"?>
<!-- payload 1 -->
<!DOCTYPE root [
	<!ENTITY xxe SYSTEM "file:///c:/windows/system.ini">
]>

<root>&xxe;</root>

这样我们就读取到了windows系统的system.ini的文件内容。

但是这样也不代表这个payload的就适用于任何情况 比如我们更换一个读取的文件xmltest2.txt,内容是

就会报错

主要是因为我们要读取的文件内容中存在很多的特殊字符,大于号、小于号等

当xml的标签内还存在小于号、大于号等特殊字符时,尤其是小于号,会被XML解析器误认为是另一个标签的开始,这样就会造成解析的错误

所以我们就要想办法绕过。这个时候我们就需要了解一下XML CDATA了

CDATA

XML CDATA:

所有XML文档中的文本均会被解析器解析, 只有CDATA中的文本会被解析器忽略

它可以使得使用其中的数据内容不会被xml解析器解析。然后我们再看其使用方式

CDATA 部分由 "" 结束:

例子:

<script>
<![CDATA[
function matchwo(a,b)
{
if (a < b && a < 0) then
  {
  return 1;
  }
else
  {
  return 0;
  }
}
]]>
</script>

CDATA 部分不能包含字符串 "]]>"。也不允许嵌套的 CDATA 部分。

标记 CDATA 部分结尾的 "]]>" 不能包含空格或折行。

那么了解了这些,我们就可以尝试使用CDATA绕过

我们尝试直接使用实体来进行拼接,但是测试失败:

这说明我们的拼接方式不可行,我们现在使用的是一般实体,我们在前面的xml基础知识中介绍过了,一般实体的引用是在xml文档内容中,既然在xml文档内容中拼接不可行,那再dtd中拼接可行吗?我们再次进行尝试,既然再dtd中拼接,那就需要用到参数实体了。

我们再次尝试构造payload:

理论上,我们完美地将这几个参数实体拼接了起来,并将值赋给了一般实体all,但是遗憾的是,我们的payload还是报错了

那么这又是为什么呢?根据XML规范所描述:“在DTD内部子集中的参数实体调用,不能混掺到标记语言中”,这是什么意思呢?就是不能在实际的标记语言中来调用参数实体,像我们这样,就是在标记语言中进行调用:

但可以在同级别中被当作标记语言调用,就像是参数实体的引用,就是将调用当成了一个标记语言,像这样:

也就是我们所构造的payload这种使用方式,不能在内部DTD中被这样使用,但是幸运的是,XML规范还声明了一点:“外部参数实体不受此限制”,这就告诉我们可以使用外部的DTD来构造payload,将我们的CDATA内容拼接起来:

DTD文件的内容:

我们再次进行攻击尝试,成功读取到文件内容:

由于环境资源的关系,我们在进行攻击时,所使用的外部dtd文件,是本地环境的。但是在实际的攻击情况下,这个DTD文件应该是我们自己所掌握的主机的DTD文件,文件的内容是受我们所控的。

无回显本地文件读取

但是,在实际情况中,大多数情况下服务器上的 XML 并不是输出用的,所以就少了输出这一环节,这样的话,即使漏洞存在,我们的payload的也被解析了,但是由于没有输出,我们也不知道解析得到的内容是什么,因此我们想要现实中利用这个漏洞就必须找到一个不依靠其回显的方法——外带数据

先看一下漏洞示例:

相较于前面有回显的漏洞代码,我们去掉了内容输出的一部分。这样,用之前的payload就没有作用了:

有了前面使用外部DTD文件来拼接内部DTD的参数实体的经验,我们可以知道,通过外部DTD的方式可以将内部参数实体的内容与外部DTD声明的实体的内容拼接起来,那么我们就可以有这样的设想:

我们可以在本地做一个端口监听,然后利用payload来从目标主机读取到文件内容后,将文件内容作为url的一部分来请求我们本地监听的端口,这样,我们只需要查看请求的url就可以知道读取到的内容是什么。

首先,我们使用ncat监听一个端口:

然后,我们构造payload:

我们选择使用外部DTD,在我们自己所能掌控(或是自己搭建)的主机上编写一个dtd文件:

我们注意到,第一个参数实体的声明中使用到了php的base64编码,这样是为了尽量避免由于文件内容的特殊性,产生xml解析器错误

Payload如下:

如图,我们先声明一个外部的DTD引用,然后再xml文档内容中引用外部DTD中的一般实体。

开始攻击:

然后查看我们的端口监听情况,会发现我们收到了一个连接请求,问号后面的内容就是我们读取到的文件内容经过编码后的字符串

有时候也会出现报错的情况(这是我们在漏洞的代码中没有屏蔽错误和警告),比如我们这里的payload没有选用php的base64编码,这里报错了,但是同时也将所读取的内容爆了出来,只是特殊字符经过了HTML实体编码。

XXE的其他攻击方式

通过XXE漏洞进行内网探测

当然进行内网探测我们还需要做一些准备工作,就是获取目标主机在内网中的IP地址,或是内网的网络划分信息,我们可以先利用 file 协议读取我们作为支点服务器的网络配置文件,看一下有没有内网,以及网段大概是什么样子(我以linux 为例),我们可以尝试读取 /etc/network/interfaces 或者 /proc/net/arp 或者 /etc/host 等跟内网配置有关的文件,我们可以通过这些文件的内容来获取更多有关内网的信息。

如果实在没有办法获取目标主机的内网配置相关信息,那就花费时间爆破吧。

内网存活主机探测:

如下,其实payload就是简单的一个外部实体的注入payload:

只不过是将http://后面的部分替换为目标主机:

就像这样,如果目标主机对应的端口开启了http服务,或是其他服务,如ftp,也是可以通过http协议来访问,这样根据目标主机的响应内容或者状态码,就可以判断主机的存活与否,根据这个原理,我们在网上找到了相应的py脚本:

运行该脚本就能够找出相应网段是否存在开启http服务的主机。

内网主机端口探测:

同样的,根据内网存活主机的扫描方式,我们也可以针对某个主机进行端口的扫描:

构造payload:

扫描的大概原理是这样的:
比如你扫描一个关闭的端口,在等待了一段时间后,返回协议连接失败且超过了最长时间限制30秒:

而你扫描开放的端口,也可能是内容错误等其他的警告信息:

有时候,端口扫描时判断端口的开放和关闭并不是虚拟机中这个样子,由于环境的不同,版本的不同,你可能会遇到的状况是:关闭的端口在超时之后,返回的是500状态码。

而开放的端口,返回的是200的状态码,以及一些xml警告信息,有时候还会附带上我们payload中期望输出的字符串。

所以,在进行内网探测时,探测结果判定的依据,还需要你自己来判断。你可以是根据返回状态码,也可以是根据返回的警告信息的内容,又或者你也可以尝试根据返回信息的时间长短。(我们在实验中就不写了,当然你也可以想办法,将扫描存活主机与端口同时进行,我觉得这样准确率反而会更高一些,就是速度会很慢)。

通过XXE漏洞进行命令执行

这种情况比较少见,所需的前提条件除了真实存在XXE漏洞外,大概还需要:

a. 目标系统为Linux系统
b. 目标系统成功安装PHP的expect扩展

而且这个漏洞所执行的命令也有限制:

a. 可执行的命令与当前用户的权限大小有关
b. 命令中不能有空格,否则会报错

一般情况下payload:

通过XXE漏洞进行DOS攻击

上面的payload就是著名的“billionlaughs”攻击,该代码可以在目标主机的内存中生成十亿个“lol”字符串,从而导致 Dos攻击

它也被称为指数实体扩展攻击,是一种名副其实的XML炸弹。原理为:通过创建一项递归的 XML 定义,构造恶意的XML实体文件耗尽可用内存,如以上代码所示,在XMl中定义了一个实体lol9,它的值包含了十个实体lol8的值,而每个lol8又包含了十个lol7的值...最后产生10亿个“lol”字符串,占用内存约高达3GB。因为许多XML解析器在解析XML文档时倾向于将它的整个结构保留在内存中,解析非常慢,这样,就会占用大量的内存资源,造成了拒绝服务器攻击。

XXE的防御:

方案一:

过滤用户输入的xml数据,比如尖括号,一些关键字:<!DOCTYPE和<!ENTITY,或者,SYSTEM和PUBLIC等

方案二:

禁用外部实体:
PHP:

libxml_disable_entity_loader(true);

JAVA:

DocumentBuilderFactorydbf =DocumentBuilderFactory.newInstance();

dbf.setExpandEntityReferences(false);

Python:

from lxml import etree

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

另外,可以自己研究一下php的其他协议在xxe注入中的使用,比如,读取本地文件的时候,如果文件过大,可能会报错,那么这个时候就可以使用php的某个协议对文件的内容进行压缩……(自己思考研究)

其他资源

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

https://blog.csdn.net/lileiyuyanqin/article/details/72828922

https://www.cnblogs.com/backlion/p/9302528.html

https://www.freebuf.com/column/156863.html

https://www.freebuf.com/column/208904.html

https://www.freebuf.com/articles/web/126788.html

https://www.freebuf.com/vuls/194112.html

https://www.cnblogs.com/r00tuser/p/7255939.html

https://www.freebuf.com/column/188849.html

posted @ 2020-07-21 21:09  10nnn4R  阅读(233)  评论(0编辑  收藏