~ 3 min read
Don't Be Fooled by Multicast, SSRF Bypass in private-ip

Greetings, developers and fellow appsec practitioners! Another SSRF (Server-Side Request Forgery) vulnerability report for us to learn from. In this one, an SSRF bypass vulnerability I discovered in a popular npm package called private-ip
. This package helps developers determine if an IP address falls within a private range. However, a clever attacker can exploit a blind spot in its logic to bypass these safeguards. Let’s dissect the issue and explore its practical implications.
Understanding private-ip: Your Ally in SSRF Prevention (Almost)
SSRF vulnerabilities arise when an application makes unintended requests to external servers based on user-controlled input. Attackers can leverage this to trick the application into fetching internal resources, performing unauthorized actions, or even launching denial-of-service attacks.
The private-ip
package aims to mitigate this risk by identifying private IP addresses. Developers can integrate it into their applications to validate user-provided URLs or IP addresses. If the input resolves to a private IP, it’s likely an internal resource, and the application can handle it accordingly.
However, a critical vulnerability renders this validation incomplete.
đź‘‹ Just a quick break
I'm Liran Tal and I'm the author of the newest series of expert Node.js Secure Coding books. Check it out and level up your JavaScript
The Vulnerability: A Gap in the Private IP Net
The crux of the issue lies in how private-ip
defines private IP ranges. The package relies on a hardcoded list of IP ranges considered “private.” Unfortunately, this list omits an important category: multicast IP addresses.
Here’s the relevant code snippet from private-ip
(for the technically curious):
const PRIVATE_IP_RANGES = [ // ... List of private IP ranges '224.0.0.0/4' // This range (multicast) is missing!];
Multicast addresses (ranging from 224.0.0.0
to 239.255.255.255
) are a special class of IP addresses used for one-to-many communication. While not technically “private” in the traditional sense, an attacker could potentially craft a request leveraging a multicast address to exploit an SSRF vulnerability in the application.
Consider the following real-world example - imagine an application utilizes private-ip
to validate incoming requests. An attacker could send a request with a hostname resolving to a multicast IP address. Since private-ip
wouldn’t flag this as suspicious, the application might unknowingly process the request, potentially leading to unauthorized access or internal resource leakage.
A Reference Case: CVE-2020-28360
This vulnerability in the ip
npm package (a similar package for IP address utilities) serves as a cautionary tale. An attacker could exploit an insufficient regular expression to bypass its private IP validation and potentially launch SSRF attacks. This incident highlights the importance of thorough validation logic when dealing with IP addresses.
SSRF Practical Example and Proof of Concept (PoC)
Let’s solidify our understanding with some code examples:
// Using private-ipconst isPrivateIP = require('private-ip');
console.log(isPrivateIP('127.0.0.1')); // True (correctly identified)console.log(isPrivateIP('10.0.0.1')); // True (correctly identified)console.log(isPrivateIP('239.255.255.250')); // **False Negative!** (incorrectly identified)
As you can see, private-ip
incorrectly classifies the multicast address 239.255.255.250
as a non-private IP. This oversight could lead to potential SSRF vulnerabilities in applications relying on this package for input validation.
For educational purposes only, here’s a PoC demonstrating the bypass:
- Install the
private-ip
package:
npm install private-ip
- Define an
app.js
file with the programmatic API ofprivate-ip
as follows:
import is_ip_private from 'private-ip'import ipaddr from 'ipaddr.js'
// -- private-ipconsole.log(is_ip_private('127.0.0.1'))console.log(is_ip_private('10.0.0.0'))console.log(is_ip_private('169.254.0.1'))console.log(is_ip_private('239.255.255.250'))
// -- ipaddr.jsconsole.log(ipaddr.parse('127.0.0.1').range())console.log(ipaddr.parse('10.0.0.0').range())console.log(ipaddr.parse('169.254.0.1').range())console.log(ipaddr.parse('239.255.255.250').range())
- Run the
app.js
file:
node app.js
- Observe that the
private-ip
package does not detect the multicast IP address239.255.255.250
as a private IP address and returnstrue
for all 4 checks.
As a case of comparison, the ipaddr.js
package correctly identifies the multicast IP address as a non-private IP address.
import ipaddr from 'ipaddr.js'
// -- ipaddr.jsconsole.log(ipaddr.parse('127.0.0.1').range())console.log(ipaddr.parse('10.0.0.0').range())console.log(ipaddr.parse('169.254.0.1').range())console.log(ipaddr.parse('239.255.255.250').range())
It would result in the following stdout:
loopbackprivatelinkLocalmulticast