Web Security

SSRF Attacks: What They Are and How to Prevent Them

A practical guide to Server-Side Request Forgery: cloud metadata exploitation, filter bypass techniques, allow-listing, and enforcing IMDSv2 on AWS.

March 9, 20266 min readShipSafer Team

SSRF Attacks: What They Are and How to Prevent Them

Server-Side Request Forgery (SSRF) is a vulnerability that allows an attacker to induce the server-side application to make HTTP requests to an arbitrary destination. The attacker does not make the request directly — they trick the server into making it on their behalf. This gives them a foothold inside the network perimeter, access to internal services, and in cloud environments, often direct access to instance metadata that yields credentials.

SSRF was added to the OWASP Top 10 in 2021 (A10) reflecting a surge in severity driven entirely by cloud adoption.

How SSRF Works

The most straightforward SSRF scenario involves a feature that fetches a user-supplied URL — a webhook, a link preview generator, an image importer, or a PDF renderer. When the server makes an outbound request to https://example.com on your behalf, what stops you from supplying http://192.168.1.1/admin or http://169.254.169.254/ instead?

Nothing, unless the application explicitly validates the destination.

A minimal example in Node.js:

// Vulnerable
app.post('/fetch-preview', async (req, res) => {
  const { url } = req.body;
  const response = await fetch(url); // attacker controls the destination
  const html = await response.text();
  res.json({ preview: html });
});

If this application runs in AWS, the attacker supplies http://169.254.169.254/latest/meta-data/ and reads instance metadata through the application's response.

Cloud Metadata Exploitation

The AWS Instance Metadata Service (IMDS) is available on every EC2 instance at 169.254.169.254. It exposes instance identity, region, security group membership, and — critically — temporary IAM credentials for any attached instance role:

GET http://169.254.169.254/latest/meta-data/iam/security-credentials/
# Returns the role name, then:
GET http://169.254.169.254/latest/meta-data/iam/security-credentials/MyRole
# Returns AccessKeyId, SecretAccessKey, Token

With those credentials, the attacker can call AWS APIs directly from their own machine with all the permissions granted to the instance role. This is how SSRF escalated from "interesting" to "catastrophic" — it can hand an attacker the keys to your entire cloud account.

Google Cloud Platform exposes metadata at http://metadata.google.internal/computeMetadata/v1/ and Azure at http://169.254.169.254/metadata/instance.

Bypassing Naive SSRF Filters

Many developers add a block for known metadata IP addresses or localhost. Attackers have catalogued a large number of bypass techniques:

IP address encoding alternatives:

  • Decimal: http://2130706433/ (127.0.0.1 as a 32-bit integer)
  • Octal: http://0177.0.0.1/
  • Hex: http://0x7f000001/
  • Mixed notation: http://0x7f.0.0.1/

DNS rebinding: Register a domain that initially resolves to a permitted IP, passes the check, then resolves to an internal IP by the time the actual request is made. The check and the request use different DNS lookups.

Redirects: Supply a URL on a server you control that issues an HTTP 302 redirect to http://169.254.169.254/. If the application follows redirects without re-validating the destination, the SSRF succeeds.

IPv6: http://[::1]/ is localhost in IPv6 and is often not blocked.

URL scheme variations: http://localhost@169.254.169.254/ exploits lenient URL parsing where the localhost@ portion is treated as authentication credentials rather than the host.

These bypasses illustrate why blocklists are fundamentally unreliable as a primary control.

The Right Defense: Allow-listing

Rather than blocking known-bad destinations, allow only the destinations your application legitimately needs to reach.

import { URL } from 'url';
import dns from 'dns/promises';
import net from 'net';

const ALLOWED_DOMAINS = new Set(['api.example.com', 'cdn.example.com']);

