1. 程式人生 > >20.Ecshop 2.x/3.x SQL註入/任意代碼執行漏洞(附實戰exp)

20.Ecshop 2.x/3.x SQL註入/任意代碼執行漏洞(附實戰exp)

list 文件中 使用 arr php文件 滿足 null sig 安全

Ecshop 2.x/3.x SQL註入/任意代碼執行漏洞

影響版本:

Ecshop 2.x

Ecshop 3.x-3.6.0

漏洞分析:

該漏洞影響ECShop 2.x和3.x版本,是一個典型的“二次漏洞”,通過user.php文件中display()函數的模板變量可控,從而造成SQL註入漏洞,而後又通過SQL註入漏洞將惡意代碼註入到危險函數eval中,從而實現了任意代碼執行。

值得一提的是攻擊者利用的payload只適用於ECShop 2.x版本導致有部分安全分析者認為該漏洞不影響ECShop 3.x,這個是因為在3.x的版本裏有引入防註入攻擊的安全代碼,通過我們分析發現該防禦代碼完全可以繞過實現對ECShop 3.x的攻擊(詳見下文分析)。

註:以下代碼分析基於ECShop 2.7.3

SQL註入漏洞分析:

首先我們看一下漏洞的起源點 user.php ,在用戶login這裏有一段代碼:

/* 用戶登錄界面 */
elseif ($action == login)
{
    if (empty($back_act))
    {
        if (empty($back_act) && isset($GLOBALS[_SERVER][HTTP_REFERER]))
        {
            $back_act = strpos($GLOBALS[‘_SERVER‘][‘HTTP_REFERER‘], ‘user.php‘) ? ‘./index.php‘ : $GLOBALS[‘_SERVER‘][‘HTTP_REFERER‘
]; } else { $back_act = user.php; } } $captcha = intval($_CFG[captcha]); if (($captcha & CAPTCHA_LOGIN) && (!($captcha & CAPTCHA_LOGIN_FAIL) || (($captcha & CAPTCHA_LOGIN_FAIL) && $_SESSION[login_fail] > 2
)) && gd_version() > 0) { $GLOBALS[smarty]->assign(enabled_captcha, 1); $GLOBALS[smarty]->assign(rand, mt_rand()); } $smarty->assign(‘back_act‘, $back_act); $smarty->display(user_passport.dwt); }

Ecshop使用了php模版引擎smarty,該引擎有兩個基本的函數assign()、display()。assign()函數用於在模版執行時為模版變量賦值,display()函數用於顯示模版。

smarty運行時,會讀取模版文件,將模版文件中的占位符替換成assign()函數傳遞過來的參數值,並輸出一個編譯處理後的php文件,交由服務器運行。

可以看到 $back_act 是從 $GLOBALS[‘_SERVER‘][‘HTTP_REFERER‘] 獲取到的,HTTP_REFERER 是外部可控的參數,這也是我們註入payload的源頭。

在模版執行時,assign()把 $back_act 賦值給模版變量 back_act ,下面我們跟進這個模版變量到 /includes/cls_template.php

    /**
     * 註冊變量
     *
     * @access  public
     * @param   mix      $tpl_var
     * @param   mix      $value
     *
     * @return  void
     */
    function assign($tpl_var, $value = ‘‘)
    {
        if (is_array($tpl_var))
        {
            foreach ($tpl_var AS $key => $val)
            {
                if ($key != ‘‘)
                {
                    $this->_var[$key] = $val;
                }
            }
        }
        else
        {
            if ($tpl_var != ‘‘)
            {
                $this->_var[$tpl_var] = $value;
            }
        }
    }

從上面的assign()函數,我們看出 $tpl_var 接受我們傳過來的參數 $back_act ,但不是個數組,所以 $this ->_var[$back_act] = $back_act ,而後調用display()函數來顯示模板,在includes/init.php文件中創建了Smarty對象cls_template來處理模版文件,對應的文件是 includes/cla_template.php

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;
    }

從user.php調用display()函數,傳遞進來的$filename是user_passport.dwt,從函數來看,首先會調用 $this->fetch 來處理 user_passport.dwt 模板文件,fetch函數中會用 $this->make_compiled 來編譯模板。user_passport.dwt其中一段如下:


<td align="left">
<input type="hidden" name="act" value="act_login" />
<input type="hidden" name="back_act" value="{$back_act}" />
<input type="submit" name="submit" value="" class="us_Submit" />

make_compiled 會將模板中的變量解析,也就是在這個時候將上面assign中註冊到的變量 $back_act 傳遞進去了,解析完變量之後返回到display()函數中。此時$out是解析變量後的html內容,後面的代碼是判斷 $this->_echash 是否在 $out 中,如果存在的話,使用 $this->_echash 來分割內容,得到$k然後交給insert_mod處理,我們來查找 _echash 的值:

技術分享圖片

發現 _echash 是個默認的值,不是隨機生成的,所以 $val 內容可隨意控制。跟進$this->insert_mod:

  function insert_mod($name) // 處理動態內容
    {
        list($fun, $para) = explode(|, $name);
        $para = unserialize($para);
        $fun = insert_ . $fun;

        return $fun($para);
    }

$val傳遞進來,先用 | 分割,得到 $fun $para$para進行反序列操作,$fun insert_ 拼接,最後動態調用 $fun($para),函數名部分可控,參數完全可控。接下來就是尋找以 insert_ 開頭的可利用的函數了,在 /includes/lib_insert.php 有一個 insert_ads() 函數,正好滿足要求。看下 insert_ads() 函數:

function insert_ads($arr)
{
    static $static_res = NULL;

    $time = gmtime();
    if (!empty($arr[‘num‘]) && $arr[‘num‘] != 1)
    {
        $sql  = SELECT a.ad_id, a.position_id, a.media_type, a.ad_link, a.ad_code, a.ad_name, p.ad_width,  .
                    p.ad_height, p.position_style, RAND() AS rnd  .
                FROM  . $GLOBALS[ecs]->table(ad) .  AS a .
                LEFT JOIN  . $GLOBALS[ecs]->table(ad_position) .  AS p ON a.position_id = p.position_id  .
                "WHERE enabled = 1 AND start_time <= ‘" . $time . "‘ AND end_time >= ‘" . $time . "".
                    "AND a.position_id = ‘" . $arr[‘id‘] . "" .
                ORDER BY rnd LIMIT  . $arr[‘num‘];
        $res = $GLOBALS[db]->GetAll($sql);
    }
    else
    {
        if ($static_res[$arr[‘id‘]] === NULL)
        {
            $sql  = SELECT a.ad_id, a.position_id, a.media_type, a.ad_link, a.ad_code, a.ad_name, p.ad_width, .
                        p.ad_height, p.position_style, RAND() AS rnd  .
                    FROM  . $GLOBALS[ecs]->table(ad) .  AS a .
                    LEFT JOIN  . $GLOBALS[ecs]->table(ad_position) .  AS p ON a.position_id = p.position_id  .
                    "WHERE enabled = 1 AND a.position_id = ‘" . $arr[‘id‘] .
                        "‘ AND start_time <= ‘" . $time . "‘ AND end_time >= ‘" . $time . "" .
                    ORDER BY rnd LIMIT 1;
            $static_res[$arr[id]] = $GLOBALS[db]->GetAll($sql);
        }
        $res = $static_res[$arr[id]];
    }
    $ads = array();
    $position_style = ‘‘;

20.Ecshop 2.x/3.x SQL註入/任意代碼執行漏洞(附實戰exp)