0%

PHP中的序列化和反序列化

php序列化和反序列化知识点

主要作用是把本来不能直接存储的数据转换成可存储的数据,如当你想把内存中的对象状态保存到一个文件中或者数据库中的时候

$this->是伪变量,具体用法如下:

简单来说就是在一个类中,调用该类中的属性或者方法。

image-20220423095249064

在php的语句中,有一些系统自带的方法名,均以双下划线开头,它会在特定的情况下被调用。即所谓的魔法函数。他们在面向编程中起着至关重要的作用。

也就是说这些函数会在特定情况下执行函数里面的语句,如果没有调用此函数,或者调用了却没有内容,相当于没有执行任何东西。

一些魔术方法(牢记函数发生情况):

方法名 调用条件
__call 调用不可访问或不存在的方法时被调用
__callStatic 调用不可访问或不存在的静态方法时被调用
__clone 进行对象clone时被调用,用来调整对象的克隆行为
__construct 构建对象的时被调用;
__debuginfo 当调用var_dump()打印对象时被调用(当你不想打印所有属性)适用于PHP5.6版本
__destruct 明确销毁对象或脚本结束时被调用;
__get 读取不可访问或不存在属性时被调用
__invoke 当以函数方式调用对象时被调用
__isset 对不可访问或不存在的属性调用isset()或empty()时被调用
__set 当给不可访问或不存在属性赋值时被调用
__set_state 当调用var_export()导出类时,此静态方法被调用。用__set_state的返回值做为var_export的返回值。
__sleep 当使用serialize时被调用,当你不需要保存大对象的所有数据时很有用
__toString 当一个类被转换成字符串时被调用
__unset 对不可访问或不存在的属性进行unset时被调用
__wakeup 当使用unserialize时被调用,可用于做些对象的初始化操作
  • 反序列化的常见起点
    • __wakeup 一定会调用
    • __destruct 一定会调用
    • __toString 当一个对象被反序列化后又被当做字符串使用
  • 反序列化的常见中间跳板:
    • __toString 当一个对象被当做字符串使用
    • __get 读取不可访问或不存在属性时被调用
    • __set 当给不可访问或不存在属性赋值时被调用
    • __isset 对不可访问或不存在的属性调用isset()或empty()时被调用。形如 $this->$func();
  • 反序列化的常见终点:
    • __call 调用不可访问或不存在的方法时被调用
    • call_user_func 一般php代码执行都会选择这里
    • call_user_func_array 一般php代码执行都会选择这里

主要还是三点:

  1. 起点
  2. 跳板
  3. 代码执行

示例:

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
55
56
57
58
59
60
61
62
63
<?php

class test{

public $varr1="abc";

public $varr2="123";

public function echoP(){

echo $this->varr1."<br>";

}

public function __construct(){

echo "__construct<br>";

}

public function __destruct(){

echo "__destruct<br>";

}

public function __toString(){

return "__toString<br>";

}

public function __sleep(){

echo "__sleep<br>";

return array('varr1','varr2');

}

public function __wakeup(){

echo "__wakeup<br>";

}

}



$obj = new test(); //实例化对象,调用__construct()方法,输出__construct

$obj->echoP(); //调用echoP()方法,输出"abc"

echo $obj; //obj对象被当做字符串输出,调用__toString()方法,输出__toString

$s =serialize($obj); //obj对象被序列化,调用__sleep()方法,输出__sleep

echo unserialize($s); //$s首先会被反序列化,会调用__wake()方法,被反序列化出来的对象又被当做字符串,就会调用_toString()方法。

// 脚本结束又会调用__destruct()方法,输出__destruct

?>

输出结果如下

image-20220424140829646

对象注入

当用户的请求在传给反序列化函数unserialize()之前没有被正确的过滤时就会产生漏洞。因为PHP允许对象序列化,攻击者就可以提交特定的序列化的字符串给一个具有该漏洞的unserialize函数,最终导致一个在该应用范围内的任意PHP对象注入。

对象漏洞出现得满足两个前提:

一、unserialize的参数可控。

二、 代码里有定义一个含有魔术方法的类,并且该方法里出现一些使用类成员变量作为参数的存在安全问题的函数。

例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php

class A{

var $test = "demo";

function __destruct(){

echo $this->test;

}

}

$a = $_GET['test'];

$a_unser = unserialize($a);

?>

比如这个列子,直接是用户生成的内容传递给unserialize()函数,那就可以构造这样的语句

1
?test=O:1:"A":1:{s:4:"test";s:5:"lemon";}

在脚本运行结束后便会调用_destruct函数,同时会覆盖test变量输出lemon。

image-20220424141411465

再看一个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php

class A{

var $test = "demo";

function __destruct(){

@eval($this->test);//_destruct()函数中调用eval执行序列化对象中的语句

}

}

$test = $_POST['test'];

$len = strlen($test)+1;

$pp = "O:1:\"A\":1:{s:4:\"test\";s:".$len.":\"".$test.";\";}"; // 构造序列化对象

$test_unser = unserialize($pp); // 反序列化同时触发_destruct函数

?>

其实仔细观察就会发现,其实我们手动构造序列化对象就是为了unserialize()函数能够触发__destruc()函数,然后执行在__destruc()函数里恶意的语句。

所以我们利用这个漏洞点便可以获取web shell了

image-20220424141833788

绕过魔法函数的方法

__wakeup():当反序列化字符串中,表示属性个数的值大于真实属性个数时,会绕过 wakeup 函数的执行(PHP5<5.6.25 PHP7<7.0.10)

(以上部分内容摘自php中文网,但略有改动)

php反序列化例题

例题1:

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
55
56
57
58
59
60
61
62
63
64
65
<?php

class crow
{
public $v1;
public $v2;

function eval()
{
echo new $this->v1($this->v2);
}

public function __invoke()
{
$this->v1->world();
}
}

class fin
{
public $f1;

public function __destruct()
{
echo $this->f1 . '114514';
}

public function run()
{
($this->f1)();
}

public function __call($a, $b)
{
echo $this->f1->get_flag();
}

}

class what
{
public $a;

public function __toString()
{
$this->a->run();
return 'hello';
}
}
class mix
{
public $m1;

public function run()
{
($this->m1)();
}

public function get_flag()
{
eval('#'.$this->m1);
}

}

例题2:

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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
<?php

class My_ClassLoader{
/*
* 我的类加载器
* 希望没有什么安全问题....XD
*/
public $class_name;
public $class_value;

// 类创建
public function __construct($class_name, $class_value)
{
$this->class_name = $class_name;
$this->class_value = $class_value;
}

public function __wakeup()
{
// 通过wakeup检查 反序列化后 类加载的合理性
if(!in_array($this->class_name, ["Person_Bean", "Person_identify"])){
$this->class_name = "";
}
}

// 销毁时执行
public function __destruct()
{
$a = new $this->class_name($this->class_value);
$a->work(); // 动态加载的类进行工作
}
}

class Manage_test{
public $func; // 调用函数
public function __construct($func)
{
$this->func = $func;
}

public function work(){
$a = $this->func;
$a();
}

}

// 下面这俩还没开发完,其实我也不知道应该再写.....我要实现的功能是啥来着..?
class Person_identify{
public $value = "ERROR";

// 当以函数方式调用类时,直接工作输出
public function __invoke()
{
$this->work($this->value);
}

// 认证
public function work($key){
if(md5($key) != "8cd80ad332a109f5f6ab764505564c60"){
exit(0);
}
else{
echo "identify Success!!!";
}
}
}

class Person_Bean{
public $p_info_file;

// 方便直接打印类信息
public function __toString()
{
echo file_get_contents($this->p_info_file);
return "";
}
}

highlight_file(__FILE__);
if (isset($_POST['cmd'])) {
unserialize($_POST['cmd']);
}
error_reporting(0);