php序列化与反序列化
序列化:把对象转换为字节序列的过程称为对象的序列化
PHP中的序列化函数是serialize()
serialize()函数用于序列化对象
或数组
,并返回一个字符串。serialize()函数序列化对象后,可以很方便的将它传递给其他需要它的地方,且其类型和结构不会改变。
语法 | string serialize ( mixed $value ) |
---|---|
参数说明 | $value: 要序列化的对象或数组。 |
返回值 | 返回一个字符串。 |
示例:
<?php
highlight_file(__FILE__);
$sites=array('I', 'Like', 'PHP');
echo'<br/>';
var_dump(serialize($sites));
echo'<br/>';
classman{
public$name="xiaocui";
public$sex="man";
private$age=26;
}
$M=newman();
var_dump(serialize($M));
?>
输出结果为:
string(47) "a:3:{i:0;s:1:"I";i:1;s:4:"Like";i:2;s:3:"PHP";}"
string(79) "O:3:"man":3:{s:4:"name";s:7:"xiaocui";s:3:"sex";s:3:"man";s:8:"manage";i:26;}"
反序列化:把字节序列恢复为对象的过程称为对象的反序列化
PHP中的反序列化函数unserialize()
序列化的作用
①可以把对象的字节序列永久放在磁盘中,需要时可以随时调用,大大节省磁盘占用空间。
②在传输过程中可以直接传输字节序列,而不是对象,这可以大大提高传输速率。
魔术方法
PHP将所有以__
(两个下划线)开头的类方法保留为魔术方法。
__construct()
具有 __construct函数的类会在每次创建新对象时先调用此方法
__destruct()
析构函数只有在对象从内存中删除之前才会被自动调用。
该函数会在到某个对象的所有方法都被引用后或者当对象被销毁时(unset()函数)执行
__sleep()
在使用serialize()
函数时,程序会检查类中是否存在一个__sleep()
魔术方法。如果存在,则该方法会先被调用,然后再执行序列化操作。
__wakeup()
在使用unserialize()
时,会检查是否存在一个__wakeup()
魔术方法。如果存在,则该方法会先被调用。
php7.1+版本对属性类型不敏感,本地序列化的时候将属性改为public进行绕过即可
php 反序列化漏洞:当反序列化字符串时,如果表示属性个数的值大于真实属性个数,就会跳过_wakeup函数的执行。
当用户先将对象序列化,然后将对象中的字符进行过滤,最后再进行反序列化,这个时候就有可能会产生PHP反序列化字符逃逸的漏洞。
过滤后字符变多
假设我们先定义一个user
类,然后里面一共有3个成员变量:username
、password
、isVIP
。
1 2 3 4 5 6 7 8 9 10 |
|
可以看到当这个类被初始化的时候,isVIP
变量默认是0
,并且不受初始化传入的参数影响。
接下来把完整代码贴出来,便于我们分析。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
这一段程序的输出结果如下:
1 |
|
可以看到,对象序列化之后的isVIP
变量是0
。
这个时候我们增加一个函数,用于对admin字符进行替换,将admin替换为hacker,替换函数如下:
1 2 3 |
|
因此整段程序如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
这一段程序的输出为:
1 |
|
这个时候我们把这两个程序的输出拿出来对比一下:
1 2 |
|
可以看到已过滤字符串中的hacker
与前面的字符长度不对应了
1 2 |
|
在这个时候,对于我们,在新建对象的时候,传入的admin
就是我们的可控变量
底层代码是以;作为字段的分隔,以"}"作为结尾, 反序列化 时,结尾后的字符串会被忽略掉
接下来明确我们的目标:将isVIP
变量的值修改为1
首先我们将我们的现有子串和目标子串进行对比:
1 2 |
|
也就是说,我们要在admin
这个可控变量的位置,注入我们的目标子串。
首先计算我们需要注入的目标子串的长度:
1 2 |
|
因为我们需要逃逸的字符串长度为47
,并且admin
每次过滤之后都会变成hacker
,也就是说每出现一次admin
,就会多1
个字符。
因此我们在可控变量处,重复47遍admin,然后加上我们逃逸后的目标子串,可控变量修改如下:
1 |
|
完整代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
程序输出结果为:
1 |
|
我们可以数一下hacker的数量,一共是47个hacker,共282个字符,正好与前面282相对应。
后面的注入子串也正好完成了逃逸。
反序列化后,多余的子串会被抛弃
我们接着将这个序列化结果反序列化,然后将其输出,完整代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
程序输出如下:
1 2 3 4 5 6 7 8 |
|
可以看到这个时候,isVIP
这个变量就变成了1
,反序列化字符逃逸的目的也就达到了。
php pop链
以上讲的都是只有一个类的例子,当类的个数多起来并且相互之间有所联系时,我们就需要通过pop链来达到漏洞。
1.回顾一下原先 PHP 反序列化攻击的必要条件
(1)首先我们必须有 unserailize() 函数
(2)unserailize() 函数的参数必须可控
这两个是原先存在 PHP 反序列化漏洞的必要条件,没有这两个条件你谈都不要谈,根本不可能,但是有了下面这个方法就有可能
2.phar:// 如何扩展反序列化的攻击面的
原来 phar 文件包在 生成时会以序列化的形式存储用户自定义的 meta-data ,配合 phar:// 我们就能在文件系统函数 file_exists() is_dir() 等参数可控的情况下实现自动的反序列化操作,于是我们就能通过构造精心设计的 phar 包在没有 unserailize() 的情况下实现反序列化攻击,从而将 PHP 反序列化漏洞的触发条件大大拓宽了,降低了我们 PHP 反序列化的攻击起点。
3.具体解释一下 phar 的使用
phar(PHp ARchive)是类似于JAR的一种打包文件。PHP ≥5.3对Phar后缀文件是默认开启支持的,不需要任何其他的安装就可以使用它。
漏洞触发是利用Phar:// 伪协议读取phar文件时,会反序列化meta-data储存的信息。
phar文件格式:
phar文件由四部分组成
1.stub
stub是phar文件的文件头,格式为xxxxxx<?php ...;__HALT_COMPILER();?>
,xxxxxx可以是任意字符,包括留空,且php闭合符与最后一个分号之间不能有多于一个的空格符。另外php闭合符也可省略。
2.manifest describing the contents
该区域存放phar包的属性信息,允许每个文件指定文件压缩、文件权限,甚至是用户定义的元数据,如文件用户或组。
这里面的metadata以serialize形式储存,为反序列化漏洞埋下了伏笔。
3.file contents
被压缩的用户添加的文件内容
4.signature
可选,phar文件的签名,允许的有MD5, SHA1, SHA256, SHA512和OPENSSL.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!