JAVASCRIPT

Preventing Server-Side Request Forgery (SSRF) Attacks

Safeguard your server-side applications from SSRF attacks by validating and restricting outbound requests to external resources and private networks.

const url = require('url');
const http = require('http');
const https = require('https');

const allowedDomains = ['api.example.com', 'trusted-cdn.com'];
const blockedIPs = ['127.0.0.1', '10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16']; // Private IP ranges

function isPrivateIP(ip) {
  // Simplified check for private IP ranges (requires more robust implementation for production)
  return blockedIPs.some(range => {
    // For simplicity, this example only checks exact matches or basic starts.
    // A real implementation would parse CIDR notations and check ranges.
    return ip === range || ip.startsWith(range.split('/')[0]);
  });
}

async function safeFetch(requestUrl) {
  const parsedUrl = url.parse(requestUrl);
  
  // 1. Validate Protocol
  if (!['http:', 'https:'].includes(parsedUrl.protocol)) {
    throw new Error('Invalid protocol: Only HTTP/HTTPS are allowed.');
  }

  // 2. Validate Hostname / Domain Whitelist
  if (!allowedDomains.includes(parsedUrl.hostname)) {
    throw new Error('Access to this domain is not allowed.');
  }

  // 3. Prevent Access to Private IP Ranges (requires DNS lookup for robust check)
  // For a basic check, we can try to resolve the hostname and check its IPs.
  // This requires a real DNS resolver in a production app.
  // For this snippet, we'll simulate a check, but actual resolution is crucial.
  // Example: 'localhost' or an internal IP provided directly in the URL
  if (isPrivateIP(parsedUrl.hostname)) { // This would need actual IP resolution
    throw new Error('Access to private network resources is blocked.');
  }

  // 4. Perform the request using appropriate protocol module
  const client = parsedUrl.protocol === 'https:' ? https : http;
  
  return new Promise((resolve, reject) => {
    client.get(requestUrl, (res) => {
      let data = '';
      res.on('data', (chunk) => data += chunk);
      res.on('end', () => resolve(data));
    }).on('error', (err) => {
      reject(new Error(`Failed to fetch: ${err.message}`));
    });
  });
}

// Example Usage:
(async () => {
  try {
    console.log('Fetching from allowed domain...');
    const data = await safeFetch('https://api.example.com/data');
    console.log('Success:', data.substring(0, 100)); // Show partial data
  } catch (error) {
    console.error('Error:', error.message);
  }

  try {
    console.log('
Attempting to fetch from disallowed domain...');
    await safeFetch('https://evil.com/data');
  } catch (error) {
    console.error('Error:', error.message);
  }
  
  try {
    console.log('
Attempting to fetch from private IP (simulated)...');
    await safeFetch('http://127.0.0.1/admin'); // This hostname is checked by isPrivateIP()
  } catch (error) {
    console.error('Error:', error.message);
  }

  try {
    console.log('
Attempting to fetch from invalid protocol...');
    await safeFetch('ftp://example.com/file');
  } catch (error) {
    console.error('Error:', error.message);
  }
})();
How it works: This Node.js snippet demonstrates a method to prevent Server-Side Request Forgery (SSRF) by creating a `safeFetch` function. It explicitly validates the requested URL's protocol to ensure only HTTP/HTTPS are used, and enforces a whitelist of `allowedDomains` for outbound requests. Critically, it includes a conceptual check (simplified for the snippet) to block access to private IP ranges, which is essential to prevent attackers from using the server to scan or interact with internal networks. By strictly controlling where the server can make requests, it mitigates the risk of an attacker forcing the server to access unauthorized internal or external resources.

Need help integrating this into your project?

Our team of expert developers can help you build your custom application from scratch.

Hire DigitalCodeLabs