Backend security is the most important thing when developing web applications. Successfully exploited backend vulnerabilities might affect every user of the application, expose sensitive data, and grant further access to internal networks. According to the OWASP top 10 list of vulnerabilities, Server-Side Request Forgery is among the most common vulnerabilities in modern web applications. In this article, we will explore what a Server-Side Request Forgery is, why it plagues backends, how dangerous it can be, common mistakes in protections against it, and of course, how to prevent this particular vulnerability.
What is Server-Side Request Forgery?
Server-Side Request Forgery - or, shortly, SSRF - is a web application vulnerability arising from insufficient checks of user-supplied URLs before sending a request to that URL. The idea is to trick a vulnerable web application into requesting and displaying resources that are not meant to be seen by regular users.
Picture 1, Typical SSRF exploitation workflow
Usually, SSRF is associated with HTTP(s) protocol, but the attack can be carried out with a wide range of URI schemes:
- file:// (Allows accessing local files)
- ftp:// or sftp:// (Allows accessing files over (Secure) File Transfer Protocol)
- tftp:// (Allows accessing files over Trivial File Transfer Protocol)
- ldap:// ( Allows accessing files over Lightweight Directory Access Protocol )
- dict:// (Allows accessing files over Dictionary Network Protocol)
- gopher:// (Allows interacting with other servers over Gopher Protocol)
- And many others…
SSRF is commonly found in web applications that have features granting the ability to interact with remote resources, for instance:
- Importing documents or images
- Using user-supplied API endpoints, like Webhooks
- Executing user-supplied code
- Generating documents from user-supplied data
- Administrative dashboards that allow pinging for checking server statuses
Consider the following example of a Node.js web application that has an “Analytics” dashboard, where admins can specify custom URL to a PDF template. Here’s a snippet of the source code:
Picture 2, AnalyticsController
Picture 3, AnalyticsService
Picture 4, HttpService
You may notice that XMLHttpRequest is opened with an unvalidated user-supplied URL, exposing the backend to Server-Side Request Forgery vulnerability. XMLHttpRequest supports file:// , http(s):// , ws(s):// schemes, so in this case, an attacker may read local files and enumerate services in the internal network.
Is SSRF really dangerous?
The severity of Server-Side Request Forgery highly depends on the environment, but under no circumstances can it be ignored. Usually, SSRF exploitation leads to sensitive data exposure, but there are scenarios where an attacker can perform even more destructive actions on the vulnerable system. Here’s a summary of what an attacker might be able to do with the SSRF vulnerability:
- Remote Code Execution (Gopherus)
- Leaking Cloud Credentials (AWS)
- Authentication Bypass
- Local File Inclusion
- Leaking NetNTLM hashes (on Windows servers)
- Interaction with mail servers
- Discovering hidden hosts, performing port scans
What kinds of SSRF are possible?
Classification isn’t really that complicated. Usually 3 types of SSRF are singled out: In-Band, Blind, and Time-Based:
- If an attacker is lucky, they can include the results of SSRF attacks directly into the backend's responses - this is called In-Band SSRF.
- With Blind SSRF exploit, results do not appear in backend’s responses at all. Instead, they are sent to some other location, like a third-party provider. An attacker would have to figure out a way to trick a web app into sending the exploit results to a server they control.
- Last but not least - Time-Based SSRF. In the case of Time-Based SSRF, the backend neither reveals SSRF results nor makes outbound connections to resources an attacker might control. However, it sends requests to some other resources, which increases the response time. An attacker might be able to recover sensitive information by conducting a timing attack.
Common mistakes when mitigating SSRF
The most common mistake is validating a URL against a regex describing allowed resources. This is always a horrible idea, as the specification for URLs is quite complicated. An attacker can use various URL features to bypass regex tests:
- Content after “#” is not being sent, so an attacker might craft a URL that has all the desired properties after the hashtag, while the actual host will be controlled by them. For example, “https://attacker.com/#expected-host.com”
- According to the URL specification, you can specify username & password between URL scheme and host. So, it is possible to create a URL like “htttps://expected-host:[email protected]” pointing to an attacker’s server.
- It is also possible to abuse the domain name resolution algorithm and create a subdomain identical to the expected one: “https://expected-host.com.attacker.com”.
- DNS rebinding attacks can be used to bypass such protections
The other common mistake is preferring blacklisting of domains/IPs over whitelisting. When developers want to protect sensitive servers, they often reject URLs that have domains/IP addresses, associated with those servers. However, such blacklists can be easily bypassed with the help of the following techniques:
- Replacing IPv4 addresses with IPv6
- Using an alternative representation of an IP address. For example - 127.0.0.1, 127.1, 2130706433 and 017700000001 are different representation of the same loopback IP address
- Registering your own domain name resolving to the desired IP address. For instance, “localh.st” is a domain resolving to the loopback IP address
The latter also helps bypass URL path filters: an attacker can host a web server always redirecting requests to a “protected” URL.
Proper ways of preventing SSRF
The first layer of protection is validating user input. You must ensure the URL is valid, has an allowed schema, the host is legitimate, and the path is allowed for that user. It goes without saying you shouldn’t write your own URL parsers, as they are difficult to maintain and prone to errors. It is better to use parsers from dedicated libraries, as they are well documented and tested. These libraries ensure that the user supplies a valid URL and provide you with a safe and clear way of interacting with parsed URL components. For example, when dealing with URLs in JavaScript, consider the following procedure to avoid SSRF:
- Create whitelists of allowed origins and paths
- Wrap the user-supplied URL string into “new URL(userSuppliedUrl)”
- Use URL class’s API to retrieve the origin and pathname from a URL and check them against your whitelist
- Check the user’s privileges before actually sending the request
The next step to prevent SSRF is configuring your firewall to reject requests from the web application if they are not meant for the services on your whitelist. Tools like ufw allow you to configure which connections should be allowed and which ones should be dropped. Always use firewalls as a second-line defense against Server-Side Request Forgery.
Finally, a good old IPS can help automatically detect and stop attacks carried out through SSRF. For example, Snort IPS has a rich collection of rules for identifying malicious traffic. It has signatures of the most common SSRF payloads, as well as signatures of publicly available exploits. For instance, here’s a rule for rejecting requests that contain SSRF payloads exploiting the CVE-2021-40438 vulnerability in Apache HTTP Servers. You also have the option of creating your own rules to prevent threats specific to your business.