Back to Blog
OWASP

SSRF (OWASP A10): Examples, Detection, and Fixes in 2026

April 25, 202623 min readSecureCodingHub Team
fetch::resolve_then_allowlist()

Server side request forgery (SSRF) broke into the OWASP Top 10 at A10 in 2021 and stayed in the 2025 revision because of a single uncomfortable truth: any application that fetches a URL on behalf of a user is one input-validation mistake away from giving an attacker a request-issuing primitive inside the cloud account, the VPC, and the corporate network. The ssrf attack surface is enormous — webhooks, image processors, PDF generators, OAuth callbacks, link previewers, API proxies — and the most damaging path points at one endpoint every cloud-deployed application has within reach: the metadata service. This guide walks the server side request forgery surface, the cloud-metadata escalation that promoted SSRF to the OWASP Top 10, the bypass techniques that defeat naive blocklists, and the defense-in-depth stack that actually works in production.

What SSRF Is and Why It Got Promoted to the Top 10 in 2021

Server side request forgery occurs when an attacker convinces an application's backend to make a network request to a location of the attacker's choosing, using the backend's network identity rather than the attacker's. The class is structurally simple — a server-side fetch with attacker-controlled destination — but the consequences depend on what the backend can reach. In a 2010-era datacenter, a server side request forgery attack was a curiosity that mostly produced port-scan results. In a 2026 cloud deployment where the application server can reach a metadata endpoint that hands out credentials, SSRF is the path from one input bug to full account takeover.

The 2021 OWASP Top 10 added SSRF at A10, and the 2025 revision retained it because the underlying conditions — applications that fetch URLs on user behalf, cloud environments where the metadata endpoint and internal services are reachable, lax default network segmentation — have not changed. SSRF stays on the list because the fix is structurally hard: application-layer URL validation is fragile, the bypasses are numerous, and the durable mitigations live at the network layer rather than in application code.

The watershed incident was the 2019 Capital One breach. An attacker exploited an SSRF vulnerability in a misconfigured WAF on AWS, used it to query the IMDSv1 endpoint at 169.254.169.254, retrieved temporary IAM credentials for the EC2 role, and used those credentials to access an S3 bucket containing the personal data of roughly 100 million applicants. The breach was the first widely-reported demonstration that SSRF in the cloud era was not a port-scanning curiosity but a credential-theft primitive, and that the metadata endpoint was the highest-value internal target most cloud-deployed applications had within reach.

The SSRF Attack Surface — Every URL Fetcher in Your App

A server side request forgery attack appears in any code path that takes a URL from a user-controllable source and issues a network request to it. The surfaces are wider than most teams realize, because many features that involve fetching a URL do not look like URL-fetching features in the product spec.

Webhooks and outgoing integrations. Any feature where the user configures a callback URL — webhook endpoints, OAuth redirect URIs, payment-gateway callbacks, push-notification targets — gives the attacker an attacker-controlled URL the backend will fetch. Webhooks are the canonical SSRF surface because the product semantics ("must be able to enter any URL") run counter to security ("must not be able to fetch the metadata service").

Image processors and PDF generators. Avatar imports, profile-picture-by-URL features, and CMS image fetches issue server-side fetches against attacker-supplied URLs. Headless-browser PDF generators (Puppeteer, Playwright, wkhtmltopdf) render attacker-supplied HTML, and the embedded browser fetches any resource the HTML references — including internal hosts and the metadata endpoint.

Link previewers, RSS readers, OAuth/SSO discovery. Slack-style link unfurling, social-media share-card previews, and RSS aggregators are SSRF surfaces. The previewer follows redirects, fetches Open Graph tags, downloads thumbnails — each secondary fetch is an opportunity to chain through to an internal target. OAuth/OIDC protocols include backend-fetched URLs (discovery documents, JWKS, userinfo); SAML federation includes metadata URLs. Each can be partially or fully user-controlled during onboarding or runtime federation.

File imports and API proxies. "Import from URL" buttons, S3-bucket-by-URL imports, and explicit HTTP-proxy or "test your webhook" debugging tools are URL-fetching features sold as product capability. They are the hardest SSRF surface to defend because the product requirement is to fetch URLs the user controls.

