- 從0到1:CTFer成長之路
- Nu1L戰隊
- 5307字
- 2021-01-07 17:31:53
2.1 SSRF漏洞
SSRF(Server Side Request Forgery,服務端請求偽造)是一種攻擊者通過構造數據進而偽造服務器端發起請求的漏洞。因為請求是由內部發起的,所以一般情況下,SSRF漏洞攻擊的目標往往是從外網無法訪問的內部系統。
SSRF漏洞形成的原因多是服務端提供了從外部服務獲取數據的功能,但沒有對目標地址、協議等重要參數進行過濾和限制,從而導致攻擊者可以自由構造參數,而發起預期外的請求。
2.1.1 SSRF的原理解析
URL的結構如下:

authority組件又分為以下3部分(見圖2-1-1):


圖2-1-1(圖片來源:維基百科)
scheme由一串大小寫不敏感的字符組成,表示獲取資源所需要的協議。
authority中,userinfo遇到得比較少,這是一個可選項,一般HTTP使用匿名形式來獲取數據,如果需要進行身份驗證,格式為username:password,以@結尾。
host表示在哪個服務器上獲取資源,一般所見的是以域名形式呈現的,如baidu.com,也有以IPv4、IPv6地址形式呈現的。
port為服務器端口。各協議都有默認端口,如HTTP的為80、FTP的為21。使用默認端口時,可以將端口省略。
path為指向資源的路徑,一般使用“/”進行分層。
query為查詢字符串,用戶將用戶輸入數據傳遞給服務端,以“?”作為表示。例如,向服務端傳遞用戶名密碼為“?username=admin&password=admin123”。
fragment為片段ID,與query不同的是,其內容不會被傳遞到服務端,一般用于表示頁面的錨點。
理解URL構造對如何進行繞過和如何利用會很有幫助。
以PHP為例,假設有如下請求遠程圖片并輸出的服務。

如果URL參數為一個圖片的地址,將直接打印該圖片,見圖2-1-2。

圖2-1-2
但是因為獲取圖片地址的URL參數未做任何過濾,所以攻擊者可以通過修改該地址或協議來發起SSRF攻擊。例如,將請求的URL修改為file:///etc/passwd,將使用FILE協議讀取/etc/passwd的文件內容(最常見的一種攻擊方式),見圖2-1-3。

圖2-1-3
2.1.2 SSRF漏洞的尋找和測試
SSRF漏洞一般出現在有調用外部資源的場景中,如社交服務分享功能、圖片識別服務、網站采集服務、遠程資源請求(如wordpress xmlrpc.php)、文件處理服務(如XML解析)等。在對存在SSRF漏洞的應用進行測試的時候,可以嘗試是否能控制、支持常見的協議,包括但不限于以下協議。
? file://:從文件系統中獲取文件內容,如file:///etc/passwd。
? dict://:字典服務器協議,讓客戶端能夠訪問更多字典源。在SSRF中可以獲取目標服務器上運行的服務版本等信息,見圖2-1-4。

圖2-1-4
? gopher://:分布式的文檔傳遞服務,在SSRF漏洞攻擊中發揮的作用非常大。使用Gopher協議時,通過控制訪問的URL可實現向指定的服務器發送任意內容,如HTTP請求、MySQL請求等,所以其攻擊面非常廣,后面會著重介紹Gopher的利用方法。
2.1.3 SSRF漏洞攻擊方式
2.1.3.1 內部服務資產探測
SSRF漏洞可以直接探測網站所在服務器端口的開放情況甚至內網資產情況,如確定該處存在SSRF漏洞,則可以通過確定請求成功與失敗的返回信息進行判斷服務開放情況。例如,使用Python語言寫一個簡單的利用程序。

運行結果見圖2-1-5。

圖2-1-5
2.1.3.2 使用Gopher協議擴展攻擊面
1.攻擊Redis
Redis一般運行在內網,使用者大多將其綁定于127.0.0.1:6379,且一般是空口令。攻擊者通過SSRF漏洞未授權訪問內網Redis,可能導致任意增、查、刪、改其中的內容,甚至利用導出功能寫入Crontab、Webshell和SSH公鑰(使用導出功能寫入的文件所有者為redis的啟動用戶,一般啟動用戶為root,如果啟動用戶權限較低,將無法完成攻擊)。
Redis是一條指令執行一個行為,如果其中一條指令是錯誤的,那么會繼續讀取下一條,所以如果發送的報文中可以控制其中一行,就可以將其修改為Redis指令,分批執行指令,完成攻擊。如果可以控制多行報文,那么可以在一次連接中完成攻擊。
在攻擊Redis的時候,一般是寫入Crontab反彈shell,通常的攻擊流程如下:


此時我們使用socat獲取數據包,命令如下:

