#10
SSRF ranking in OWASP Top 10 2021: the first time it appeared as its own category, reflecting its growth as a cloud-era attack
169.254.169.254
AWS EC2 instance metadata IP: the most commonly targeted SSRF destination in cloud environments
Capital One
2019 breach (100M+ customer records): achieved via SSRF against the EC2 metadata endpoint to steal IAM credentials
IMDSv2
AWS Instance Metadata Service v2: session-oriented protocol that prevents SSRF-based metadata credential theft

SponsoredRetool

Retool's new app builder is where AI-generated code ships safely

Building apps with AI is easy. Getting them to production safely is another story.

Start building for free today

SSRF attacks exploit a simple pattern: the application fetches a URL that the user specifies (to generate a preview, fetch an image, validate a webhook, or import data), and the attacker points that URL at something they should not be able to reach.

In traditional data centers, the target is usually internal services: a Redis instance, an internal admin panel, or a service mesh endpoint. In cloud environments, the target is almost always the instance metadata endpoint at 169.254.169.254, which returns the IAM role credentials attached to the EC2 instance. With those credentials, the attacker has AWS API access at whatever privilege level the role has.

How SSRF Works: The Attack Flow

Basic SSRF example:

An application has a feature that fetches a URL to generate a preview:

GET /preview?url=https://example.com/image.jpg

The server fetches the URL and returns the content. An attacker modifies the URL:

GET /preview?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/

If the application fetches this without validation, it returns the names of IAM roles attached to the EC2 instance. The attacker then requests the credentials:

GET /preview?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/MyAppRole

The response includes:

{
  "AccessKeyId": "ASIAXXX",
  "SecretAccessKey": "wJalr...",
  "Token": "IQoJb3JpZ2luX2VjEA...",
  "Expiration": "2026-06-26T18:00:00Z"
}

The attacker now has valid, temporary AWS credentials. They can use these to enumerate S3 buckets, access Secrets Manager, launch EC2 instances, or pivot to other AWS services: limited only by the IAM role's permissions.

Other common SSRF targets:

  • http://localhost:6379: Redis (no auth by default on many configurations)
  • http://10.0.0.1/admin: internal admin panels
  • http://169.254.169.254/latest/user-data: EC2 user data (often contains deployment scripts with secrets)
  • http://100.100.100.200/latest/meta-data/: Alibaba Cloud metadata
  • http://metadata.google.internal/computeMetadata/v1/: GCP metadata
  • http://169.254.169.254/metadata/: Azure metadata

Blind SSRF: When the application does not return the response body to the attacker, the attack is blind. The attacker can still confirm internal network topology by observing timing differences (request took 200ms: that port is open) or DNS callbacks (the internal service resolved the attacker's DNS hostname).

Prevention Control 1: URL Allowlisting

The most effective SSRF prevention is to validate URLs against an allowlist of permitted destinations before fetching. If your application only needs to fetch images from specific CDNs, only permit those CDN domains.

Python example: allowlist-based URL validation:

from urllib.parse import urlparse
import ipaddress
import socket

ALLOWED_DOMAINS = {'images.example.com', 'cdn.trusted-vendor.com'}

def is_safe_url(url: str) -> bool:
    try:
        parsed = urlparse(url)
        
        # Only allow https
        if parsed.scheme not in ('https',):
            return False
        
        # Check domain against allowlist
        if parsed.hostname not in ALLOWED_DOMAINS:
            return False
        
        # Resolve the hostname and check the IP
        # (prevents DNS rebinding: where an allowed domain resolves to a private IP)
        ip = socket.gethostbyname(parsed.hostname)
        if is_private_ip(ip):
            return False
        
        return True
    except Exception:
        return False

def is_private_ip(ip: str) -> bool:
    addr = ipaddress.ip_address(ip)
    return addr.is_private or addr.is_loopback or addr.is_link_local or addr.is_reserved

# Usage
user_url = request.args.get('url')
if not is_safe_url(user_url):
    abort(400, 'URL not permitted')
response = requests.get(user_url, timeout=5)

The DNS rebinding check is critical: An attacker can register a domain that initially resolves to a legitimate IP (passing your allowlist check) and then updates its DNS to point to 169.254.169.254 before the actual HTTP request. Resolve the hostname and check the resulting IP, not just the hostname.

Free daily briefing

Briefings like this, every morning before 9am.

Threat intel, active CVEs, and campaign alerts, distilled for practitioners. 50,000+ subscribers. No noise.

Prevention Control 2: Block Reserved IP Ranges

If your application must accept arbitrary URLs (webhook validators, link previewers, general URL importers), you cannot use a strict allowlist. Instead, block requests to private, loopback, link-local, and reserved IP ranges.

IP ranges to block for SSRF prevention:

REJECTED_NETWORKS = [
    ipaddress.ip_network('10.0.0.0/8'),        # RFC 1918 private
    ipaddress.ip_network('172.16.0.0/12'),      # RFC 1918 private
    ipaddress.ip_network('192.168.0.0/16'),     # RFC 1918 private
    ipaddress.ip_network('127.0.0.0/8'),        # Loopback
    ipaddress.ip_network('169.254.0.0/16'),     # Link-local (includes AWS metadata)
    ipaddress.ip_network('::1/128'),            # IPv6 loopback
    ipaddress.ip_network('fc00::/7'),           # IPv6 unique local
    ipaddress.ip_network('fe80::/10'),          # IPv6 link-local
    ipaddress.ip_network('100.64.0.0/10'),      # Shared address space (RFC 6598)
    ipaddress.ip_network('0.0.0.0/8'),          # 'This' network
    ipaddress.ip_network('240.0.0.0/4'),        # Reserved
]

def is_blocked_ip(ip_str: str) -> bool:
    try:
        addr = ipaddress.ip_address(ip_str)
        return any(addr in network for network in REJECTED_NETWORKS)
    except ValueError:
        return True  # Reject if unparseable

Also block non-standard ports if your application only needs to fetch web content:

ALLOWED_PORTS = {80, 443}
if parsed.port and parsed.port not in ALLOWED_PORTS:
    return False  # Block requests to Redis :6379, Elasticsearch :9200, etc.

Prevention Control 3: IMDSv2 Enforcement on EC2

Even with application-level SSRF prevention, enforce IMDSv2 at the infrastructure level. IMDSv2 requires a session token (obtained via a PUT request) before any metadata can be retrieved: a GET request to 169.254.169.254 returns an error, not credentials. SSRF attacks that use simple GET-based HTTP fetching cannot retrieve IMDSv2-protected metadata.

Enforce IMDSv2 on a running EC2 instance:

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

# Verify the setting
aws ec2 describe-instances \
  --instance-ids i-1234567890abcdef0 \
  --query 'Reservations[].Instances[].MetadataOptions'

Enforce IMDSv2 for all new instances in an account (AWS Config + SCP):

// Service Control Policy to require IMDSv2 on all EC2 launches
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Deny",
    "Action": "ec2:RunInstances",
    "Resource": "arn:aws:ec2:*:*:instance/*",
    "Condition": {
      "StringNotEquals": {
        "ec2:MetadataHttpTokens": "required"
      }
    }
  }]
}

