Back to Learning Center
criticalA10:2021CWE-918

Server-Side Request Forgery (SSRF)

Server-Side Request Forgery (SSRF) is a critical vulnerability that allows attackers to make HTTP requests from your server to internal systems, cloud metadata endpoints, and other protected resources. This vulnerability has led to some of the largest data breaches in history, including the 2019 Capital One breach that exposed over 100 million customer records.

What is SSRF?

SSRF occurs when an application fetches a remote resource based on user-supplied input without properly validating the destination. Common features that can be exploited include:

  • URL preview/unfurling (like Slack, Discord)
  • Webhook integrations
  • PDF generators that fetch external resources
  • Image/file importers from URLs
  • API proxies and gateways

How SSRF Works

Basic SSRF Attack

An attacker provides a malicious URL that the server fetches, allowing access to internal resources:

javascript
// Vulnerable: User controls the URL
app.get('/fetch', async (req, res) => {
  const url = req.query.url;
  
  // Server fetches whatever URL the user provides
  const response = await fetch(url);
  const data = await response.text();
  
  res.send(data);
});

// Attacker's request:
// GET /fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/
// Returns AWS IAM credentials!

Cloud Metadata Attacks

Cloud providers expose instance metadata at predictable IP addresses. SSRF can access these to steal credentials:

bash
# AWS Instance Metadata Service
http://169.254.169.254/latest/meta-data/
http://169.254.169.254/latest/meta-data/iam/security-credentials/
http://169.254.169.254/latest/user-data/

# Google Cloud
http://metadata.google.internal/computeMetadata/v1/
http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token

# Azure
http://169.254.169.254/metadata/instance?api-version=2021-02-01
http://169.254.169.254/metadata/identity/oauth2/token

Internal Port Scanning

Attackers can use SSRF to discover internal services:

python
# Attacker script to scan internal network
import requests

for port in [22, 80, 443, 3306, 5432, 6379, 27017]:
    url = f"http://target.com/fetch?url=http://192.168.1.1:{port}/"
    try:
        r = requests.get(url, timeout=3)
        if "Connection refused" not in r.text:
            print(f"[+] Port {port} is open")
    except:
        pass

Bypassing Filters

Simple blocklists are easily bypassed. Attackers use various techniques:

text
# IP address obfuscation
http://127.0.0.1         → http://2130706433      (decimal)
http://127.0.0.1         → http://0x7f000001      (hex)
http://127.0.0.1         → http://0177.0.0.1      (octal)
http://127.0.0.1         → http://127.1           (shortened)
http://127.0.0.1         → http://[::1]           (IPv6)

# DNS rebinding
http://your-evil-domain.com → initially resolves to allowed IP
                            → later resolves to 127.0.0.1

# URL parsing confusion
http://expected.com@evil.com
http://evil.com#expected.com
http://evil.com?url=expected.com

Real-World Breaches

Capital One (2019) - An SSRF vulnerability in a WAF configuration allowed an attacker to access AWS metadata and steal IAM credentials. Result: 100+ million customer records exposed, $80 million fine.

Microsoft Exchange (2021) - The ProxyLogon vulnerability chain included an SSRF that allowed attackers to authenticate as the Exchange server itself.

Prevention Strategies

Use an Allow List

Never rely on blocklists. Always use strict allow lists:

javascript
const allowedDomains = ['api.github.com', 'api.stripe.com'];

function isAllowedUrl(urlString) {
  try {
    const url = new URL(urlString);
    
    // Only allow HTTPS
    if (url.protocol !== 'https:') {
      return false;
    }
    
    // Strict domain matching
    if (!allowedDomains.includes(url.hostname)) {
      return false;
    }
    
    return true;
  } catch {
    return false;
  }
}

app.get('/fetch', async (req, res) => {
  const url = req.query.url;
  
  if (!isAllowedUrl(url)) {
    return res.status(400).json({ error: 'URL not allowed' });
  }
  
  // Safe to fetch
  const response = await fetch(url);
  res.json(await response.json());
});

Network-Level Controls

Defense in depth with network segmentation:

yaml
# AWS Security Group - Block metadata access
resource "aws_security_group_rule" "block_metadata" {
  type              = "egress"
  from_port         = 80
  to_port           = 80
  protocol          = "tcp"
  cidr_blocks       = ["169.254.169.254/32"]
  security_group_id = aws_security_group.app.id
  # Use DENY rule in NACL instead
}

# Better: Use IMDSv2 which requires session tokens
resource "aws_instance" "app" {
  metadata_options {
    http_tokens = "required"  # Enforce IMDSv2
    http_put_response_hop_limit = 1
  }
}

Validate Resolved IPs

Validate the IP address after DNS resolution to prevent DNS rebinding:

javascript
import dns from 'dns';
import { isIP } from 'net';

const BLOCKED_RANGES = [
  /^127\./,           // Localhost
  /^10\./,            // Private
  /^172\.(1[6-9]|2[0-9]|3[0-1])\./, // Private
  /^192\.168\./,      // Private
  /^169\.254\./,      // Link-local (metadata)
  /^0\./,             // Invalid
];

async function isSafeToFetch(hostname) {
  return new Promise((resolve, reject) => {
    dns.resolve4(hostname, (err, addresses) => {
      if (err) return reject(err);
      
      for (const ip of addresses) {
        for (const range of BLOCKED_RANGES) {
          if (range.test(ip)) {
            return resolve(false);
          }
        }
      }
      
      resolve(true);
    });
  });
}

Security Checklist

  • Use strict allow lists for outbound requests (never blocklists)
  • Validate resolved IP addresses, not just hostnames
  • Use IMDSv2 for AWS instances (requires session tokens)
  • Segment networks - separate application from internal services
  • Block outbound traffic to cloud metadata IPs at firewall level
  • Disable unnecessary URL schemes (file://, gopher://, dict://)
  • Implement request timeouts to prevent slow attacks
  • Log and monitor all outbound requests from application servers

Practice Challenges

View all

Related Articles

View all