PHP反序列化
一、简介
本文主要介绍基于CTF的PHP中的反序列化(序列化)的基础知识。
二、反序列化介绍
虽然是介绍反序列化,但是实际上是序列化和反序列化的组合介绍,此处先从正向过程序列化开始介绍。
序列化是将变量按照特定规则转化为字符串的过程,而反序列化就是将转化后字符串重新恢复成各种变量数据类型的过程,序列化对应的函数是serialize()而反序列化则是unserialize()。
序列化转化规则:

序列化规则即如图(最外层一对双引号为var_dump()函数输出内容时自带的,并不包含在序列化之后的内容中),大致为:数据类型代号字符+数据长度(字符串数据才有)+数据内容。其中的类序列化得到的字符串在花括号外层是:类的代号字符+类名长度+类的名称+类属性的个数,内层则是类的各种属性序列化结果的拼接(类属性的名称也按照相同规则进行了序列化),CTF中的反序列化则是和类序列化和反序列化有关。
类的序列化:
根据类中属性的不同,序列化之后会得到三种不同的序列化字符串。


其中protected属性和private属性和public属相相比,多了一些额外字符,并且部分字符是不可见的字符(ASCII码为0,输出时也因为这个原因序列化后字符串中并不会出现),所以直接复制序列化后字符串会导致遗漏这些不可见字符。


故在传入序列化字符串时应该手动补上被遗漏的不可见字符,如在URL中传入值就应该在不可见字符位置补上其URL编码%00。在序列化后的字符中,protected属性的名称前会多一个0+*+0(这里的0指ASCII码为0的不可见字符);private属性的名称前则是0+该类的名称+0(这里的0指ASCII码为0的不可见字符),在传入值时在这些位置补上不可见字符即可。
类序列化的魔术方法:
类的的序列化涉及到一些类魔术方法,而这些方法与普通的方法不同,他们不需要主动调用来触发,在一些特定条件下便会被触发执行。
__construct()魔术方法,当一个类被实例化时自动调用,官方定义如下:

__destruct()魔术方法,当一个类的实例化对象被清除时会被调用,那什么时候一个类的实例会被清除呢?在PHP代码运行完毕时和一个反序列化得到的实例化对象未被赋值给一个变量时均可以被调用,CTF题中就会将反序列化结果不赋值给变量来调用__destruct()魔术方法,以此来实现对其他代码调用。官方定义如下:

具体效果如下图:

test类中定义了在调用__destruct()魔术方法时会输出一句del并换行,按照代码运行顺序来讲,code变量的值的反序列化结果赋值给变量a,__destruct()魔术方法未被调用,接着输出了水平线(这里就是想起到一个分割符的作用),然后code变量的值再次被反序列化,但这次未被赋值,反序列化得到的实例化对象因未被赋值而被销毁,所以自动调用了__destruct()魔术方法输出了第一句del,然后又输出了水平线,这时执行到PHP代码末端,此前被赋值的变量a被销毁,调用了__destruct()魔术方法输出了第二句del。
__sleep()魔术方法和__wakeup()魔术方法,前者在serialize()函数执行序列化前会被调用,后者在unserialize()函数执行反序列化前会被调用,官方定义如下:

(__sleep()魔术方法被要求了一个必须的返回值来指定类中将被序列化的那些对象,否则会导致报错,所以并不是类中所有的属性都会被序列化,除了关注__sleep()魔术方法调用时执行的其他代码外,还应注意被哪些属性被序列化)
__wakeup()魔术方法的绕过,在CTF题中有时会在__wakeup()魔术方法设置障碍,需要跳过__wakeup()魔术方法才能得到想要的结果,而恰巧__wakeup()具有一个可被绕过执行的漏洞,将序列化后字符串中标志类属性个数的数值改为大于实际数值(这里的个数是指序列化后的字符串中含有的属性个数,不是指类本身的属性个数),此时再执行反序列化即可绕过__wakeup()魔术方法。效果如图:

test类的属性个数为1,在变量code1中为进行绕过,在变量code2中进行了绕过,结果为仅在变量code1反序列化时触发了__wakeup()魔术方法,输出了wake up。
三、附
本文主要作用为个人笔记,陈述语言、代码逻辑皆有不足,日后若学得更多相关技巧或有趣相关例题会对本文进行修订。欢迎各位师傅扶正。

浙公网安备 33010602011771号