AWS Config rule to detect non-compliant instances:

aws configservice put-config-rule --config-rule '{
  "ConfigRuleName": "ec2-imdsv2-check",
  "Source": {"Owner": "AWS", "SourceIdentifier": "EC2_IMDSV2_CHECK"}
}'

IMDSv2 is not a complete SSRF fix: it only prevents SSRF-based metadata credential theft. SSRF can still reach other internal services. All three controls (allowlisting, IP blocking, IMDSv2) should be applied together.

The bottom line

SSRF allows attackers to make HTTP requests from your server to internal targets: with the AWS EC2 metadata endpoint at 169.254.169.254 being the most dangerous target because it returns IAM credentials. Three controls prevent SSRF: allowlist-based URL validation (only permit expected domains, resolve hostnames and check resulting IPs), block all requests to private/loopback/link-local IP ranges at the application level, and enforce IMDSv2 on all EC2 instances to protect the metadata endpoint even if application-level controls fail.

Frequently asked questions

What is a server-side request forgery (SSRF) attack?

SSRF tricks a web application into making HTTP requests to a destination the attacker specifies: typically an internal service, localhost, or cloud instance metadata endpoint that the attacker cannot reach directly from the internet. In cloud environments, the most common SSRF target is the EC2 metadata endpoint at 169.254.169.254, which returns IAM role credentials that the attacker can use for AWS API access.

How do you prevent SSRF attacks?

Three controls in combination: validate URLs against an allowlist of permitted domains (and resolve hostnames to check the resulting IP against private ranges to prevent DNS rebinding), block all requests to private/loopback/link-local IP ranges including 169.254.0.0/16 at the application level, and enforce IMDSv2 on EC2 instances to require a session token before metadata can be accessed.

What is the AWS EC2 metadata service and why is it an SSRF target?

The EC2 metadata service is an HTTP endpoint at 169.254.169.254 that provides instance configuration data including IAM role credentials. Any application running on the EC2 instance can query it without authentication (under IMDSv1). An SSRF vulnerability allows an attacker to make the server fetch this URL on their behalf, retrieving the IAM credentials and gaining the instance role's permissions in AWS. Enforce IMDSv2, which requires a session token obtained via a PUT request: a standard HTTP redirect in SSRF cannot obtain the token, blocking the credential theft. Enforce IMDSv2 via the AWS Console or: 'aws ec2 modify-instance-metadata-options --instance-id i-xxx --http-tokens required'.

How do I test my application for SSRF vulnerabilities?

SSRF testing requires sending URL parameters to your application pointing at internal resources and observing whether responses contain internal data. Test vectors: submit requests with the URL parameter set to http://169.254.169.254/latest/meta-data/ (AWS metadata), http://localhost:80, http://internal-hostname, and http://192.168.1.1 — if the response contains internal data, the application is vulnerable. Use Burp Suite's Collaborator to detect blind SSRF: if the server makes a DNS lookup or HTTP request to the Collaborator URL, SSRF is confirmed even if the response is not reflected. Include SSRF testing in your web application penetration test scope explicitly, as it is often missed in automated scanner testing.

