from flask import request
from urllib.request import urlopen
@app.route('/example')
def example():
url = request.args["url"]
urlopen(url).read() # Noncompliant
Server-Side Request Forgery (SSRF) occurs when attackers can coerce a server to
perform arbitrary requests on their behalf.
An SSRF vulnerability can either be basic or blind, depending on whether the
server’s fetched data is directly returned in the web application’s response.
The absence of the corresponding response for the coerced request on the
application is not a barrier to exploitation and thus must be treated in the
same way as basic SSRF.
SSRF usually results in unauthorized actions or data disclosure in the vulnerable application or on a different system it can reach. Conditional to what is reachable, remote command execution can be achieved, although it often requires chaining with further exploitations.
Information disclosure is SSRF’s core outcome. Depending on the extracted data, an attacker can perform a variety of different actions that can range from low to critical severity.
Below are some real-world scenarios that illustrate some impacts of an attacker exploiting the vulnerability.
An attacker manipulates an application into performing a local request for a
sensitive file, such as ~/.ssh/id_rsa
, by using the File URI scheme
file://
.
Once in possession of the SSH keys, the attacker establishes a remote
connection to the system hosting the web application.
An attacker enumerates internal accessible ports from the affected server or
others to which the server can communicate by iterating over the port field in
the URL http://127.0.0.1:{port}
.
Taking advantage of other supported URL schemas (dependent on the affected
system), for example, gopher://127.0.0.1:3306
, an attacker would be able to
connect to a database service and perform queries on it.
The following code is vulnerable to SSRF as it opens a URL defined by untrusted data.
from flask import request
from urllib.request import urlopen
@app.route('/example')
def example():
url = request.args["url"]
urlopen(url).read() # Noncompliant
from flask import request
from urllib.parse import urlparse
from urllib.request import urlopen
SCHEMES_ALLOWLIST = ['https']
DOMAINS_ALLOWLIST = ['trusted1.example.com', 'trusted2.example.com']
@app.route('/example')
def example():
url = request.args["url"]
if urlparse(url).hostname in DOMAINS_ALLOWLIST and urlparse(url).scheme in SCHEMES_ALLOWLIST:
urlopen(url).read()
Create a list of authorized and secure URLs that you want the application
to be able to request.
If a user input does not match an entry in this list, it should be rejected
because it is considered unsafe.
Important note: The application must do validation on the server side. Not on client-side front-ends.
While whitelisting URLs is the preferred approach to ensure only safe URLs are accessible, there are scenarios where blacklisting may be necessary.
If whitelisting is not feasible, blacklisting can serve as a partial defense against SSRF attacks, particularly when the objective is to block access to internal resources or specific known malicious URLs.
When implementing blacklisting, it is crucial to:
Comprehensively Check URLs: Ensure that the URL scheme, domain, and path are all scrutinized. This prevents attackers from circumventing the blacklist by altering schemes or paths.
Understand Limitations: Recognize that blacklisting is not a foolproof solution. It should be part of a multi-layered security strategy to effectively mitigate SSRF risks.
By adhering to these guidelines, blacklisting can be a useful, albeit secondary, measure in protecting against SSRF attacks.
When validating untrusted URLs by checking if they start with a trusted scheme
and authority pair scheme://authority
, ensure that the validation string
contains a path separator /
as the last character.
If the validation string does not contain a terminating path separator, the SSRF vulnerability remains; only the exploitation technique changes.
Thus, a validation like startsWith("https://example.com")
or an equivalent
with the regex ^https://example\.com.*
can be exploited with the following
URL https://example.commit.malicious.io
.
When employing a blacklist to mitigate SSRF attacks, it is essential to guard against Time-Of-Check Time-Of-Use (TOCTOU) vulnerabilities in the validation logic.
A common example of a TOCTOU vulnerability occurs when the domain name is resolved to an IP address for blacklist validation, but the hostname is resolved again later by the request library to make the actual request. An attacker could exploit DNS rebinding to change the IP address between these two resolutions and bypass the blacklist.
To prevent this, ensure that the domain name is resolved to an IP address only once, and this IP address is used consistently throughout the validation and request process.
The following code is vulnerable to SSRF as it performs an HTTP request to a URL defined by untrusted data.
from flask import request
import requests
@app.route('/example')
def example():
url = request.args["url"]
requests.get(url).content # Noncompliant
from flask import request
import requests
from urllib.parse import urlparse
DOMAINS_ALLOWLIST = ['trusted1.example.com', 'trusted2.example.com']
@app.route('/example')
def example():
url = request.args["url"]
if urlparse(url).hostname in DOMAINS_ALLOWLIST:
requests.get(url).content
Create a list of authorized and secure URLs that you want the application
to be able to request.
If a user input does not match an entry in this list, it should be rejected
because it is considered unsafe.
Important note: The application must do validation on the server side. Not on client-side front-ends.
The compliant code example uses such an approach.
The requests
library implicitly validates the scheme as it only allows http
and https
by default.
While whitelisting URLs is the preferred approach to ensure only safe URLs are accessible, there are scenarios where blacklisting may be necessary.
If whitelisting is not feasible, blacklisting can serve as a partial defense against SSRF attacks, particularly when the objective is to block access to internal resources or specific known malicious URLs.
When implementing blacklisting, it is crucial to:
Comprehensively Check URLs: Ensure that the URL scheme, domain, and path are all scrutinized. This prevents attackers from circumventing the blacklist by altering schemes or paths.
Understand Limitations: Recognize that blacklisting is not a foolproof solution. It should be part of a multi-layered security strategy to effectively mitigate SSRF risks.
By adhering to these guidelines, blacklisting can be a useful, albeit secondary, measure in protecting against SSRF attacks.
When validating untrusted URLs by checking if they start with a trusted scheme
and authority pair scheme://authority
, ensure that the validation string
contains a path separator /
as the last character.
If the validation string does not contain a terminating path separator, the SSRF vulnerability remains; only the exploitation technique changes.
Thus, a validation like startsWith("https://example.com")
or an equivalent
with the regex ^https://example\.com.*
can be exploited with the following
URL https://example.commit.malicious.io
.
When employing a blacklist to mitigate SSRF attacks, it is essential to guard against Time-Of-Check Time-Of-Use (TOCTOU) vulnerabilities in the validation logic.
A common example of a TOCTOU vulnerability occurs when the domain name is resolved to an IP address for blacklist validation, but the hostname is resolved again later by the request library to make the actual request. An attacker could exploit DNS rebinding to change the IP address between these two resolutions and bypass the blacklist.
To prevent this, ensure that the domain name is resolved to an IP address only once, and this IP address is used consistently throughout the validation and request process.
The following code is vulnerable to SSRF as it performs an HTTP request to a URL defined by untrusted data.
from fastapi import FastAPI
import aiohttp
app = FastAPI()
@app.get('/example')
async def example(url: str):
async with aiohttp.request('GET', url) as response: # Noncompliant
return {"response": await response.text()}
from fastapi import FastAPI
from fastapi.responses import JSONResponse
import aiohttp
from urllib.parse import urlparse
DOMAINS_ALLOWLIST = ['trusted1.example.com', 'trusted2.example.com'];
app = FastAPI()
@app.get('/example')
async def example(url: str):
if urlparse(url).hostname not in DOMAINS_ALLOWLIST:
return JSONResponse({"error": f"URL {url} is not whitelisted."}, 400)
async with aiohttp.request('GET', url.unicode_string()) as response:
return {"response": await response.text()}
Create a list of authorized and secure URLs that you want the application
to be able to request.
If a user input does not match an entry in this list, it should be rejected
because it is considered unsafe.
Important note: The application must do validation on the server side. Not on client-side front-ends.
The compliant code example uses such an approach.
When validating untrusted URLs by checking if they start with a trusted scheme
and authority pair scheme://authority
, ensure that the validation string
contains a path separator /
as the last character.
If the validation string does not contain a terminating path separator, the SSRF vulnerability remains; only the exploitation technique changes.
Thus, a validation like startsWith("https://example.com")
or an equivalent
with the regex ^https://example\.com.*
can be exploited with the following
URL https://example.commit.malicious.io
.
The following code is vulnerable to SSRF as it performs an HTTP request to a URL defined by untrusted data.
from fastapi import FastAPI
import httpx
app = FastAPI()
@app.get('/example')
def example(url: str):
r = httpx.get(url) # Noncompliant
return {"response": r.text}
from fastapi import FastAPI
from fastapi.responses import JSONResponse
import httpx
from urllib.parse import urlparse
DOMAINS_ALLOWLIST = ['trusted1.example.com', 'trusted2.example.com']
app = FastAPI()
@app.get('/example')
def example(url: str):
if not urlparse(url).hostname in DOMAINS_ALLOWLIST:
return JSONResponse({"error": f"URL {url} is not whitelisted."}, 400)
r = httpx.get(url)
return {"response": r.text}
Create a list of authorized and secure URLs that you want the application
to be able to request.
If a user input does not match an entry in this list, it should be rejected
because it is considered unsafe.
Important note: The application must do validation on the server side. Not on client-side front-ends.
The compliant code example uses such an approach.
HTTPX implicitly validates the scheme as it only allows http
and https
by default.
While whitelisting URLs is the preferred approach to ensure only safe URLs are accessible, there are scenarios where blacklisting may be necessary.
If whitelisting is not feasible, blacklisting can serve as a partial defense against SSRF attacks, particularly when the objective is to block access to internal resources or specific known malicious URLs.
When implementing blacklisting, it is crucial to:
Comprehensively Check URLs: Ensure that the URL scheme, domain, and path are all scrutinized. This prevents attackers from circumventing the blacklist by altering schemes or paths.
Understand Limitations: Recognize that blacklisting is not a foolproof solution. It should be part of a multi-layered security strategy to effectively mitigate SSRF risks.
By adhering to these guidelines, blacklisting can be a useful, albeit secondary, measure in protecting against SSRF attacks.
When validating untrusted URLs by checking if they start with a trusted scheme
and authority pair scheme://authority
, ensure that the validation string
contains a path separator /
as the last character.
If the validation string does not contain a terminating path separator, the SSRF vulnerability remains; only the exploitation technique changes.
Thus, a validation like startsWith("https://example.com")
or an equivalent
with the regex ^https://example\.com.*
can be exploited with the following
URL https://example.commit.malicious.io
.
When employing a blacklist to mitigate SSRF attacks, it is essential to guard against Time-Of-Check Time-Of-Use (TOCTOU) vulnerabilities in the validation logic.
A common example of a TOCTOU vulnerability occurs when the domain name is resolved to an IP address for blacklist validation, but the hostname is resolved again later by the request library to make the actual request. An attacker could exploit DNS rebinding to change the IP address between these two resolutions and bypass the blacklist.
To prevent this, ensure that the domain name is resolved to an IP address only once, and this IP address is used consistently throughout the validation and request process.
OWASP - Top 10 2021 Category A10 - Server-Side Request Forgery (SSRF)
STIG Viewer - Application Security and Development: V-222609 - The application must not be subject to input handling vulnerabilities.
(visible only on this page)
Change this code to not construct the request from user-controlled data.
"[varname]" is tainted (assignments and parameters)
this argument is tainted (method invocations)
the returned value is tainted (returns & method invocations results)