php反序列化原理

php序列化前提知识

php序列化

序列化:将对象转为字节流,目的是方便对象在内存、文件、数据库或者网络之间的传递。
反序列化:序列化的逆过程,即将字节流转为对象的过程。
序列化和反序列化结合起来,可以轻松地存储和传输数据,使得程序更具维护性。

反序列化漏洞

原因是程序没有对用户输入的反序列化字符串进行检测,导致反序列化过程可以被恶意控制,进而造成代码执行、getshell等一系列不可控的后果。
反序列化漏洞并不是php特有,也存在与Java、python等语言之中,但其原理基本相同。

php反序列化

PHP反序列化是一个过程,它涉及将存储的数据(例如,一个字符串)转换回其原始结构,以便程序可以理解和操作这些数据。在PHP中,unserialize()函数用于将序列化后的字符串转换回PHP值,比如数组或者对象。

如何反序列化

在PHP中,使用unserialize()函数来进行反序列化。这个函数接收一个字符串参数,并尝试将其解析为PHP值。下面是一个简单的例子:

$data = 'a:2:{i:0;s:3:"foo";i:1;s:3:"bar";}'; // 序列化后的字符串
$result = unserialize($data); // 将字符串反序列化为PHP数组
print_r($result); // 输出反序列化后的数组

在这个例子中,$data是一个序列化后的字符串,表示一个键值对数组。使用unserialize()函数后,我们得到了一个PHP数组,可以通过print_r()函数查看它的结构。

面向过程与面向对象

众所周知,在编程中分为面向过程、面向对象及面向百度编程,接下来我们来大致了解一下,前两者。

什么是面向过程?

面向过程编程(Procedural Programming)是一种编程范式,它强调的是通过算法和过程来解决问题。在这种编程范式中,程序被视为一系列按特定顺序执行的操作的集合。每个操作都是一个过程,它包含了一系列步骤,用于处理数据或执行任务。

下面是一段伪代码,展示了如何用面向过程的方式来描述“把大象放进冰箱”的步骤:

/* 伪代码示例:把大象放进冰箱 */

// 步骤一:定义打开冰箱门的函数
void OpenRefrigeratorDoor() {
    // 打印打开冰箱门的操作
    printf("打开冰箱门\n");
}

// 步骤二:定义放入大象的函数
void PutElephantInRefrigerator() {
    // 打印放入大象的操作
    printf("把大象放进冰箱\n");
}

// 步骤三:定义关闭冰箱门的函数
void CloseRefrigeratorDoor() {
    // 打印关闭冰箱门的操作
    printf("关闭冰箱门\n");
}

// 主函数,负责调用上述三个函数
int main() {
    // 调用打开冰箱门的函数
    OpenRefrigeratorDoor();
    // 调用放入大象的函数
    PutElephantInRefrigerator();
    // 调用关闭冰箱门的函数
    CloseRefrigeratorDoor();

    // 返回成功标志
    return 0;
}

那么可以很清晰的看到,面向过程把大象放进冰箱分为了3步,开门==》装进冰箱==》关门。

什么是面向对象?

面向对象编程(Object Oriented Programming,简称OOP)是一种编程范式,它将对象作为程序的基本构建块。在面向对象的系统中,每个对象都包含数据(属性)和操作这些数据的函数(方法)。这与面向过程编程形成对比,后者通常涉及一系列按顺序排列的函数或指令集。

面向对象编程通常涉及到定义类(class),类是用来描述具有共同特性的对象群体的蓝图。通过类,我们可以创建多个对象实例,每个实例都有自己的属性值和方法实现。

以面向对象的方式描述“把大象装进冰箱”的过程:

<?php
// 定义一个名为Refrigerator的类
class Refrigerator {
    // 定义一个方法来打开冰箱门
    public function openDoor() {
        return "打开冰箱门";
    }

    // 定义一个方法来关闭冰箱门
    public function closeDoor() {
        return "关闭冰箱门";
    }
}

// 定义一个名为Elephant的类
class Elephant {
    // 定义一个方法来把大象放进冰箱
    public function putInto($refrigerator) {
        return $refrigerator->openDoor() . ", 把大象放进冰箱, " . $refrigerator->closeDoor();
    }
}

// 创建一个冰箱对象
$myRefrigerator = new Refrigerator();

// 创建一个大象对象
$myElephant = new Elephant();

// 调用大象对象的方法,把大象放进冰箱
echo $myElephant->putInto($myRefrigerator);
?>

在这个伪代码示例中,我们创建了两个类:Refrigerator 和 Elephant。Refrigerator 类有两个方法:openDoor() 和 closeDoor(),分别表示打开和关闭冰箱门。Elephant 类有一个方法 putInto(),它接受一个 Refrigerator 对象作为参数,并在方法内部调用冰箱对象的打开和关闭门的方法,以及执行把大象放进冰箱的动作。

同样的,将语言换成Java也是基本相通的。

// 定义一个名为 Refrigerator 的类
class Refrigerator {
    // 定义一个方法来打开冰箱门
    void openDoor() {
        System.out.println("打开冰箱门");
    }

    // 定义一个方法来关闭冰箱门
    void closeDoor() {
        System.out.println("关闭冰箱门");
    }
}

// 定义一个名为 Rabbit 的类
class Rabbit {
    // 定义一个方法来把兔子放进冰箱
    void putInto(Refrigerator refrigerator) {
        refrigerator.openDoor();
        System.out.println("把兔子放进冰箱");
        refrigerator.closeDoor();
    }
}