將本地1234端口轉發到6379端口,再依次執行攻擊流程的指令,將得到攻擊數據,見圖2-1-6。

圖2-1-6
然后將其中的數據轉換成Gopher協議的URL。先舍棄開頭為“>”和“<”的數據,這表示請求和返回,再舍棄掉+OK的數據,表示返回的信息。在剩下的數據中,將“\r”替換為“%0d”,將“\n”(換行)替換為“%0a”,其中的“$”進行URL編碼,可以得到如下字符串:

如果需要直接在該字符串中修改反彈的IP和端口,則需要同時修改前面的“$56”,“56”為寫入Crontab中命令的長度。例如,此時字符串為

要修改反彈的IP為172.28.0.33,則需要將“56”改為“57”(56+1)。將構造好的字符串填入進行一次攻擊,見圖2-1-7,返回了5個OK,對應5條指令,此時在目標機器上已經寫入了一個Crontab,見圖2-1-8。

圖2-1-7

圖2-1-8
寫Webshell等與寫文件操作同理,修改目錄、文件名并寫入內容即可。
2.攻擊MySQL
攻擊內網中的MySQL,我們需要先了解其通信協議。MySQL分為客戶端和服務端,由客戶端連接服務端有4種方式:UNIX套接字、內存共享、命名管道、TCP/IP套接字。
我們進行攻擊依靠第4種方式,MySQL客戶端連接時會出現兩種情況,即是否需要密碼認證。當需要進行密碼認證時,服務器先發送salt,然后客戶端使用salt加密密碼再驗證。當不需進行密碼認證時,將直接使用第4種方式發送數據包。所以,在非交互模式下登錄操作MySQL數據庫只能在空密碼未授權的情況下進行。
假設想查詢目標服務器上數據庫中user表的信息,我們先在本地新建一張user表,再使用tcpdump進行抓包,并將抓到的流量寫入/pcap/mysql.pcap文件。命令如下:

開始抓包后,登錄MySQL服務器進行查詢操作,見圖2-1-9。

圖2-1-9
然后使用wireshark打開/pcap/mysql.pcap數據包,過濾MySQL,再隨便選擇一個包并單擊右鍵,在彈出的快捷菜單中選擇“追蹤流 → TCP流”,過濾出客戶端到服務端的數據包,最后將格式調整為HEX轉儲,見圖2-1-10。
此時便獲得了從客戶端到服務端并執行命令完整流程的數據包,然后將其進行URL編碼,得到如下數據:

進行攻擊,獲得user表中的數據,見圖2-1-11。

圖2-1-10

圖2-1-11
3.PHP-FPM攻擊
利用條件如下:Libcurl,版本高于7.45.0;PHP-FPM,監聽端口,版本高于5.3.3;知道服務器上任意一個PHP文件的絕對路徑。
首先,FastCGI本質上是一個協議,在CGI的基礎上進行了優化。PHP-FPM是實現和管理FastCGI的進程。在PHP-FPM下如果通過FastCGI模式,通信還可分為兩種:TCP和UNIX套接字(socket)。
TCP模式是在本機上監聽一個端口,默認端口號為9000,Nginx會把客戶端數據通過FastCGI協議傳給9000端口,PHP-FPM拿到數據后會調用CGI進程解析。
Nginx配置文件如下所示:

PHP-FPM配置如下所示:

既然通過FastCGI與PHP-FPM通信,那么我們可以偽造FastCGI協議包實現PHP任意代碼執行。FastCGI協議中只可以傳輸配置信息、需要被執行的文件名及客戶端傳進來的GET、POST、Cookie等數據,然后通過更改配置信息來執行任意代碼。
在php.ini中有兩個非常有用的配置項。
? auto_prepend_file:在執行目標文件前,先包含auto_prepend_file中指定的文件,并且可以使用偽協議如php://input。
? auto_append_file:在執行目標文件后,包含auto_append_file指向的文件。
php://input是客戶端HTTP請求中POST的原始數據,如果將auto_prepend_file設定為php://input,那么每個文件執行前會包含POST的數據,但php://input需要開啟allow_url_include,官方手冊雖然規定這個配置規定只能在php.ini中修改,但是FastCGI協議中的PHP_ADMIN_VALUE選項可修改幾乎所有配置(disable_functions不可修改),通過設置PHP_ADMIN_VALUE把allow_url_include修改為True,這樣就可以通過FastCGI協議實現任意代碼執行。
使用網上已公開的Exploit,地址如下:

這里需要前面提到的限制條件:需要知道服務器上一個PHP文件的絕對路徑,因為在include時會判斷文件是否存在,并且security.limit_extensions配置項的后綴名必須為.php,一般可以使用默認的/var/www/html/index.php,如果無法知道Web目錄,可以嘗試查看PHP默認安裝中的文件列表,見圖2-1-12。
使用Exploit進行攻擊,結果見圖2-1-13。
使用nc監聽某個端口,獲取攻擊流量,見圖2-1-14。將其中的數據進行URL編碼得到:

