php序列化与反序列化
前言
打算考研了,没时间学习这些知识了,只能抽时间总结一下。
正文
序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程。
PHP中的两个函数,序列化函数serialize()和反序列化函数unserialize()
简介
**serialize()**——将PHP中的变量如对象(Object)、数组(Array)等序列化为字符串.
**unserialize()**——将序列化的字符串转换为原先的值.
PHP手册上的说明:
想要将已序列化的字符串变回 PHP 的值,可使用 unserialize()。serialize() 可处理除了 resource 之外的任何类型。甚至可以 serialize() 那些包含了指向其自身引用的数组。serialize() 的数组/对象中的引用也将被存储。
当序列化对象时,PHP 将试图在序列动作之前调用该对象的成员函数 __sleep()。这样就允许对象在被序列化之前做任何清除操作。类似的,当使用 unserialize() 恢复对象时, 将调用 __wakeup() 成员函数。
详情可以查看PHP手册
serialize()
当在php中创建了一个对象后,可以通过serialize()把这个对象转变成一个字符串,保存对象的值方便之后的传递与使用。测试代码如下:
1 |
|
这样就可以把specter这个对象给序列化,然后打印出来
O:7:”specter”:1:{s:4:”test”;s:3:”123”;}
然后介绍一下格式以及意义:
O表示对象(Object),如果是数组序列化后,则O变成a(array)
7表示对象有7个字符
specter表示对象名称
1表示有一个值
s表示字符串
4表示字符串长度
unserialize()
与 serialize() 对应的,unserialize()可以从已存储的表示中创建PHP的值,单就本次所关心的环境而言,可以从序列化后的结果中恢复对象(object)。
测试代码:
1 |
|
输出如下:
O:7:”specter”:1:{s:4:”test”;s:3:”123”;}
specter Object ( [test] => 123 )
反序列化漏洞
由前面可以看出,当传给 unserialize() 的参数可控时,我们可以通过传入一个精心构造的序列化字符串,从而控制对象内部的变量甚至是函数。
介绍几个magic函数
__construct()
__contstruct()函数被称为构造函数,当实例化类的时候会自动调用该函数__destruct()
__destruct()函数被称为析构函数,当类结束的时候自动调用该函数__sleep()
__sleep()函数是当php进行序列化操作(serialize)的时候自动调用该函数,可以用于清理对象,并返回一个包含对象中所有应被序列化的变量名称的数组。如果该方法未返回任何内容,则 NULL 被序列化,并产生一个 E_NOTICE 级别的错误。__wakeup()
__wakeup()函数是当php进行反序列化操作(unserialize)的时候自动调用该函数__toString()
__toString()函数是当对象被当做字符串的时候会自动调用该函数
测试一下:
1 |
|
利用场景
由上图可以看到,unserialize()后会导致__wakeup()
或__destruct()的直接调用,中间无需其他过程。因此最理想的情况就是一些漏洞/危害代码在__wakeup()
或__destruct()中,从而当我们控制序列化字符串时可以去直接触发它们。这里针对 __wakeup()
场景做个实验。创建一个空的文件shell.php,假设服务器index.php源码如下:
1 |
|
把test的赋值为<?php phpinfo(); ?>
,
1 |
|
得到
O:7:”specter”:1:{s:4:”test”;s:19:””;}
构造payload:
O:7:”specter”:1:{s:4:”test”;s:19:”“;}
可以看出利用成功,shell.php中也写入了test的值.
再看一个例子
1 | //index.php |
这里面也涉及到一个命令执行函数**eval()**,上面的代码执行反序列化之后,会调用__destruct函数,输出hello world,那么我该如何构造我们的反序列化语句呢。
我们需要利用B类的action()函数,只要使变量b是我们的php命令,通过eval函数,就可以执行这个命令了。
构造序列化如下
1 |
|
输出payload:O:7:"Specter":1:{s:1:"a";O:1:"B":1:{s:1:"b";s:10:"phpinfo();";}}
其他Magic function的利用
但如果一次unserialize()中并不会直接调用的魔术函数,比如前面提到的__construct(),是不是就没有利用价值呢?非也。类似于PWN中的ROP,有时候反序列化一个对象时,由它调用的__wakeup()中又去调用了其他的对象,由此可以溯源而上,利用一次次的“gadget”找到漏洞点。
1 |
|
这里我们给test传入构造好的序列化字符串后,进行反序列化时自动调用 __wakeup()函数,从而在new test1()会自动调用对象test1中的__construct()方法,从而把<?php phpinfo() ?>
写入到 shell.php中。
利用普通成员方法
前面谈到的利用都是基于“自动调用”的magic function。但当漏洞/危险代码存在类的普通方法中,就不能指望通过“自动调用”来达到目的了。这时的利用方法如下,寻找相同的函数名,把敏感函数和类联系在一起。
1 |
|
test=new Test1(),会执行__construct(),然后又new了一个Test2(),执行完后,又会执行__destruct(),在调用action(),输出Test2.
构造payload:
1 |
|
得到payload:
O:5:”Test1”:1:{s:5:”test1”;O:5:”Test2”:1:{s:5:”test3”;s:10:”phpinfo();”;}}
还看到一篇关于[session反序列化的(https://blog.csdn.net/nzjdsds/article/details/82703639)