The pattern is identical across every surface: the application takes a URL from the user input boundary, issues a request, and returns the response or some derivative back to the user. Variation is in what the URL is used for and how the response is exposed.

Cloud Metadata Endpoints — The Crown Jewel Target

The reason SSRF is an A10-grade vulnerability is the cloud metadata endpoint. AWS, GCP, and Azure expose an endpoint reachable from inside a VM that returns instance information — name, tags, IAM role, and, critically, temporary credentials for that role. The endpoint lives on a link-local IP (169.254.169.254 on AWS and GCP), unroutable on the public internet but routable from any process on the instance. SSRF gives an attacker a request-issuing primitive on the instance; the metadata endpoint converts that primitive into IAM credentials.

AWS IMDSv1. The original Instance Metadata Service responded to any HTTP GET to 169.254.169.254 with the requested metadata — no authentication, no header check. Any code path coercible into issuing GETs to attacker-controlled URLs could read credentials through SSRF. The Capital One breach was IMDSv1 exploitation through a single curl-equivalent request.

IMDSv2. AWS introduced IMDSv2 in 2019 in direct response to the SSRF threat model. IMDSv2 requires a session token, obtained through a PUT request with a TTL header, on every subsequent metadata request. Most SSRF primitives produce plain GETs and cannot easily issue PUTs with custom headers, so the token-acquisition step is blocked. IMDSv2 also enforces a default IP TTL of 1 on responses, dropping any response that would route through a hop — preventing containerized SSRF from relaying responses across network namespaces.

The vulnerable curl pattern with IMDSv1:

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

The same operation with IMDSv2 requires acquiring a token first:

