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

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

缺陷编号: WooYun-2014-66459

漏洞标题: qibocmsV7整站系统任意文件下载导致无限制注入多处(可提升自己为管理 Demo演示)

相关厂商: 齐博CMS

漏洞作者: ′雨。认证白帽子

提交时间: 2014-06-30 10:43

公开时间: 2014-09-28 10:44

漏洞类型: SQL注射漏洞

危害等级: 高

自评Rank: 20

漏洞状态: 厂商已经确认

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

Tags标签: 无

5人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

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

简要描述:

我擦 写完标题后发现标题如此的长。

应该是qibo中用得最多的系统了把。

与之前我发的那个有所不同。 Fuzz。

发现qibo是不是换人了? 给分给的越来越低? 之前18 到 10 到现在的5分了?

用demo来演示演示把。

应该可以直接登录后台 懒得弄了。

如果这个洞还不给20的话 我只能呵呵了。

详细说明:

http://**.**.**.**/down2.php?v=v7#down



下载地址 刚下载的。



在inc/job/download.php中



code 区域
$url=trim(base64_decode($url));
$fileurl=str_replace($webdb[www_url],"",$url);
if( eregi(".php",$fileurl) && is_file(ROOT_PATH."$fileurl") ){
die("ERR");
}

if(!$webdb[DownLoad_readfile]){
$fileurl=strstr($url,"://")?$url:tempdir($fileurl);
header("location:$fileurl");
exit;
}

