写在前边
做了不少 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.cnblogs.com/Lee-404/p/12771032.html