Server Side Request Forgery ('SSRF')
ID |
javascript.server_side_request_forgery |
Severity |
critical |
Remediation Complexity |
medium |
Remediation Risk |
medium |
Remediation Effort |
medium |
Resource |
Channel |
Language |
JavaScript |
Tags |
ASVS50:v1.3.6, ASVS50:v13.2.5, CWE:918, NIST.SP.800-53, OWASP:2021:A10, PCI-DSS:6.5.8 |
Description
Improper validation of external input used to retrieve the content of a URL ('SSRF').
SSRF vulnerabilities arise when an application accepts user input to create or control URLs or networking requests without appropriate validation or restriction. This can allow attackers to craft malicious requests to sensitive internal endpoints or external services on behalf of the server.
Rationale
Similar to other injection attacks, an SSRF vulnerability allows an attacker to manipulate the URLs used by a server-side application, to point to unexpected internal or external resources.
By carefully selecting the URLs, the attacker can read or update internal resources, such as cloud metadata, internal services, or databases. In addition, SSRF can bypass network access controls like firewalls and VPNs.
An SSRF vulnerability can be exploited by threat actors for network reconnaissance, data exfiltration, denial of service or pivot attacks to other systems.
The following is an example of vulnerable code:
let targetUrl = req.headers.url; // untrusted input
const https = require('https')
const url = require('url')
let _url = url.parse(targetUrl)
let _port = Number(_url.port || (_url.protocol === 'https:' ? 443 : 80))
let config = {
hostname: _url.hostname,
port: _port,
path: _url.pathname,
method: 'POST'
}
// Vulnerable to SSRF !
let req = https.request(config)
// ...
The arguments a command-line program reads from process.argv are not reported: whoever runs the program chooses them, and the program sends its requests with that person’s own rights. A command-line program is a file starting with a #! line, or one that runs its code only as the main module (require.main === module); what it reads from a request it serves is still reported.
Nor is a request whose URL comes from the page, its address (location.href) or one of its elements (el.getAttribute('data-url')): that code runs in the browser, which sends the request from the user’s own network with the user’s credentials, so no server is made to send it. A page letting someone else choose what it fetches is reported by javascript.resource_injection. A file that builds a DOM on the server, with jsdom or the like, is still reported.
Severity when the destination server is fixed
The severity in the table above applies when the external input can reach the destination of the request. When the URL is built from a constant prefix that already fixes the scheme, the host and the port, the input lands after the authority and can only choose a path or a query string, so the request cannot be steered to another server. The detector recognizes this and reports the finding at low severity instead.
With BASE a constant "https://api.internal.example.com", a URL built as BASE + "/" + target
puts target in the path: it selects which endpoint of that fixed server is invoked, not which
server is invoked.
The finding is lowered rather than dropped, because the input still decides which endpoint of that server runs and may reach operations that were not meant to be callable this way. A path is also not always inert: if the fixed host answers with a redirect, an HTTP client that follows redirects can still end up requesting a location the caller did not choose.
The severity is left unchanged whenever the input can still affect the destination:
-
it reaches the scheme, the host or the port - for example
SCHEME + target + "/api","https://" + tenant + ".example.com/api"orBASE + ":" + port + "/api"; -
the prefix does not close the authority, as in
BASE + suffix, where the input can extend the host name into a different domain; -
the prefix cannot be resolved to a constant - a value imported from another module, a parameter default, or a name that is assigned again after its declaration, is not treated as fixed;
-
the prefix does not describe a host that a URL could carry, one with an empty label say. That is not a URL anyone writes, it is what is left when part of the expression could not be resolved, and the part that could not be resolved is the one under external control.
It is also left unchanged when the fixed host is itself the sensitive target, because there
choosing the path is the attack rather than a limitation of it: with a URL fixed to
http://169.254.169.254/latest, the path selects which instance credentials are read. This covers
cloud instance-metadata endpoints, loopback addresses, Kubernetes and cloud-internal service names,
and private or otherwise reserved address ranges. The check is purely lexical, so a public host name
that resolves to a private address at run time is not recognized.
Remediation
To fix the previous issue, whitelist the target URL in the application code.
let targetUrl = req.headers.url; // untrusted input
const allowed = /(http|https):\/\/\w+.example.com(:\d+)?\/.+/
if (!targetUrl.match(allowed)) {
return res.status(400).send('Bad Request')
}
// logic as before
let _url = url.parse(targetUrl)
// ...
To prevent SSRF vulnerabilities in applications, follow these remediation practices:
-
Input Validation and Whitelisting: Always sanitize and validate user-supplied data in URLs using a whitelist of known, safe URLs. Reject any URLs not meeting established criteria to limit exposure to external or unauthorized internal requests.
-
Use a Proxy or Gateway Approach: Route all outbound requests through a proxy or gateway, allowing finer control over which requests should be allowed and logged.
-
Network Segmentation and Firewalls: Implement network segmentation to restrict the application’s ability to access sensitive internal services. Use firewalls to block traffic by default and allow only specific, necessary requests.
-
Monitoring and Logging: Implement comprehensive request logging and monitoring solutions to detect suspicious outbound requests indicative of SSRF attempts.
By following these recommended practices, applications can effectively defend against SSRF vulnerabilities, ensuring that server-side requests are controlled and properly secured from unauthorized exploitation.
Configuration
The detector has the following configurable parameters:
-
sources, that indicates the source kinds to check. -
neutralizations, that indicates the neutralization kinds to check.
Unless you need to change the default behavior, you typically do not need to configure this detector.
References
-
CWE-918 : Server-Side Request Forgery (SSRF).
-
OWASP - Top 10 2021 Category A10 : Server-Side Request Forgery (SSRF).
-
Server-Side Request Forgery Prevention Cheat Sheet, in OWASP Cheat Sheet Series.