长亭百川云 - 文章详情

PHP反序列化漏洞浅入浅出

InBug实验室

53

2024-07-13

                                                                                                    

序列化与反序列化

序列化就是将对象的状态信息转换为可存储或传输的形式的过程;反序列化将可存储或传输的形式的过程恢复为对象的过程。面向对象的语言都存在序列化和反序列化操作,如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代码如下:

  • 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),所以根据需要选择不同编码方式,不编码会导致空字节丢失,进制利用失败。

  • 编写利用代码 exp.php
<?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和魔术函数。

相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2