圖2-1-12

圖2-1-13

圖2-1-14

其攻擊結果見圖2-1-15。

圖2-1-15
4.攻擊內網中的脆弱Web應用
內網中的Web應用因為無法被外網的攻擊者訪問到,所以往往會忽視其安全威脅。
假設內網中存在一個任意命令執行漏洞的Web應用,代碼如下:

在本地監聽任意端口,然后對此端口發起一次POST請求,以抓取請求數據包,見圖2-1-16。
去掉監聽的端口號,得到如下數據包:


圖2-1-16

將其改成Gopher協議的URL,改變規則同上。執行uname-a命令:

攻擊結果見圖2-1-17。

圖2-1-17
2.1.3.3 自動組裝Gopher
目前已經有人總結出多種協議并寫出自動轉化的腳本,所以大部分情況下不需要再手動進行抓包與轉換。推薦工具https://github.com/tarunkant/Gopherus,使用效果見圖2-1-18。
2.1.4 SSRF的繞過
SSRF也存在一些WAF繞過場景,本節將簡單進行分析。
2.1.4.1 IP的限制
使用Enclosed alphanumerics代替IP中的數字或網址中的字母(見圖2-1-19),或者使用句號代替點(見圖2-1-20)。

圖2-1-18

圖2-1-19

圖2-1-20
如果服務端過濾方式使用正則表達式過濾屬于內網的IP地址,那么可以嘗試將IP地址轉換為進制的方式進行繞過,如將127.0.0.1轉換為十六進制后進行請求,見圖2-1-21。
可以將IP地址轉換為十進制、八進制、十六進制,分別為2130706433、17700000001、7F000001。在轉換后進行請求時,十六進制前需加0x,八進制前需加0,轉換為八進制后開頭所加的0可以為多個,見圖2-1-22。

圖2-1-21

圖2-1-22
另外,IP地址有一些特殊的寫法,如在Windows下,0代表0.0.0.0,而在Linux下,0代表127.0.0.1,見圖2-1-23。所以,某些情況下可以用http://0進行請求127.0.0.1。類似127.0.0.1這種中間部分含有0的地址,可以將0省略,見圖2-1-24。
2.1.4.2 302跳轉
網絡上存在一個名叫xip.io的服務,當訪問這個服務的任意子域名時,都會重定向到這個子域名,如127.0.0.1.xip.io,見圖2-1-25。

圖2-1-23

圖2-1-24

圖2-1-25
這種方式可能存在一個問題,即在傳入的URL中存在關鍵字127.0.0.1,一般會被過濾,那么,我們可以使用短網址將其重定向到指定的IP地址,如短網址http://dwz.cn/11SMa,見圖2-1-26。
有時服務端可能過濾了很多協議,如傳入的URL中只允許出現“http”或“https”,那么可以在自己的服務器上寫一個302跳轉,利用Gopher協議攻擊內網的Redis,見圖2-1-27。
2.1.4.3 URL的解析問題
CTF線上比賽中出現過一些利用組件解析規則不同而導致繞過的題目,代碼如下:

圖2-1-26

圖2-1-27


如果傳入的URL為http://a@127.0.0.1:80@baidu.com,那么進入safe_request_url后,parse_url取到的host其實是baidu.com,而curl取到的是127.0.0.1:80,所以實現了檢測IP時是正常的一個網站域名而實際curl請求時卻是構造的127.0.0.1,以此實現了SSRF攻擊,獲取flag時的操作見圖2-1-28。
除了PHP,不同語言對URL的解析方式各不相同,進一步了解可以參考:https://www.blackhat.com/docs/us-17/thursday/us-17-Tsai-A-New-Era-Of-SSRF-Exploiting-URL-Parser-In-Trending-Programming-Languages.pdf。

圖2-1-28
2.1.4.4 DNS Rebinding
在某些情況下,針對SSRF的過濾可能出現下述情況:通過傳入的URL提取出host,隨即進行DNS解析,獲取IP地址,對此IP地址進行檢驗,判斷是否合法,如果檢測通過,則再使用curl進行請求。那么,這里再使用curl請求的時候會做第二次請求,即對DNS服務器重新請求,如果在第一次請求時其DNS解析返回正常地址,第二次請求時的DNS解析卻返回了惡意地址,那么就完成了DNS Rebinding攻擊
DNS重綁定的攻擊首先需要攻擊者自己有一個域名,通常有兩種方式。第一種是綁定兩條記錄,見圖2-1-29。這時解析是隨機的,但不一定會交替返回。所以,這種方式需要一定的概率才能成功。

