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、 title、author、$genre 和 $subject。
- 方法是类的行为,如 getDetails()。
简单例子:
类的定义
在面向对象编程(OOP)中,类是一个蓝图或模板,用于创建对象(实例)。类定义了一组具有相似特征(属性)和行为(方法)的对象的共同特征。类是对象的抽象表示,它封装了数据(属性)和操作数据的代码(方法)。
- 属性(成员变量):该事物的状态信息(比如电脑的配置、材料等)
- 行为(成员方法):该事物能够做什么(比如用电脑打游戏、看电影等)
类的定义通常包括以下几个部分:
-
访问修饰符:定义了类成员(属性和方法)的可访问性。常见的访问修饰符有 public(公共的,可以在任何地方访问)、private(私有的,只能在类内部访问)和 protected(受保护的,只能在类及其子类中访问)。
-
属性:也称为成员变量或字段,是类的数据成员,用于存储对象的状态。属性可以是基本数据类型,也可以是其他类的对象。
-
方法:也称为成员函数,是类的行为成员,用于定义对象可以执行的操作。方法是用来操作属性或与其他对象交互的代码块。
-
构造函数:是一种特殊的方法,用于初始化新创建的对象的状态。当使用 new 关键字创建对象时,构造函数会被自动调用。
-
析构函数:是一种特殊的方法,用于清理资源或执行其他清理操作。在对象不再需要时(通常是在对象被销毁前),析构函数会被自动调用。
-
静态成员:包括静态属性和静态方法,它们属于类本身而不是类的实例。可以通过类名直接访问静态成员,无需创建类的实例。
-
常量:是类中定义的不可变的值。常量一旦定义,其值就不能被修改。
下面结合一段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()函数来完成。序列化后的字符串包含了原始数据的完整结构和内容,即使是私有属性也会被序列化,但它们会被标记为私有。
序列化对象的步骤
- 定义类:首先,你需要定义一个类,该类将包含你想要序列化的对象的属性和方法。
- 创建对象实例:然后,你可以创建这个类的一个实例。
- 序列化对象:最后,使用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!
解释如下:
-
Serialized object::这是代码中输出的提示信息,表明接下来将显示序列化后的对象字符串。
-
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。
-
Unserialized object is identical to original object.:这行输出表明反序列化后的对象与原始对象是完全相同的。代码中使用了===运算符来比较两个对象是否完全相同(即类型和值都相等)。
-
Hello, my boy!:这是调用$unserializedRyan对象的hello方法后输出的第一行文本。
-
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.";
}
}
?>
-
这段PHP代码首先设置了页面的字符编码为UTF-8,然后展示了当前脚本的源代码。
-
代码中定义了一个名为Girl的类,该类有公共属性姓名和年龄,以及一个构造函数用于初始化这两个属性。
-
此外,Girl类还有一个hello方法,用于打招呼。
-
代码检查是否有名为’str’的GET参数,如果存在,会尝试将该参数的值反序列化为Girl对象。
-
如果反序列化成功,就调用对象的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中,访问控制符用于定义类的属性和方法的访问级别。主要有以下四种:
-
public:
- 公共(public)成员可以在类的任何地方以及类的外部被访问。
-
private:
- 私有(private)成员只能在定义它们的类的内部被访问。外部代码(包括子类)无法直接访问私有属性或方法。
-
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
属性和方法(如color
和drive
)可以在任何地方被访问和调用。private
属性和方法(如brand
和startEngine
)只能在Car
类内部使用,外部代码无法访问。protected
属性和方法(如year
和checkOil
)可以在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)时,序列化格式会在属性名前面加上一个特殊的标记:
- 对于受保护属性,属性名前面会加上*\00(星号后面跟着NUL字符),以指示这是一个受保护的属性。
- 对于私有属性,属性名前面会加上类名\00(类名的长度加上NUL字符),以指示这是一个私有属性,并且属于哪个类。
\00出现在属性名前面,用来区分不同访问级别的属性:
- S:6:“\00*\00age”;:这里的\00*\00表示age是一个受保护的属性。
- 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'.
讲解:
-
当创建
MagicObject
实例时,__construct()
会被调用,输出:Object created with initial data: Array ( [name] => Alice [age] => 30 )
-
访问
name
属性时,__get()
会被调用,输出:Alice
-
设置
address
属性时,__set()
会被调用,输出:Value for 'address' set to '123 Main St'.
-
调用不存在的
greet()
方法时,__call()
会被调用,输出:Method 'greet' does not exist, but I'll handle it.
-
调用不存在的静态
greetStatic()
方法时,__callStatic()
会被调用,输出:Static method 'greetStatic' does not exist, but I'll handle it statically.
-
检查不存在的
city
属性时,__isset()
会被调用,输出:bool(false)
-
设置
city
属性为New York
,然后再次检查,输出:bool(true)
-
删除
city
属性时,__unset()
会被调用,输出:Value for 'city' removed.
-
转换
MagicObject
为字符串时,__toString()
会被调用,输出:MagicObject with data: Array ( [name] => Alice [age] => 30 [address] => 123 Main St [job] => Engineer )
-
将
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的类,它有三个关键的魔术方法:
- __construct(name):构造函数,当创建新对象时自动调用。它接收一个参数name,并将其赋值给类的公共属性$name。然后,它会输出一条消息,指示对象已经被创建,并显示对象的名字。
- __sleep():序列化时调用的魔术方法。当对象被序列化时,这个方法会被自动调用。它输出一条消息,并返回一个数组,指定了哪些属性需要被序列化。在这个例子中,只有name属性被序列化。
- __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);
这部分代码执行了以下操作:
- 创建了一个名为
Ikun
的Girl
对象,并存储在变量ikun
中。 - 序列化
ikun
对象,并将序列化后的字符串存储在变量r中。 - 输出序列化后的字符串。
- 定义了一个已经序列化的字符串
serializedStr
,它代表了另一个Girl
对象。 - 输出一个换行符,以便在控制台或终端中更好地分隔输出。
- 通过
unserialize()
函数反序列化字符串serializedStr
,创建了一个新的Girl
对象,并存储在变量newIkun
中。这个操作不会输出任何东西,因为没有对这个新对象执行任何操作。
输出结果
__construct: 创建对象时,create object:Ikun
__sleep: 序列化时调用
O:4:"Girl":1:{s:4:"name";s:4:"Ikun";}
__wakeup: 反序列化时调用
二次讲解:
-
__construct
: 创建对象时,create object:Ikun
:这是在创建Girl
对象$ikun
时,构造函数__construct
被调用并输出的信息。 -
__sleep
: 序列化时调用:当对象$ikun
被序列化时,魔术方法__sleep
被调用并输出的信息。 -
O:4:"Girl":1:{s:4:"name";s:4:"Ikun";}
:这是序列化后的对象字符串,它包含了类的名称和序列化的属性name
的值。 -
__wakeup
: 反序列化时调用:当通过unserialize
函数反序列化字符串serializedStr
创建新的Girl
对象newIkun
时,魔术方法__wakeup
被调用并输出的信息。 -
注意,虽然
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
的类,它有两个公共属性:name
和file
。name
默认值为"小明",而file默认值为"a.txt"
。__wakeup()
是一个魔术方法,它在对象被反序列化时自动调用。在这个方法中,它会打开file
属性指定的文件,并将name
属性值写入该文件,然后关闭文件。
输出标题
echo "<h1>反序列化漏洞案例</h1>";
处理GET请求参数
if(isset($_GET["str"])){
$str = $_GET['str'];
$o = unserialize($str);
echo "name:".$o->name;
}
这部分代码检查是否有名为str
的GET
请求参数。如果有,它将该参数值反序列化为一个Girl
对象,并输出该对象的$name
属性值。
漏洞利用
payload:
O:4:"Girl":2:{s:4:"name";s:18:"<?php phpinfo();?>";s:4:"file";s:5:"a.php";}
讲解:
O:4:"Girl"
表示一个对象(Object),其类名是"Girl"
(长度为4个字符)。2:
表示该对象有两个属性。s:4:"name";
表示一个属性名为"name"
(长度为4个字符)。s:18:"<?php phpinfo();?>"
表示"name"
属性的值是一个字符串,内容是 “”(长度为18个字符)。这是一个简单的PHP代码片段,用于输出当前PHP环境的配置信息。s:4:"file";
表示另一个属性名为"file"
(长度为4个字符)。s:5:"a.php";
表示"file"
属性的值是一个字符串,内容是"a.php"
(长度为5个字符)。- 当这段序列化字符串被传递给
unserialize()
函数时,它会创建一个Girl
类的实例,其中name
属性的值被设置为包含恶意代码的字符串,而$file
属性的值被设置为"a.php"
。 - 如果这段代码被执行,那么
__wakeup()
魔术方法会被调用,导致以下操作发生:
打开 “a.php” 文件(如果不存在则会创建)。
将 “” 写入 “a.php” 文件。
关闭文件。
这样,攻击者就在服务器上成功创建了一个名为 "a.php"
的文件,其中包含了能够执行phpinfo()
函数的PHP代码。如果该文件随后被访问,那么服务器会执行这段代码并输出敏感的PHP环境信息,这可能有助于进一步的攻击。