// Private IP ranges to block
function isPrivateIP(ip: string): boolean {
  const privateRanges = [
    /^10\./,
    /^172\.(1[6-9]|2\d|3[01])\./,
    /^192\.168\./,
    /^127\./,
    /^169\.254\./,  // link-local / metadata
    /^::1$/,        // IPv6 loopback
    /^fc00:/,       // IPv6 private
  ];
  return privateRanges.some(r => r.test(ip));
}

async function safeFetch(rawUrl: string): Promise<Response> {
  const parsed = new URL(rawUrl);

  // 1. Allow only specific domains
  if (!ALLOWED_DOMAINS.has(parsed.hostname)) {
    throw new Error('URL not in allowed list');
  }

  // 2. Resolve the hostname and verify it resolves to a public IP
  const addresses = await dns.resolve4(parsed.hostname);
  for (const addr of addresses) {
    if (isPrivateIP(addr)) {
      throw new Error('URL resolves to private address');
    }
  }

  // 3. Only allow https
  if (parsed.protocol !== 'https:') {
    throw new Error('Only HTTPS URLs are permitted');
  }

  return fetch(rawUrl);
}

Key points in this implementation:

  • The domain allowlist is the primary gate. If you only need to reach three external APIs, allow only those three.
  • DNS resolution is validated after the allowlist check to prevent DNS rebinding — resolve the hostname yourself and verify the resulting IP is public.
  • Only https: is permitted. Blocking file://, dict://, gopher://, and ftp:// eliminates entire classes of SSRF variants.

Enforcing IMDSv2 on AWS

AWS released IMDSv2 to specifically address SSRF-based metadata theft. IMDSv2 requires a session token obtained through a PUT request with a X-aws-ec2-metadata-token-ttl-seconds header before any metadata can be read. An SSRF vulnerability that only allows GET requests cannot obtain the token and therefore cannot read metadata.

Enforce IMDSv2 on all EC2 instances:

# Require IMDSv2 on a running instance
aws ec2 modify-instance-metadata-options \
  --instance-id i-1234567890abcdef0 \
  --http-tokens required \
  --http-endpoint enabled

# Enforce at the account level via SCP or launch template
aws ec2 modify-instance-metadata-defaults \
  --http-tokens required

For new instances, set HttpTokens: required in your launch template or Terraform configuration:

resource "aws_instance" "app" {
  ami           = var.ami_id
  instance_type = "t3.medium"

  metadata_options {
    http_tokens   = "required"
    http_endpoint = "enabled"
  }
}

IMDSv2 enforcement is a high-ROI control because it neutralizes the most dangerous class of SSRF impact in AWS environments without any application code changes.

Network-Level Controls

Beyond application logic, network controls provide defense in depth:

Egress filtering: Configure security groups and NACLs so that application servers cannot make outbound connections to arbitrary internet destinations. Route egress through a proxy or NAT gateway with an allowlist of permitted destinations.

Dedicated egress proxies: For applications that need to fetch external URLs, route all outbound HTTP through a dedicated proxy (Squid, nginx, or a managed service). The proxy enforces the allowlist and logs all outbound traffic, creating an audit trail.

Instance metadata hop limit: AWS allows you to set HttpPutResponseHopLimit to 1, which prevents SSRF from a containerized workload inside the EC2 instance from reaching the metadata service (since the TTL is decremented at each hop).

Detecting SSRF in Your Codebase

Look for any code path where a URL is constructed from user-controlled input and then used in an outbound HTTP call. Common patterns include:

  • Webhook registration and delivery
  • URL preview / link unfurling
  • Server-side image or PDF rendering
  • Import features (import from URL)
  • OAuth callback URL handling
  • Integrations that proxy requests to third-party APIs

Static analysis tools including Semgrep and Snyk can detect these patterns. Dynamic testing with Burp Suite's Collaborator module or an interactsh server helps verify exploitability in running applications.

Check Your Security Score — Free

See exactly how your domain scores on DMARC, TLS, HTTP headers, and 25+ other automated security checks in under 60 seconds.