圖2-1-29
第二種方式則比較穩定,自己搭建一個DNS Server,在上面運行自編的解析服務,使其每次返回的都不同。
先給域名添加兩條解析,一條A記錄指向服務器地址,一條NS記錄指向上條記錄地址。
DNS Server代碼如下:


請求結果見圖2-1-30。

圖2-1-30

圖2-1-30(續)
2.1.5 CTF中的SSRF
1.胖哈勃杯第十三屆CUIT校賽Web300短域名工具
本題考察的知識點主要是重綁定繞過WAF和DICT協議的利用。PHP的WAF在進行判斷時,第一次會解析域名的IP,然后判斷是否為內網IP,如果不是,則用CURL去真正請求該域名。這里涉及CURL請求域名的時候會第二次進行解析,重新對DNS服務器進行請求獲取一個內網IP,這樣就繞過了限制。實際效果見1.3.4.4節。
在題目中,請求http://域名/tools.php?a=s&u=http://ip:88/_testok等價于http://127.0.0.1/tools.php?a=s&u=http://ip:88/_testok;同時,信息搜集可以從phpinfo中獲得很多有用的信息,如redis的主機,見圖2-1-31。

圖2-1-31
另外,libcurl為7.19.7的老版本,只支持TFTP、FTP、Telnet、DICT、HTTP、FILE協議,一般使用Gopher協議攻擊Redis,但其實使用DICT協議同樣可以攻擊Redis,最后的攻擊流程如下:

攻擊結果見圖2-1-32。

圖2-1-32
2.護網杯2019 easy_python
2019年護網杯中有一道SSRF攻擊Redis的題目。我們賽后模擬了題目進行復盤,當作實例進行分析。
首先,隨意登錄,發現存在一個flask的session值,登錄后為一個請求的功能,隨意對自己的VPS進行請求,會得到圖2-1-33所示的信息。
關鍵信息是使用了Python 3和urllib,查看返回包,可以得到如圖2-1-34所示的信息。

圖2-1-33

圖2-1-34
看到返回包中的Nginx,有經驗的參賽者會猜到是Nginx配置錯誤導致目錄穿越的漏洞,而題目雖然沒有開目錄遍歷,但是仍然可以構造從/static../__pycache__/獲取pyc文件。由于不知道文件名,遍歷常用文件名,可以得到main.cpython-37.pyc和views.cpython-37.pyc,見圖2-1-35。

圖2-1-35
然后對請求功能進行測試,發現不允許請求本機地址,見圖2-1-36。
其實這里針對本地的繞過很簡單,查看代碼發現過濾并不嚴格,使用0代表本機即可,見圖2-1-37。

圖2-1-36

圖2-1-37
pyc反編譯,得到源碼后,可知后端存在一個沒有密碼的Redis,那么明顯需要攻擊Redis。這里結合之前得到的信息,猜測使用CVE-2019-9740(Python urllib CRLF injection)應該可以實現攻擊目的。而這里無法通過常規的攻擊方法反彈shell或者直接寫webshell,通過閱讀flask-session庫的代碼可知存入的數據是pickle序列化后的字符串,那么我們可以通過這個CRLF漏洞寫入一個惡意的序列化字符串,再訪問頁面觸發反彈回shell,寫入惡意序列化字符串代碼如下:


通過在彈回來的shell中查看信息,可以知道需要進行提權,見圖2-1-38。

圖2-1-38
拿到shell后,信息搜集發現,Redis是使用root權限啟動的,但寫SSH私鑰和webshell等不太現實,于是考慮可以利用Redis的主從模式(在2019年的WCTF2019 Final上,LC?BC戰隊成員在賽后分享上介紹了由于redis的主從復制而導致的新的RCE利用方式)去RCE讀flag。
這里介紹Redis的主從模式。Redis為了應對讀寫量較大的問題,提供了一種主從模式,使用一個Redis實例作為主機只負責寫,其余實例都為從機,只負責讀,主從機間數據相同,其次在Redis 4.x后新增加了模塊的功能,通過外部的擴展可以實現一條新的Redis命令,因為此時已經完全控制了Redis,所以可以通過將此機設置為自己VPS的從機,在主機上通過FULLSYNC同步備份一個惡意擴展到從機上加載。在Github上可以搜到關于該攻擊的exp,如https://github.com/n0b0dyCN/redis-rogue-server。
這里因為觸發點的原因,不能完全使用上述exp提供的流程去運行。
先在shell中設置為VPS的從機,再設置dbfilename為exp.so,手動執行完exp中的前兩步,見圖2-1-39。

圖2-1-39
然后去掉加載模塊后面的所有功能,在VPS上運行exp。最后在Redis上手動執行剩下的步驟,使用擴展提供的功能讀取flag即可,見圖2-1-40。

圖2-1-40