Server-Side Request Forgery (SSRF) vulnerabilities can be a nightmare for web applications. When libraries that process IP addresses fail to properly categorize which addresses are "public," they might open up security holes that attackers can exploit. Such is the case with CVE-2023-42282, which affects the popular ip npm package prior to version 1.1.9.

In this post, we'll break down how the vulnerability works, show some easy-to-follow code snippets to demonstrate the bug, and locate trusted sources for more information. We'll also walk through a real-world exploit scenario, so you'll know exactly why this is serious.

What is the ip Package?

The ip package is a utility for working with IP addresses in Node.js applications. It's used everywhere—from user authentication and logging to microservices.

Common Usage Example

const ip = require('ip');

const myIp = '127...1';
console.log(ip.isPublic(myIp)); // false (local IP)

The function isPublic is supposed to return true for globally routable addresses and false for local or private addresses. This helps developers block dangerous requests—at least, in theory.

The Problem (CVE-2023-42282)

Prior to version 1.1.9, the ip package didn't handle unusual IP address formats correctly. For example, it misclassified addresses like x7f.1 (hex notation) or certain octal notations as public—even when they actually reference local interfaces (like 127...1).

Why is this dangerous?

Attackers can use these "tricky" representations to bypass SSRF protections. If an app uses ip.isPublic() to stop requests to internal addresses, a hacker might slip requests through using weird-looking addresses that really point to localhost or other protected infrastructure.

Sample Vulnerable Code

Let's say you have a Node.js API that fetches images from user-supplied URLs, but blocks requests to private addresses:

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

const app = express();

app.get('/fetch', async (req, res) => {
  const imageUrl = req.query.url;
  const parsedUrl = new URL(imageUrl);
  const host = parsedUrl.hostname;

  // BLOCK private IPs
  if (!ip.isPublic(host)) {
    return res.status(400).send('Invalid remote host.');
  }

  try {
    const response = await axios.get(imageUrl, { responseType: 'arraybuffer' });
    res.set('Content-Type', 'image/jpeg');
    res.send(response.data);
  } catch (e) {
    res.status(500).send('Error fetching image.');
  }
});

app.listen(300);

You think you're safe, but a user can request

/fetch?url=http://x7f.1:80/somefile.jpg

Here, x7f.1 is actually 127...1, but the old ip package sees it as a public address. SSRF!

Exploit Scenario: How an Attacker Can Use This

1. Find the vulnerable endpoint (e.g., /fetch?url=...).

Bypass SSRF protections by crafting requests like

- http://x7f...1:808/secret
- http://0177.1:808/secret (octal for 127...1)

Read admin-only files

- Hit internal metadata services (http://169.254.169.254/) with obfuscation if the parser is weak enough

How was it fixed?

The maintainers patched this in ip@1.1.9, so any ambiguous address is handled correctly.

In package.json

"ip": "^1.1.9"

And run

npm install ip@latest

If you can't upgrade:
Consider using stricter parsing, or block non-decimal IP notations before calling isPublic(), or reject any hostnames containing x or starting with zeros.

References and Further Reading

- CVE-2023-42282 at NVD
- GitHub commit fixing the bug
- ip package at npmjs
- SSRF, Explained

TL;DR

If your Node.js app relies on the ip package for SSRF protection, make sure you’re running version 1.1.9 or later. Don't let attackers sneak past your defenses with sneaky-looking IP addresses!

Timeline

Published on: 02/08/2024 17:15:10 UTC
Last modified on: 03/06/2024 15:26:20 UTC