Server-side request forgery (SSRF) is a common vulnerability that can crop up unknowingly in any Node.js application. It poses a significant threat because attackers can manipulate a server into making unintended requests to both internal and external resources.
This article will explore SSRF, its potential risks, and the strategies to mitigate SSRF in Node.js applications.
Highly recommended: Take the interactive server-side request forgery lesson on Snyk Learn.
What is SSRF?
SSRF occurs when an attacker exploits the requests sent from a web application's server by input tampering or URL manipulation.
Input tampering is the unauthorized modification of data, often user-generated, to exploit vulnerabilities in an application. In contrast, URL confusion vulnerabilities are the alteration of URLs to trick a system into performing unintended actions, such as accessing unauthorized resources or making rogue requests.
Under the attacker's influence, the server makes requests to the manipulated URLs, appearing as if initiated by the server itself, thus bypassing typical security measures. A successful SSRF attack can give an attacker access to internal resources, databases, and files, exposing sensitive data like credentials.
For example, in one of the most notable SSRF incidents, a former Amazon software development engineer exploited an SSRF vulnerability that exposed 140,000 Social Security numbers, 80,000 bank account numbers, and 1 million social insurance numbers. The breach affected 106 million people and incurred $250 million USD in damages.
A simple SSRF attack example
To better understand SSRF vulnerabilities, take a look at a specific example. In this scenario, you're working on a web application that enables users to input a URL, retrieve an image from that URL, and display it to the user.
If you want to follow along, clone this GitHub repository, which contains the source code for the web application. This application uses Express, a Node.js framework that simplifies building web services. It also uses Axios, an HTTP library that lets you make requests from Node.js.
To clone the repository using Git, run the following command:
git clone https://github.com/davidekete/ssrf.git
Please note: You must have Git installed on your system to run this command.
After cloning the GitHub repository, run the following command to install the dependencies:
npm install
Please note: You must have Node.js and npm installed to run this command.
The application has three routes:
-
**/upload-image-url**
allows a user to upload an image using the URL. It's publicly accessible and, at a glance, does not do any harm. -
**/users**
returns all the user information for users in your application. It should not be publicly accessible. -
**/users/:id/profile**
returns a particular user's profile. It should not be publicly accessible.
Start the application with the following command:
node index.js
When you make a request using Insomnia (or any other API testing tool) to /upload-image-url
with a valid image URL, you should get the following response:
When malicious users get this response, they know your server makes API calls internally using the provided URL. Now, they can use this information to test different URLs and figure out your server's internal structure or pass URLs, such as http://169.254.169.254
or http://instance-data
, to access your application's Amazon Web Services (AWS) metadata if your application is running on an Amazon Elastic Compute Cloud (Amazon EC2) instance. This type of SSRF is known as basic SSRF.
Basic SSRF occurs when an attacker manipulates the server into making requests to unintended resources, often without direct knowledge of the server's internal structure. In this case, the attacker can usually see the request's response.
Another type of SSRF is blind SSRF, where an attacker makes SSRF requests but doesn't have direct access to the server's response. Instead, they rely on the application's behavior to indirectly confirm the success of their SSRF attack.
For instance, if an attacker decides to try the URL localhost:8050/users
, they'll receive the following response (ie a basic SSRF), leaking all your users' data:
Your code is vulnerable to SSRF attacks because it accepts user-provided URLs without proper validation or access control, allowing users to make requests to potentially sensitive internal or private resources. There's also no mechanism in place for allowlist or denylist of specific domains.
To mitigate this vulnerability, your code should sanitize user input, restrict access to trusted domains, use firewalls, and establish proper error handling and monitoring practices.
How to prevent SSRF vulnerabilities in Node.js
If you want to prevent SSRF vulnerabilities in your Node.js application, you have some options. In the following section, you'll learn about some methods and best practices that can help.
Use the latest versions of libraries
Using the latest versions of libraries is an important part of enhancing your application's security. New versions often include critical security patches and bug fixes, which may make your application less vulnerable to common attacks like SSRF.
However, it's essential to consider compatibility issues, breaking changes, and backward compatibility when updating. Keep an eye on deprecation cycles, gradually update larger codebases, and regularly analyze dependencies for potential security vulnerabilities to maintain a secure and well-maintained application.
Use Snyk to upgrade libraries and keep your dependencies up-to-date and free of publicly known security vulnerabilities.
Use a firewall
A firewall is a network security system that acts as a protective barrier and monitors and controls network traffic based on predetermined rules.
To prevent SSRF attacks using firewalls, you can deploy a Web Application Firewall (WAF) to inspect and block malicious requests by creating an allowlist of trusted domains and denying suspicious URLs or IP addresses.
Sanitize user input
Input sanitization is a security practice you can use to clean and modify user-provided data to make it safe for processing within your application. Its primary purpose is to prevent security vulnerabilities that can stem from untrusted or malicious input.
You can prevent SSRF by sanitizing all user input to ensure the URLs are properly formatted and don't contain unexpected characters before processing them in your application.
While input sanitization can help ensure that URLs are correctly formatted, you should not use input sanitization in isolation.
Enforce URL schemas
Another way you can prevent SSRF vulnerabilities is by enforcing specific URL schemas. By specifying your application's allowed URL schemes (eg HTTP and HTTPS), you can restrict users from inputting URLs with potentially dangerous or unwanted schemas (eg file://
and ftp://
).
To implement this in the application you cloned earlier, you can explicitly check that the provided URL starts with an allowed schema, typically http://
or https://
. If the URL doesn't adhere to these schemas, you can reject it:
const validURLPattern = /^https?:\/\//;
const imageURL = req.body.imageURL;
if (!imageURL || !validURLPattern.test(imageURL)) {
return res.status(400).send("Invalid or missing image URL");
}
Create an allowlist of trusted domains
Creating an allowlist of trusted domains prevents SSRF vulnerabilities by allowing requests only to specific domains that are known to be safe and necessary for your application.
To implement this in the application you cloned earlier, create an array (in a real-world application, you might need a more robust way) containing the domains you want to trust and allow your application to request. These domains should be those your application interacts with and considers safe.
Then before requesting the user-provided URL, check if it matches any of the trusted domains in your list. If it doesn't match, block the request. After implementing this, your /upload-image-url
route should look like this:
app.post("/upload-image-url", async (req, res) => {
try {
const trustedDomains = [
"https://unsplash.com/",
"https://example.com",
"https://pexels.com/",
];
const imageURL = req.body.imageURL;
if (!imageURL) {
return res.status(400).send("Image URL is required");
}
let isTrustedDomain = false;
for (const domain of trustedDomains) {
if (imageURL.startsWith(domain)) {
isTrustedDomain = true;
break;
}
}
if (!isTrustedDomain) {
return res.status(403).send("Access to this domain is not permitted.");
}
// Proceed with the request to the trusted domain.
const image = await axios.get(imageURL);
return res.json({
response: "Image uploaded successfully",
image: image.data,
});
} catch (error) {
if (axios.isAxiosError(error)) {
return res.status(error.response.status).send(error.response.data);
}
res.status(500).send("Something went wrong");
}
});
This code modifies the /upload-image-url
route by adding an array of allowed domains, checking if the URL passed by the user is part of the allowlist, and rejecting the request if the URL is not in the allowlist.
Use the Snyk IDE extension to detect vulnerabilities
Snyk is a popular security tool that can help identify and fix vulnerabilities, including SSRF, in open source libraries. Snyk offers integrations with various development environments and IDEs, including Visual Studio Code (VS Code).
To install the Snyk IDE extension on VS Code, navigate to the Extensions view by clicking the square icon on the left sidebar or pressing Ctrl + Shift + X (or Cmd + Shift + X on macOS):
Search for "Snyk" in the Extensions Marketplace and click the Install button next to the Snyk extension:
After installing the extension, you must authenticate it with your Snyk account. If you don't have a Snyk account, make sure you sign up for one now. Click on the Snyk icon and sign in with your Snyk credentials.
Next, open the project repository you cloned on VS Code. You should see a prompt asking if Snyk should trust these folders — click Trust folders and continue:
You should see some red-colored squiggly lines on parts of your code with security vulnerabilities (ie axios.get
on line 19). When you hover over the squiggly line, you'll get a modal explaining the vulnerability.
Alternatively, you can click the Snyk icon and select CODE SECURITY to view security issues. When you select a particular vulnerability, Snyk opens the left sidebar and provides code examples to help you solve the issue.
Conclusion
In this article, you learned that it's critical to safeguard your Node.js applications against SSRF, as well as some strategies and best practices that can help you do just that.
By using the latest library version, enforcing URL schemas, implementing allowlists, and using security tools like Snyk, you can significantly reduce the likelihood of SSRF vulnerabilities in your applications. Additionally, by integrating Snyk into your development workflow, you can efficiently catch SSRF and other vulnerabilities in your code before they hit production, fortifying your code and protecting your systems against potential attacks.
Don't wait! Get started protecting your applications with Snyk today!
Top comments (0)