[代碼審計]雲ec電商系統代碼審計


0x00 前言

看了一下博客內最新的文章,竟然是3月28號的,一個多月沒寫文章了,博客都長草了。

主要是臨近畢業,事情繁多,也沒有啥時間和心情靜下來寫。。

不過現在的話,畢業的東西告一段落了,幾乎沒啥事了,總算能靜下來好好學習。

發了篇文章在t00ls,不過是精簡版的,有些地方沒有細講。之前也有伙伴留言說,文章放到t00ls,有些沒賬號的朋友看不到。這里我搬運一下。

0x01 基礎環境
審計版本為V1.2
最后更新時間:2018-04-20
用到的工具:vscode,phpstudy

0x02 前台注入漏洞分析
漏洞其實很簡單,就是我們常見的獲取ip沒過濾的問題的,但這里需要點條件。
看到inc\funciton\functions_admin.php getip()函數

function getip(){
       static $ip = null;
       if($ip)
         return $ip; // 不需要計算第二次.
       $ip=false;
       if($_SERVER['HTTP_VIA']){ //使用了代理
           if(!empty($_SERVER["HTTP_CLIENT_IP"])){ //設置client_ip頭
                $ip = $_SERVER["HTTP_CLIENT_IP"];
           }else if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])){
                $ips = explode (", ", $_SERVER['HTTP_X_FORWARDED_FOR']);
                if ($ip){
                    array_unshift($ips, $ip); $ip = '';
                }
                $ipss = count($ips);
                for ($i = 0; $i < $ipss; $i++){
                     if (!preg_match('/^(10|172\.16|192\.168)\./', $ips[$i])){
                               $ip = $ips[$i];
                               break;
                     }
                }
           }
       }else{
            $ip = $_SERVER['REMOTE_ADDR'];
       }
         
       # 更兼容的獲取.
        if(!$ip)//if $ip為false
        if(!$ip = getenv("REMOTE_ADDR"))
        if (!$ip = getenv("HTTP_CLIENT_IP"))
        if(!$ip = getenv("HTTP_X_FORWARDED_FOR"))
            $ip = '';
       return $ip;
}

看到幾個判斷語句,先是判斷是否使用了代理訪問,如果沒有使用直接用$_SERVER['REMOTE_ADDR']來獲取ip,這個我們是沒辦法控制的。
如果使用了代理訪問的話,進到判斷$_SERVER['HTTP_CLIENT_IP']是否為空,不為空,直接設置$ip=$_SERVER['HTTP_CLIENT_IP'],而這個頭我們是可以通過Client-Ip進行控制。
因為這里是SERVER變量,繞過了對GPC的過濾,看到過濾文件inc\function\global.php

$getfilter="\\b(and|or)\\b.+?(>|<|=|in|like)|\\/\\*.+?\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT|UPDATE.+?SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)";
$postfilter="\\b(and|or)\\b.{1,6}?(=|>|<|\\bin\\b|\\blike\\b)|\\/\\*.+?\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT|UPDATE.+?SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)";
$cookiefilter="\\b(and|or)\\b.{1,6}?(=|>|<|\\bin\\b|\\blike\\b)|\\/\\*.+?\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT|UPDATE.+?SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)";

function StopAttack($StrFiltKey,$StrFiltValue,$ArrFiltReq){  
        if(is_array($StrFiltValue))
        {
                $StrFiltValue=implode($StrFiltValue);
        }                
        if (preg_match("/".$ArrFiltReq."/is",urldecode($StrFiltValue))){
                        print "網址有誤~";
                        exit();
        }      
}  

foreach($_GET as $key=>$value){
        StopAttack($key,$value,$getfilter);
}
if ($_GET["p"]!=='admin'){
        foreach($_POST as $key=>$value){ 
                StopAttack($key,$value,$postfilter);
        }
}

foreach($_COOKIE as $key=>$value){ 
        StopAttack($key,$value,$cookiefilter);
}
unset($_GET['_SESSION']);
unset($_POST['_SESSION']);
unset($_COOKIE['_SESSION']);

全局尋找getip()使用的位置。

找到了一處比較好利用的一個點,看到文件inc\module\user.php

