当前位置:网站首页 > 技术博客 > 正文

序列化和反序列化实现



 

通过序列化与反序列化我们可以很方便的在PHP中进行对象的传递。本质上反序列化是没有危害的。但是如果用户对数据可控那就可以利用反序列化构造payload攻击

 
  • 返回结果

  • O代表对象,这里是序列化的一个对象,要序列化数组的就用A
  • 6表示的是类的长度
  • sunset表示对是类名
  • 3表示类里面有3个属性也称为变量
  • s表示字符串的长度
  • 比如 这里表示的是 flag属性名(变量名)为4个字符串长度 字符串 属性长度 属性值

这里是把上面序列化之后返回的数据进行反序列化

image-20230222124032420

 

public修饰的属性和方法可以在任何地方被访问,包括类的内部、子类和外部代码。

示例:

 

image-20230223095619107

protected修饰的属性和方法只能在当前类及其子类中被访问,外部的代码访问不了

 

image-20230223100736605

private修饰的属性和方法只能在当前类中被访问,子类和外部代码不能访问。

 

image-20230223101020415

总结

 

方法是 PHP 中的一个魔术方法(magic method),用于在对象被序列化(serialized)时触发。在这个方法中,你可以指定哪些属性需要被序列化,哪些属性不需要被序列化。

具体来说,当调用 函数将一个对象序列化时,PHP 会先自动调用对象的 方法,该方法需要返回一个数组,包含需要被序列化的属性名。然后 PHP 会将这些属性序列化成字符串。

假设有一个 类,它有一个私有属性 ,你不希望在序列化对象时将密码属性暴露出来。那么你可以在 类中实现 方法

 

image-20230222190827201

在上面的例子中, 类的 方法返回了一个只包含 属性名的数组,这意味着在序列化对象时,只有用户名会被序列化。如果你运行上面的代码,你会看到输出的序列化字符串只包含了 属性的值。

关于序列化后的字符串中 中的 ,实际上是指 "Userusername" 的长度为 12 个字符,而不是 10 或 14 个字符。这是因为在 PHP 序列化字符串中,每个字符串的前面都会有一个类似 的字符串长度标识,表示该字符串的长度为 6 个字符。这个字符串长度标识包括 、冒号和数字长度,加起来占用了 4 个字符,所以实际上字符串长度标识的长度为字符串长度加 2。在您的输出结果中, 中的

unserialize() 会检查是否存在一个 __wakeup() 方法。如果存在,则会先调用 __wakeup 方法,预先准备对象需要的资源 而 用于在从字符串反序列化为对象时自动调用。一个 PHP 对象被序列化成字符串并存储在文件、数据库或者通过网络传输时,我们可以使用 函数将其反序列化为一个 PHP 对象。在这个过程中,PHP 会自动调用该对象的 方法,对其进行初始化。

方法的作用是对一个对象进行一些必要的初始化操作。例如,如果一个对象中包含了一些需要进行身份验证的属性,那么在从字符串反序列化为对象时,就可以在 方法中进行身份验证。或者如果一个对象中包含了一些需要在每次初始化时计算的属性,也可以在 方法中进行计算

示例1:

 

在上面的示例中 类实现了 和 方法。 方法返回了一个包含 和 属性名的数组,表示只有这两个属性需要被序列化。 方法会调用 方法进行身份验证。如果身份验证失败,则会抛出一个异常。

实例2:

 
  • 输出结果

image-20230222202135506

__toString() 方法用于一个类被当成字符串时应怎样回应。例如 echo $obj; 应该显示些什么。此方法必须返回一个字符串,否则将发出一条 E_RECOVERABLE_ERROR 级别的致命错误。

示例:

 

image-20230222204503970

方法是 PHP 中的一个特殊方法,用于在对象实例被销毁时自动调用。该方法通常用于清理对象所占用的资源,例如关闭数据库连接、释放文件句柄等。

示例:

 

在上面的示例中, 类的构造函数打开了一个文件,并将其保存在 属性中。 方法使用该文件句柄将文本写入文件中。

当 实例被销毁时, 方法会自动调用,关闭文件句柄以释放资源。这意味着在 方法执行后,即使没有调用 方法关闭文件,该文件也会被正确地关闭

  • 关于示例在上面时候被销毁

具体 执 变量, 对象将会被销毁,并且 方法会被自动调用,关闭文件句柄。如果在此之后仍然有其他变量引用 对象,那么对象不会被销毁,直到所有引用都被释放为止。

对象的生命周期取决于它的引用计数,只有当所有引用都被释放后,对象才会被销毁。 方法会在对象销毁时自动调用,用于执行清理操作。

魔术方法运行的先后顺序

  • :当对象创建时会被调用,是在new对象时才调用, 时不对被自动调用
  • : 当对象被销毁时自动调用,有新的对象创建 之后会自动销毁 相当于调用了 后一定会调用 现在传入一个对象,他后面被销毁时会调用

实例:

 

image-20230227200158200

创建对象 调用 序列号之后调用销毁对象

image-20230228145524017

  • 在对象被序列化之前调用
  • 在对象被反序列化之前调用

示例:

 

image-20230228150412706

这里可以看出在序列化之前调用了 方法然后进行销毁

 

image-20230228153615159

这里我们直接提交序列化的内容就调用了

__toString作为pop链关键的一步,很容易被调用。当对象被当作字符串的时候, 会被调用,不管对象有没有被打印出来,在对象被操作的时候,对象在和其他的字符串做比较的时候也会被调用。

  1. echo($obj)或print($obj)打印对象时会触发
  2. 反序列化对象与字符串连接时
  3. 反序列化对象参与格式化字符串时
  4. 反序列化对象字符串进行==比较时(多为preg_match正则匹配),因为php进行弱比较时会转换参数类型,相当于都转换成字符串进行比较
  5. 反序列化对象参与格式化sql语句时,绑定参数时(用的少)
  6. 反序列化对象经过php字符串函数时,如strlen(),addslashes()时(用的少)
  7. 在in_array()方法中,第一个参数是反序列化对象,第二个参数的数组中有tostring返回的字符串的时候tostring会被调用
  8. 反序列化的对象作为class_exists()的参数的时候(用的少)
 

image-20230228154507214

:当尝试以调用函数的方式调用一个对象时,方法会被自动调用,而调用函数的方式就是在后面加上,当我们看到像这种语句时,就应该意识到后面可能会调用,下图是直接在对象后面加调用

 

image-20230228154938791

  • :从不可访问的属性中读取数据,或者说是调用一个类及其父类方法中未定义属性时
  • :当给一个未定义的属性赋值时,或者修改一个不能被修改的属性时()(用的不多)
 

image-20230228160828446

这里创建一个对象调用了 然后echo 指向的mkk没有被定义然后调用

  • :在对象中调用类中不存在的方法时,或者是不可访问方法时被调用
  • :在静态上下文中调用一个不可访问静态方法时被调用
 

image-20230228161341173

其他魔术方法
 

参考手册

unserialize3

地址

image-20230221173359329

image-20230221173419994

 

image-20230222104613367

示例

 

构造payload:http://127.0.0.1/1.php?str=s:6:"sunset";

image-20230222212554336

版本限制 PHP5:<5.6.25

​ PHP7:<7.0.10

CVE-2016-7124

 
代码分析
  • 类名: sunset
  • 属性名: name 和age
  • 魔术方法: __wakeup和__destruct

在代码中这里用到了反序列化函数 , 只要用到这个函数是里面就会检测类sunset 里面有没有__wakeup()方法 ,如果有的话就会执行这个方法 这里的 wakeup()没有太大的作用然后后面的 __destruct打开了一个flag.php文件,然后把$this-->name 的值作为内容写入flag.php里面

 

image-20230222225336669 对上面代码进行序列化

  • O 代表是一个对象
  • 6 长度为6 "sunset"
  • 2 表示里面有两个属性
  • s: 4:name 表示属性的长度为4
  • s:8:makabaka 属性的长度为8

在上面的代码中我们可以看到 方法把name的东西写入里面这里我们可以直接写入shell

但是由于进行 之前会进行方法 所以需要先绕过 这里需要增加类的属性值使大于类里面的就可以绕过>2

 

image-20230223093004784

示例2
 
  • 构造序列化的对象:O:5:"SoFun":1:
  • :O:5:"SoFun":2:

上面类的属性为 protected 可以加上 00*00绕过之后进行base64绕过

 

image-20230223105641072

image-20230223125334840

image-20230223125415077

设置session的存储路径
设定用户自定义存储函数如果想使用PHP内置会话存储机制之外的可以使用本函数(数据库等方式)
指定会话模块是否在请求开始时启动一个会话
定义用来序列化/反序列化的处理器名字。默认使用php(<5.5.4)

session 的存储机制

php中session中的内容是以文件方式来存储的,由由来决定。文件名由命名,文件内容则为session序列化后的值。

session.serialize_handler是用来设置session的序列化引擎的,除了默认的PHP引擎之外,还存在其他引擎,不同的引擎所对应的session的存储方式不相同。

引擎 session存储方式 php(php<5.5.4) 存储方式是,键名+竖线` php_serialize(php>5.5.4) 存储方式是,经过serialize()函数序列化处理的键和值(将session中的key和value都会进行序列化) php_binary 存储方式是,键名的长度对应的ASCII字符+键名+经过serialize()函数序列化处理的值

在PHP (php<5.5.4) 中默认使用的是PHP引擎,如果要修改为其他的引擎,只需要添加代码进行设置

php_serialize 引擎
 

image-20230223131040185

这里汇总seesion存储路径存储一个序列化后的文件

内容为

其中 是使用php_serialize引擎都会加上的,同时使用php_serialize会把session里面的key和value都会反序列化

  • a代表的是一个数组
php引擎
 

image-20230223142244594

内容为

这里name 为键值 是sunset序列化后的结果

php引擎存储方式为:

php_binary 引擎
 

image-20230223144018347

返回值

 

前面那个是一个特殊字符 因为 序列化的过程中,会把数据编码为二进制格式,需要把数据长度信息加入到编码数据的开头,这样在解码的时候才可以读取数据,也是为了在解码的时候确定数据的长度。

image-20230224125012199

例题: ctfshow_web263

image-20230226175136672

文件拿到源码

  • index.php
 
  • check.php
 
  • inc.php
 

根据index.php 源码 前面的 导致后续条件不可能成立,而 文件里面设定了指定的php解释器为php的 会对session文件进行解析,进行反序列化

image-20230226192653776

image-20230226192118556

  • check.php 调用 cookie

image-20230226192810506

  • inc.php文件

    image-20230227143927277

  • 抓取数据包

image-20230226192236413

通过解码发现limit为1

  • EXP
 

image-20230227144300331

然后通过抓包修改cookie字段

image-20230227144742440

  • 修改cookie后

image-20230227144955203

写入成功

然后访问inc/inc.php触发条件 在和页面反序列化的时候|前面的会被看做键名,会对|后面的进行反序列化

image-20230227154645754

魔术方法运行的先后顺序

  • 思路

利用现有的环境,找到一系列的代码或者调用指令,然后构造成一组连续的调用链,然后进行攻击。任何一条链子的构造,我们都要先找到它的头和尾,pop链也不例外,pop链的头部一般是用户能传入参数的地方,而尾部是可以执行我们操作的地方,比如说读写文件,执行命令等等;找到头尾之后,从尾部(我们执行操作的地方)开始,看它在哪个方法中,怎么样可以调用它,一层一层往上倒推,直到推到头部为止,也就是我们传参的地方,一条pop链子就出来了;在ctf中,头部一般都会是GET或者POST传参,然后可能就有一个直接将我们传入的参数反序列化了,尾部都是拿flag的地方;然后一环连着一环构成pop链

例题1

有php反序列化漏洞是因为有不安全的魔术方法,魔术方法会自己调用,我们构造恶意的exp就可以来触发他,有时候的漏洞代码不在魔术方法里面,在普通的方法中,我们应该寻找魔术方法是否调用了同名的函数,然后用同名函数名和类的属性和魔术方法联系起来

 

上面代码的危险函数是evil类里面的 我们需要把命令写入eval()里面,这里的action()函数有2个我们需要调用的是下面的 这里代码的流程是先有一个 类然后我们创建对象的时候调用 方法之后会在创建一个新的 对象然后就调用了函数 输出

这里可以先创建一个test类然后利用test类创建一个 对象再通过对象写入危险函数

  • EXP
 

image-20230228175323438

image-20230228175346663

例题2

 
  • 这段代码的含义:

通过GET传入a参数 对传入的内容进行反序列化 分别传入 里面 创建类的对象时候调用 方法把传递的参数存在了里面在该对象被销毁的时候调用 方法,并且把 的值给到了里面并且通过echo输出,然后创建 对象会调用 方法返回的值 ,创建 方法的时候调用了 然后把 存入了 里面然后访问这个属性时 会调用 方法并且调用 方法然后把 里面的内容写到然后输出

这里需要先把类里面的变成对象这样才可以继续让里面的类 执行,然后把类的赋值成对象,来调用类中的

  • EXP
 

image-20230228211944062

image-20230228212008893

例题3

[MRCTF2020]Ezpop

image-20230228212353907

 

这里通过get方法传入一个get参数,如果没有传入pop参数然后就调用方法 显示源码,这里的思路就是在 类里面有一个 函数如果可以调用就可以通过文件包含,包含 文件然后使用php伪协议就可以获取flag

有关类的魔术方法简介就往__invoke 看

image-20230228225846626

image-20230228230222173

在反序列化的时候会直接被触发里面的正则匹配了一些敏感的关键词 然后函数对s ource进行访问会触发 然后这个方法又会访问str里面的source 我们创建一个新的类,里面没有source,然后会触发 方法,函数返回的时候我们再创建一个类 之后又会触发 方法然后用 的var读取flag.php

image-20230301105907755

  • exp
 

image-20230301121720101

版权声明


相关文章:

  • 图数据库neo4j的查询语言2025-05-09 15:01:02
  • 分布式缓存原理架构go实现2025-05-09 15:01:02
  • linux 流量监控软件2025-05-09 15:01:02
  • 王码五笔输入法86版的字根全面吗?2025-05-09 15:01:02
  • java pojo是什么意思2025-05-09 15:01:02
  • ir2110引脚图2025-05-09 15:01:02
  • rx fifo overrun2025-05-09 15:01:02
  • arm ipi2025-05-09 15:01:02
  • html登陆页面密码加密2025-05-09 15:01:02
  • vmware虚拟机版本不兼容2025-05-09 15:01:02