相对于网上其他关于Serialize的讲解,不下万篇,学习笔记,稍微总结一番。本篇文章按照浅谈php反序列化漏洞,进行学习指导,基础内容略过。
举个栗子
先举个例子说明serialize()与unserialize()

显而易见,两个函数的例子所表达的形式就是这么简单。
再捡个栗子
接下来看看下面的例子:
1 |
|
针对其中的代码分析,可以看到serialize序列化对象后(echo serialize($example);),得到的是O:9:"DemoClass":3:{s:4:"name";s:7:"yinfeng";s:3:"sex";s:3:"man";s:3:"age";s:2:"18";};而unserialize序列化对象后(print_r($NewExample);)。 下图呈现两种函数执行形式:

可以看到serialize序列化对象,得到的是对象引用类的字符串输出,unserialize得到的结果与之相反,将对象引用类的字符串输出转化为类中引用值的呈现模式。
深入栗子
在php官方的简述中,有以下红标记的描述,提到了__sleep()和__wakeup(),只是啥呢,以第一个栗子引申出来剖析

还是一样的代码,不过类中我们加了一些东西
1 |
|
__wakeup()、__construct()、__destruct()这三个函数是干嘛的呢
- 构造函数
__construct():当对象创建(new)时会自动调用。但在unserialize()时是不会自动调用的。 - 析构函数
__destruct():当对象被销毁时会自动调用。 - 魔法函数
__wakeup():unserialize()时会自动调用。
我们先来看看运行结果:

可以看到,在创建a对象时,自动调用了__construct();serialize($a)时调用__sleep函数;unserialize($a_value)时调用了__wakeup()。
而在其中,不知你是否发现其中__sleep()我是这样写的
1 | function __sleep() |
相对于其他函数,多了个return array("name");,为什么呢?
在php文档中,针对这个原因,是这样描述的

其中,不知你看到这句没

我们试试没有return返回值的情况

不加这句的后果,如下图:

那如果,我返回的值设置不是name时,即这里改为return array("namef");,以下是报错结果

这样的描述,是否能更好解决你的疑问呢?
构造栗子
现在我们看看下面这段代码,尝试加在刚刚的代码后
1 | $Str_1 = 'O:1:"A":1:{s:4:"name";s:4:"Anny";}'; |
也许你注意到了$Str_1的值,没错,这次我们通过自行构造一个name为Anny,看看反序列化后的值。

这里就需要我们想想,反序列化的漏洞是如何体现的呢?本质原因就是我们可以构造一串伪造的字符串,通过unserialize函数,覆盖掉原本类成员变量的默认值。这一点就像C语言中的构造函数,传入的值会取代原本其成员变量的默认值。
有兴趣的同学可以试试下面的$Str_1的替换,看看会产生什么结果?
1 | $Str_1 = 'O:14:"Test_serialize":1:{s:4:"name";s:4:"test";}'; |
你能直观的看到,应是下面的内容

再对比下图

发现了没,析构函数多了一个?__wakeup函数也多了?这是不是意味着什么呢?
说了这么多,烦了嘛?接下来,我们来模拟下漏洞场景吧!
在前面我们提到过,unserialize()时会自动调用魔法函数__wakeup() 。这个漏洞的关键点就在于如何通过__wakeup()中的执行语句,执行危险命令,下面是个简单模拟,在__wakeup()中我们只是输出引用的name的值(那假如__wakeup()中存在其他的**命令呢?)

再挑白点,看看下面的例子

如果我将Anny再改成其他,后果可想而知。

再构栗子
两个不同类,在一个类中调用另一个类的对象
1 | <?php |
通过username传入构造好的序列化字符串后,进行反序列化时自动调用 __wakeup()函数,从而在Object_serialize会自动调用对象test的类Object_Example中的__construct()方法,从而写入到东西到文件中。
炭烤栗子
某次比赛test.php源码(2018fj)
1 |
|
看看上面的代码,首先执行就两句话
1 | $a=@$_POST['a']; |
传入post的数据,然后反序列化该值。我们知道,unserialize()函数执行后,会自动执行__wakeup()预备执行对象的资源,进行初始化。而当__wakeup()函数执行完后,会调用析构函数__destruct()释放资源,仔细观察 __wakeup()
1 | function __wakeup(){ |
发现反序列化的值会被其中waf()进行空格过滤,如下图

再接着调用了__destruct(),方便观察我把每个函数执行的过程呈现出来

其中语句call_user_func_array(array($this, $this->method), $this->args);,指的是回调当前类$this的$this->method方法,$this->args为传入的$this->method方法的参数值。在本题中就是往test类的ping()中传入了一个$host为类变量$args的值,随后通过构造好的反序列化的字符串,我们拿到了想要的东西。

exp:a=O:4:"home":2:{s:12:"%00home%00method";s:4:"ping";s:10:"%00home%00args";a:1:{i:0;s:7:"1 | dir";}}
至于其中为啥跟我们之前将的不一样,构造时,我们加入了在私有成员变量method前加了个%00home%00,这是因为当成员变量是私有的时候,会在成员变量前面添加类名;当成员变量是被保护的时候,会在被保护成员前面添加一个*,并且,在所添加的类名或者*的左右两边都会有一个null字节,也就是%00,因此,长度都增加了2。下面给出不一样的代码,请大伙自行尝试构造exp。
1 | <?php |
exp:a=O:4:"home":2:{s:9:"%00*%00method";s:4:"ping";s:10:"%00home%00args";a:1:{i:0;s:7:"1 | dir";}}
关于序列化的问题,发现并不是很难,只要代码看的懂,执行步骤明白,知道漏洞的原理,没有想象的那么难,这篇还没完,持续更新,关于反序列化这只是冰山一角。青山不改,绿水长流。