PHP序列化/反序列化漏洞原理

PHP反序列化原理详解

引言

PHP反序列化是PHP中一个重要的概念,它允许将序列化后的数据重新转换为原始的数据结构。在PHP中,可以使用serialize()函数将数据序列化为字符串,然后使用unserialize()函数将序列化后的字符串反序列化为原来的数据结构。这个过程在数据存储、传输和对象持久化等方面起着关键作用。然而,不当使用反序列化可能导致安全漏洞,因此理解其原理和正确使用方法至关重要。

原理

  1. 序列化: PHP中的serialize()函数用于将数据(如数组、对象)转换为字符串格式,以便存储或传输。序列化后的字符串包含数据类型和值的信息。
  2. 反序列化: unserialize()函数用于将序列化后的字符串转换回PHP值,如数组或对象。在反序列化过程中,PHP根据序列化字符串中的信息重新构建原始数据结构。

PHP序列化原理演示

序列化

php 复制代码
<?php
class Test {
    // 声明属性
    public $publicVar = "public value";
    private $privateVar = "private value";
    protected $protected = "protected value";

    // 声明方法
    public function printVar() {
        echo $this->publicVar . "\n";
        echo $this->privateVar . "\n";
        echo $this->protected . "\n";
    }
}

$test = new Test();
$test->printVar();

$testSerial = serialize($test);
echo $testSerial;

当一个方法在类定义内部被调用时,有一个可用的伪变量 t h i s 。 this。 this。this 是一个到当前对象的引用。

运行结果

复制代码
public value
private value
protected value
O:4:"Test":3:{s:9:"publicVar";s:12:"public value";s:16:"TestprivateVar";s:13:"private value";s:12:"*protected";s:15:"protected value";}

将序列化之后的值格式化来分析

复制代码
O:4:"Test":3:{
	s:9:"publicVar";
	s:12:"public value";
	s:16:"TestprivateVar";
	s:13:"private value";
	s:12:"*protected";
	s:15:"protected value";
}
  • O:4:"Test":3:{:这表示一个对象(O),其类名长度为4("Test"),并且有3个属性。
    • 第一个属性:
      • s:9:"publicVar";:这是一个字符串(s),长度为9,内容为 "publicVar",表示属性名。
      • s:12:"public value";:这是一个字符串,长度为12,内容为 "public value",表示公共属性的值。
    • 第二个属性:
      • s:16:"TestprivateVar";:这是一个字符串,长度为16,内容为 "TestprivateVar",表示私有属性的名称。在PHP中,私有属性在序列化时会以类名作为前缀。
      • s:13:"private value";:这是一个字符串,长度为13,内容为 "private value",表示私有属性的值。
    • 第三个属性:
      • s:12:"*protected";:这是一个字符串,长度为12,内容为 "*protected",表示受保护属性的名称。在PHP中,受保护属性在序列化时会以星号(*)作为前缀。
      • s:15:"protected value";:这是一个字符串,长度为15,内容为 "protected value",表示受保护属性的值。

公共属性很好理解,但私有属性和保护属性就不一样了

protected:受保护成员只能在其定义的类及其子类中访问。

​ 序列化后属性名 \x00\*\x00属性名

**private:**私有成员只能在其定义的类内部访问,不能在子类或类外部访问。

​ 序列化后属性名 \x00类名\x00属性名

注意:浏览器输出默认不显示\x00

PHP序列化字符串中使用的类型标识符及其对应的数据类型

类型标识符 数据类型 描述
s 字符串(String) 表示一个字符串,例如 s:5:"hello"
i 整数(Integer) 表示一个整数,例如 i:123
d 浮点数(Double) 表示一个浮点数,例如 d:3.14
b 布尔值(Boolean) 表示一个布尔值,b:1 表示 true,b:0 表示 false。
a 数组(Array) 表示一个数组,例如 a:2:{i:0;s:5:"apple";i:1;s:6:"banana"}
O 对象(Object) 表示一个对象,例如 O:6:"MyClass":1:{s:3:"foo";s:5:"bar"}
N NULL值 表示 NULL 值,例如 N;
r 引用(Reference) 表示指向当前对象中的引用,例如 r:2;
R 引用(Reference) 表示指向序列化数据中另一个对象的引用,例如 R:3;
C 自定义序列化(Custom serialization) 表示自定义序列化的对象,例如 C:6:"MyClass":0:{}

魔术方法

魔术方法是一种特殊的方法,当对对象执行某些操作时会覆盖 PHP 的默认操作。

魔术方法 功能描述
__construct() 构造方法,在对象创建时自动调用。
__destruct() 析构方法,在对象被销毁前自动调用。
__call() 在调用不可访问的方法时自动调用。
__callStatic() 在静态上下文中调用不可访问的方法时自动调用。
__get() 在读取不可访问的属性时自动调用。
__set() 在设置不可访问的属性时自动调用。
__isset() 在使用isset()empty()检测不可访问的属性时自动调用。
__unset() 在使用unset()销毁不可访问的属性时自动调用。
__sleep() 在序列化对象之前自动调用,用于清理对象或返回一个包含要序列化的属性名的数组。
__wakeup() 在反序列化对象之后自动调用,用于重建对象。
__serialize() 自定义序列化逻辑,返回一个数组,包含要序列化的属性和值。PHP 7.4+
__unserialize() 自定义反序列化逻辑,接收一个数组,包含序列化的属性和值。PHP 7.4+
__toString() 在尝试将对象转换为字符串时自动调用。返回对象的字符串表示。
__invoke() 当尝试将对象作为函数调用时自动调用。
__set_state() 在使用var_export()导出类时,此静态方法会被调用,用于恢复类的属性。
__clone() 在对象被复制时自动调用,用于自定义克隆逻辑。
__debugInfo() 在使用var_dump()打印对象时自动调用,用于提供调试信息。PHP 5.6+
php 复制代码
<?php
class Test {
    // 声明属性
    public $publicVar = "public value";
    private $privateVar = "private value";
    protected $protected = "protected value";

    // 声明方法
    public function printVar() {
        echo $this->publicVar . "\n";
        echo $this->privateVar . "\n";
        echo $this->protected . "\n";
    }

    public function __wakeup() {
        echo '__wakeup';
    }
}

$test = new Test();
$test->printVar();

$testSerial = serialize($test);
echo $testSerial;
echo "\n";
unserialize($testSerial);

可以看到运行最后调用了 __wakeup 魔术方法

复制代码
public value
private value
protected value
O:4:"Test":3:{s:9:"publicVar";s:12:"public value";s:16:" Test privateVar";s:13:"private value";s:12:" * protected";s:15:"protected value";}
__wakeup

如果__wakeup方法中有高敏感度代码,例如命令执行等,可能会导致意外的风险

例如:

php 复制代码
<?php
class Test {
    public $cmd = '';

    public function __wakeup() {
        system($this->cmd);
    }
}

unserialize($_GET['data']);

传入?data=O:4:"Test":1:{s:3:"cmd";s:2:"id";}即可成功返回命令执行结果

相关推荐
BingoGo17 小时前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php
JaguarJack17 小时前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php·服务端
BingoGo2 天前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php
JaguarJack2 天前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php·服务端
JaguarJack3 天前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
后端·php·服务端
BingoGo3 天前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
php
JaguarJack4 天前
告别 Laravel 缓慢的 Blade!Livewire Blaze 来了,为你的 Laravel 性能提速
后端·php·laravel
郑州光合科技余经理4 天前
代码展示:PHP搭建海外版外卖系统源码解析
java·开发语言·前端·后端·系统架构·uni-app·php
QQ5110082854 天前
python+springboot+django/flask的校园资料分享系统
spring boot·python·django·flask·node.js·php
WeiXin_DZbishe4 天前
基于django在线音乐数据采集的设计与实现-计算机毕设 附源码 22647
javascript·spring boot·mysql·django·node.js·php·html5