elseif ($act == 'add_comment_reply')
        {
                $content = isset($content) ? trim($content) : '';
                $cid = isset($cid) ? intval($cid) : '';
                /*if(intval($gid)==0)
                {
                        $res['err'] = '獲取商品號失敗';
                        die(json_encode_yec($res));
                }*/
                if(intval($pid)==0)
                {
                        $res['err'] = '回復的對象錯誤';
                        die(json_encode_yec($res));
                }
                if(!isset($content) || $content == '')
                {
                        $res['err'] = '請輸入回復內容';
                        die(json_encode_yec($res));
                }
                elseif(mb_strlen($content,'utf-8')>120) {
                        $res['err'] = '字數請控制在120個以內';
                        die(json_encode_yec($res));
                }
                if($ym_uid==0) //需要登錄
                {
                        $ym_uid =check_login(1);
                        if($ym_uid==0)
                        {
                                $res['url'] = 'login.html';
                                die(json_encode_yec($res));
                        }
                }
                dbc();                
                if(check_comment_reply($ym_uid, getip())==false)
                {
                        $res['err'] = '您評論的有點頻繁了,請休息一小時再來吧~';
                        die(json_encode_yec($res));
                }
                $reply_id = get_comment_uid(intval($pid), intval($ptype));
                $id = add_comment_reply($cid,$ym_uid, $reply_id, intval($pid), intval($ptype), role_user, $content);
                $res['res'] = $id;
        }

這里是用戶評論的邏輯代碼,看到最后一個if判斷中,使用到了getip()函數,跟進check_comment_reply函數,inc\lib\user.php

function check_comment_reply($uid, $ip='')
{
        global $db;
        $where ='';
        $row_uid = $db->query("select count(*) count from ".$db->table('comment_reply')." where addtime>=".strtotime(date('Y-m-d H:i:s',strtotime("-1 hour")))." and uid=".$uid);
        $row_ip = $db->query("select count(*) count from ".$db->table('comment_reply')." where addtime>=".strtotime(date('Y-m-d H:i:s',strtotime("-1 hour")))." and ip='".$ip."'");

        if( (!$row_uid || count($row_uid)==0 || intval($row_uid['count']) < 10) && (!$row_ip || count($row_ip)==0 || intval($row_ip['count']) < 10))        
        {
                return true;
        }
        
        return false;
}

沒有過濾帶進去了,那么就可以注入了。getip()有很多地方用到,但是僅限於使用query,查詢的。insert,update方法都在底層做了過濾,不展開講。而且這個站默認是開啟報錯的,可以直接利用報錯獲取數據。
那么整理一下漏洞利用條件:
1,需要開啟會員注冊,因為注冊要發送驗證碼,很多商城可能已經廢了。
2,需要代理訪問。
媽的,不知道誰改了demo站演示賬號的密碼,找了個站演示:

文字表達一下利用:
請求URL:/user.html?act=add_comment_reply&content=1&cid=1&pid=1
header:Client-Ip:注入payload
獲取管理賬號

獲取密碼,分兩段獲取,最后一個字符獲取不出來。

 

最后的密碼為:f9c8c87e0a1f9d085b1008010fbd7781
這個系統密碼的加密方式是這樣的:

//加密字符串
function encryptStr($val, $salt='')
{        
        return md5(md5(trim($val)).$salt);
}

加了鹽,我們需要把鹽也注出來:

 

之后就是拉去解一下md5,md5解不出也沒有關系。
因為這個系統底層用的是pdo,支持多語句執行。
那么可以直接插入一個管理員,或者修改已有管理員的密碼,搞完之后修改回來就好了。這里以修改管理員密碼為例:

 

 進入后台

0x03 后台任意文件包含

后台有一個任意文件包含,上傳圖片馬,訪問URL

http://example.com/admin.html?do=express_track&oauth_code=1&sp_file=圖片馬路徑

 

這里主要是因為sp_file沒有定義,系統存在全局變量注冊,可直接定義變量,看到inc\admin_inc\page\express_track.php

<?php
if (!defined('in_mx')) {exit('Access Denied');}
 
checkAuth($login_id, 'system');//權限檢測 
 
 
//$sp_file = plugin."oauth/".$code.'/'.$code.'.php';
$path = plugin."express/";
$dirs =get_dir($path); 

if($oauth_code)
{
    if(file_exists($sp_file)==false){message("獲取不到文件 ".$code.'.php');}
    require($sp_file);//任意文件包含
    $row = $db->fetch('express_track', '*',  array('code' => $code));
}

$row = $db->fetchall('express_track', '*'); 

 //die("a".$p);

     
?>

$sp_file未定義,無過濾,直接require,造成了文件包含。這個利用前台的頭像+后台的csrf可以直接getshell。

這個系統還有一個session固定的漏洞,結合組合拳直接進后台。

還有之前和Adog師傅聊,他說有xss,我也沒找,這里就不細說了。

上面的兩個利用無須xss,只有一個img標簽的請求即可。利用原理參考我的這篇文章([代碼審計]yxcms從偽xss到getshell,https://www.cnblogs.com/r00tuser/p/8419300.html)

img請求+csrf+get請求任意文件包含=getshell

img請求+csrf+get請求session固定=進后台

 

0x04 總結

這個系統還有很多問題,不一一細講,拋磚引玉。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM