网鼎杯-2020-青龙组-AreUSerialz

网鼎杯-2020-青龙组-AreUSerialz

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

}
2.过程:

从下看起:

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

GET提交str,isvalid判断是否有效,反序列化;这里有一点,上次学到private和protected属性的变量会有特殊的形式包裹,不属于ASCII中的可见字符,会被过滤掉……

image-20210226113435590

若php版本较高,可以用public绕过

construct是在new的时候执行,可以省略;看destruct:

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

content赋值为空,要考虑读取flag

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

通过file get contents 读取……

思路还是比较清晰的,op值为int型2,去read flag.php:

image-20210226141847188

这里表明了文件名……

还是有一个关于析构函数的知识点:

析构函数的话,删除对象的所有引用或销毁对象或脚本执行结束才会自动调用

如果是脚本执行完毕,HTTP 头信息已经发出。这时的工作目录有可能和在 SAPI中时不同

可以用getcwd函数测试。

(SAPI……不4很懂)

本地测试:

image-20210226142518729

image-20210226142545452

可以看到,如果是脚本结束后自动释放内存执行析构,服务器的工作目录会发生改变,不再是脚本运行的目录,这对读取flag文件会造成一定的影响……

1.读取服务器配置,找到绝对路径:

Linux系统的/proc目录介绍:

在GUN/Linux操作系统中,/proc是一个位于内存中的伪文件系统(in-memory pseudo-file system)。该目录下保存的不是真正的文件和目录,而是一些“运行时”信息,如系统内存、磁盘io、设备挂载信息和硬件配置信息等。proc目录是一个控制中心,用户可以通过更改其中某些文件来改变内核的运行状态。proc目录也是内核提供给我们的查询中心,我们可以通过这些文件查看有关系统硬件及当前正在运行进程的信息。在Linux系统中,许多工具的数据来源正是proc目录中的内容。例如,lsmod命令就是cat /proc/modules命令的别名,lspci命令是cat /proc/pci命令的别名。

经常会用到的文件:

  1. maps 记录一些调用的扩展或者自定义 so 文件
  2. environ 环境变量
  3. comm 当前进程运行的程序
  4. cmdline 程序运行的绝对路径
  5. cpuset docker 环境可以看 machine ID
  6. cgroup docker环境下全是 machine ID 不太常用

可以通过读 /proc/self/cmdline 来找到对应的路径:

image-20210226145345422

……好吧,这是啥我也不是很清楚,当时原题目环境:

image-20210226145638365

读取/web/config/httpd.conf:

image-20210226145909106

获取路径,读取/web/html/flag.php

2.提前触发destruct,防止工作目录的更换

参考dalao:http://imagin.vip/?p=1391

image-20210226150527915

image-20210226150547043

反序列化执行逻辑:

  1. 首先执行反序列化
  2. 解析字符串时出错,于是删除已经生成一半的对象
  3. 对象被删除,执行析构函数
  4. 析构函数执行完毕,对应的变量被赋值为 false
  5. 返回到处理错误的代码,抛出 notice
  6. 执行 $t++ 并输出

人为构造错误的序列化字符串,提前执行析构函数,在工作目录改变之前read。

3.报错获取路径

原题目报错后会显示相关部署工具信息,查找得到路径……大概……

image-20210226151213161

image-20210226151226410

4.关于protected

php打序列化字符串中只要把其中的s改成大写打S,后面的字符串就可以用十六进制表示,

即%00可以用/00替换。

…………ok

作者

inanb

发布于

2021-02-26

更新于

2021-08-23

许可协议


:D 一言句子获取中...