public class Main {
    public static void main(String[] args) {
        // 创建一个冰箱对象
        Refrigerator myRefrigerator = new Refrigerator();

        // 创建一个兔子对象
        Rabbit myRabbit = new Rabbit();

        // 调用兔子对象的方法,把兔子放进冰箱
        myRabbit.putInto(myRefrigerator);
    }
}

在这个Java伪代码示例中,我们定义了两个类:Refrigerator 和 Rabbit。Refrigerator 类有两个方法:openDoor() 和 closeDoor(),分别表示打开和关闭冰箱门。Rabbit 类有一个方法 putInto(),它接受一个 Refrigerator 对象作为参数,并在方法内部调用冰箱对象的打开和关闭门的方法,以及执行把兔子放进冰箱的动作。

在 Main 类中,我们创建了这两个类的实例,并调用了 Rabbit 对象的 putInto() 方法来完成任务。这个例子展示了面向对象编程中的封装和对象间交互的概念。在实际的Java代码中,我们可以创建这些类的实例,并调用它们的方法来完成特定的任务

面向对象编程的主要特点

封装、继承、类与对象、实例化、属性、方法

封装:在面向对象的系统中,数据和行为被封装在一起,形成了对象。这意味着对象可以隐藏其内部状态和实现细节,只公开必要的接口供外部交互。

继承:在面向对象的系统中,可以通过继承机制来创建新的对象类型(称为子类),这些子类继承了父类的属性和方法,同时还可以添加新的属性和方法。

多态:在面向对象的系统中,不同的对象可以响应相同的消息,但以不同的方式。这就是所谓的多态,它允许同一个接口用于不同的实现。

让我们通过一个PHP代码示例来展示封装、继承、类与对象、实例化、属性、方法等概念。我们将创建一个简单的图书管理系统,其中包含不同类型的书籍。

// 定义一个基类 Book
abstract class Book {
    protected $title;
    protected $author;

    public function __construct($title, $author) {
        $this->title = $title;
        $this->author = $author;
    }

    abstract public function getDetails();
}

// 定义子类 Novel,继承自 Book
class Novel extends Book {
    private $genre;

    public function __construct($title, $author, $genre) {
        parent::__construct($title, $author);
        $this->genre = $genre;
    }

    public function getDetails() {
        return "Title: {$this->title}, Author: {$this->author}, Genre: {$this->genre}";
    }
}

// 定义子类 ReferenceBook,继承自 Book
class ReferenceBook extends Book {
    private $subject;

    public function __construct($title, $author, $subject) {
        parent::__construct($title, $author);
        $this->subject = $subject;
    }

    public function getDetails() {
        return "Title: {$this->title}, Author: {$this->author}, Subject: {$this->subject}";
    }
}

// 实例化对象
$novel = new Novel("Pride and Prejudice", "Jane Austen", "Romance");
$referenceBook = new ReferenceBook("The Elements of Style", "William Strunk Jr. and E.B. White", "Writing");

// 调用方法获取书籍详情
echo $novel->getDetails(); // 输出:Title: Pride and Prejudice, Author: Jane Austen, Genre: Romance
echo $referenceBook->getDetails(); // 输出:Title: The Elements of Style, Author: William Strunk Jr. and E.B. White, Subject: Writing

在这个示例中:

  • Book 是一个抽象基类,它定义了一个构造函数和抽象方法 getDetails()。
    Novel 和 ReferenceBook 是继承自 Book 的子类,它们各自实现了 getDetails() 方法,并且添加了自己特有的属性。
  • 封装体现在每个类都有自己的私有属性,只能通过公共方法访问。
  • 继承允许子类共享父类的属性和方法,同时还可以添加自己特有的属性和方法。
  • 类与对象的关系体现在我们使用类来创建对象,即实例化。
  • 实例化是通过 new 关键字创建类的实例。
  • 属性是类的数据,如 t i t l e 、 title、 titleauthor、$genre 和 $subject。
  • 方法是类的行为,如 getDetails()。

简单例子:
在这里插入图片描述

类的定义

在面向对象编程(OOP)中,类是一个蓝图或模板,用于创建对象(实例)。类定义了一组具有相似特征(属性)和行为(方法)的对象的共同特征。类是对象的抽象表示,它封装了数据(属性)和操作数据的代码(方法)。

  • 属性(成员变量):该事物的状态信息(比如电脑的配置、材料等)
  • 行为(成员方法):该事物能够做什么(比如用电脑打游戏、看电影等)

类的定义通常包括以下几个部分:

  1. 访问修饰符:定义了类成员(属性和方法)的可访问性。常见的访问修饰符有 public(公共的,可以在任何地方访问)、private(私有的,只能在类内部访问)和 protected(受保护的,只能在类及其子类中访问)。

  2. 属性:也称为成员变量或字段,是类的数据成员,用于存储对象的状态。属性可以是基本数据类型,也可以是其他类的对象。

  3. 方法:也称为成员函数,是类的行为成员,用于定义对象可以执行的操作。方法是用来操作属性或与其他对象交互的代码块。

  4. 构造函数:是一种特殊的方法,用于初始化新创建的对象的状态。当使用 new 关键字创建对象时,构造函数会被自动调用。

  5. 析构函数:是一种特殊的方法,用于清理资源或执行其他清理操作。在对象不再需要时(通常是在对象被销毁前),析构函数会被自动调用。

  6. 静态成员:包括静态属性和静态方法,它们属于类本身而不是类的实例。可以通过类名直接访问静态成员,无需创建类的实例。

  7. 常量:是类中定义的不可变的值。常量一旦定义,其值就不能被修改。

下面结合一段php代码来看看:

class Car {
    // 属性
    public $brand;
    private $color;
    protected $speed;

    // 构造函数
    public function __construct($brand, $color) {
        $this->brand = $brand;
        $this->color = $color;
        $this->speed = 0;
    }

    // 方法
    public function accelerate($amount) {
        $this->speed += $amount;
    }

    public function brake() {
        $this->speed = 0;
    }

    // 静态方法
    public static function honk() {
        return "Honk!";
    }
}

在上面的示例中,Car 类定义了汽车的属性(品牌、颜色和速度)和方法(加速和刹车)。构造函数 __construct 用于初始化新创建的汽车对象。静态方法 honk 可以不依赖于任何特定汽车对象而被调用。

创建对象

对象指的是根据类定义创建的实例。类是一个蓝图,定义了对象的属性(数据)和方法(行为)。当你使用new关键字创建一个类的实例时,你实际上是在创建一个按照类定义构建的对象。

在这里插入图片描述
要创建一个类的实例,你使用new关键字后跟类名。例如,如果你有一个名为ikun的类,你可以这样创建它的实例:

//类实例化成为对象
$ikun = new Girl('美女1号',18);
//调用方法
$ikun->love();

在PHP中,类的定义使用class关键字开始,后面跟着类名和一对花括号{}。在花括号内部,你可以定义属性(使用var关键字,或者从PHP 5开始使用public, private, protected等访问修饰符)和方法(函数)。

php 序列化:对象转为字符序列

序列化是将对象或数组转换为可存储或传输的字符串格式的过程。在PHP中,序列化通常使用serialize()函数来完成。序列化后的字符串包含了原始数据的完整结构和内容,即使是私有属性也会被序列化,但它们会被标记为私有。

序列化对象的步骤
  1. 定义类:首先,你需要定义一个类,该类将包含你想要序列化的对象的属性和方法。
  2. 创建对象实例:然后,你可以创建这个类的一个实例。
  3. 序列化对象:最后,使用serialize()函数将对象实例转换为字符串。

举个简单的例子
假设你有一个名为User的类,它有两个私有属性name和age:

class User {
    private $name;
    private $age;

    public function __construct($name, $age) {
        $this->name = $name;
        $this->age = $age;
    }
}

$user = new User("Alice", 25);
$serializedUser = serialize($user);

在上述代码中,$serializedUser将包含一个序列化后的字符串,该字符串代表了User对象的所有信息

下面请看稍微难一点点的举例代码:

<?php
// 定义类
class Girl {
    // 声明属性
    public $name;
    public $age;

    // 声明方法
    public function __construct($name, $age) {
        // 类型检查和错误检查
        if (!is_string($name)) {
            throw new InvalidArgumentException('Name must be a string.');
        }
        if (!is_int($age) || $age < 0 || $age > 120) {
            throw new InvalidArgumentException('Age must be an integer between 0 and 120.');
        }
        
        $this->name = $name;
        $this->age = $age;
    }

    public function hello() {
        echo "Hello, my boy! \n";
        echo "My name is {$this->name}, my age is {$this->age}!";
    }
}

try {
    // 类实例化成为对象
    $ryan = new Girl('小美', 18);
    
    // 序列化对象
    $str = serialize($ryan);
    
    // 输出序列化后的字符串
    echo "Serialized object:\n{$str}\n";
    
    // 反序列化字符串
    $unserializedRyan = unserialize($str);
    
    // 验证反序列化的对象是否与原对象一致
    if ($unserializedRyan instanceof Girl && $unserializedRyan === $ryan) {
        echo "Unserialized object is identical to original object.\n";
    } else {
        echo "Unserialized object is different from original object.\n";
    }
    
    // 调用对象的方法
    $unserializedRyan->hello();
} catch (InvalidArgumentException $e) {
    echo "Error: {$e->getMessage()}\n";
}
?>

在这个示例中,我们首先定义了一个名为Girl的类,它有两个公共属性name和age,以及一个构造函数和一个hello方法。接着,我们实例化了Girl类的一个对象$ryan,并将其实例化为’小美’和18,然后使用serialize()函数将该对象序列化为一个字符串,并将其输出。

这段代码的输出结果为:

Serialized object:
O:4:"Girl":2:{s:4:"name";s:6:"小美";s:3:"age";i:18;}
Unserialized object is identical to original object.
Hello, my boy! 
My name is 小美, my age is 18!

解释如下:

  1. Serialized object::这是代码中输出的提示信息,表明接下来将显示序列化后的对象字符串。

  2. O:4:“Girl”:2:{s:4:“name”;s:6:“小美”;s:3:“age”;i:18;}:这是序列化后的对象字符串。

    • O:4:"Girl"表示这是一个对象,对象名称为Girl,长度为4个字符。
    • 2:表示该对象有两个属性。
    • 接下来的部分分别是两个属性的序列化表示:
      • s:4:“name”;s:6:“小美”;表示name属性是一个长度为4的字符串,内容为小美;
      • s:3:“age”;i:18;表示age属性是一个整数,值为18。
  3. Unserialized object is identical to original object.:这行输出表明反序列化后的对象与原始对象是完全相同的。代码中使用了===运算符来比较两个对象是否完全相同(即类型和值都相等)。

  4. Hello, my boy!:这是调用$unserializedRyan对象的hello方法后输出的第一行文本。

  5. My name is 小美, my age is 18!:这是调用$unserializedRyan对象的hello方法后输出的第二行文本,显示了对象的name和age属性值。

整个输出结果展示了从对象序列化到字符串,再到反序列化回对象,并验证了对象的一致性和功能的完整性。

来看这个结果:

O:4:“Girl”:2:{s:4:“name”;s:6:“小美”;s:3:“age”;i:18;}

序列化后的字符串通常遵循以下格式:

O::表示对象开始。
strlen(class name):class name::表示类的名称和长度。
s:strlen(property name):property name::表示属性的名称和长度。
i:d: b: N;:分别表示整数、双精度浮点数、布尔值或NULL。
a::表示数组开始,后面跟着数组的大小和每个元素的序列化表示。

php反序列化:字符序列还原为对象

PHP的反序列化(unserialize)是将之前使用serialize函数序列化的字符串还原为PHP的对象、数组、值等数据结构的过程。序列化是将变量转换为字符串,以便存储或传输,而反序列化则是将这个字符串转换回原始的变量形式。

<?php
// 创建一个数组
$array = array('name' => 'John', 'age' => 30, 'city' => 'New York');
// 序列化数组
$serialized = serialize($array);
echo "Serialized: " . $serialized . "<br>";
// 反序列化字符串
$array_back = unserialize($serialized);
print_r($array_back);
?>

在这个例子中,serialize函数将数组转换为一个字符串,然后unserialize函数将这个字符串还原为原来的数组。输出如下:

Serialized: a:3:{s:4:"name";s:4:"John";s:3:"age";i:30;s:4:"city";s:8:"New York";}
Array
(
[name] => John
[age] => 30
[city] => New York
)

再看一个例子:

<?php
// 定义类
class Girl {
	// 声明属性
	public $name = '小红';
	public $age = 18;
	// 声明构造函数
	public function __construct($name, $age) {
		$this->setName($name);
		$this->setAge($age);
	}
	// 设置名字的方法
	public function setName($name) {
		$this->name = $name;
	}
	// 设置年龄的方法
	public function setAge($age) {
		$this->age = $age;
	}
	// 声明打招呼的方法
	public function sayHello() {
		echo "Hello, my boy!\n";
		echo "My name is " . $this->name . ", my age is " . $this->age . "!";
	}
}
// 序列化字符串
$str = 'O:4:"Girl":2:{s:4:"name";s:6:"小美";s:3:"age";s:2:"18";';
// 反序列化,还原为Girl对象
$object = unserialize($str);
// 对象调用方法
$object->sayHello();
?>
//输出结果为:
Hello, my boy!
My name is 小美, my age is 18!

解释:

代码首先定义了一个名为Girl的类,其中包含一个构造函数、两个设置属性的方法和一个sayHello方法。然后,它创建了一个序列化字符串$str,该字符串表示一个Girl对象,其中name属性为"小美",age属性为18。接着,使用unserialize函数将这个字符串反序列化为Girl对象,并调用sayHello方法输出问候语。

案例

<?php
// 设置页面的字符编码为UTF-8
header("Content-Type: text/html; charset=utf-8");

// 显示当前脚本的源代码
show_source(__FILE__);

// 关闭错误报告,以便不在页面上显示错误信息
error_reporting(0);

/**
 * Girl类代表一个女孩
 */
class Girl {
    // 公共属性:姓名和年龄
    public $name;
    public $age;

    /**
     * 构造函数,初始化姓名和年龄
     * @param string $name 姓名
     * @param int $age 年龄
     */
    public function __construct($name, $age) {
        $this->name = $name;
        $this->age = $age;
    }

    /**
     * 打招呼的方法
     */
    public function hello() {
        echo "Hello, my boy! \n";
        echo "My name is {$this->name}, and I am {$this->age} years old!";
    }
}

// 检查是否有名为'str'的GET参数
if (isset($_GET['str'])) {
    // 获取GET参数'str'的值
    $str = $_GET['str'];

    // 尝试反序列化字符串为Girl对象
    $object = unserialize($str);

    // 如果反序列化成功,调用对象的hello方法
    if ($object instanceof Girl) {
        $object->hello();
    } else {
        echo "Invalid serialized data.";
    }
}
?>
  1. 这段PHP代码首先设置了页面的字符编码为UTF-8,然后展示了当前脚本的源代码。

  2. 代码中定义了一个名为Girl的类,该类有公共属性姓名和年龄,以及一个构造函数用于初始化这两个属性。

  3. 此外,Girl类还有一个hello方法,用于打招呼。

  4. 代码检查是否有名为’str’的GET参数,如果存在,会尝试将该参数的值反序列化为Girl对象。

  5. 如果反序列化成功,就调用对象的hello方法;否则,显示“Invalid serialized data.”。

利用==》xss

O:4:"Girl":2:{s:4:"name";s:29:"<script>alert('xss')</script>";s:3:"age";i:18;}

解释:

  • O:4:“Girl”:O表示这是一个对象,4是类名的长度,"Girl"是类名。

  • :2:冒号后面跟的是属性列表的长度,2表示有两个属性。

  • {:开始属性列表。

  • s:29:“"是字符串的值。这里,字符串被赋值给name属性。

  • ;:结束一个属性的定义。

  • s:3:“age”:另一个属性age,3表示字符串长度,"age"是属性名。

  • :i:18;:冒号后跟的是属性值,i表示整数,18是整数值。这里,整数18被赋值给age属性。

  • }:结束属性列表。

  • ;:结束整个对象的序列化表示。

当将这个序列化字符串传递给unserialize函数时,PHP会尝试根据这个描述创建一个Girl类的实例,并将name属性设置为,age属性设置为18。

访问控制符

在PHP中,访问控制符用于定义类的属性和方法的访问级别。主要有以下四种:

  1. public:

    • 公共(public)成员可以在类的任何地方以及类的外部被访问。
  2. private:

    • 私有(private)成员只能在定义它们的类的内部被访问。外部代码(包括子类)无法直接访问私有属性或方法。
  3. protected:

    • 保护(protected)成员可以在定义它们的类以及该类的子类中被访问。外部代码不能直接访问。
class Car {
	// 公共属性,任何地方都可以访问
	public $color = "blue";
	// 私有属性,只能在Car类内部访问
	private $brand = "Toyota";
	// 保护属性,可以在Car类和其子类中访问
	protected $year = 2020;
	// 公共方法,任何地方都可以调用
	public function drive() {
		echo "Driving the " . $this->brand . " car.";
	}
	// 私有方法,只能在Car类内部调用
	private function startEngine() {
		echo "Engine started.";
	}
	// 保护方法,可以在Car类和其子类中调用
	protected function checkOil() {
		echo "Checking oil level.";
	}
}
// 创建Car类的实例
$myCar = new Car();
// 公共属性和方法可以自由访问
echo $myCar->color;
// 输出: blue
$myCar->drive();
// 输出: Driving the Toyota car.
// 私有属性和方法不能直接访问
// echo $myCar->brand; // 错误,不能直接访问私有属性
// $myCar->startEngine(); // 错误,不能直接调用私有方法
// 保护属性和方法在子类中可以访问
class SportsCar extends Car {
	public function race() {
		$this->checkOil();
		// 在子类中可以调用保护方法
		echo "Starting the race!";
	}
}
$sportsCar = new SportsCar();
$sportsCar->race();
// 输出: Checking oil level. Starting the race!

在这个例子中:

  • public属性和方法(如colordrive)可以在任何地方被访问和调用。
  • private属性和方法(如brandstartEngine)只能在Car类内部使用,外部代码无法访问。
  • protected属性和方法(如yearcheckOil)可以在Car类及其子类中访问,但不能在类的外部访问。

反序列化

<?php

/**
 * Class Girl
 * 描述一个女孩的基本信息,包括姓名、年龄和金钱。
 */
class Girl {
    // 公共属性:姓名
    public $name;
    
    // 受保护属性:年龄
    protected $age;
    
    // 私有属性:金钱
    private $money;

    /**
     * Girl constructor.
     * 构造函数,用于初始化女孩的姓名、年龄和金钱。
     *
     * @param string $name 姓名
     * @param int $age 年龄
     * @param float $money 金钱
     */
    public function __construct($name, $age, $money) {
        $this->name = $name;
        $this->age = $age;
        $this->money = $money;
    }

    /**
     * 打招呼的方法,显示女孩的姓名、年龄和金钱。
     */
    public function hello() {
        echo "你好!我的名字是 {$this->name},我今年 {$this->age} 岁。";
        echo "我现在有 {$this->money} 元人民币。";
    }
}

// 定义一个序列化的字符串,表示一个Girl对象
$serializedString = 'O:4:"Girl":3:{s:4:"name";s:4:"ikun";S:6:"\00*\00age";i:99;S:11:"\00Girl\00money";d:1000.5;}';

// 反序列化字符串为Girl对象
$girlObject = unserialize($serializedString);

// 调用hello方法显示信息
$girlObject->hello();

?>

这段PHP代码定义了一个名为Girl的类,该类代表了关于一个女孩的信息,包括她的姓名、年龄和金钱。下面是对代码的逐部分解释:

类定义

class Girl {
    // 公共属性:姓名
    public $name;
    
    // 受保护属性:年龄
    protected $age;
    
    // 私有属性:金钱
    private $money;

    // ...
}

这里定义了一个Girl类,它包含三个属性:

  • public $name;:公共属性,意味着可以在类的内部和外部访问。
  • protected $age;:受保护属性,只能在类的内部和子类中访问。
  • private $money;:私有属性,只能在类的内部访问。

构造函数

public function __construct($name, $age, $money) {
    $this->name = $name;
    $this->age = $age;
    $this->money = $money;
}

构造函数是一种特殊的方法,它在创建类的实例时自动调用。这里的构造函数接收三个参数:姓名、年龄和金钱,并将它们分别赋值给对应的属性。

hello方法

public function hello() {
    echo "你好!我的名字是 {$this->name},我今年 {$this->age} 岁。";
    echo "我现在有 {$this->money} 元人民币。";
}

hello方法是一个公共方法,它可以被类的实例调用。这个方法通过echo语句输出一个问候语,其中包括对象的姓名、年龄和金钱。

序列化字符

$serializedString = 'O:4:"Girl":3:{s:4:"name";s:4:"ikun";S:6:"\00*\00age";i:99;S:11:"\00Girl\00money";d:1000.5;}';

这是一个序列化的字符串,它代表了Girl类的一个特定实例。序列化是将对象转换为可以存储或传输的字符串形式的过程。在这个字符串中,我们可以看到对象的类型、属性的数量以及每个属性的名称和值。

反序列化

$girlObject = unserialize($serializedString);

这行代码使用unserialize函数将序列化的字符串转换回Girl类的实例。这样,我们就得到了一个Girl对象的实例,其中包含了序列化字符串中指定的属性值。

调用hello方法

$girlObject->hello();

最后,我们调用hello方法来显示刚刚反序列化得到的Girl对象的信息。

执行结果

你好!我的名字是 ikun,我今年 99 岁。
我现在有 1000.5 元人民币。

这表明反序列化过程成功地恢复了对象的状态,并且hello方法正确地输出了对象的属性值。

\00
在PHP的序列化(serialization)格式中,\00(两个字符:反斜杠后面跟着一个零)代表ASCII字符NUL(Null字符),它的十六进制值是00。这个字符在序列化格式中用于表示字符串的结束。

