【反序列化】万字详解php反序列化漏洞

作者 Yuppie001
作者主页 传送
本文专栏 Web漏洞篇
🌟🌟🌟🌟🌟🌟🌟🌟

一.前置知识介绍:

1.编程范式:

    编程范式是编写和组织计算机程序的方法或风格。程序员根据不同项目的需求选择适合的编程范式。

程序的开发过程中有两种编程方法:面向对象(OOP)和面向过程(Procedural Programming)两种,php语言支持两种范式进行编程。

1.1面向过程:

概念:

  • 以过程(函数)为中心,通过调用函数一步一步执行任务。
  • 代码按照顺序执行,每个过程完成特定的任务。
    示例:
<?php
// 定义函数
function add($a, $b) {
    return $a + $b;
}

// 调用函数
$result = add(2, 3);
echo $result; // 输出 5
?>

在这个例子中,我们定义了一个函数 add 来执行加法运算,并调用这个函数来获取结果。

1.2面向对象:

概念:

  • 以对象为中心,通过定义类和创建对象来组织代码。
  • 类是对象的模板,包含属性(数据)和方法(操作)。
    示例:
<?php
// 定义类
class Calculator {
    // 定义方法
    public function add($a, $b) {
        return $a + $b;
    }
}

// 创建对象
$calc = new Calculator();
// 调用方法
$result = $calc->add(2, 3);
echo $result; // 输出 5
?>

    在这个例子中,我们定义了一个 Calculator 类,其中包含一个 add 方法。然后,我们创建 Calculator 类的一个对象 $calc,并使用这个对象调用 add 方法来获取结果。

    在实际的项目开发中,面向对象范式更加适合大型和复杂的项目,而面向过程范式则更适合简单和小型的项目。

    PHP反序列化漏洞则是在面向对象编程的基础上产生的,下面我们进一步学习面向对象的相关概念,理解这部分的可以自行跳过。

(1)类:

概念:

  • 类是面向对象编程的核心概念,是对现实世界中事物的一种抽象和模板。
  • 它定义了一组属性(数据)和方法(行为),描述了对象的共同特征和行为。
    类由成员变量(属性)+成员函数(方法)构成

作用:

  • 类用来创建对象,每个对象都是类的实例。
  • 类提供了代码的组织结构,使代码更具模块化和可维护性。

下面是一个类的创建:

<?php
// 定义一个类
class Car {
    // 属性
    public $color;
    public $model;

    // 构造方法
    public function __construct($color, $model) {
        $this->color = $color;
        $this->model = $model;
    }

    // 方法
    public function display() {
        echo "This car is a " . $this->color . " " . $this->model . ".";
    }
}
?>

  在这个示例中,Car 类定义了两个属性 $color 和 $model,以及一个构造方法 __construct 和一个方法 display。

构造方法魔术方法的一种,这里我们先不管,暂时把它理解为类中定义的函数,后续会讲解。

(2)对象:

概念:

  • 对象是类的实例,是类的具体实现。
  • 每个对象都有自己的属性值和方法行为。

作用:

  • 通过对象可以访问和操作类定义的属性和方法。

  • 对象是程序运行时实际操作的实体。

示例:

<?php
// 定义一个类
class Car {
  // 属性
  public $color;
  public $model;
}
// 实例化一个对象(创建对象)
$myCar = new Car("red", "Toyota"); 
$myCar->color='yellow';
echo $myCar->color;
?>

重点:如何来理解实例化对象呢?
    可以这么理解:在房屋构建中,我们需要先规划蓝图 ,预估使用多少材料,如何建筑。我们的就类似于房屋建筑前的蓝图。蓝图构建完成后并不代表房屋已经建成,只有当开始实际利用材料进行建筑时,房屋才真正完成。同样地,对象实例化的过程就类似于按照蓝图实际建造房屋。定义时,真正的对象还没有建成,只有当我们将“材料”实例化并进行建造时,对象才真正创建完成。

再来理一下关系:
1.类是蓝图
    在编写类时,你定义了对象的属性和方法,但此时还没有具体的对象存在。就像你绘制了一张房屋的建筑蓝图,定义了房屋的结构和功能,但此时还没有建成任何实际的房屋。
