1、认识类与对象
要了解序列化就要清楚“类”和“对象”的概念,那么什么是“类”和“对象“呢?
class 类
object 对象 变量
特征(成员变量): 姓名 性别 年龄
方法(动作) : 喝水 打篮球 打游戏
class类就如同人一样,有:中国人、外国人
object对象就是具体到一个人,就比如,“你”就是一个对象
特征就比如是你的:姓名、性别、年龄
方法就是你的动作:喝水、打篮球、打游戏
一个变量里面包含了很多东西,对应很多值,所以说对象是一种语言结构。
2、用PHP来创建一个对象
<?php
class you{
public $name;
public $age;
public $sex;
}
?>
首先我们先创建一个class类,然后接下来我们创建一个对象,如下:
class you{
public $name;
public $age;
public $sex;
public $len;
}
$a = new you();
$a->name = '大能';
$a->age = 20;
$a->sex = '男';
$a->len = '1000mm';
这样我们就实例化了一个“大能”。
那么我们如果要知道大能的各种特征,怎么输出呢?
echo $a->name."的长度是".$a->len;
如下图,成功获取到大能的长度。
我们再来获取一下完整的大能
echo "<hr />";
var_dump($a);
可以看到我们获取到了这整个对象。
3、认识序列化与反序列化
现在我们是一个最简单的对象,但是如果换做是更多的需求时候呢,就比如淘宝,你添加了一个宝贝到购物车里,你下次打开的时候是不是还在购物车里,这就是保证了一个用户的会话状态,淘宝的用户量很大,如果很多很多用户都在内存里这样保持会话状态,那么是不是也不太现实呢?
我们知道内存可是比硬盘贵很多很多很多的,所以需要把这些不活跃的用户状态去存储到硬盘里,但是对象是一个很抽象的东西,所以:
内存里的一个变量怎么存到硬盘里?
这里就用到了序列化,序列化就是将内存里的变量转换成一个字符串,然后就可以存储到硬盘里了,等需要的时候直接调用该字符串进行反序列化成一个对象,就恢复了用户状态。
下面是百度百科对序列化的解释:
序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。
其实这个持久性存储区都是存储到了数据库里,那么一般选择什么数据库来存储序列化对象呢?
Mysql?、 Oracle?、还是Mssql?
错了,这三大数据库都是关系型数据库,而我们的序列化对象首选都去存储在Redis这类的键值对类型的数据库当中。
Redis可以理解成一个大的数组,通过键值对的方式去访问,但Redis真正内部机制不是这么实现的,只是可以这样理解哦。
Redis通过键值对方式存储,很容易查找我们的值,这也是为什么拿Redis来做缓存的原因。
简单的说序列化就是将对象转换成一个可以传输的字符串,那么要转换肯定就要遵守规则吧,如果所有的平台都遵循这个规则,那么是不是就可以跨平台传输了呢,比如java到PHP。
4、反序列化带来的危害
我们知道了反序列化可以将字符串转换为对象,并在内存中执行,那么如果客户端有操控这个字符串的权限,就可以反序列化任意一个对象,这还并不可怕,如果是反序列化一个可以执行的PHP命令呢?比如phpinfo,就会造成命令执行。
漏洞形成的根本原因是因为程序没有对用户输入的反序列化字符串进行检测,导致反序列化过程可以被恶意控制,进而造成了代码执行、getshell等一系列毁天灭地的效果。
反序列化并不是PHP独有的,还有python、最经典的还是要看java反序列化,等后面会详细再写一篇java反序列化,其原理都相通。
5、穿插一个json例子
PHP中的序列化和反序列化基本都是围绕serialize()和unserialize()两个函数展开的,在介绍这两个函数之前我们先来看一个例子。
*简单的例子
我们可以用json的编码和解码来理解序列化和反序列过程,虽然json和咱们说的这个反序列化没啥关系,但是能帮助理解。别弄混了
<?php
$you = array('name'=>'daneng','age'=>'20','sex'='男','len'=>'1000mm');
echo $you;
echo "<hr />";
$you_json=json_encode($you);
echo $you_json;
?>
我们定义一个数组,数组属于抽象的数据结构,为了方便跨平台输出数据,可以将其进行json编码,json格式的数据是以键值对的形式出现的。
6、真正的序列化dome
我们前面创建了一个对象还记得吧,这里我们直接拿过来,将对象进行序列化,如下:
class you{
public $name;
public $age;
public $sex;
public $len;
}
$a = new you();
$a->name = '大能';
$a->age = 20;
$a->sex = '男';
$a->len = '1000mm';
echo serialize($a);
serialize函数就是序列化用的,结果如下:
我们来解读一下这个序列化字符串:
从左到右按顺序来
O: object
3: 类的名称长度为3
you: 就是我们的对象名称
4: 对象有4个属性
接下来是括号里面的四个属性:
其中每两个“;”为一个属性,s代表该类型,4代表长度。
6、反序列化dome
首先我们需要将这个序列化的字符串重新传入变量:
<?php
$str = <<<HTML
O:3:"you":4:{s:4:"name";s:6:"大能";s:3:"age";i:20;s:3:"sex";s:3:"男";s:3:"len";s:6:"1000mm";}
HTML;
#因为我们的序列化字符串中有一些“”双引号,所以我们需要使用定界符。>>>HTML
?>
接下来使用函数unserialize来进行反序列化
var_dump (unserialize($str));
结果如下:
那么我们是不是可以控制字符串呢?我们通过序列化的字串符把对象“大能”改为“女”行不行呢?
O:3:"you":4:{s:4:"name";s:6:"大能";s:3:"age";i:20;s:3:"sex";s:3:"女";s:3:"len";s:6:"1000mm";}
反序列化以后的结果如下:大能真的变成女的了!
所以这样子是不是会出现一些问题~
7、那么真正的漏洞在哪里呢?
这里我们再来看一个dome,先来创建一个类
<?php
class Test{
public $str='AJEST;';
function __destruct(){
//echo "This is function __construct()";
@eval($this->str);
}
}
再来创建一个类的对象,并将其序列化。
test = new Test();
echo serialize($test);
执行结果:
然后我们再写一个反序列化,这次我们不直接写这个字符串了,我们利用一个函数(daneng)来接收序列化字符串,然后将这个函数(daneng)进行反序列化。制造一个用户可控的序列化字符串。
class Test{
public $str='AJEST;';
function __destruct(){
//echo "This is function __construct()";
@eval($this->str);
}
}
$test = new Test();
echo serialize($test);
echo "<hr />";
$a = $_GET['daneng'];
var_dump(unserialize($a));
下图是我们没有传参时候的样子:
然后我们给函数“daneng”传进去上面的序列化字符串,看能否输出:
可以看到已经成功的把daneng函数传入的字符串进行了反序列化。
这时候我们可以将字符串内容改为攻击测试代码phpinfo()试一试:记得也要修改前面的字符串长度!
是不是已经执行了phpinfo()这个代码呢~
8、为什么会这样呢?
我们可以观察代码,发现在类中有一个函数 __destruct() ,并且这个函数调用的eval语句,执行$this->str变量,但是为什么我们手写的代码中没有调用__destruct(),但是函数内的语句被执行了呢?
其实当销毁实例类的时候,__destrutc()函数会被自动调用。
以[__]开头的方法,是PHP中的魔术方法,类中的魔术方法,在特定情况下会被自动调用。
主要魔术方法如下:
__construct 在创建对象时自动调用
__destruct 在销毁对象时自动调用
__call() 在对象中调用一个不可访问方法时,会被调用
__callStatic() 在静态上下文中调用一个不可访问方法时调用
__get() 读取不可访问属性的值时,会被调用
__set() 在给不可访问属性赋值时,会被调用
太多了。。。不想码字了~自己查查吧~
后面找个反序列化的例子日一下吧~
再后面会写一写最经典的java反序列化,也是面试最容易问的!
原文始发于微信公众号(Qingy之安全):PHP序列化与反序列化知识扫盲
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论