在上述案例中,序列化的字符串包含了\00,它们用于在序列化格式中标识属性名的边界。具体来说,当你有一个属性是受保护的(protected)或私有的(private)时,序列化格式会在属性名前面加上一个特殊的标记:

  1. 对于受保护属性,属性名前面会加上*\00(星号后面跟着NUL字符),以指示这是一个受保护的属性。
  2. 对于私有属性,属性名前面会加上类名\00(类名的长度加上NUL字符),以指示这是一个私有属性,并且属于哪个类。

\00出现在属性名前面,用来区分不同访问级别的属性:

  1. S:6:“\00*\00age”;:这里的\00*\00表示age是一个受保护的属性。
  2. S:11:“\00Girl\00money”;:这里的\00Girl\00表示money是一个私有属性,并且它是Girl类的一部分。

魔术方法

魔术方法是PHP中一种特殊的方法,当对对象执行某些操作时会覆盖PHP的默认行为。这些方法通常以双下划线__开头。

PHP中的魔术方法确实有点类似于JavaScript中的生命周期事件。魔术方法是PHP提供的一组特殊方法,它们在特定的时机自动被调用,以执行一些预定义的动作。这些方法通常用于处理对象的创建、销毁、属性访问、方法调用等方面的行为。

例如,__construct() 方法在创建对象时被调用,类似于JavaScript中的构造函数;__destruct() 方法在对象被销毁时触发,类似于JavaScript中的析构函数;__call() 和 __callStatic() 方法在尝试调用不可访问的方法时被调用,类似于JavaScript中的未捕获异常处理;__get() 和 __set() 方法分别在尝试获取和设置不可访问的属性时被调用,类似于JavaScript中的getter和setter。

注意注意,以下的魔术方法十分重要!
__construct():对象的构造函数,用于初始化对象的属性。
__destruct():对象的析构函数,用于在对象销毁前进行清理工作。
__call()__callStatic():当尝试调用不可访问的方法时,这些方法会被调用。
__get()__set():当尝试获取或设置不可访问的属性时,这些方法会被调用。
__isset() __unset():当尝试检查或取消设置不可访问的属性的存在性时,这些方法会被调用。
__sleep() __wakeup():在序列化和反序列化过程中,这些方法可以用于保存和恢复对象的状态。
__serialize() 和 __unserialize():在序列化和反序列化过程中,这些方法可以用于自定义序列化和反序列化逻辑。
__toString():当对象被当作字符串处理时,这些方法会被调用。
__invoke():当尝试像函数一样调用对象时,这些方法会被调用。
__set_state():当使用var_export()函数导出对象状态时,这些方法会被调用。
__debugInfo():当使用var_dump()函数转储对象信息时,这些方法会被调用。

案例1

class MagicObject {
	private $data = [];
	// __construct() - 构造函数
	public function __construct($initialData = []) {
		$this->data = $initialData;
		echo "Object created with initial data: " . print_r($initialData, true);
	}
	// __destruct() - 析构函数
	public function __destruct() {
		echo "Object destroyed.";
	}
	// __get() 和 __set() - 访问不存在的属性
	public function __get($key) {
		if (array_key_exists($key, $this->data)) {
			return $this->data[$key];
		} else {
			echo "The key '$key' does not exist.";
		}
	}
	public function __set($key, $value) {
		$this->data[$key] = $value;
		echo "Value for '$key' set to '$value'.";
	}
	// __call() 和 __callStatic() - 调用不存在的方法
	public function __call($method, $args) {
		echo "Method '$method' does not exist, but I'll handle it.";
	}
	public static function __callStatic($method, $args) {
		echo "Static method '$method' does not exist, but I'll handle it statically.";
	}
	// __isset() 和 __unset() - 检查不存在的属性和删除它
	public function __isset($key) {
		return isset($this->data[$key]);
	}
	public function __unset($key) {
		if (isset($this->data[$key])) {
			unset($this->data[$key]);
			echo "Value for '$key' removed.";
		} else {
			echo "The key '$key' does not exist.";
		}
	}
	// __toString() - 转换为字符串
	public function __toString() {
		return "MagicObject with data: " . print_r($this->data, true);
	}
	// __invoke() - 当对象像函数一样调用时
	public function __invoke($key, $value) {
		$this->__set($key, $value);
		echo "Invoked with key '$key' and value '$value'.";
	}
}
// 创建对象并初始化数据
$magic = new MagicObject(['name' => 'Alice', 'age' => 30]);
// 使用__get()和__set()
echo $magic->name;
// 输出: Alice
$magic->address = '123 Main St';
// 输出: Value for 'address' set to '123 Main St'.
// 使用__call()调用不存在的方法
$magic->greet('Hello');
// 输出: Method 'greet' does not exist, but I'll handle it.
// 使用__callStatic()调用不存在的静态方法
MagicObject::greetStatic('Hi');
// 输出: Static method 'greetStatic' does not exist, but I'll handle it statically.
// 使用__isset()和__unset()
var_dump(isset($magic->city));
// 输出: bool(false)
$magic->city = 'New York';
var_dump(isset($magic->city));
// 输出: bool(true)
unset($magic->city);
// 输出: Value for 'city' removed.
// 使用__toString()
echo $magic;
// 输出: MagicObject with data: Array ( [name] => Alice [age] => 30 )
// 使用__invoke()
$magic('job', 'Engineer');
// 输出: Invoked with key 'job' and value 'Engineer'.