2.实例化对象是建造房屋
    当你实例化一个类时(即创建一个类的对象),你就是根据蓝图建造出一栋具体的房屋。在这一步,你分配了内存空间,并实际创建了对象,赋予它具体的属性值。这就好比你开始利用实际的建筑材料,按照蓝图一步步建造出房屋。
3.使用对象是住进房屋
    一旦对象被实例化,你可以使用这个对象的属性和方法。就像房屋建成后,你可以进入并使用房屋的各个功能一样。

    当对象被创建成功后,我们便可以访问对象中的属性和方法。这里的属性和方法是按照被实例化的类来产生的。

<?php
// 定义一个类
class Car {
  // 属性
  public $color;

  // 定义一个方法
  public function myCar() {
    return "This car is a " . $this->color;
  }
}

// 实例化一个对象(创建对象)
$myCar = new Car(); 
// 访问color属性并赋值
$myCar->color = 'yellow';
// 访问myCar方法并输出结果![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/038340140bba40e6a5ac37067c83d6d6.png)

echo $myCar->myCar();  
?>

输出:
在这里插入图片描述
    这些知识只是最基本的类和对象的关系,还需要掌握的知识细节还有很多,如:定义类属性的修饰符、继承等等问题,这里就不作详细解释了。

2.序列化:

    序列化是将数据结构(如对象、数组)转换为可存储或传输的格式的过程。
    在 PHP 中,序列化通常将数据结构转换为字符串

    大部分数据都可以进行序列化操作,序列化操作不是对象独有的,只是反序列化漏洞中是对象的序列和反序列化操作。

<?php

// 定义一个简单的类
class MyClass {
    public $property = 'value';
}

// 各种数据类型
$int = 1;
$float = 3.14;
$string = "Hello, World!";
$bool = true;
$array = array(1, 2, 3, "four", "five");
$object = new MyClass();

// 序列化不同的数据类型
$serializedInt = serialize($int);
$serializedFloat = serialize($float);
$serializedString = serialize($string);
$serializedBool = serialize($bool);
$serializedArray = serialize($array);
$serializedObject = serialize($object);

// 输出序列化结果
echo "Serialized integer: " . $serializedInt . "\n";
echo "Serialized float: " . $serializedFloat . "\n";
echo "Serialized string: " . $serializedString . "\n";
echo "Serialized boolean: " . $serializedBool . "\n";
echo "Serialized array: " . $serializedArray . "\n";
echo "Serialized object: " . $serializedObject . "\n";

?>

输出结果:

Serialized integer: i:1;
Serialized float: d:3.14;
Serialized string: s:13:"Hello, World!";
Serialized boolean: b:1;
Serialized array: a:5:{i:0;i:1;i:1;i:2;i:2;i:3;i:3;s:4:"four";i:4;s:4:"five";}
Serialized object: O:7:"MyClass":1:{s:8:"property";s:5:"value";}

我们重点关注对象的序列化:

3.反序列化:

    反序列化是将序列化后的数据(通常是字符串格式)转换回其原始数据结构的过程。
为什么需要序列化和反序列化?
    通常有以下几点,对于我们初步学习反序列化漏洞可不着急理解,关键在于它们在数据存储、传输和管理方面的应用
在这里插入图片描述

    反序列化的目的是恢复原始的数据,以便可以再次使用这些数据。

序列化和反序列化通常用于分布式系统中
分布式系统指将不同的系统组件分布在不同的计算机中,并通过网络连接

4.魔术方法:

    PHP 中的魔术方法(Magic Methods)是一组特殊的方法,它们以双下划线 __ 开头,之所以称之为“魔术方法”,是因为它们提供了“魔术般”的功能,在特定条件下自动被调用,而不需要显式地调用这些方法。这种行为让它们在某些情况下表现得像魔术一样。

    在反序列化漏洞中魔术方法起着非常重要的作用,所谓构造pop链,其中的就是通过对各种对象和方法的调用,攻击过程像链条一样,所以称之为“链”,至于pop即是对对象和属性进行的攻击。
所以理解各种魔术方法的调用规则对后续的构造pop链有着至关重要的作用
下面介绍几个常用的:

1.__construct()

功能:对象实例化时调用的构造函数,用于初始化对象属性。