if( is_file(ROOT_PATH."$fileurl") ){
$filename=basename($fileurl);
$filetype=substr(strrchr($filename,'.'),1);
$_filename=preg_replace("/([\d]+)_(200[\d]+)_([^_]+)\.([^\.]+)/is","\\3",$filename);

if(eregi("^([a-z0-9=]+)$",$_filename)&&!eregi("(jpg|gif|png)$",$filename)){
$filename=urldecode(base64_decode($_filename)).".$filetype";
}
ob_end_clean();
header('Last-Modified: '.gmdate('D, d M Y H:i:s',time()).' GMT');
header('Pragma: no-cache');
header('Content-Encoding: none');
header('Content-Disposition: attachment; filename='.$filename);
header('Content-type: '.$filetype);
header('Content-Length: '.filesize(ROOT_PATH."$fileurl"));
readfile(ROOT_PATH."$fileurl");
}else{
if(eregi(".php",$fileurl)){
header("location:$fileurl");
exit;
}
$filename=basename($fileurl);
$filetype=substr(strrchr($filename,'.'),1);
$fileurl=strstr($url,"://")?$url:tempdir($fileurl);
ob_end_clean();
header('Last-Modified: '.gmdate('D, d M Y H:i:s',time()).' GMT');
header('Pragma: no-cache');
header('Content-Encoding: none');
header('Content-Disposition: attachment; filename='.$filename);
header('Content-type: '.$filetype);
readfile($fileurl);





code 区域
$url=trim(base64_decode($url))
$fileurl=str_replace($webdb[www_url],"",$url);
if( eregi(".php",$fileurl) && is_file(ROOT_PATH."$fileurl") ){
die("ERR");





这里由于是解码后再匹配 所以不能靠编码绕过。

只要匹配到.php就退出 。 测试了一下.php. 也会被匹配出。

这里还开启了i模式 所以像phP之类的大小写绕过也没办法。

难道真的没办法了?



code 区域
if( is_file(ROOT_PATH."$fileurl") ){
$filename=basename($fileurl);
$filetype=substr(strrchr($filename,'.'),1);
$_filename=preg_replace("/([\d]+)_(200[\d]+)_([^_]+)\.([^\.]+)/is","\\3",$filename);

if(eregi("^([a-z0-9=]+)$",$_filename)&&!eregi("(jpg|gif|png)$",$filename)){
$filename=urldecode(base64_decode($_filename)).".$filetype";
}
ob_end_clean();
header('Last-Modified: '.gmdate('D, d M Y H:i:s',time()).' GMT');
header('Pragma: no-cache');
header('Content-Encoding: none');
header('Content-Disposition: attachment; filename='.$filename);
header('Content-type: '.$filetype);
header('Content-Length: '.filesize(ROOT_PATH."$fileurl"));
readfile(ROOT_PATH."$fileurl");





在这里调用了is_file这函数来检测文件是否存在,如果存在的话才会进入这语句块。



由于匹配出.php 就会退出。 能有什么办法呢?



这里我们来fuzz is_file这函数一下。



code 区域
<?php
for ($i=0; $i<255; $i++) {
$yu = '1.ph' . chr($i);
$yu1 = @is_file($yu);

if (!empty($yu1)){
echo chr($i);
echo "</br>";
}

}
?>





在本地新建一个1.php的文件。 然后is_file 看看有神么能输出来。



v1.jpg





可以看到除开 P p 还有其他的



因为开启了i 所以P p 都不行 来试试<



code 区域
<?Php 

$a=$_GET[a];

$b=is_file($a);

var_dump($b);





v2.jpg





v3.jpg





可以看到1.ph< 返回了true 这样不就可以绕过这个的过滤了?



因为我看英文看不怎么懂。。 那些什么翻译 翻译来又太蛋疼了 一大堆翻译错误的。



以下是我的理解 可能有错 也请大牛来指导指导了。



因为当PHP解析器解析这些函数的时候 会调用winapi



调用了Winapi的函数Findfirstfile



然后<字符被转换成了* 成了通配符。



所以导致1.ph< 找到了1.php。



也就导致了这个漏洞的产生。



这里不止is_file函数调用了这个api 大部分的函数都调用了这个api



v4.jpg



可以看到unlink函数用这方法就不行。

没调用这api的函数大概有unlink、rename、rmdir就这三个了。

其他的函数基本都调用了。

_______________________________________________________________________________



上面那个介绍完了, 继续回到qibocms。。



code 区域
if( is_file(ROOT_PATH."$fileurl") ){
$filename=basename($fileurl);
$filetype=substr(strrchr($filename,'.'),1);
$_filename=preg_replace("/([\d]+)_(200[\d]+)_([^_]+)\.([^\.]+)/is","\\3",$filename);

if(eregi("^([a-z0-9=]+)$",$_filename)&&!eregi("(jpg|gif|png)$",$filename)){
$filename=urldecode(base64_decode($_filename)).".$filetype";
}
ob_end_clean();
header('Last-Modified: '.gmdate('D, d M Y H:i:s',time()).' GMT');
header('Pragma: no-cache');
header('Content-Encoding: none');
header('Content-Disposition: attachment; filename='.$filename);
header('Content-type: '.$filetype);
header('Content-Length: '.filesize(ROOT_PATH."$fileurl"));
readfile(ROOT_PATH."$fileurl");



在这里通过is_file的判断后。

code 区域
$filename=basename($fileurl);
$filetype=substr(strrchr($filename,'.'),1);
$_filename=preg_replace("/([\d]+)_(200[\d]+)_([^_]+)\.([^\.]+)/is","\\3",$filename);

if(eregi("^([a-z0-9=]+)$",$_filename)&&!eregi("(jpg|gif|png)$",$filename)){
$filename=urldecode(base64_decode($_filename)).".$filetype";
}



对这些有进行了各种处理, 但是我没搞懂对这些的处理有什么用?

readfile(ROOT_PATH."$fileurl")

最后带入readfile 的是$fileurl。



Come on 利用来吧。



code 区域
$url=trim(base64_decode($url));
$fileurl=str_replace($webdb[www_url],"",$url);
if( eregi(".php",$fileurl) && is_file(ROOT_PATH."$fileurl") ){
die("ERR");
}



这里由于会先解码所以首先要自己编码一次。

这里我们来下载data/config.php 这文件。

对data/config.php base64 encode

试试

v5.jpg



被匹配出了 再对data/config.ph< base64 encode



v6.jpg





成功下载到配置文件

_________________________________________________________________________



这里如何让任意文件下载变成注入?



这里qibocms 里面有一个加密解码的函数



code 区域
function mymd5($string,$action="EN",$rand=''){ //字符串加密和解密 
global $webdb;
if($action=="DE"){//处理+号在URL传递过程中会异常
$string = str_replace('QIBO|ADD','+',$string);
}
$secret_string = $webdb[mymd5].$rand.'5*j,.^&;?.%#@!'; //绝密字符串,可以任意设定
if(!is_string($string)){
$string=strval($string);
}
if($string==="") return "";
if($action=="EN") $md5code=substr(md5($string),8,10);
else{
$md5code=substr($string,-10);
$string=substr($string,0,strlen($string)-10);
}
//$key = md5($md5code.$_SERVER["HTTP_USER_AGENT"].$secret_string);
$key = md5($md5code.$secret_string);
$string = ($action=="EN"?$string:base64_decode($string));
$len = strlen($key);
$code = "";
for($i=0; $i<strlen($string); $i++){
$k = $i%$len;
$code .= $string[$i]^$key[$k];
}
$code = ($action == "DE" ? (substr(md5($code),8,10)==$md5code?$code:NULL) : base64_encode($code)."$md5code");
if($action=="EN"){//处理+号在URL传递过程中会异常
$code = str_replace('+','QIBO|ADD',$code);
}
return $code;
}





这里的key是保存到配置文件里面的, 当我们拿到key过后就可以调用这函数自己来生成一个加密的字符串。

再找哪里调用了这函数来解密的。 这样就无视了qibocms的全局转义。

key 就是保存到data/config.php里面的 刚才通过任意文件下载已经拿到了。



v7.jpg

还是给官方的key打个码、



来找找哪里调用了这函数的。



首先在member/yz.php里面



code 区域
elseif($action=='mobphone2')
{
if($lfjdb[mob_yz]){
showerr("请不要重复验证手机号码!");
}
if(!$yznum){
showerr("请输入验证码");
}elseif(!$md5code){
showerr("资料有误");
}else{
unset($code,$mobphone,$uid);
list($code,$mobphone,$uid)=explode("\t",mymd5($md5code,"DE") );
if($code!=$yznum||$uid!=$lfjuid){
showerr("验证码不对");
}
}
add_user($lfjuid,$webdb[YZ_MobMoney],'手机号码审核奖分');
$db->query("UPDATE {$pre}memberdata SET mobphone='$mobphone',mob_yz='1' WHERE uid='$lfjuid'");
refreshto("yz.php?job=mob","恭喜你,你的手机号码成功通过审核,你同时得到 {$webdb[YZ_MobMoney]} 个积分奖励!",10);





这里调用了mymd5 而且是decode 所以解码后就能直接注入了。

而且可以发现update的表是memberdata 这个表里面groupid column 就是用来判断是不是管理员的。

而且$mobphone 是解码后来的 而且直接在set位 这里只要稍微构造一下

就可以直接update groupid=3 然后就提升自己为管理员了。

这里在之前的图片系统里提到过 就不多说了。

再继续来看看。



在inc/common.inc.php中 登录后台的时候也调用了这个

code 区域
if($_COOKIE["adminID"]&&$detail=mymd5($_COOKIE["adminID"],'DE',$onlineip)){
unset($_uid,$_username,$_password);
list($_uid,$_username,$_password)=explode("\t",$detail);
$lfjdb=$db->get_one("SELECT * FROM {$pre}memberdata WHERE uid='$_uid' AND username='$_username'");
}





mymd5($_COOKIE["adminID"],'DE',$onlineip)



这里解码的时候还调用了$onlineip进了第三个参数

$secret_string = $webdb[mymd5].$rand.'5*j,.^&;?.%#@!'; //绝密字符串,可以任意设定



可以看到第三个参数是进了这个变量然后带入了加密中 看看$onlineip怎么来的。



来看看全局文件



code 区域
if($_SERVER['HTTP_CLIENT_IP']){
$onlineip=$_SERVER['HTTP_CLIENT_IP'];
}elseif($_SERVER['HTTP_X_FORWARDED_FOR']){
$onlineip=$_SERVER['HTTP_X_FORWARDED_FOR'];
}else{
$onlineip=$_SERVER['REMOTE_ADDR'];
}
$onlineip = preg_replace("/^([\d\.]+).*/", "\\1", filtrate($onlineip));
preg_match("/[\d\.]{7,15}/", $onlineip, $onlineipArray);
$onlineip = $onlineipArray[0] ? $onlineipArray[0] : '**.**.**.**';



可以看到是获取的xff 但是后面用了正则来验证ip是否合法

如果不合法的话 就return的是**.**.**.** 这里我们就随便让xff不合法就行了

然后把**.**.**.** 带入到加密函数当中

________________________________________________________________________





不多说了 直接调用一下函数生成一下加密的字符串。



v8.jpg





在测试demo的时候发现竟然不报错。



v9.jpg





这怎么可能呢? 后面想了一想

$secret_string = $webdb[mymd5].$rand.'5*j,.^&;?.%#@!'; //绝密字符串,可以任意设定



$rand 后面设定的是可以任意设定的 可能demo修改了。



然后果断继续利用刚才的方法下载inc/function.inc.php



code 区域
function mymd5($string,$action="EN",$rand=''){ //字符串加密和解密 
global $webdb;
$secret_string = $webdb[mymd5].$rand.'5*j,.^&;?.%#@!=67987d'; //绝密字符串,可以任意设定





呵呵 demo果然修改了。 把这个修改后 继续调用一下这函数 再生成一下语句。



v10.jpg





成功报错。



后面的不用多说了。 生成一个加密的报错注入的语句就能注入了。



不想多说。



这里应该可以直接登录后台,懒得弄了。

漏洞证明:

是不需要登录后台的 是在后台登录页面 等 其他多个地方注入。





见上面。

修复方案:

漏洞的源头是任意文件下载。



过滤<等特殊字符。

版权声明:转载请注明来源 ′雨。@乌云


漏洞回应

厂商回应:

危害等级:高

漏洞Rank:20

确认时间:2014-07-01 11:35

厂商回复:

感谢提出来。

最新状态:

暂无


漏洞评价:

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

漏洞评价(少于3人评价):
登陆后才能进行评分
100%
0%
0%
0%
0%

评价

  1. 2014-06-30 10:46 | ( 普通白帽子 | Rank:1218 漏洞数:105 | 传闻中魇是一个惊世奇男子, 但是除了他华...)
    0

    给的分越来越低是逼你发大洞呢..

  2. 2014-06-30 11:04 | 微尘 ( 普通白帽子 | Rank:226 漏洞数:73 )
    0

    前排留名

  3. 2014-06-30 11:14 | 小川 认证白帽子 ( 核心白帽子 | Rank:1583 漏洞数:223 | 一个致力要将乌云变成搞笑论坛的男人)
    0

    雨神威武

  4. 2014-06-30 13:32 | 疯子 ( 普通白帽子 | Rank:259 漏洞数:45 | 世人笑我太疯癫,我笑世人看不穿~)
    0

    雨神威武

  5. 2014-07-01 02:55 | Let a person cry. ( 实习白帽子 | Rank:31 漏洞数:2 | xxoo)
    0

    来个后台拿shell的呗

  6. 2014-07-01 07:33 | ′ 雨。 认证白帽子 ( 普通白帽子 | Rank:1332 漏洞数:196 | Only Code Never Lie To Me.)
    0

    @Let a person cry. 。。后台那么多方法, 还有命令执行。 懒得写了,

  7. 2014-07-05 17:48 | 走火入魔 ( 路人 | Rank:18 漏洞数:2 | 多读书,多看报,少吃零食,多睡觉。)
    0

    雨神威武

  8. 2014-07-06 14:27 | saline ( 普通白帽子 | Rank:294 漏洞数:29 | Focus On Web Secur1ty)
    0

    @Let a person cry. 后台拿shell应该好拿的吧。

  9. 2014-07-08 00:52 | 打酱油的小男孩 ( 路人 | Rank:0 漏洞数:1 | 俺也来乌云看看)
    0

    任意文件下载导致无限制注入?。。这俩漏洞能扯上关系啊?- -#不懂。。

  10. 2014-07-13 17:55 | Hero ( 普通白帽子 | Rank:145 漏洞数:43 | 药药切克闹,充气娃娃迷幻药)
    0

    记录写进数据库?

  11. 2014-07-13 18:09 | ′ 雨。 认证白帽子 ( 普通白帽子 | Rank:1332 漏洞数:196 | Only Code Never Lie To Me.)
    0

    @Hero no

  12. 2014-07-13 18:48 | Hero ( 普通白帽子 | Rank:145 漏洞数:43 | 药药切克闹,充气娃娃迷幻药)
    0

    @′ 雨。 Like this?http://www.2cto.com/Article/201212/176488.html

  13. 2014-07-13 18:54 | ′ 雨。 认证白帽子 ( 普通白帽子 | Rank:1332 漏洞数:196 | Only Code Never Lie To Me.)
    0

    @Hero 恩 还用到了个小特性来绕黑名单

  14. 2014-07-13 19:03 | Hero ( 普通白帽子 | Rank:145 漏洞数:43 | 药药切克闹,充气娃娃迷幻药)
    0

    @′ 雨。 soga学到了 thx

  15. 2014-07-29 11:32 | wefgod ( 核心白帽子 | Rank:1825 漏洞数:176 | 力不从心)
    0

    牛逼哦

  16. 2014-09-29 17:26 | 开心一下1313 ( 实习白帽子 | Rank:75 漏洞数:17 | 喝口水,压压惊......)
    0

    眼睛都看痛了,果然是凶!

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