Description

Why is this an issue?

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.

What is the potential impact?

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.

Local file read to host takeover

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.

Internal Network Reconnaissance

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.

How to fix it in Node.js

Code examples

The following code is vulnerable to SSRF as it opens a URL defined by untrusted data.

Noncompliant code example

const axios = require('axios');
const express = require('express');

const app = express();

app.get('/example', async (req, res) => {
    try {
        await axios.get(req.query.url); // Noncompliant
        res.send("OK");
    } catch (err) {
        console.error(err);
        res.send("ERROR");
    }
})

Compliant solution

const axios = require('axios');
const express = require('express');

const schemesList = ["http:", "https:"];
const domainsList = ["trusted1.example.com", "trusted2.example.com"];

app.get('/example', async (req, res) => {
    const url = (new URL(req.query.url));

    if (schemesList.includes(url.protocol) && domainsList.includes(url.hostname)) {
        try {
            await axios.get(url);
            res.send("OK");
        } catch (err) {
            console.error(err);
            res.send("ERROR");
        }
    }else {
        res.send("INVALID_URL");
    }
})

How does this work?

Pre-Approved URLs

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.

Blacklisting

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.

Pitfalls

The trap of 'StartsWith' and equivalents

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.

Blacklist TOCTOU

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.

Resources

Implementation Specification

(visible only on this page)

Message

Change this code to not construct the request from user-controlled data.

Highlighting

"[varname]" is tainted (assignments and parameters)

this argument is tainted (method invocations)

the returned value is tainted (returns & method invocations results)