PHP反序列化漏洞总结(一)
写在前边
做了不少PHP反序列化的题了,是时候把坑给填上了。参考了一些大佬们的博客,自己再做一下总结
1.面向对象
2.PHP序列化和反序列化
3.PHP反序列化漏洞实例
1.面向对象
在了解序列化和反序列化之前,先简单了解一下PHP的面向对象。
万物皆可对象。根据官方手册,PHP中,以关键字class定义一个类,一个类可以包含有属于自己的常量,变量(称为“属性”)以及函数(称为“方法”)。
class People { //声明属性 public $name; //声明方法 public function eat() { echo $this->name."在吃饭"; } }
现在已经定义好了一个类,接下来用关键字new来实例化这个类
$people_1 = new People(); //实例化对象 $people_1->name = '张三'; //属性赋值 $people_1->Eat(); //调用方法
完整代码
<?php class People { public $name; public function eat() { echo $this->name."在吃饭"; } } $people_1 = new People(); $people_1->name = '张三'; $people_1->Eat(); ?>
效果:
2.PHP序列化和反序列化
根据官方手册,所有php里面的值都可以使用函数serialize()来返回一个包含字节流的字符串来表示。unserialize()函数能够重新把字符串变回php原来的值。 序列化一个对象将会保存对象的所有变量,但是不会保存对象的方法,只会保存类的名字。emmmm。。。。个人理解其实就是为了解决PHP在执行当前脚本需要跨脚本文件传递某些变量内容时,但因为之前脚本执行完后把内容释放掉从而无法获取的问题。serialize可以将变量转换为字符串,并且在转换的过程中可以保存当前变量的值,而unserialize则可以将serialize生成的字符串转换回变量。
根据之前的例子,再添加个age属性
<?php class People { public $name; public $age; //新加属性 public function Eat() { echo $this->name."在吃饭</br>"; } } $people_1 = new People(); $people_1->name = '张三'; $people_1->age = 18; $people_1->Eat(); //序列化 echo serialize($people_1); ?>
效果:
O:6:"People":2:{s:4:"name";s:6:"张三";s:3:"age";i:18;}就是当前people_1这个对象序列化后的形式。"O"表示对象,“6”表示对象所属的类长度为6,“People”为类名,“2”表示有2个参数。“{}”里面是参数的key和value,s:4:"name"表示这个参数的string类型,长度为4,key值是name。后面以此类推,i表示int类型
然后反序列化这段,新建一个文件test2.php
<?php class People { public $name; public $age; public function eat() { echo $this->name."在吃饭</br>"; } } //重建对象 $usr = unserialize('O:6:"People":2:{s:4:"name";s:6:"张三";s:3:"age";i:18;}'); //输出 $usr->eat();
?>
效果:
其实吧,个人感觉序列化和反序列化就类似于存取数组。举个不恰当的例子,就像积木,序列化一个把搭建好后的积木收拾好,反序列化就是把收拾好的积木再按照原来的图纸搭起来。接下来说到的反序列化漏洞就类似于把原来的积木换了个颜色,某块积木形状对的但颜色不对,按照图纸搭起来就是感觉违和。
3.PHP反序列化漏洞
了解了什么是序列化和反序列化后,是时候研究一下PHP反序列化漏洞了,实际上,PHP反序列化漏洞利用的条件在实际环境中比较苛刻,但是如果可以利用一般都会产生很严重的后果
该漏洞需要有以下条件:
1.unserialize函数的参数可控
2.所写的内容需要有对象中的成员变量的值
3.脚本中存在一个构造函数(__construct())、析构函数(__destruct())、__wakeup()函数中有向php文件中写数据的操作的类
__construct()当一个对象创建时被调用
__destruct()当一个对象销毁时被调用
__toString()当一个对象被当作一个字符串使用
__sleep() 在对象在被序列化之前运行
__wakeup将在序列化之后立即被调用所写的内容需要有对象中的成员变量的值
这里先借大佬的举个的例子(后续再填坑)
创建test3.php
<?php class Test{ var $test = "123"; function __wakeup(){ $fp = fopen("test.php", 'w'); fwrite($fp, $this -> test); fclose($fp); } } $test1 = $_GET['test']; print_r($test1); echo "<br />"; $seri = unserialize($test1); require "test.php"; ?>
先盲目分析一波,Test这类有个重写的__wakeup()这个魔术方法,当序列化后,打开test.php,权限为写,将$test值重写到test.php,用GET方式将值传入$test1,然后反序列化。就是我们如果传入一个序列化后的EXP传入,text.php就会变成我们出传入内容
当值为空时,访问http://本地环境/test3.php?test=
根据刚刚的方法,新建一个脚本,序列化一下
<?php class Test{ var $test = "123"; function __wakeup(){ $fp = fopen("test.php", 'w'); fwrite($fp, $this -> test); fclose($fp); } } $test = new Test(); echo serialize($test); ?>
然后我们跟上参数 O:4:"Test":1:{s:4:"test";s:3:"123";}
发现不但页面更改了而且连原文件都被重写了,这时一个PHP反序列化漏洞就产生了
(目前只是简单总结,实战方面先挖个坑,后续再补)
参考
https://www.freebuf.com/articles/web/167721.html
https://www.jianshu.com/p/be6de8511cb9
如有错误还请指出,谢谢