<?php
class MyClass {
    public $property;

    public function __construct($param) {
        $this->property = $param;
        echo "Constructor called with parameter: $param\n";
    }
}
//当对象被实例化时,自动调用__construct
$obj = new MyClass("Hello World");
// 输出:Constructor called with parameter: Hello World
?>

2. __destruct()
功能:对象销毁时调用的析构函数,用于清理资源。

<?php
class MyClass {
    public function __destruct() {
        echo "Destructor called\n";
    }
}

$obj = new MyClass();
unset($obj); // 手动销毁对象
// 输出:Destructor called

// 或者当脚本执行完毕时被调用
?>

    这里需要注意,为什么当脚本执行完毕时被调用,因为当脚本执行结束时,PHP 会自动销毁所有在当前作用域内的对象
    每个魔术方法都有每个独特的调用方式,这里就介绍到这里,剩下的需要自己去慢慢研究

  • __call($name, $arguments):在对象上下文中调用一个不可访问的方法时调用。
  • __get($name):访问不可访问的属性时调用。
    等等…

二.反序列化漏洞

1.介绍

    当程序在进行反序列化时,会自动调用一些函数,例如__wakeup(),__destruct()等魔术方法,但是如果传入函数的参数可以被用户控制的话,用户可以输入一些恶意代码到函数中,从而导致反序列化漏洞
这里以一个简单的例子举例:
假设有一个类 Example,其 __destruct() 方法中包含文件删除操作

class Example {
    private $file;

    public function __construct($file) {
        $this->file = $file;
    }

    public function __destruct() {
        unlink($this->file);
    }
}

    如果应用程序从不受信任的来源反序列化数据:

$data = unserialize($_POST['data']);

    攻击者可以传递如下恶意数据:

O:7:"Example":1:{s:10:"Examplefile";s:12:"/path/to/file";}

    这将导致 unserialize() 创建一个 Example 对象,并设置其 $file 属性为 /path/to/file。当对象销毁时,__destruct() 方法将被调用,从而删除指定的文件。

2.安全措施及绕过思路

(1)正则过滤
    反序列化造成的原因是是因为反序列化过程中,序列化的字符串数据可以包含对象的属性和值,攻击者可以修改这些数据如果这些数据未经过验证直接反序列化,可能会导致恶意代码执行。
    最简单的一个防御思路是在反序列化时将序列化的字符串进行关键字的正则过滤。
这种我们通常使用字符串逃逸的方式进行绕过
附带的额外小效果:如果输入点只能修改其中一个属性的值,但我们需要修改其他属性的值,通过字符串逃逸就可以达到隔山打牛的效果。
🌟详见这篇文章
    除了这种方法绕过还有其他的通用绕过正则的方法,大小写重写、根据编码规则 等等,具体要看服务端如何过滤的,这里因为不具备代表性就省略了
使用其他序列化格式
    使用 JSON 代替 PHP 序列化,因为 JSON 不支持复杂对象的序列化,从而减少攻击面。
    可寻找json的相关漏洞以及寻找其他反序列化的业务系统。
    这种绕过就比较鸡肋了,因为完全和反序列化漏洞不相干了
(3) 限制反序列化的类
    通过 allowed_classes 参数限制可以反序列化的类,防止反序列化恶意对象。

$allowedClasses = ['AllowedClass'];
$data = unserialize($input, ['allowed_classes' => $allowedClasses]);

绕过方式待以后补充…
(4)签名或哈希验证
    在反序列化之前,通过签名或哈希验证数据的完整性,确保数据未被篡改。

$input = $_POST['data'];
$hash = $_POST['hash'];
$secretKey = 'secret';

// 验证哈希
if (hash_hmac('sha256', $input, $secretKey) === $hash) {
    $data = unserialize($input);
} else {
    echo "Invalid data!";
}

绕过方式待以后补充…

3.利用方式

    根据不同的魔术方法利用方式分为了显式利用隐式利用 。显式利用通过对 “看得见” 的魔术方法进行pop链构造;隐式则是对 “看不见” 的魔术方法进行pop链构造,即原生类的利用

(3)原生类

待更新…


    当然,反序列化的内容远不止这些。本篇博客将会陆续更新更多相关信息,敬请关注!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值