Server-Side Request Forgery (SSRF) sits at number 10 on the OWASP Top 10 list at the time of writing, and is still extremely prevalent.
An SSRF vulnerability occurs when input from the client results in the backend server making network requests, whether that’s reading a file or calling service on an internal network which is not directly accessible, or calling third-party services. By taking advantage of the trust relationship between the application and other services within the internal network, an attacker can bypass firewalls and access controls.
An example scenario is a profile page on a website, which allows a user to provide a URL to a hosted version of the image they want to use as their profile picture. Without sufficient input validation and sanitization an attacker can provide the URL of an internal service and scan for open ports on the internal network, or pass a URI to access files on the system.
Scanning for ports
To scan for what ports are open on the backend server, we can replace the filepath in our example SSRF vulnerability described above with a request like the following:
GET example.com/profile.php?image=http://127.0.0.1:22
This will try to connect to port 22 (SSH) on the backend, and present evidence of the request and its response, which is then forwarded to us in the response we receive back from the server. Through an SSRF vulnerability it is possible to access ports that are only open on the internal network.
Reading files
To read files we can manipulate the same input as before, but provide reference to a file on the server, for example:
GET example.com/profile.php?image=file:///etc/passwd
This is a universally readable file on a UNIX-like operating system, which contains all the available users on the server, and so the contents of the file may be read and subsequently returned in the response to us from the server. Through an SSRF vulnerability we can extract more information regarding the server and potentially even expose credentials to other servers and external services.
Discovering an SSRF vulnerability
SSRF vulnerabilities can only be present when requests are made by the server, the best way to identify what request may be susceptible is to ensure the origin of requests don’t come from your IP address.
You can observe the origin of requests using Portswigger’s Burp Suite platform and the Collaborator tool to poll network requests, however this is a paid for tool and doesn’t come with the free community version of Burp. An open source alternative is interactsh, I’ll link to the repository at the end of this post.
Once we have established the origin we can isolate the server-side requests for further inspection. We can gather information on the ownership of any IP addresses by making a request to ipinfo.io using curl:
curl ipinfo.io/127.0.0.1
Additional information regarding the ownership of the IP address might be useful for further enumeration or exploitation. If we know the server is hosted in AWS for example we can try to reach the metadata service using http://169.254.169.254.
If the metadata service is not accessible we can look at scanning ports and trying to access files as shown previously.
Example vulnerable code
The following isn’t functional code, and it is over-simplified, but hopefully it can help visualise how an SSRF vulnerability can find its way into our code.
getProfilePicture('/profile', image) {
return fetch(image)
}
We can pass the metadata service as our url parameter using the following URL:
/profile?image=https://169.254.169.254
The API will return the metadata service response to us. This is made possible due to the lack of sanitization or validation of the `url` parameter.
Mitigations
We can mitigate against SSRF vulnerabilities by validating and sanitising our inputs, and if we want to prevent the previous example from being vulnerable we can introduce a whitelist for domains, and add a check to ensure the url string is a valid domain and uses specific protocols only - removing access to the file protocol for reading files off the system.
getProfilePicture('/profile', image) {
if (!isValidUrl(image) || !allowList.contains(image.hostname)) return error
return fetch(image)
}
Final thoughts
Hopefully this post has helped to explain what an SSRF vulnerability is, how they find their way into our code, and how we can prevent them. I used some simple examples to explain what is likely to be a complex process to exploit in reality. Although entirely possible, it’s not likely a web application will return perfect responses when an SSRF vulnerability is present. There is a lot more work involved, and in some cases the SSRF vulnerability might be blind, which means we get no obvious feedback from the server for our efforts and need to craft payloads to see if code is being executed on the server. It’s also important to understand that just because a server is making requests it doesn’t mean it’s vulnerable to SSRF.
These types of vulnerabilities are often found where there are integrations between different systems, where webhooks are involved, or where media is being rendered to display to the user on the frontend. We can leverage Static Application Security Testing (SAST), Dynamic Application Security Testing (DAST), and Out-of-band Application Security Testing (OAST) tools to discover potential SSRF vulnerabilities in our systems. By testing early and often we can prevent these vulnerabilities from reaching production.
