PHP反序列化漏洞之面向对象基础

一、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";}
?>

常见的序列化之后的数据类型:

  • aarray 数组型
  • bboolean 布尔型
  • ddouble 浮点型
  • iinteger 整数型
  • robjec reference 对象引用
  • snon-escaped binary string 非转义的二进制字符串
  • Sescaped binary string 转义的二进制字符串
  • Ccustom object 自定义对象
  • Oclass 对象
  • Nnull
  • Rpointer reference 指针引用
  • Uunicode 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";}
    ?>
    

    "对象"序列化之后的解释:

    • OObject
    • 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
    ?>
    

    当属性$pubprivate修饰符修饰时,将“对象”序列化之后,和上一个例子的不同点在于: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反序列化漏洞学习,并结合自己的理解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值