一、PHP面向对象基础
要谈PHP反序列化,不得不涉及面向对象,因为在反序列化漏洞利用中,大多涉及的都是“对象”的反序列化。所以需要了解面向对象基础。
面向面向对象是一种以“对象”为中心的编程思想,把要解决的问题分解成各个“对象”,对象是一个由信息及对信息进行处理的描述所组成的整体,是对现实世界的抽象。
1、基本概念
-
对象
对象是对事物的抽象表示。 在面向对象的术语中,一切皆是对象,一个对象就代表一个具体的功能操作,我们不需要了解这个对象是如何实现某个操作的,只需要知道该对象可以完成哪些操作即可。 -
抽象
抽象(Abstract
)就是忽略事物中与当前目标无关的非本质特征 ,更充分地注意与当前目标有关的本质特征,从而找出事物的共性,并把具有共性的事物划为一类,得到一个抽象的概念。 -
封装
封装(Encapsulation
)是指把对象的属性和行为结合成一个独立的单位,并尽可能隐藏对象的内部细节。通常来说封装有两个含义:
- 把对象的全部属性和行为结合在一起,形成一个不可分割的独立单位,对象的属性值(除了公有的属性值)只能由这个对象的行为来读取和修改;
- 尽可能隐藏对象的内部细节,对外形成一道屏障,与外部的联系只能通过外部接口实现
-
继承
继承(Inheritance
)是一种连接类与类的层次模型。 继承性是指特殊类的对象拥有其一般类的属性和行为。 继承意昧着“自动地拥有”,即特殊类中不必重新定义己在一般类中 定义过的属性和行为,而是自动地、隐含地拥有其一般类的属性和行为。 当这个特殊类又被它更下层的特殊类继承时,它继承来的及自己定义的属性和行为又被下一层的特殊类继承下去。因此,继承是传递的,体现了大自然中特殊与一般的关系 。父类:一个类被其他类继承,可将该类称为父类,或者基类,超类
子类:一个集成其他类的类称为子类,也可称为派生类
以上概念对于初学者来说可能比较抽象,晦涩。在面向对象中有许多概念,如类、对象、继承、多态等。面向对象编程思想的培养需要在学习过程中不断积累,对于学习PHP反序列化漏洞知识的学习,先了解基础的“类”、“对象”、“实例化”等基础知识即可,之后随着相关知识的深入,可以深入了面向对象编程。
2、类和对象
类是定义了一件事物的抽象特点,他将数据的形式以及这些数据上的操作封装在一起。类的内部由成员变量(属性)和成员函数(方法)构成,
- 成员变量:定义在类内部的变量,该变量的值对外是不可见的,但是可以通过成员函数访问,在类被实例化为对象后,该变量即可成为对象的属性
- 成员函数:定义在类的内部与特定对象相关联的函数。成员函数也称为方法,它用于执行特定的操作,并可以访问和操作类的属性,通过使用关键字
function
来定义成员函数。
// 定义类的语法格式
class class_name
{
//类中定义的属性和方法
}
// 定义一个Person类
class Person
{
//类中定义的属性和方法
// 属性
public $name; // 姓名
public $age; // 年龄
// 成员函数
public function tell(){
print($this->name."今年".$this->age."岁!");
}
}
对象是类实例化后的结果。在PHP中定义好类之后,便可以使用 new
关键字实例化一个类的对象。 其语法格式如下所示:
$object_name = new class_name();
<?php
class Person
{
public $name; // 姓名
public $age; // 年龄
// 成员函数
public function tell(){
print($this->name."今年".$this->age."岁!");
}
}
$p = new Person(); // 创建 Person 类的实例,即对象,名为$p
$p -> name = "张三"; // 给对象的 name 属性赋值
$p -> age = 18; //
echo $p -> tell(); // 张三今年18岁!
?>
3、类的常见修饰符介绍
private
private
表示私有的,通过private
关键字修饰的字段和方法只能在类的内部使用
,不能通过类的实例化对象调用,也不能通过类的子类调用。protected
protected表示受保护的,只能在类本身和子类中使用
。public
public
关键字修饰的字段或者方法表示它是公共的,即在PHP的任何位置都可以通过对象名来访问该字段和方法。同时,public
也是字段和方法的默认修饰符。
4、构造函数与析构函数
构造函数在类的实例化对象时自动执行,在这里可以对成员进行初始化,或者执行一些特殊操作。与它对应的还有一个析构函数,析构函数在实例化对象销毁时自动执行。它通常用于执行一些清理资源的工作,例如释放内存、删除变量和关闭数据库连接等。
1)、构造函数
PHP 中,构造函数的名称被统一命名为__construct()
。 也就是说,如果在一个类中声明一个命名为__construct()
的函数,那么该函数将被当成是一个构造函数,并且在建立对象实例时被执行。构造函数的语法格式如下所示:
function __construct([参数列表]){
// 构造函数体
}
<?php
class Student{
private $name;
private $age;
function __construct()
{
$this->name = "zhangsan";
$this->age = 18;
}
public function toString(){
echo $this->name."今年".$this->age."岁。";
}
}
$s = new Student(); // 实例化对象的时候,自动去调用构造函数__construct,对属性进行初始化
$s->toString();
?>
2)、析构函数
析构函数也有一个统一的命名,即__destruct()
。 析构函数允许在使用一个对象之后执行任意代码来清除内存。默认时仅仅释放对象属性所占用的内存并删除对象相关的资源。与构造函数不同的是,析构函数不接受任何参数。
<?php
class Counter{
private static $count = 0;
function __construct()
{
self::$count++;
}
function __destruct()
{
self::$count--;
}
function getCount()
{
return self::$count;
}
}
$num1 = new Counter(); // 实例化对象,触发构造方法,count的值+1,变为1
echo $num1->getCount()."<br>"; // 1
$num2 = new Counter(); // 实例化对象,count的值+1,变为2
echo $num2->getCount()."<br>"; // 2
$num2 = NULL; // 销毁对象$num2,count的值-1,变为1
echo $num1->getCount()."<br>"; // 1
?>
二、序列化基础知识
1、序列化的作用
序列化是将对象的状态信息(属性)转换为可以存储或者传输的字符串形式的过程。将对象或者数组转化为可存储/传输的字符串
在php中使用函数
serialize()
来将对象或者数组进行序列化,并返回一个包含字节流的字符串来表示
2、序列化之后的表达式
// 所有序列化之后的格式第一位都是数据类型的英文字母的简写
<?php
echo serialize(null); // N;
echo serialize(666); // i:666;
echo serialize(66.6); // d:66.599999999999994315658113919198513031005859375;
echo serialize(true); // b:1;
echo serialize(false); // b:0;
echo serialize("benben"); // s:6(长度):"benben";(如果字符串中有双引号“"”,序列化之后,被双引号闭合,正因为有前面字符串的长度,所以才可以区分哪个是字符串内容中的双引号)
echo serialize(array('benben','dazhuang','laoliu')) // a:3:{i:0;s:6:"benben";i:1;s:8:"dazhuang";i:2;s:6:"laoliu";}
?>
常见的序列化之后的数据类型:
a
:array
数组型b
:boolean
布尔型d
:double
浮点型i
:integer
整数型r
:objec reference
对象引用s
:non-escaped binary string
非转义的二进制字符串S
:escaped binary string
转义的二进制字符串C
:custom object
自定义对象O
:class
对象N
:null
空R
:pointer reference
指针引用U
:unicode string Unicode
编码的字符串
3、对象序列化
-
不能序列化“类”,可以序列化“对象”;只序列化成员变量,不序列化成员函数
<?php class test{ public $pub='benben'; function jineng(){ echo $this->pub; } } $a = new test(); echo serialize($a); // O:4:"test":1:{s:3:"pub";s:6:"benben";} ?>
"对象"序列化之后的解释:
O
:Object
4
:类名长度"test"
:表示类名1
:成员属性数量s
:变量名pub的类型为字符串3
:变量名的长度"pub"
:变量名s
:变量值的类型为字符串6
:变量值的长度"benben"
:变量值
-
用
private
修饰符修饰的私有属性在序列化后,在私有属性名前加“%00类名%00
”(这里的%00
为空字符(Null
)的URL编码,实际是看不到的)。<?php class test{ private $pub='benben'; function jineng(){ echo $this->pub; } } $a = new test(); echo serialize($a); // O:4:"test":1:{s:9:"testpub";s:6:"benben";} echo urlencode(serialize($a)); // O%3A4%3A%22test%22%3A1%3A%7Bs%3A9%3A%22%00test%00pub%22%3Bs%3A6%3A%22benben%22%3B%7D ?>
当属性
$pub
被private
修饰符修饰时,将“对象”序列化之后,和上一个例子的不同点在于:s:9:"testpub"
,如果使用URL编码输出后,会看到test
前后会有%00
,所以成员属性名的长度为9
-
用
protected
修饰符修饰的成员属性在序列化后,会在变量名前加“%00*%00
”(这里的%00
为空字符(Null
)的URL编码,实际是看不到的)。<?php class test{ protected $pub='benben'; function jineng(){ echo $this->pub; } } $a = new test(); echo serialize($a); // O:4:"test":1:{s:6:"*pub";s:6:"benben";} ?>
-
当我们序列化一个对象,该对象的成员变量的值为实例化后的另一个对象时,其序列化后,变量的值为另一个对象序列化后的值。
<?php class test1{ var $pub='benben'; function jineng(){ echo $this->pub; } } class test2{ var $ben; function __construct(){ $this->ben=new test1(); } } $a = new test2(); echo serialize($a); // O:5:"test2":1:{s:3:"ben";O:5:"test1":1:{s:3:"pub";s:6:"benben";}} ?>
三、反序列化
1、反序列化的作用
将序列化后的字符串还原成实例化的对象
2、反序列化的特性
-
反序列化之后的内容为一个对象
<?php class test { public $a = 'benben'; protected $b = 666; private $c = false; public function displayVar() { echo $this->a; } } $d = serialize(new test()); echo $d; // "对象"序列化:O:4:"test":3:{s:1:"a";s:6:"benben";s:4:"*b";i:666;s:7:"testc";b:0;} var_dump(unserialize($d)); // object(test)#1 (3) { ["a"]=> string(6) "benben" ["b":protected]=> int(666) ["c":"test":private]=> bool(false)} ?>
-
反序列化生成的对象里的值,由反序列化里的值提供;与原有类预定义的值无关
<?php class test { public $a = 'benben'; protected $b = 666; private $c = false; public function displayVar() { echo $this->a; } } // 将序列化之后的字符串中变量a的值,改为dazhang,将私有属性c的值改为true $d = 'O:4:"test":3:{s:1:"a";s:8:"dazhuang";s:4:"%00*%00b";i:666;s:7:"%00test%00c";b:1;}'; $d = urldecode($d); // 反序列化之后对象的值与原有类中的值无关,而只与反序列化中的值有关 var_dump(unserialize($d)); // object(test)#1 (3) { ["a"]=> string(8) "dazhuang" ["b":protected]=> int(666) ["c":"test":private]=> bool(true)} ?>
-
反序列化不触发类的成员方法(除魔术方法以外);需要调用方法后才能触发(原有类必须存在)
<?php class test { public $a = 'benben'; protected $b = 666; private $c = false; public function displayVar() { echo $this->a; } } $d = 'O:4:"test":3:{s:1:"a";s:8:"dazhuang";s:4:"%00*%00b";i:666;s:7:"%00test%00c";b:1;}'; $e = urldecode($d); $f = unserialize($e); $f->displayVar(); // 反序列化后,生成对象,调用displayVar(),输出:dazhuang ?>
以上知识总结来自橙子科技php反序列化漏洞学习,并结合自己的理解。