目录
反序列化
为什么要序列化
class 类
obj 对象
百度百科关于序列化的定义是,将对象的状态信息转换为可以存储或传输的形式(字符串)的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区(把存储放在数据库中,首选数据库是Redis,或者是类型Redis这种键值对型数据库,可以理解为Redis数据库就是一个大数组通过键值对的方式去访问,关系型数据库就是一张大的Excel表格)。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。
简单地说,序列化就是把一个对象变成可以传输的字符串(便于传输),反序列化就是将字符串转换成对象的过程(如果这个字符串客户端可控,就可以让web应用反序列化任意对象,严重的是我们在反序列化过程中会触发一些可执行的PHP代码,比如说phpinfo),可以以特定的格式在进程之间跨平台、安全的进行通信。
<?php
class Stu{
public $name;
public $sex;
public $agel;
public $score;
}
$stu1= new Stu();//创建了一个对象 new
$stu1->name="ZQX";
$stu1->sex=true;
$stu1->age=16;
$stu1->score=60;
$stu2= new Stu();
$stu2->name="HMM";
$stu2->sex=false;
$stu2->age=16;
$stu2->score=90;
echo $stu1->name."'s score = ".$stu1->score; //ZQX's score = 60 先找到对象名,再找到他的属性
echo $stu2->name."'s score = ".$stu2->score;
echo "<hr />";
var_dump($stu1);
//把stu1这个对象,一个抽象的数据结构转化成字符串并存储在硬盘的文件当中,这个过程叫序列化
//序列化好处:我们可以把一个用户的状态相当做一个暂停,等用户激活的时候再从硬盘把字符串取出来,再反序列化成一个对象,然后再存储在内存中让他工作。
?>
PHP 中序列化与反序列化
PHP 反序列化漏洞也叫PHP 对象注入,是一个常见的漏洞,这种类型的漏洞虽然有些难以利用,但一旦利用成功就会造成非常威胁的后果。漏洞的形成的根本原因是程序员没有对用户输入的反序列化字符串进行检测,导致反序列化过程可以被恶意控制,进而造成代码执行、getshell 等一系列不可控的后果。反序列化漏洞并不是PHP 特有的,也存在于Java、Python 等语言之中但其原理基本相通。
PHP中的序列化与反序列化,基本都是围绕serialize()和 unserialize() 两个函数展开的。
*简单的例子
我们可以用json(json这种格式就是格式化的一个字符串,也就是说Json格式的数据具备自己一定的格式) 格式数据的编码与解码,来理解序列化与反序列化过程。虽然json 数据与反序列化漏洞没有什么关系,但是这个例子有助于我们理解。
测试代码:
<?php
$stu=array('name'=>'Waffle','age'=>20,'SEX'=>true,'score'=>96.6);
echo $stu; //echo是不能输出数组的
echo "<hr />";
$stu_json=json_encode($stu);//但是如果我们把 数字$stu做一个json的格式转换json_encode
//array一个抽象的数组,经过json的格式编码之后他就会变成一个字符串,变成字符串之后就可以用echo来输出
echo $stu_json;
?>
我们定义一个数组,数组属于抽象的数据结构,为了方便跨平台传输,可以将其进行json 编码。json 格式的数据是以键值对的形式出现的。
Array{"name":"Waffle","age":20,"SEX":true,"score":96.6}
<?php
class Stu{
public $name;
public $sex;
public $agel;
public $score;
}
$stu1= new Stu();//创建了一个对象 new
$stu1->name="ZQX";
$stu1->sex=true;
$stu1->age=16;
$stu1->score=60;
$stu2= new Stu();
$stu2->name="HMM";
$stu2->sex=false;
$stu2->age=16;
$stu2->score=90;
echo $stu1->name."'s score = ".$stu1->score; //ZQX's score = 60 先找到对象名,再找到他的属性
echo $stu2->name."'s score = ".$stu2->score;
echo "<hr />";
//var_dump($stu1);
//把stu1这个对象,一个抽象的数据结构转化成字符串并存储在硬盘的文件当中,这个过程叫序列化
//序列化好处:我们可以把一个用户的状态相当做一个暂停,等用户激活的时候再从硬盘把字符串取出来,再反序列化成一个对象,然后再存储在内存中让他工作。
//echo $stu1;//(x)不能以字符串方式去输出,接下来我们需要将stu1做一个转换
echo serialize($stu1);//serialize这个函数会把我们的对象作一个序列化,把它序列化成一个字符串
//O:3:"Stu":5:{s:4:"name";s:3:"ZQX";s:3:"sex";b:1;s:4:"agel";N;s:5:"score";i:60;s:3:"age";i:16;}
//O代表object,3是类名的长度,5说明我们类中有四个属性,每句话以分号结束,s表示string类型,b表示bool类型
?>
*序列化Demo
序列化会将一个抽象的转换为字符串。
我们可以写一个Demo 来说明序列化的过程,首先创建一个类,代码如下
<?php
class Stu{
public $name;
public $sex;
public $age;
public $score;
}
?>
类名是Stu ,有四个变量。
下面我们创建一个对象,并给对象中变量赋值。代码如下
<?php
include "classStu.php";
$stu1 = new Stu();
$stu1->name = "Waffle";
$stu1->sex = true;
$stu1->age = 18;
$stu1->score = 89.9;
echo serialize($stu1);
?>
我们最后使用serialize() 函数,将$stu1 这个对象序列化成一个字符串。这样的字符串,就很容易传输和存储。如下
O:3:"Stu":4: //O 代表Object 对象;3对象名有三个字符;对象中有4个变量
{s:4:"name";s:3:"GGG";
s:3:"sex";b:1;
s:3:"age";i:18;
s:5:"score";d:89.900000000000006;}
同样,我们也可以使用unserialize()函数,将字符串反序列化为一个对象。由于字符串中含有双引号,我们可以使用定界符,代码如下
<?php
include "classStu.php";
$stu1 =<<<HTML
O:3:"Stu":4:{s:4:"name";s:3:"GGG";s:3:"sex";b:1;s:3:"age";i:18;s:5:"score";d:89.900000000000006;}
HTML;
$stu1=unserialize($stu1);
var_dump($stu1);
?>
运行这个脚本,我们可以看到反序列化后的对象
object(Stu)#1 (4) { ["name"]=> string(3) "GGG" ["sex"]=> bool(true) ["age"]=> int(18) ["score"]=> float(89.9) }
*漏洞何在?
@ 创建一个类,一个对象并将其序列化和反序列化
<?php
class Test{
public $str='Waffle';
function __destruct(){
//echo "This is function __contruct()";
@eval($this->str);
}
}
$test = new Test();
echo serialize($test);//serialize是序列化的意思 //O:4:"Test":1:{s:3:"str";s:6:"Waffle";}这里面只有属性没有方法
echo "<hr />";
//$t=serialize($test);
//var_dump(unserialize($t)); //object(Test)#2 (1) { ["str"]=> string(6) "Waffle" }
var_dump(unserialize($_GET['obj'])); //反序列化一下
?>
@ 反序列化注入
构造序列化字符
…/class/loudong.php?obj=O:4:"Test":1:{s:3:"str";s:10:"phpinfo();";}
会发现phpinfo() 函数被执行了
由以上代码可以发现,PHP的反序列化漏洞需要与其他漏洞配合,比如代码执行SQLi等
*为什么会这样呢
我们注入的字符串[phpinfo()],为什么会作为PHP 语句运行呢?观察代码,发现在类中有一个函数 __destruct() 并且这个函数调用的eval 语句,执行$this->str 变量。为什么__destruct() 没有被调用,函数内的语句就被执行了呢?
可以用如下代码测试 __destruct() 函数
<?php
class Test{
public $str='GGG';
function __destruct(){
echo "This is function __destruct()";
//@eval($this->str);
}
}
$test = new Test();
echo serialize($test);
echo "<hr />";
var_dump(unserialize($_GET['obj']));
?>
我们会发现,在销毁实例化类(就是对象)的时候,__destruct() 函数会被调用,并输出字符串
以 __ 开头的方法,是PHP 中的魔术方法,类中的魔术方法,在特定的情况下会被自动调用。主要魔术方法如下
__construct() |
在创建对象时自动调用 |
__destruct() |
在销毁对象时自动调用 |
__call() |
在对象中调用一个不可访问方法时,__call() 会被调用 |
__callStatic() |
在静态上下文中调用一个不可访问方法时调用 |
__get() |
读取不可访问属性的值时,__get() 会被调用 |
__set() |
在给不可访问属性赋值时,__set() 会被调用 |
__isset() |
当对不可访问属性调用 isset() 或 empty() 时,__isset() 会被调用 |
__unset() |
当对不可访问属性调用 unset() 时,__unset() 会被调用 |
__sleep() |
serialize() 函数会检查类中是否存在一个魔术方法__sleep() ,如果存在,该方法会先被调用,然后再执行序列化操作 |
__wakeup() |
unserialize() 会检查是否存在一个 __wakeup() 方法,如果存在会先调用__wakeup方法,预先准备对象需要的资源 |
__toString() |
__toString() 方法用于一个类被当成字符串时应增氧回应 |
__invoke() |
当尝试以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用 |
__set_state() |
字PHP 5.1.0 起调用 var_export() 导出类时,此静态 方法会被调用 |
__clone() |
当复制完成时,如果定义了 __clone 方法,则新创建的对象(复制生成的对象)中的 __clone() 方法会被调用,可用于修改属性的值。 |
__debugInfo() | This method is called by var_dump() anobject to get the properties that should be shown .If the method isn't on an object ,then all public ,protec‘’ted and private propertieswill be shown. |
参考链接:
http://www.freebuf.com/articles/web/167721.html
https://baike.baidu.com/item/%E5%BA%8F%E5%88%97%E5%8C%96/2890184?fr=aladdin
https://www.cnblogs.com/magic-zero/p/7737274.html
https://mp.weixin.qq.com/s?__biz=MzAxNDY2MTQ2OQ==&mid=2650942666&idx=1&sn=5c84d6d69463a0a430e01dfa68c2d3ab&chksm=80796ef8b70ee7ee8ba5d88feb8d794bee19a55b6e17dff45fcee6ba6ee726fb4e2e029d50bd&scene=0#rd