SSRF

SSRF remediation in Node.js (Express) [March 2026] [CVE-1999-1016]

[Updated March 2026] Updated CVE-1999-1016

Overview

Historically, CVE-1999-1016 described how a Microsoft HTML control used in IE5, FrontPage Express, Outlook Express 5 and Eudora could be forced by a malicious web page or HTML email to consume 100% CPU via oversized form fields. This kind of flaw exploited unbounded input in client components. While that CVE predates Node.js, it demonstrates the risk of unbounded input and heavy processing. In modern apps, SSRF can be used to coax a server into fetching arbitrary resources, exhausting upstream systems or accessing internal networks. In Node.js/Express, always validate and restrict user-supplied URLs. Use allowlists, explicit schemes, timeouts, redirect limits, and avoid untrusted remote fetches unless strictly necessary; use a proxy or safe fetch helper.

Affected Versions

N/A (historical vulnerability; not applicable to Node.js/Express)

Code Fix Example

Node.js (Express) API Security Remediation
Vulnerable pattern (Node.js/Express):
const express = require('express');
const fetch = require('node-fetch');
const app = express();

// Vulnerable: fetch user-provided URL without validation or timeouts
app.get('/fetch', async (req, res) => {
  const url = req.query.url;
  if (!url) return res.status(400).send('Missing url');
  try {
    const response = await fetch(url);
    const body = await response.text();
    res.set('Content-Type', response.headers.get('content-type') || 'text/plain');
    res.send(body);
  } catch (err) {
    res.status(502).send('Upstream fetch failed');
  }
});

app.listen(3000, () => console.log('Server listening on 3000'));

----------------------------------------
Fixed pattern (Node.js/Express):
const express = require('express');
const fetch = require('node-fetch');
const { URL } = require('url');
const app = express();

// Whitelisted hosts (update with your domains)
const ALLOWED_HOSTS = ['example.com', 'api.example.com'];

app.get('/fetch', async (req, res) => {
  const urlStr = req.query.url;
  if (!urlStr) return res.status(400).send('Missing url');

  let url;
  try {
    url = new URL(urlStr);
  } catch (e) {
    return res.status(400).send('Invalid URL');
  }
  if (!['http:', 'https:'].includes(url.protocol)) {
    return res.status(400).send('Unsupported protocol');
  }
  if (!ALLOWED_HOSTS.includes(url.hostname)) {
    return res.status(403).send('Host not allowed');
  }

  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), 5000); // 5s timeout

  try {
    const response = await fetch(url.href, {
      method: 'GET',
      redirect: 'manual', // do not follow redirects automatically
      signal: controller.signal,
      headers: { 'User-Agent': 'my-app' }
    });
    clearTimeout(timeout);
    if (!response.ok) {
      return res.status(response.status).send('Upstream error');
    }
    res.set('Content-Type', response.headers.get('content-type') || 'application/octet-stream');
    response.body.pipe(res);
  } catch (err) {
    if (err.name === 'AbortError') {
      res.status(504).send('Upstream request timed out');
    } else {
      res.status(502).send('Upstream fetch failed');
    }
  }
});

app.listen(3000, () => console.log('Server listening on 3000'));

CVE References

Choose which optional cookies to allow. You can change this any time.