# Step 1 — acquire token (PUT, requires TTL header)
TOKEN=$(curl -X PUT \
  "http://169.254.169.254/latest/api/token" \
  -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")

# Step 2 — use token in subsequent GETs
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/role-name \
  -H "X-aws-ec2-metadata-token: $TOKEN"

IMDSv2 raises the bar from "any GET-issuing SSRF reads credentials" to "an SSRF primitive supporting method-control and arbitrary headers reads credentials" — a much smaller subset. The 2026 baseline for any AWS account is IMDSv2-required across the entire account, enforced through Service Control Policies. Accounts that still allow IMDSv1 are accumulating risk that compounds with every new SSRF surface their applications ship.

GCP and Azure. GCP's metadata server requires a Metadata-Flavor: Google header; Azure's IMDS requires a Metadata: true header. These are not as strong as IMDSv2's PUT-then-GET design (a header can sometimes be added by an SSRF primitive; a PUT cannot) but they raise the bar above plain-GET exploitation. The defense pattern is uniform: assume your application has SSRF surfaces, configure metadata in its hardened mode, and audit IAM scopes so that even successful credential theft yields narrow access. IAM scoping is the durable defense, independent of any application-layer SSRF mitigation.

Internal Network Reconnaissance via SSRF

Beyond the metadata endpoint, SSRF is a reconnaissance primitive into the internal network. Cloud VPCs and Kubernetes clusters routinely contain unauthenticated internal services unreachable from the public internet but reachable from any pod or instance on the same network — and SSRF moves the attacker's request issuance into that network.

Port scanning and unauthenticated admin panels. SSRF lets an attacker probe arbitrary IP-and-port combinations; even timing-only signals fingerprint services across many requests. Internal services often ship with admin interfaces that trust their network position as the access-control boundary — Jenkins on 8080, Consul on 8500, RabbitMQ on 15672, Kibana, Grafana with anonymous access, Spring Boot Actuator endpoints. SSRF turns the application server into a request issuer on the internal network, and unauthenticated panels become directly exploitable.

Cache, database, and container runtime services. Redis, Memcached, and Elasticsearch frequently bind to internal IPs without authentication, on the assumption that segmentation is the boundary. Several — particularly Redis and Memcached — accept commands through interfaces that overlap enough with HTTP request parsing to be exploitable via SSRF. Kubernetes kubelet on 10250, exposed Docker daemons, and AWS service endpoints (ECR, ECS, Lambda invoke, SQS, S3) reachable through VPC routing extend the same problem. The mitigation here is at the IAM and authentication layers — internal services should require authentication regardless of network position, and successful credential theft should yield narrow access.

SSRF to RCE — Chains That Produce Code Execution

SSRF is most damaging when it chains into remote code execution. The chains below are documented in pentest reports, bug-bounty disclosures, and post-mortems.

Redis SLAVEOF and arbitrary file write. The classic server side request forgery example, and the canonical chain. An SSRF primitive that supports gopher:// or dict:// can issue TCP-level commands to a Redis instance. The attacker issues SLAVEOF to designate their server as master, combines it with CONFIG SET dir and dbfilename, and writes arbitrary files — typically an SSH authorized_keys or a cron job — producing code execution. The educational gopher payload structure looks like:

gopher://internal-redis:6379/_*1%0d%0a$8%0d%0aFLUSHALL%0d%0a*3%0d%0a$3%0d%0aSET%0d%0a$1%0d%0a1%0d%0a...

Included for pattern recognition only — unauthenticated Redis on the internal network is the configuration error, and the mitigation is requirepass plus disabling SLAVEOF where unused. Memcached has similar TCP-protocol overlap that lets SSRF primitives issue SET commands, writing attacker-controlled values into keys the application later reads and trusts.

Jenkins, Vault, and internal API credential theft. Jenkins with the script console accessible accepts Groovy on /script — SSRF that reaches /script is direct code execution. Vault before authentication is configured exposes secrets to anyone who can reach the API. The subtler chain is internal API credential theft: the application server reaches internal APIs that authenticate by source IP or environment-injected service tokens, and SSRF lets the attacker inherit that authentication context. APIs respond as if the application itself was calling them, returning credentials, tokens, or business data. Across every chain, SSRF is the primitive and the second-stage depends on what the application server can reach without authentication.

SSRF Bypass Techniques — Why Naive Defenses Fail

The instinct on first encountering SSRF is to add a blocklist: reject any URL whose host is 127.0.0.1, 169.254.169.254, or starts with 10. or 192.168.. This defense is fragile. URL parsers are inconsistent across libraries, IP addresses can be expressed in many formats, DNS decouples the validated hostname from the resolved IP, and several classes of bypass routinely defeat naive blocklists.

Decimal, octal, hex, and IPv6 encoding. 127.0.0.1 can be expressed as 2130706433 (decimal), 0x7f000001 (hex), 0177.0.0.01 (octal), and mixed-radix forms. Many URL parsers accept these and resolve to 127.0.0.1, but string-match blocklists miss all of them. IPv6 representations (::1, ::ffff:127.0.0.1) produce additional bypasses, and dual-stack HTTP clients that prefer IPv6 evade IPv4-only blocklists entirely.

DNS rebinding. The most durable bypass against application-level validation. The attacker controls a DNS name that resolves to a public IP at validation time and a private IP at fetch time. The application validates by resolving the hostname (gets public IP, passes), then issues the fetch (resolves again, gets private IP, fetches the internal target). With a TTL of zero or one second, validation and fetch resolutions are independent. DNS rebinding defeats every "resolve and validate" pattern that does not pin the IP between validation and fetch — and most HTTP clients do not pin out of the box.

URL parser confusion, redirects, CRLF, alternate schemes. Different parsers interpret pathological URLs differently — http://expected.com#@evil.com/, http://user@evil.com/, unusual percent-encodings — producing disagreement between validator and fetcher. If the HTTP client follows redirects, an attacker hosts a public URL returning a 302 to an internal URL and the validator never sees the target. CRLF in URLs can inject headers in some clients. gopher://, dict://, ftp://, file://, and ldap:// produce TCP-level connections that bypass HTTP-only assumptions; file:// turns SSRF into local-file disclosure when the client supports it. The mitigation is to allowlist only http and https — never blocklist dangerous schemes, because the list grows over time.

An illustrative table of bypass forms (rendered as a code block):

Target: 127.0.0.1
  Decimal:        http://2130706433/
  Hex:            http://0x7f000001/
  Octal:          http://0177.0.0.01/
  IPv6 mapped:    http://[::ffff:127.0.0.1]/
  IPv6 loopback:  http://[::1]/

Target: 169.254.169.254 (AWS metadata)
  Decimal:        http://2852039166/
  Hex:            http://0xa9.0xfe.0xa9.0xfe/
  Mixed:          http://0251.0376.0251.0376/

URL parser confusion:
  http://safe.com#@evil.com/
  http://safe.com@evil.com/
  http://safe.com.evil.com/  (DNS suffix attack)

DNS rebinding:
  http://attacker-controlled.tld/
  (resolves public during validation, private during fetch)

The bypass landscape is wide enough that any server side input validation of URLs must be treated as defense-in-depth rather than primary mitigation. The primary mitigation against a server side request forgery attack is at the network layer.

Blind SSRF — Detecting Without a Response Channel

Many SSRF surfaces do not return the fetched response. A webhook delivery returns success or failure; an image processor returns a processed image; a link previewer returns extracted metadata. These surfaces are still SSRF-vulnerable but the attacker cannot read the response directly. This class is called blind SSRF, and the exploitation techniques are well-developed.

Timing and out-of-band callbacks. The attacker measures how long the application takes to process a URL pointing at an internal host — fast for a host that responds, slow for a filtered host, medium for TCP RST. Across enough samples, timing alone fingerprints the internal service map. The standard confirmation technique is out-of-band: the attacker uses Burp Collaborator, interactsh, or a self-hosted equivalent and supplies URLs pointing at the collaborator. When the application fetches the URL, the connection appears in the attacker's log, confirming SSRF and providing the application's outbound IP and request metadata.

DNS exfiltration and side channels. A specialization of out-of-band: the attacker controls a DNS server and encodes information about the application's behavior in DNS query names — interpolating environment variables or fetch-result fragments into hostnames that resolve through the attacker's DNS. DNS exfiltration works in environments where outbound HTTP is filtered but outbound DNS is not, which describes many corporate networks. Beyond DNS, any application response that varies with the SSRF outcome — error messages, log entries, queue depths, response headers, downstream effects, and timing — is a potential exfiltration channel. Constant-time response handling is the only durable defense against side-channel detection.

The Defense-in-Depth Mitigation Stack

SSRF mitigation that survives in production is layered. No single layer is sufficient; the combination produces durable defense.

Allowlist over blocklist. If the feature requires fetching URLs, define the allowed targets explicitly — by domain, by IP range, by URL prefix — and reject everything else. An allowlist of "github.com webhooks may fetch from any github.com URL" is enforceable; a blocklist of "no internal IPs" is bypassable. The allowlist is the only application-layer mitigation that does not have a list of bypasses against it.

Resolve-before-fetch with private-CIDR validation and pinning. When an allowlist is too restrictive, the next-best application-layer defense is server side input validation that resolves the hostname first, validates that the resolved IP is not in any private CIDR range (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 127.0.0.0/8, 169.254.0.0/16, IPv6 equivalents), then issues the fetch with the resolved IP — pinned, so DNS rebinding cannot cause a different resolution at fetch time. Pinning is the step most implementations miss, and it is the step that makes the defense work against rebinding.

The vulnerable Python pattern:

import requests
url = request.GET.get('url')
response = requests.get(url)  # No validation, full SSRF

The defended pattern with allowlist, resolution, IP validation, and pinning:

import socket
import ipaddress
import requests
from urllib.parse import urlparse

ALLOWED_SCHEMES = {'http', 'https'}
PRIVATE_NETS = [
    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('127.0.0.0/8'),
    ipaddress.ip_network('169.254.0.0/16'),
    ipaddress.ip_network('::1/128'),
    ipaddress.ip_network('fc00::/7'),
    ipaddress.ip_network('fe80::/10'),
]

def safe_fetch(url, allowed_hosts=None):
    parsed = urlparse(url)
    if parsed.scheme not in ALLOWED_SCHEMES:
        raise ValueError('Disallowed scheme')
    if allowed_hosts is not None and parsed.hostname not in allowed_hosts:
        raise ValueError('Host not in allowlist')

    # Resolve once
    addr_info = socket.getaddrinfo(parsed.hostname, None)
    resolved_ip = addr_info[0][4][0]
    ip = ipaddress.ip_address(resolved_ip)
    for net in PRIVATE_NETS:
        if ip in net:
            raise ValueError('Private IP target rejected')

    # Pin the resolved IP — fetch by IP, set Host header
    pinned_url = url.replace(parsed.hostname, resolved_ip, 1)
    return requests.get(
        pinned_url,
        headers={'Host': parsed.hostname},
        allow_redirects=False,  # never follow redirects
        timeout=5,
        verify=True,
    )

The pattern is correct in shape but has limits — TLS verification against the original hostname requires careful configuration, IPv6 dual-stack requires resolving every address family, and a custom client may handle redirects or proxies in ways that bypass the pinning. Necessary, not sufficient.

Egress proxy and network segmentation. The architectural pattern that produces durable defense. All outbound HTTP from application services flows through a dedicated egress proxy that enforces the allowlist, the IP-range blocklist, and the protocol restrictions at the network boundary. The application services themselves have no direct outbound network access. Bugs in application code that produce requests to unintended URLs are caught by the proxy, because the defense lives outside the application code. At the cloud-network layer, restrict outbound reachability to the minimum required: database, message queue, observability backend, specific external API targets — and nothing else. The metadata endpoint is unreachable except through the IMDSv2 protocol. Internal admin panels are unreachable. A Kubernetes NetworkPolicy snippet that restricts outbound traffic:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: app-egress-restrict
spec:
  podSelector:
    matchLabels:
      app: webhook-sender
  policyTypes:
    - Egress
  egress:
    # Only DNS to cluster DNS
    - to:
        - namespaceSelector: {}
          podSelector:
            matchLabels:
              k8s-app: kube-dns
      ports:
        - protocol: UDP
          port: 53
    # Only the egress proxy
    - to:
        - podSelector:
            matchLabels:
              app: egress-proxy
      ports:
        - protocol: TCP
          port: 8080
    # Block everything else, including 169.254.169.254

Response inspection and HTTP-client hardening. When the application does expose fetched content, limit what is returned — size limits, content-type restrictions, response-header stripping. Configure URL-fetching clients to allow only http and https schemes, disable redirects (or validate redirect targets), set strict timeouts, disable IPv6 if the application does not require it, and use a non-default User-Agent that allows tracing. Each control is small; the combination eliminates several bypass classes.

Detection — SAST Limits, DAST Coverage, Runtime Instrumentation

SSRF is harder to detect than most injection classes because the vulnerability is the request issuance itself, in any code path that touches an HTTP client with a URL parameter. The detection coverage runs across multiple tools, and the layering is the same as for other OWASP categories — see our IAST vs DAST vs SAST comparison guide.

SAST. Static scanners trace data flow from URL-shaped sources to HTTP-client sinks and reliably catch the syntactic pattern — a URL drawn from a request parameter and passed to requests.get is recognized as SSRF-prone. The limits appear at validation logic: a scanner cannot easily prove the URL has been validated against a sufficient allowlist or that DNS rebinding has been mitigated. CodeQL, Semgrep, and Snyk Code have SSRF rule sets that produce useful findings on common patterns and noise on edge cases.

DAST. Dynamic scanners send URL-fetching payloads at endpoints that accept URLs and observe responses or out-of-band callbacks. DAST is the strongest category for confirming SSRF: the scanner supplies a collaborator URL and observes whether the collaborator receives a connection. The limit is endpoint coverage — DAST sees only the endpoints it knows to test, and SSRF surfaces frequently appear in less-obvious paths (background jobs, scheduled imports) that DAST misses.

Runtime instrumentation and outbound traffic monitoring. IAST and RASP-style tooling instrument the HTTP client and flag outbound requests to private CIDR ranges, the metadata endpoint, or URLs not seen in normal traffic. Runtime instrumentation is the strongest category for catching the bypass cases that defeat application-layer validation, because it runs after the validation. At the network layer, monitor outbound traffic from application servers using VPC Flow Logs and SIEM correlation — anomalous connections to internal IP ranges or to the metadata endpoint produce the network signature.

IMDS access logging. Specifically log every access to the metadata endpoint from each instance. The endpoint should receive metadata requests from the cloud-provider agent and the application's bootstrap code, and access from other code paths is anomalous. IMDS logging is the last-line detection for credential theft attempts that succeeded against IMDSv1 or against a misconfigured IMDSv2 setup.

The Mitigation Playbook — Architecture First, Code Second

The application-level defenses above are necessary but fragile. Getting validation right at every URL-fetching code path across a large codebase is hard discipline. The durable architectural pattern is to move SSRF defense out of application code and into infrastructure.

Dedicated egress service. Every outbound HTTP request flows through an egress service that application teams cannot bypass, owned by platform or security engineering. It enforces the allowlist, handles DNS resolution and pinning, and issues the actual outbound request. Feature-team code calls the egress service through an internal RPC and maintains no allowlists or IP validation of its own. The pattern eliminates the discipline failure where SSRF defense is implemented in 30 places, 28 correctly and 2 with subtle bugs.

IAM scoping so even successful SSRF yields nothing useful. Assume SSRF will eventually succeed and make exploitation produce as little damage as possible. The IAM role attached to the application server has minimum permissions — read on specific S3 buckets, write on specific SQS queues, no access elsewhere. An attacker who steals credentials gets the same minimum-privileged operations the application does, and nothing more. This overlaps with the configuration discipline in our security misconfiguration deep dive and with the access-control discipline that prevents internal services from being unauthenticated — internal services without auth are an A01 problem that SSRF is the path to reach.

Training and code-review discipline. The team responsibility shifts from "implement SSRF defense" to "recognize when a feature needs URL fetching and route it through the egress service." Every PR that touches HTTP-client code is reviewed for whether the call goes through the egress service — a one-line finding rather than a multi-paragraph validation analysis. This complements the broader discipline in our A03 injection guide: untrusted input flows into a downstream system, and the durable defense is to route it through a validated boundary application code cannot bypass.

· OWASP A10 · DEVELOPER ENABLEMENT ·

SSRF Is a Network-Layer Problem Wearing an Application-Layer Disguise

A pentest report that says "SSRF in the webhook endpoint" is the visible symptom; the underlying condition is that the application has direct outbound network access, the metadata endpoint is reachable, and the IAM role has more permissions than the feature requires. SecureCodingHub builds the architectural fluency that turns SSRF from a recurring application-code finding into a class your developers route through an egress service before they write the first line of HTTP client code. Hands-on SSRF training for developers — covering the bypass landscape, the cloud-metadata escalation, and the egress-service pattern — is part of the platform. If your team is tired of every cloud pentest producing another SSRF chain into IAM credentials, we'd be glad to show you how the program changes the architecture before the application code gets written.

See the Platform

Closing: The Pattern Beneath Every SSRF Surface

SSRF is the OWASP category that most cleanly illustrates the mismatch between application-layer mitigation and architectural mitigation. The application-layer defenses are well-documented and routinely defeated by bypass techniques public for years. DNS rebinding alone invalidates any "validate the URL, then fetch" pattern that does not pin the resolved IP — and most implementations do not pin. Maintaining application-layer defenses across a large codebase is, realistically, a losing battle.

The architectural defenses survive. An egress service that all outbound HTTP flows through removes the application code's ability to make the mistake. A network policy that prevents the application from reaching the metadata endpoint except through IMDSv2 defends against credential theft regardless of what SSRF surfaces ship. An IAM role scoped to minimum required permissions makes successful theft yield limited access regardless of what SSRF the attacker exploited. None of these defenses depend on application code being correct; all survive application-code bugs.

Server side request forgery was promoted to the OWASP Top 10 in 2021 because the cloud era turned a curiosity into a credential-theft primitive, and it stayed in the 2025 revision because the conditions that made it dangerous have not changed. The path off the list is to recognize the application-layer fight is harder than it looks and the architecture-layer fight is winnable. Teams that win it share a small set of decisions: an egress service through which all outbound HTTP flows, IMDSv2-required across the cloud account, network policies that prevent direct application-to-metadata reachability, IAM roles scoped to specific resources, and internal services that require authentication regardless of network position. The shift is architectural, the discipline is platform-engineering, and the result is that SSRF stops being a recurring pentest finding and becomes, by construction, a class the application code cannot produce.