序列化与反序列化
1、什么是序列化与反序列化?
把对象转换为字符串进行存储的过程称为对象的序列化(Serialization)
把存储的字符串恢复为对象的过程称为对象的反序列化(Deserialization)
应用场景
当对象需要被网络传输时
当对象状态需要被持久化
什么是反序列化?
序列化就是将对象转换为字节流,便于保存在文件,内存,数据库中;反序列化就是将字节流转化为对象,也就是把数据转化为一种可你的数据结构,再把这种可逆的数据结构转化回数据,这就是序列化与反序列化。
反序列化的函数
serialize():将一个对象转换为字符串
unserialize():将字符串还原成一个对象
漏洞原理
PHP将所有以__(两个下划线)开头的类方法保留为魔术方法,所以在定义类的方法时,除了上述方法,建议不要以__为前缀
魔术方法:指的是一类可以自动调用的方法,这个方法使用特殊名称,以双下划线(__)开头和结尾,并在调用他们时具有特殊行为。
PHP中常用的魔术方法
1、举例:__construct()
在创建一个新的对象时,__construct()函数会被调用,初始化对象的属性或执行其他必要的操作
class MyClass{ private $str; public function __construce($str){ $this->str = $str; } public function printStr(){ echo $this->str } } $obj = new MyClass('hello'); $obj-printStr();//输出hello
2、举例:__toString()
当一个对象需要表示为字符串时,__toString()函数会被自动调用,值得注意的是,这个方法必须返回一个字符串。
class MyClass{ public function __toString(){ return "This is MyClass"; } } $obj = new MyClass(); echo $obj;//This is MyClass
总结
以上所讲为PHP常用魔术方法,还有其他魔术方法比如__isset(),__unset(),__sleep(),__wakeup(),__clone(),魔术方法好处在于可以简化代码,提高开发效率,过度使用会使代码变得难以理解和调式,所以,量力而行。
PHP序列化
在写程序时,尤其是写网站的时候,经常会构造类,并且会将实例化的类作为变量进行传输,序列化就是在此为了减少传输内容的大小孕育而生的一种压缩方法,PHP类中含有几个特定元素:类属性,类常量,类方法。每个类至少包含有以上是按个元素,这三个元素也可以组成最基本的类。那么按照特定的格式,将这三个元素表达出来就可以将一个完整的类表示出来并传递,序列化就是将一个类压缩成字符串的方法。
序列化例子:
<?php class TEST{ public $a="public"; private $b="private"; protected $c="protected"; static $d="static"; } $ob = new TEST(); echo serialize($ob); ?>
output:
解析
O(大写英文字母o):表示这是一个对象
4:对象的名称TEST有4个字符
TEST:对象名称
3:对象属性的个数,不算static
s:数据类型为string
1:变量的名称长度
a:变量名
s:表示数据类型
6:变量值的长度
public:变量值
s:数据类型
7:变量名的长度,private属性序列话会在两个加空字节%00,比明文长度多2
TESTb:private属性变量名在序列化时会加类名,即类名+变量名
s:数据类型
7:变量值的长度
private:变量值
s:数据类型
4:变量名长度
*c:protected属性的变量名会在序列化时在变量名前加一个\00*\00
s:数据类型
9:数据值的长度
protected:数据值
不同类型的数据
PHP对不同类型的数据用不同的字母进行表示
a:数组
b:布尔值
d:实数型
i:整形
r:对象引用
s:字符串
C:自定义的对象序列化
O:对象序列化
N:NULL
R:指针引用
U:Unicode编码字符串
魔术方法:
__construct():创建对象时触发
__destruct():对象被销毁时触发
__call():在对象上下文中调用不可访问的方法时触发
__callStatic():在静态上下文中调用不可访问的方法时触发
__get():用于从不可访问的属性读取数据
__set():用于将数据写入不可访问的属性
__isset():在不可访问的属性上调用isset()或empty()触发
__unset():在不可访问的属性上使用unset()时触发
__invoke():当脚本尝试将对象调用为函数时触发
序列化格式
1、JSON(JavaScript Object Notation):轻量级数据交换格式,易于阅读和编写,也易于机器解析生成
{"name":"John":"age":30,"city":"New York"}
2、XML(eXtensible Markup Language):标记语言,用于存储和传输数据
<person> <name>John</name> <age>30</age> <city>New York</city> </person>
3、pickle(Python特有的序列化格式):Python中的序列化模块,可以将python对象序列化为二进制格式
import pickle data = {"name":"john","age":30,"ciry":"New York"} serialized_data = pickle.dumps(data)
实例
serialize()
<?php $site = array('Google','Runoob','Facebook'); $serialized_data = serialize($site); echo $serialized_data; ?> output: a:3:{i:0;s:6:"Google";i:1;s:6:"Runoob";i:2;s:8:"Facebook";}
unserialize()
<?php $str = 'a:3:{i:0;s:6:"Google";i:1;s:6:"Runoob";i:2;s:8:"Facebook";}'; $unserialized_data = unserialize($str); print_r($unserialized_data); ?> output: Array ( [0] => Google [1] => Runoob [2] => Facebook )
技巧思路
1、首先找其实和结尾,万能用的起始:
2、__wakeup()完美的起始:
3、__destruct()完美的起始
4、通过反序列化可以给属性进行任意赋值,因此只要是属性,其值完全可控
5、首位相连,找链式调用,注意不要被变量名骗了,一定要找到所有可能性
6、本地构造序列化,反着写!哪个类是最后调用的就先new哪个类
反序列化的常见起点
__wakeup一定会调用 //使用unserialize时触发
__destruct一定会调用 //对象被销毁时触发
__toString当一个对象被反序列化后又被当作字符串使用
反序列化常见的中间跳板
__toString当一个对象被当作字符串使用
__get读取不可访问或不存在属性时被调用
__set当给不可访问或不存在属性赋值时被调用
__isset对不可访问或不存在的属性调用isset或empty时被调用
形如:$this->$func();
反序列化常见终点
__call:调用不可访问或不存在的方法时被调用
call_user_func:php代码执行都会选择这里
call_user_func_array:一般php代码执行都会选择这里
$this->的用法介绍
PHP中一般先声明一个类,然后用这个类去实例化对象!$this的含义时实例化后的具体对象!$this->表示在类本身内部使用本类的属性和方法。->符号是插入式解引用操作符。说白了,他是调用由引用传递参数的子程序的方法。
举例:声明一个User类,它只含有一个属性$name;
<?php class User{ public $_name; } ?>
现在,我们给User类加一个方法,就用getName()方法,输出$name属性值
<?php class User{ public $_name; function getName(){ echo $this->name; } } //如何使用? $user1 = new User(); $user1->name = '张三'; $user1->getName(); //输出张三 $user2 = new User(); $user2->name = '李四''; $user2->getName(); //输出李四 ?>
上面创建了两个User对象,分别式$user1和$user2
当调用$user1->getName()的时候。上面User类中的代码echo $this->name;相当于是echo $user->name
反序列化简单入门实例
目的:使用反序列化,任意操作属性值
构造payload:http://172.16.12.3/test.php?test=O:1:"A":1:{s:4:"test";s:5:"hello";}
test.php
<?php class A{ var $test = "demo"; function __destruct(){ echo $this->test; } } $a = $_GET['test']; $a_unser = unserialize($a); ?>
1.php
<?php class A{ var $test = "demo"; function __construct(){ $this->test = " 123"; echo "1"; } function __destruct(){ echo $this->test; } } $b = new A(); $a = $_GET['test']; $a_unser = unserialize($a); ?>
序列化总结
序列化试讲对象的状态信息(属性)转换为可以存储或传输的形式的过程(方便存储和传输)
将对象或数组转化为可存储/传输的字符串
PHP中使用serialize()函数来讲对象或者数组进行序列化,并返回包含字节流的字符串来表示
序列化之后的表达凡是/格式
1、简单序列化
<?php $a=null; echo serialize(); ?>
2、数组序列化
<?php $a = array('a','b','c'); echo $a[0]; echo serialize(); ?>
3、对象序列化
<?php class test{ public $pub='gazi'; function tougou(){ echo $this->pub; } } $a = new test(); echo serialize($a); ?> output: 0:4:"test":1:{s:3:"pub";s:4:"gazi";}
4、私有修饰符序列化
<?php class test{ private $pub='gazi'; function tougou(){ echo $this->pub; } } $a = new test(); echo serialize($a); ?>
output的值因为pub是私有属性,蓄力护额会在前面加上类名,并且类名前后由空字符,所以一共9个字符。
5、保护修饰符序列化
<?php class test{ protected $pub='gazi'; function tougou(){ echo $this->pub; } } $a = new test(); echo serialize($a); ?>
如果一个属性属于保护的修饰符,序列化的时候会在属性前面加一个* ,并且*的前后也会有空字符,一共6个字符
6、成员属性调用对象序列化
<?php class test{ var $pub='gazi'; function tougou(){ echo $this->pub; } } class test2{ var $gangdan; function __construct(){ $this->gangdan=new test(); } } $a = new test2(); echo serialize($a); ?> output: O:5:"test2":1:{s:7:"gangdan";O:4:"test":1:{s:3:"pub";s:4:"gazi";}}
7、整体演示
<?php highlight_file(__FILE__); class TEST{ public $data; public $data2 = "dazhaung"; private $pass; public function __construct($data, $pass){ $this->data = $data; $this->pass = $pass; } } $number = 34; $str = 'user'; $bool = true; $null = NULL; $arr = array('a' => 10,'b' => 200); $arr2 = array2('zhangsan','dazhuang','gazi'); $test= new TEST('uu',true); $test2 = new TEST('uu',true); $test2->data = &$test2->data2; echo serialize($number)."<br />"; echo serialize($str)."<br />"; echo serialize($bool)."<br />"; echo serialize($null)."<br />"; echo serialize($arr)."<br />"; echo serialize($test)."<br />"; echo serialize($test2)."<br />"; ?>
输出结果
反序列化总结
特性
1、反序列化之后的内容为一个对象
2、反序列化生成的对象里的值,由反序列化的值提供;与原有类预定义的值无关
3、反序列化不处罚类的成员方法;需要调用方法后才能触发
举例:
<?php class tets{ public $a = 'gazi'; protected $b = 666; private $c = flase; public function displayVar(){ echo $this->a; } } $b = new test();//将类实例化为一个对象 $d = serialize();//进行序列化 echo $d."<br />";//输出序列化后的值 echo urlencode()."<br />";//进行url编码,能看到空字符的%00 $a = urlencode($d);//将ur1编码后赋值给变量a $b = unserialize(urlencode($a));//对a进行反序列化,赋值给b var_dump($b); ?>
反序列化的输出和正常对象的输出是一样的,如果将序列化字符串里面的gazi改成gangdan,反序列化后的a就变成gangdan,通过序列化反序列化构造的木马很难发现。
反序列化漏洞POC
<?php error_reporting(0); class test{ public $a = 'echo "this is test!!!";'; public function displayVar(){ eval($this->a); } } $get = $_GET["gazi"];//通过get传参传的是序列化后的值 $b = unserialize($get); //吧参数值反序列化付给对象b $b->displayVar(); //对象b不属于test类的,但是通过反序列化后系统会认为b是类test实例化的对象,从这里可以执行类中的方法,因为传参的时候已经把类的属性修改为我们想要的代码,实现了反序列化的漏洞。 //这里有一个前提,我们必须要知道源代码 ?>
payload:http://172.16.12.3/zz.php?gazi=O:4:"test":1:{s:1:"a";s:12:"system(dir);";}
下面是执行结果: