• PHP 反序列化漏洞:手写序列化文本


    参考

    项目描述
    搜索引擎BingGoogle
    AI 大模型文心一言通义千问讯飞星火认知大模型ChatGPT
    PHP 手册PHP Manual

    环境

    项目描述
    PHP8.0.0

    序列化文本

    PHP 反序列化漏洞中,我们都需要使用到 序列化文本。常常采用的方法是通过 PHP 代码创建目标数据后再使用 serialize() 函数将其转化为序列化文本。对此,请参考如下示例:

    
    
    
    # 定义目标数据
    class MyClass
    {
        public $name = 'RedHeart';
        public $nation = 'China';
    }
    
    $myClass = new MyClass();
    
    # 通过 serialize 函数获得序列化文本
    var_dump(serialize($myClass));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    执行效果

    string(69) "O:7:"MyClass":2:{s:4:"name";s:8:"RedHeart";s:6:"nation";s:5:"China";}"
    
    • 1

    此办法虽然可行,但我们仍需要 对 PHP 中各种数据类型的序列化文本的结构有一定认识。在某些特殊场景下,手写序列化文本 才是最好的选择🧙🏻‍♂️。

    Scalar Type

    在 PHP 中,标量数据类型 Scalar Type 是多种基础类型的统称。标量数据类型具体而言,包含 整型 int浮点型 float布尔型 boolean字符串 string 四种类型。

    整数

    在 PHP 中,整数数据类型用于表示 整型数值,可以是 正数负数。整数的序列化文本由 i: 和具体的数值组成,且序列化文本以 ; 结尾。对此,请参考如下示例:

    
    
    
    var_dump(serialize(1));
    var_dump(serialize(999));
    var_dump(serialize(-0));
    var_dump(serialize(-93));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    执行效果

    string(4) "i:1;"
    string(6) "i:999;"
    string(4) "i:0;"
    string(6) "i:-93;"
    
    • 1
    • 2
    • 3
    • 4

    浮点数

    数学中,数值包含整数及小数。而在 PHP 中,小数被称为浮点数。浮点数的序列化文本由 d: 和具体的浮点数值组成,且序列化文本以 ; 结尾。对此,请参考如下示例:

    
    
    
    var_dump(serialize(1.000));
    var_dump(serialize(999.36));
    var_dump(serialize(-0.374));
    # 数值中的 e 或 E 表示科学计数法,
    # 就 339E-10 而言,表示 339 * 10 ** -10
    var_dump(serialize(339E-10));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    执行效果

    string(4) "d:1;"
    string(9) "d:999.36;"
    string(9) "d:-0.374;"
    string(10) "d:3.39E-8;"
    
    • 1
    • 2
    • 3
    • 4

    为什么使用 d: 而不是 f: 呢?d 即双精度浮点数,全称为 double。在PHP的内部实现中,浮点数使用 双精度浮点数(double-precision floating-point numbers) 来表示。双精度浮点数相比于单精度浮点数(通常表示为 float),具体存在如下区别:

    1. 单精度浮点数
      使用 32 位存储,通常以 IEEE 754 单精度标准表示。单精度浮点数提供约 7 位有效数字的精度,适用于大多数应用场景,但对于某些需要更高精度的计算来说可能不够。

    2. 双精度浮点数
      使用 64 位存储,通常以 IEEE 754 双精度标准表示。双精度浮点数提供约 15 ~ 16 位有效数字的精度,因此比单精度浮点数具有更高的精度,适用于需要更精确计算的应用。

    布尔值

    布尔值常用于 逻辑运算,用于表示某一 个条件语句的对错。布尔值由 组成,分别与序列化文本 b:1;b:0; 相对应。

    字符串

    在 PHP 中,字符串用于表示 文本数据。字符串的序列化文本由 s字符串所占用的字节数 以及 具体的字符串值 三部分组成,每两部分内容 使用 : 进行分隔,且序列化文本以分号 ; 进行结尾。对此,请参考如下示例:

    
    
    
    var_dump(serialize('Hello World'));
    var_dump(serialize('3.1415926'));
    var_dump(serialize('����.?'));
    var_dump(serialize('你好,中国'));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    执行效果

    string(19) "s:11:"Hello World";"
    string(16) "s:9:"3.1415926";"
    string(23) "s:15:"����.?";"
    string(23) "s:15:"你好,中国";"
    
    • 1
    • 2
    • 3
    • 4

    Compound Type

    Compound Type 类型指的是 由其他数据类型组合而成的复合数据类型。在 PHP 中,主要有两种复合数据类型,即 数组 Array对象 Object。复合类型 可以包含多个值或属性,并且允许以不同的方式组合不同类型的数据

    数组

    数据结构

    数组是一个包含 键值对 的数据结构,其中键通常是 整数或字符串,而值可以是 任意数据类型(标量数据类型、复合数据类型或 NULL)。数组用于存储和操作多个相关的值。对此,请参考如下示例:

    
    
    
    $arr = [1, 2, 3, 4];
    $arr1 = [
        'arr' => $arr, 
        'arr1' => 'Nested Array', 
        3 => 'I am three'
    ];
    
    var_dump($arr1);
    var_dump($arr1['arr1']);
    var_dump($arr[2]);
    var_dump($arr1[3]);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    执行效果

    array(3) {
      ["arr"]=>
      array(4) {
        [0]=>
        int(1)
        [1]=>
        int(2)
        [2]=>
        int(3)
        [3]=>
        int(4)
      }
      ["arr1"]=>
      string(12) "Nested Array"
      [3]=>
      string(10) "I am three"
    }
    string(12) "Nested Array"
    int(3)
    string(10) "I am three"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    序列化文本

    数组的序列化文本的大致格式如下:

    a:数组中的元素个数:{键的序列化文本(分号结尾)值的序列化文本(分号结尾)}
    
    • 1

    举个栗子

    
    
    
    $arr = [1, 2, 3, 4];
    $arr1 = [
        'arr' => $arr,
        'arr1' => 'Nested Array',
        3 => 'I am three'
    ];
    $arr2 = [];
    
    var_dump(serialize($arr));
    var_dump(serialize($arr1));
    var_dump(serialize($arr2));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    执行效果

    string(38) "a:4:{i:0;i:1;i:1;i:2;i:2;i:3;i:3;i:4;}"
    string(107) "a:3:{s:3:"arr";a:4:{i:0;i:1;i:1;i:2;i:2;i:3;i:3;i:4;}s:4:"arr1";s:12:"Nested Array";i:3;s:10:"I am three";}"
    string(6) "a:0:{}"
    
    • 1
    • 2
    • 3

    对象

    数据结构

    对象是基于 类 Class 定义的数据类型,每个对象都是该类的实例,并且可以包含属性和方法。对象用于表示具有 一组属性和相关行为 的实体。对此,请参考如下示例:

    
    
    
    class MyClass
    {
        public $name = 'RedHeart';
        public $nation = 'China';
    
        function sayHello() {
            print('Hello China' . "\n");
        }
    }
    
    # 实例化对象
    $myClass = new MyClass();
    
    # 访问对象的属性
    var_dump($myClass -> name);
    var_dump($myClass -> nation);
    # 访问对象的方法
    $myClass -> sayHello();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    执行效果

    string(8) "RedHeart"
    string(5) "China"
    Hello China
    
    • 1
    • 2
    • 3
    序列化文本

    对象的序列化文本的大致结构为:

    O:对象名称所包含的字符数:对象的名称:对象的属性个数:{属性的序列化文本(分号结尾)属性值的序列化文本(分号结尾)}
    
    • 1

    举个栗子

    
    
    
    class MyClass
    {
        public $name = 'RedHeart';
        public $nation = 'China';
    
        function sayHello() {
            print('Hello China' . "\n");
        }
    }
    
    $myClass = new MyClass();
    
    var_dump(serialize($myClass));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    执行效果

    string(69) "O:7:"MyClass":2:{s:4:"name";s:8:"RedHeart";s:6:"nation";s:5:"China";}"
    
    • 1

    Special Type

    在 PHP 中,Special Type 类型用于描述一些具有 特殊用途或特殊性质的数据类型或值。这类特殊类型 不属于标量类型或复合类型,在编程中 具有独特的用途和特性

    NULL

    数据结构

    在 PHP 中,NULL 是一个特殊的值,常用于处理缺失的数据或未初始化的变量

    注:

    在 PHP 中,NULLTRUEFALSE 一般,都是 不区分大小写的。对此,请参考如下示例:

    
    
    
    var_dump(NULL);
    var_dump(NuLL);
    var_dump(null);
    
    var_dump(TRue);
    var_dump(falSe);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    执行效果

    NULL
    NULL
    NULL
    bool(true)
    bool(false)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    序列化文本

    对于 不同形态(大小写之分)NULL,其序列化文本均为 N;,对此,请参考如下示例:

    
    
    
    var_dump(serialize(NULL));
    var_dump(serialize(NuLL));
    var_dump(serialize(null));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    执行效果

    string(2) "N;"
    string(2) "N;"
    string(2) "N;"
    
    • 1
    • 2
    • 3

    手写序列化文本过程中的注意事项

    个数描述须于现实相符

    在部分 PHP 序列化文本中,存在 描述元素个数的数值,这些数值 需要与实际的元素个数相符合。否则,PHP 将为此抛出异常。对此,请参考如下示例:

    
    
    
    # 错误的序列化文本
    $serialize_text = 'a:2:{s:4:"name";s:8:"RedHeart";}';
    var_dump(unserialize($serialize_text));
    
    # 正确的序列化文本
    $serialize_text = 'a:1:{s:4:"name";s:8:"RedHeart";}';
    var_dump(unserialize($serialize_text));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    执行效果

    PHP Notice:  unserialize(): Unexpected end of serialized data in C:\test.php on line 6
    PHP Notice:  unserialize(): Error at offset 31 of 32 bytes in C:\test.php on line 6
    
    Notice: unserialize(): Unexpected end of serialized data in C:\test.php on line 6
    
    Notice: unserialize(): Error at offset 31 of 32 bytes in C:\test.php on line 6
    bool(false)
    array(1) {
      ["name"]=>
      string(8) "RedHeart"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    序列化文本前缀的大小写变化

    对于不同的数据类型,PHP 在将其转化为序列化文本后都将为其添加 独特的前缀。前缀中的 字母的大小写需要进行严格控制。否则,PHP 将为此抛出异常。
    具体而言,除字符串前缀 s: 中的字母 对大小写不敏感外,其他数据类型的前缀中的字母都 仅存在一种大小写形式(大写或小写)。对此,请参考如下示例:

    
    
    
    var_dump(unserialize('s:11:"Hello World";'));
    var_dump(unserialize('S:11:"Hello World";'));
    
    var_dump(unserialize('i:1;'));
    var_dump(unserialize('I:1;'));
    
    var_dump(unserialize('a:1:{S:4:"name";S:8:"RedHeart";}'));
    var_dump(unserialize('A:1:{s:4:"name";s:8:"RedHeart";}'));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    执行效果

    PHP Notice:  unserialize(): Error at offset 0 of 4 bytes in C:\test.php on line 8
    PHP Notice:  unserialize(): Error at offset 0 of 32 bytes in C:\test.php on line 11
    string(11) "Hello World"
    string(11) "Hello World"
    int(1)
    
    Notice: unserialize(): Error at offset 0 of 4 bytes in C:\test.php on line 8
    bool(false)
    array(1) {
      ["name"]=>
      string(8) "RedHeart"
    }
    
    Notice: unserialize(): Error at offset 0 of 32 bytes in C:\test.php on line 11
    bool(false)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    符号

    在 PHP 中的序列化文本中,所有符号都必须使用英式符号,而不能使用 中式符号。否则,PHP 将为此抛出异常。
    在序列化文本中,如果需要使用到引号,则 引号必须使用双引号 而不是单引号。否则,PHP 将为此抛出异常。

    公共属性

    在 PHP 中,仅推荐在 PHP 对象中 仅包含公共属性 时手写相关的序列化文本 (不推荐不意味着不可行,因篇幅原因不在本篇博客中进行说明)。这是因为,在 PHP 中,当对象被序列化时,对象的非公共属性的名称会被特殊处理以表示其可见性。对此,请参考如下示例:

    
    
    
    class MyClass
    {
        # 私有属性
        private $name = 'RedHeart';
        # 受保护属性
        protected $nation = 'China';
    }
    
    var_dump(serialize(new MyClass()));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    执行效果

    string(82) "O:7:"MyClass":2:{s:13:" MyClass name";s:8:"RedHeart";s:9:" * nation";s:5:"China";}"
    
    • 1

    MyClass 类中的私有属性名称 name 在转化为序列化文本后,成了 s:13:" MyClass name;",相比原先的 name 增加了两个空格(实际上是 控制字符 NUL,无法通过空格进行代替)及所属类的名称 MyClass

    这样的处理方式是为了 在反序列化对象时能够正确地还原属性的可见性。当 PHP 在反序列化时遇到这些特殊的前缀,PHP 会 知道如何正确地设置属性的可见性。公共属性不会有这种特殊处理,它们在序列化后的文本中保持原始的属性名。

  • 相关阅读:
    《PostgreSQL事务管理深入解析》
    flutter中使用缓存
    微信小程序如何在切换页面后原页面状态不变
    Linux操作系统和进程基本概念
    Java俄罗斯方块,老程序员花了一个周末,连接中学年代!
    Python之并发编程(进程)
    97 只出现一次的数字
    Vue 项目进行 SEO 优化
    连续六个季度实现盈利改善,达达集团内外双重确定性凸显
    json、pikcle序列化模块
  • 原文地址:https://blog.csdn.net/qq_44879989/article/details/133453573