讲解:

  1. 当创建MagicObject实例时,__construct()会被调用,输出:

    
    Object created with initial data: Array
    (
        [name] => Alice
        [age] => 30
    )
    
    
  2. 访问name属性时,__get()会被调用,输出:

    Alice
    
  3. 设置address属性时,__set()会被调用,输出:

    Value for 'address' set to '123 Main St'.
    
  4. 调用不存在的greet()方法时,__call()会被调用,输出:

    Method 'greet' does not exist, but I'll handle it.
    
  5. 调用不存在的静态greetStatic()方法时,__callStatic()会被调用,输出:

    Static method 'greetStatic' does not exist, but I'll handle it statically.
    
  6. 检查不存在的city属性时,__isset()会被调用,输出:

    bool(false)
    
  7. 设置city属性为New York,然后再次检查,输出:

    bool(true)
    
  8. 删除city属性时,__unset()会被调用,输出:

    Value for 'city' removed.
    
  9. 转换MagicObject为字符串时,__toString()会被调用,输出:

    MagicObject with data: Array
    (
        [name] => Alice
        [age] => 30
        [address] => 123 Main St
        [job] => Engineer
    )
    
  10. MagicObject实例作为函数调用时,__invoke()会被调用,输出:

    Invoked with key 'job' and value 'Engineer'.
    

案例2

<?php

// 设置页面编码为UTF-8
header("Content-Type: text/html; charset=utf-8");

/**
 * Class Girl
 * 定义一个名为Girl的类,该类包含构造函数、序列化魔术方法和反序列化魔术方法。
 */
class Girl {
    // 公共属性,表示女孩的名字
    public $name;

    /**
     * Girl constructor.
     * 构造函数,当创建新对象时被调用。
     * @param string $name 女孩的名字
     */
    public function __construct($name) {
        // 将传入的名字赋值给对象的name属性
        $this->name = $name;
        // 输出构造函数调用的信息
        echo "__construct: 创建对象时,create object:{$this->name}\n";
    }

    /**
     * __sleep magic method
     * 序列化时调用的魔术方法,返回一个数组,指定要序列化的属性。
     * @return array 包含要序列化的属性名的数组
     */
    public function __sleep() {
        // 输出序列化时调用的信息
        echo "__sleep: 序列化时调用\n";
        // 只序列化name属性
        return ['name'];
    }

    /**
     * __wakeup magic method
     * 反序列化时调用的魔术方法。
     */
    public function __wakeup() {
        // 输出反序列化时调用的信息
        echo "__wakeup: 反序列化时调用\n";
    }
}

// 创建一个新的Girl对象,名为Ikun
$ikun = new Girl("Ikun");

// 序列化对象$ikun
$r = serialize($ikun);

// 输出序列化后的字符串
echo $r . "\n";

// 定义一个序列化字符串
$serializedStr = 'O:4:"Girl":1:{s:4:"name";s:4:"Ikun";}';

// 输出换行符,增加输出内容的可读性
echo "\n";

// 反序列化字符串,创建新的Girl对象
$newIkun = unserialize($serializedStr);

?>

讲解
头部信息将不再解释
类定义

/**
 * Class Girl
 * 定义一个名为Girl的类,该类包含构造函数、序列化魔术方法和反序列化魔术方法。
 */
class Girl {
    // 公共属性,表示女孩的名字
    public $name;

    /**
     * Girl constructor.
     * 构造函数,当创建新对象时被调用。
     * @param string $name 女孩的名字
     */
    public function __construct($name) {
        // 将传入的名字赋值给对象的name属性
        $this->name = $name;
        // 输出构造函数调用的信息
        echo "__construct: 创建对象时,create object:{$this->name}\n";
    }

    /**
     * __sleep magic method
     * 序列化时调用的魔术方法,返回一个数组,指定要序列化的属性。
     * @return array 包含要序列化的属性名的数组
     */
    public function __sleep() {
        // 输出序列化时调用的信息
        echo "__sleep: 序列化时调用\n";
        // 只序列化name属性
        return ['name'];
    }

    /**
     * __wakeup magic method
     * 反序列化时调用的魔术方法。
     */
    public function __wakeup() {
        // 输出反序列化时调用的信息
        echo "__wakeup: 反序列化时调用\n";
    }
}

这里定义了一个名为Girl的类,它有三个关键的魔术方法:

  1. __construct(name):构造函数,当创建新对象时自动调用。它接收一个参数name,并将其赋值给类的公共属性$name。然后,它会输出一条消息,指示对象已经被创建,并显示对象的名字。
  2. __sleep():序列化时调用的魔术方法。当对象被序列化时,这个方法会被自动调用。它输出一条消息,并返回一个数组,指定了哪些属性需要被序列化。在这个例子中,只有name属性被序列化。
  3. __wakeup():反序列化时调用的魔术方法。当对象被反序列化时,这个方法会被自动调用。它输出一条消息,但没有执行其他操作。

对象创建与操作

// 创建一个新的Girl对象,名为Ikun
$ikun = new Girl("Ikun");

// 序列化对象$ikun
$r = serialize($ikun);

// 输出序列化后的字符串
echo $r . "\n";

// 定义一个序列化字符串
$serializedStr = 'O:4:"Girl":1:{s:4:"name";s:4:"Ikun";}';

// 输出换行符,增加输出内容的可读性
echo "\n";

// 反序列化字符串,创建新的Girl对象
$newIkun = unserialize($serializedStr);

