• 漏洞原理

在提及漏洞前,首先要先理解PHP中的serialize()和unserialize()两个函数(由于没有PHP开发基础,请各位大哥指出小弟本文中的错误),serizlize()就是将变量、对象、数组等数据类型转换成字符串方便进行网络传输和存储,再通过unserialize()将字符串进行转换成原来的数据。当存在反序列化函数及可利用魔术方法时,且unserialize()接受到的字符串对于用户是可控时,用户可针对使用的magic function(魔术方法)来构造特定的语句,从而达到控制整个反序列化过程。

  • 序列化基础

下面先展示一个例子,通过序列化将一个对象转换成字符串。

可以看到序列化后将对象转换成了O:4:"test":1:{s:2:"s1";s:11:"helloworld!";}这样字符串;

其中O代表对象,4代表对象的类名长度,"test"是对象名,1是对象中的字段数,s:2:"s1"代表的是字段名和字段名长度,s:11:"helloworld!"代表的是字段的值,和值的长度。

不同的数据类型有不同的缩写:a - array b - boolean d - double i - integer o - common object r - reference s - string C - custom object O - class N - null R - pointer reference U - unicode string

android 序列化漏洞 序列化漏洞原理_序列化

 

再通过unserialize()函数将字符串转换成对象

android 序列化漏洞 序列化漏洞原理_序列化_02

 

本身这个序列化过程没有问题,但是在反序列化的过程中如果没有对用户的输入进行安全检查,从而导致改变对象中属性的值,那么反序列化的结果就会有所不同。

可以在PHP官方看到魔术方法包含了图中的这么些,这里就分开实验其中的一些函数。

android 序列化漏洞 序列化漏洞原理_反序列化_03

 

 

  • __wakeup()

当执行反序列化函数时会先检查是否有__wakeup()方法,如果有则先执行该方法的语句。

一个简单的wakeup()场面,从url接收cd的内容然后执行反序列化将字符串转换成对象,在执行反序列时可以看到先执行了__wakeup()魔术方法里的内容,在执行的反序列化。

android 序列化漏洞 序列化漏洞原理_php_04

 

当序列化内容中对象成员的个数大于真实个数时就可以绕过__wakeup()的执行,借用xctf中的一道反序列化题目来实验。

android 序列化漏洞 序列化漏洞原理_反序列化_05

 

 打开看到有一个xctf的类,类里面有一个flag的变量和一个__wakeup()魔术方法,还提示了用code来传递参数。

android 序列化漏洞 序列化漏洞原理_序列化_06

 

构造得到序列化后的字符串内容

android 序列化漏洞 序列化漏洞原理_反序列化_07

 

 回到题目,我们正常通过code参数提交返回了bad request,触发了__wakeup()方法

android 序列化漏洞 序列化漏洞原理_php_08

 

修改字段数大于真实的个数,成功绕过wakeup(),返回flag


android 序列化漏洞 序列化漏洞原理_php_09

 

 再借用一道buuctf上的题目

android 序列化漏洞 序列化漏洞原理_android 序列化漏洞_10

 

 打开题目,提示网站备份,一般是index.php.bak或者www.zip

android 序列化漏洞 序列化漏洞原理_序列化_11

 

 输入www.zip获得备份文件。

 

android 序列化漏洞 序列化漏洞原理_序列化_12

 

 下载后发现源码文件。

android 序列化漏洞 序列化漏洞原理_反序列化_13

 

 重点在class.php中,基本可以看出password要求等于100,username要求是admin才能返回flag。

在wakeup()中,触发时将username更改为guest。

 

android 序列化漏洞 序列化漏洞原理_反序列化_14

 

注意到变量username和password的模式是private。

变量模式有三种:public、private、protected。

三种序列化后的字符串格式都不一样。

public:序列化后的格式不变;

private:序列化在字段名处添加了类名和分别在类名前后添加了%00;

protected:序列化后在字段名处添加了一个星号*,并分别在星号前后添加了%00。

android 序列化漏洞 序列化漏洞原理_php_15

 在index.php中发现序列化过程

android 序列化漏洞 序列化漏洞原理_android 序列化漏洞_16

 

这时候自己构建php代码进行序列化,得到O:4:"Name":2:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";i:100;}

android 序列化漏洞 序列化漏洞原理_php_17

 

 将对象字段数更改为3,并通过select传递,返回flag

android 序列化漏洞 序列化漏洞原理_序列化_18

 

  •  __destruct()

__destruct()析构函数,在对象被销毁时执行

android 序列化漏洞 序列化漏洞原理_反序列化_19

 

 实验可以看出当对象被销毁后自动执行了__destruct()函数

 

android 序列化漏洞 序列化漏洞原理_android 序列化漏洞_20

利用一个简单的demo,在test对象中定义了一个flag变量,值为echo "123";,在调用完成后通过__destruct()方法执行flag变量的值。但是在最下面通过test1接收了一个通过url传递来的参数并进行反序列化。

 

 

 

android 序列化漏洞 序列化漏洞原理_android 序列化漏洞_21

 

 

 正常运行时是这样。

android 序列化漏洞 序列化漏洞原理_反序列化_22

 

 构造一个输出phpinfo()的poc

android 序列化漏洞 序列化漏洞原理_android 序列化漏洞_23

 

 通过url中的dev传递poc,可以看到__destruct()方法调用了两次,并执行了我们传递的phpinfo()

android 序列化漏洞 序列化漏洞原理_序列化_24

 

 在利用一个文件读取的demo,读取位于D:\phpstudy_pro\WWW\php\serializetest\config.txt文件

android 序列化漏洞 序列化漏洞原理_android 序列化漏洞_25

 

 文本里面随便输入一些内容

android 序列化漏洞 序列化漏洞原理_android 序列化漏洞_26

 

 正常运行demo,返回文本内容

android 序列化漏洞 序列化漏洞原理_android 序列化漏洞_27

 

 当在实际利用时获取到一个文件的绝对路径时就可以利用这个方法去读取处文件内的内容

这里我们用这个方法去读取位于D:\phpstudy_pro\WWW\sqli-labs-master\sql-connections下的db-creds.inc配置文件

 

android 序列化漏洞 序列化漏洞原理_反序列化_28

 

O:4:"test":1:{s:8:"filename";s:65:"D:\phpstudy_pro\WWW\sqli-labs-master\sql-connections\db-creds.inc";}

android 序列化漏洞 序列化漏洞原理_android 序列化漏洞_29

 

 在通过dev进行传参,可以再网页源码看到返回我们要读取的文件内容

android 序列化漏洞 序列化漏洞原理_php_30

 

同理可以读取文件,当方法内存在删除文件操作时同样存在任意文件删除

还有其他一些魔术方法也可根据情况进行利用:__toString()、__sleep()等

 当然这里只是简单复现,实际利用时没那么容易

对反序列漏洞的防御方法:保持用户输入不可靠原则,对输入进行安全检查和过滤