What is blind SSRF and how is it different from regular SSRF?

Regular SSRF returns the fetched content in the HTTP response, making internal data directly visible to the attacker. Blind SSRF triggers an internal request but does not reflect the response: the attacker can confirm the server is making requests (via out-of-band DNS or HTTP beaconing) but cannot read the response directly. Blind SSRF is harder to exploit for data exfiltration but still dangerous: it can be used for port scanning internal networks (by timing responses to detect open vs closed ports) and for hitting internal services that perform actions on receiving an HTTP request (webhooks, metadata modification endpoints). Blind SSRF requires out-of-band detection methods like Burp Collaborator.

How do DNS rebinding attacks bypass SSRF protections and how do you prevent them?

DNS rebinding is an attack technique that defeats hostname-based SSRF allowlists. The attacker registers a domain (attacker.com) with a very short TTL and initially configures it to resolve to a legitimate public IP that passes your allowlist check. After your application validates the hostname but before it makes the actual HTTP request, the attacker updates the DNS record to resolve to an internal IP (such as 169.254.169.254). The application fetches the cached-then-expired DNS record and receives the internal address, bypassing the hostname validation. Prevention requires resolving the hostname to an IP address and checking that IP against your blocked ranges before making the request, not just validating the hostname string. After resolving, compare the resulting IP against all private, loopback, and link-local ranges. Critically, make the DNS lookup and the HTTP connection in a single operation or pin the IP after the check: some HTTP libraries re-resolve the hostname for each connection, which reintroduces the rebinding window. Alternatively, use a dedicated SSRF-safe HTTP fetching library that performs these checks internally rather than building the validation manually.

How do you test for SSRF vulnerabilities using Burp Suite and ffuf?

Burp Suite SSRF testing workflow: (1) Identify all parameters that accept URLs, hostnames, or IP addresses -- including less obvious fields like webhook URLs, import-from-URL features, PDF generators, and image-fetch endpoints. (2) Replace each URL value with http://169.254.169.254/latest/meta-data/ and observe responses; if the response contains AWS metadata, SSRF is confirmed. (3) Enable Burp Collaborator and submit your Collaborator URL as the parameter value -- any out-of-band DNS lookup or HTTP request to Collaborator confirms blind SSRF. (4) Test filter bypasses: decimal IP notation (2852039166 for 169.254.169.254), hex notation (0xa9fea9fe), IPv6 representation (::ffff:169.254.169.254), and URL encoding. ffuf-based SSRF fuzzing: use ffuf to sweep a parameter across a wordlist of internal IP ranges and ports: 'ffuf -u https://target.com/fetch?url=FUZZ -w internal-ssrf-payloads.txt -mc 200' where the wordlist contains http://10.0.0.1:6379 (Redis), http://10.0.0.1:9200 (Elasticsearch), http://172.16.0.1:8080 (internal app). Response-size differences indicate open ports even without response body reflection. SSRF is frequently missed in automated scanner output -- always include it explicitly in manual testing scope for applications with URL-fetching features.

Sources & references

  1. OWASP: Server-Side Request Forgery Prevention Cheat Sheet
  2. AWS: Defending Against SSRF in EC2
  3. PortSwigger Web Academy: SSRF

Free resources

25
Free download

Critical CVE Reference Card 2025–2026

25 actively exploited vulnerabilities with CVSS scores, exploit status, and patch availability. Print it, pin it, share it with your SOC team.

No spam. Unsubscribe anytime.

Free download

Ransomware Incident Response Playbook

Step-by-step 24-hour IR checklist covering detection, containment, eradication, and recovery. Built for SOC teams, IR leads, and CISOs.

No spam. Unsubscribe anytime.

Free newsletter

Get threat intel before your inbox does.

50,000+ security professionals read Decryption Digest for early warnings on zero-days, ransomware, and nation-state campaigns. Free, daily, no spam.

Unsubscribe anytime. We never sell your data.

Eric Bang
Author

Founder & Cybersecurity Evangelist, Decryption Digest

Cybersecurity professional with expertise in threat intelligence, vulnerability research, and enterprise security. Covers zero-days, ransomware, and nation-state operations for 50,000+ security professionals every morning.

Black Hat Giveaway

Win a $2,495 Black Hat pass.

Full-access to Black Hat USA 2026 in Las Vegas. Subscribe free to enter.

Joins Decryption Digest daily briefing. Unsubscribe anytime.

Giveaway: Black Hat USA 2026 Full-Access Pass ($2,495 value)

Details →
Daily Briefing

Subscribe to enter the giveaway

Every subscriber is automatically entered. You also get daily threat intel every morning: zero-days, ransomware, and nation-state campaigns. Free. No spam.

Already subscribed? You're already entered.

Giveaway

Win a $2,495 Black Hat USA 2026 pass.