这部分代码执行了以下操作:

  1. 创建了一个名为IkunGirl对象,并存储在变量ikun中。
  2. 序列化ikun对象,并将序列化后的字符串存储在变量r中。
  3. 输出序列化后的字符串。
  4. 定义了一个已经序列化的字符串serializedStr,它代表了另一个Girl对象。
  5. 输出一个换行符,以便在控制台或终端中更好地分隔输出。
  6. 通过unserialize()函数反序列化字符串serializedStr,创建了一个新的Girl对象,并存储在变量newIkun中。这个操作不会输出任何东西,因为没有对这个新对象执行任何操作。

输出结果

__construct: 创建对象时,create object:Ikun
__sleep: 序列化时调用
O:4:"Girl":1:{s:4:"name";s:4:"Ikun";}

__wakeup: 反序列化时调用

二次讲解:

  1. __construct: 创建对象时,create object:Ikun:这是在创建Girl对象$ikun时,构造函数__construct被调用并输出的信息。

  2. __sleep: 序列化时调用:当对象$ikun被序列化时,魔术方法__sleep被调用并输出的信息。

  3. O:4:"Girl":1:{s:4:"name";s:4:"Ikun";}:这是序列化后的对象字符串,它包含了类的名称和序列化的属性name的值。

  4. __wakeup: 反序列化时调用:当通过unserialize函数反序列化字符串serializedStr创建新的Girl对象newIkun时,魔术方法__wakeup被调用并输出的信息。

  5. 注意,虽然newIkun对象是通过反序列化创建的,但是代码中并没有直接输出$newIkun的内容或状态,因此没有额外的输出信息。

案例3

<?php

// 设置页面的内容类型和字符编码为 UTF-8
header("content-type:text/html;charset=utf-8");

// 高亮显示当前文件的代码
highlight_file(__FILE__);

/**
 * Class Girl
 * 定义一个名为 Girl 的类,该类包含一个公共属性 name 和一个公共属性 file。
 * 当对象被反序列化时,会调用 __wakeup 魔术方法,该方法会将 name 属性的值写入到 file 属性指定的文件中。
 */
class Girl {
    public $name;      // 公共属性,表示女孩的名字
    public $file;      // 公共属性,表示要写入的文件名

    /**
     * __wakeup magic method
     * 反序列化时调用的魔术方法。
     * 该方法会在对象被反序列化后自动执行,将 name 属性的值写入到 file 属性指定的文件中。
     */
    function __wakeup() {
        // 打开文件,如果不存在则创建,如果存在则覆盖原有内容
        $fileHandle = fopen($this->file, 'w');
        // 将 name 属性的值写入文件
        fwrite($fileHandle, $this->name);
        // 关闭文件句柄
        fclose($fileHandle);
    }
}

// 输出标题
echo "<h1>反序列化漏洞案例</h1>";

// 检查是否通过 GET 请求传递了名为 str 的参数
if(isset($_GET["str"])) {
    // 获取 GET 参数 str 的值
    $inputString = $_GET['str'];
    // 反序列化输入的字符串
    $object = unserialize($inputString);
    // 输出对象的 name 属性值
    echo "name: " . $object->name;
}

?>

讲解
这段PHP代码主要用于演示反序列化漏洞的一个简单案例。
头部设置

header("content-type:text/html;charset=utf-8");

代码高亮

highlight_file(__FILE__);

定义类 Girl

class Girl {
    public $name = "小明";
    public $file = "a.txt";

    function __wakeup(){
        $file = fopen($this->file, 'w');
        fwrite($file, $this->name);
        fclose($file);
    }
}

这里定义了一个名为Girl的类,它有两个公共属性:namefilename默认值为"小明",而file默认值为"a.txt"__wakeup()是一个魔术方法,它在对象被反序列化时自动调用。在这个方法中,它会打开file属性指定的文件,并将name属性值写入该文件,然后关闭文件。

输出标题

echo "<h1>反序列化漏洞案例</h1>";

处理GET请求参数

if(isset($_GET["str"])){
    $str = $_GET['str'];
    $o = unserialize($str);
    echo "name:".$o->name;
}

这部分代码检查是否有名为strGET请求参数。如果有,它将该参数值反序列化为一个Girl对象,并输出该对象的$name属性值。

漏洞利用

payload:

O:4:"Girl":2:{s:4:"name";s:18:"<?php phpinfo();?>";s:4:"file";s:5:"a.php";}

讲解:

  1. O:4:"Girl" 表示一个对象(Object),其类名是 "Girl"(长度为4个字符)。
  2. 2: 表示该对象有两个属性。
  3. s:4:"name"; 表示一个属性名为 "name"(长度为4个字符)。
  4. s:18:"<?php phpinfo();?>" 表示 "name" 属性的值是一个字符串,内容是 “”(长度为18个字符)。这是一个简单的PHP代码片段,用于输出当前PHP环境的配置信息。
  5. s:4:"file"; 表示另一个属性名为 "file"(长度为4个字符)。
  6. s:5:"a.php"; 表示"file"属性的值是一个字符串,内容是 "a.php"(长度为5个字符)。
  7. 当这段序列化字符串被传递给unserialize()函数时,它会创建一个Girl类的实例,其中name属性的值被设置为包含恶意代码的字符串,而$file属性的值被设置为 "a.php"
  8. 如果这段代码被执行,那么__wakeup()魔术方法会被调用,导致以下操作发生:

打开 “a.php” 文件(如果不存在则会创建)。
将 “” 写入 “a.php” 文件。
关闭文件。

这样,攻击者就在服务器上成功创建了一个名为 "a.php" 的文件,其中包含了能够执行phpinfo()函数的PHP代码。如果该文件随后被访问,那么服务器会执行这段代码并输出敏感的PHP环境信息,这可能有助于进一步的攻击。

  • 24
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值