SSRF Filter Bypasses
Server-Side Request Forgery (SSRF) vulnerabilities occur when an attacker can coerce the server to fetch remote resources using HTTP requests; this might allow an attacker to identify and enumerate services running on the local network of the web server, which an external attacker would generally be unable to access due to a firewall blocking access.
Confirming SSRF
Let us consider the following vulnerable web application to illustrate how a developer might address SSRF vulnerabilities.
Code Review - Identifying the Vulnerability
Our sample web application allows us to take screenshots of websites for which we provide URLs.
The web application contains two endpoints. The first one handles taking screenshots, while the second endpoint responds with a debug page and is only accessible from localhost:
@app.route('/', methods=['GET', 'POST'])
def index():
if request.method == 'GET':
return render_template('index.html')
try:
screenshot = screenshot_url(request.form.get('url'))
except Exception as e:
return f'Error: {e}', 400
# b64 encode image
image = Image.open(screenshot)
buffered = BytesIO()
image.save(buffered, format="PNG")
img_data = base64.b64encode(buffered.getvalue())
return render_template('index.html', screenshot=img_data.decode('utf-8'))
@app.route('/debug')
def debug():
if request.remote_addr != '127.0.0.1':
return 'Unauthorized!', 401
return render_template('debug.html')Since our target is to obtain unauthorized access to the debug page, we need to bypass the check in the /debug endpoint. However, we cannot manipulate the request.remote_addr variable, as this represents the IP address from which the request originates (i.e., our external IP address).
Screenshot Function
The web application performs basic checks including the scheme (blocking file://), but does not restrict the domain or IP address.
Exploitation
Since the web application only restricts us to the http and https schemes but does not restrict the domain or IP address, we can provide a URL pointing to the /debug endpoint:
The web application will visit its own debug endpoint such that the request originates from 127.0.0.1, granting access.
SSRF Basic Filter Bypasses
1. Obfuscation of localhost
The simplest SSRF filter explicitly blocks certain domains like localhost or 127.0.0.1:
Bypass Methods - Many ways exist to represent localhost:
Localhost Address Block
127.0.0.0 - 127.255.255.255
Shortened IP Address
127.1
Prolonged IP Address
127.000000000000000.1
All Zeroes
0.0.0.0
Shortened All Zeroes
0
Decimal Representation
2130706433
Octal Representation
0177.0000.0000.0001
Hex Representation
0x7f000001
IPv6 loopback address
0:0:0:0:0:0:0:1 (also ::1)
IPv4-mapped IPv6 loopback
::ffff:127.0.0.1
Example bypass:
2. Bypass via DNS Resolution
Improved filter that blocks private IP ranges:
Problem: The filter only blocks IP addresses, not domain names that resolve to private IPs.
Bypass: Use a domain that resolves to 127.0.0.1:
Example bypass:
3. Bypass via HTTP Redirect
Further improved filter that resolves domain names:
Problem: The filter does not account for HTTP redirects.
Bypass: Host a redirect on your server:
Then provide your server URL:
Note: Blocking redirects completely is difficult. Other redirect methods exist: JavaScript, meta tags, etc. Even if all redirects are prevented, DNS rebinding can still bypass the filter.
Prevention
The simplest and safest way to prevent SSRF is via firewall rules. The system running the vulnerable application should be separated from internal web applications, with firewall rules preventing incoming connections from the vulnerable system to internal services.
Question Walkthrough
Task: Bypass the SSRF filter to obtain the flag. The staging environment is behind a firewall that blocks all outgoing web requests.
Code Analysis
Download and analyze the source:
The /flag endpoint only allows localhost:
The check_domain function:
Vulnerability
The is_loopback function does not consider 0.0.0.0 as a loopback address (which includes all IPv4 addresses on a local machine, including 127.0.0.1).
Exploit
Or simply use 0:
Last updated