- 漏洞原理
在提及漏洞前,首先要先理解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
再通过unserialize()函数将字符串转换成对象
本身这个序列化过程没有问题,但是在反序列化的过程中如果没有对用户的输入进行安全检查,从而导致改变对象中属性的值,那么反序列化的结果就会有所不同。
可以在PHP官方看到魔术方法包含了图中的这么些,这里就分开实验其中的一些函数。
- __wakeup()
当执行反序列化函数时会先检查是否有__wakeup()方法,如果有则先执行该方法的语句。
一个简单的wakeup()场面,从url接收cd的内容然后执行反序列化将字符串转换成对象,在执行反序列时可以看到先执行了__wakeup()魔术方法里的内容,在执行的反序列化。
当序列化内容中对象成员的个数大于真实个数时就可以绕过__wakeup()的执行,借用xctf中的一道反序列化题目来实验。
打开看到有一个xctf的类,类里面有一个flag的变量和一个__wakeup()魔术方法,还提示了用code来传递参数。
构造得到序列化后的字符串内容
回到题目,我们正常通过code参数提交返回了bad request,触发了__wakeup()方法
修改字段数大于真实的个数,成功绕过wakeup(),返回flag
再借用一道buuctf上的题目
打开题目,提示网站备份,一般是index.php.bak或者www.zip
输入www.zip获得备份文件。
下载后发现源码文件。
重点在class.php中,基本可以看出password要求等于100,username要求是admin才能返回flag。
在wakeup()中,触发时将username更改为guest。
注意到变量username和password的模式是private。
变量模式有三种:public、private、protected。
三种序列化后的字符串格式都不一样。
public:序列化后的格式不变;
private:序列化在字段名处添加了类名和分别在类名前后添加了%00;
protected:序列化后在字段名处添加了一个星号*,并分别在星号前后添加了%00。
在index.php中发现序列化过程
这时候自己构建php代码进行序列化,得到O:4:"Name":2:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";i:100;}
将对象字段数更改为3,并通过select传递,返回flag
- __destruct()
__destruct()析构函数,在对象被销毁时执行
实验可以看出当对象被销毁后自动执行了__destruct()函数
利用一个简单的demo,在test对象中定义了一个flag变量,值为echo "123";,在调用完成后通过__destruct()方法执行flag变量的值。但是在最下面通过test1接收了一个通过url传递来的参数并进行反序列化。
正常运行时是这样。
构造一个输出phpinfo()的poc
通过url中的dev传递poc,可以看到__destruct()方法调用了两次,并执行了我们传递的phpinfo()
在利用一个文件读取的demo,读取位于D:\phpstudy_pro\WWW\php\serializetest\config.txt文件
文本里面随便输入一些内容
正常运行demo,返回文本内容
当在实际利用时获取到一个文件的绝对路径时就可以利用这个方法去读取处文件内的内容
这里我们用这个方法去读取位于D:\phpstudy_pro\WWW\sqli-labs-master\sql-connections下的db-creds.inc配置文件
构造poc:O:4:"test":1:{s:8:"filename";s:65:"D:\phpstudy_pro\WWW\sqli-labs-master\sql-connections\db-creds.inc";}
在通过dev进行传参,可以再网页源码看到返回我们要读取的文件内容
同理可以读取文件,当方法内存在删除文件操作时同样存在任意文件删除
还有其他一些魔术方法也可根据情况进行利用:__toString()、__sleep()等
当然这里只是简单复现,实际利用时没那么容易
对反序列漏洞的防御方法:保持用户输入不可靠原则,对输入进行安全检查和过滤