Safeguard
Vulnerability Guides

What is SSRF (Server-Side Request Forgery)?

Server-side request forgery tricks your own backend into making attacker-chosen requests — often against internal systems it should never reach. Here's how SSRF works and how to shut it down.

Daniel Osei
Security Researcher
Updated 5 min read

Your application fetches a URL a user supplied — a webhook target, an image to thumbnail, a PDF to render. That single feature can hand an attacker a foothold inside your network, and most teams never notice until an incident report lands on their desk. So what is SSRF, exactly, and why does one innocent-looking URL fetch cause so much damage?

What is SSRF? The one-paragraph answer

Server-Side Request Forgery (SSRF, CWE-918) is a vulnerability where an attacker coerces a server into sending HTTP or other network requests to a destination of the attacker's choosing. Because the request originates from inside your trust boundary, it can reach things the attacker never could directly: cloud metadata endpoints, internal admin panels, databases bound to localhost, and services protected only by network position. SSRF is ranked in the OWASP Top 10 (A10:2021) and was the pivot point in the 2019 Capital One breach, where an attacker abused SSRF to read AWS instance-metadata credentials and exfiltrate data on more than 100 million people.

How SSRF actually works

Any time your backend takes a URL, hostname, IP, or file path influenced by user input and turns it into an outbound request, you have a potential SSRF sink. The classic escalation targets are:

  • Cloud metadata services. On AWS the link-local address 169.254.169.254 serves instance credentials to whatever process asks. IMDSv1 required no authentication, which is exactly what made Capital One exploitable. IMDSv2 added a token-based, hop-limited handshake specifically to blunt SSRF.
  • Internal services. http://localhost:6379, http://10.0.0.5:8500, or a Kubernetes control plane that assumes anything on the pod network is trusted.
  • Cloud and CI internals. GCP metadata, Azure IMDS, and CI runner tokens all live at predictable internal addresses.

Attackers defeat naive defenses with encoding and redirect tricks: decimal or octal IP encoding (http://2130706433/ is 127.0.0.1), DNS names that resolve to internal IPs, an HTTP endpoint that returns a 302 redirect to 169.254.169.254, or alternate schemes like file://, gopher://, and dict:// to reach non-HTTP services. This class was also the mechanism behind CVE-2021-26855 (ProxyLogon), where an unauthenticated SSRF into Microsoft Exchange kicked off one of the most widely exploited attack chains of the decade.

Vulnerable vs. fixed

A webhook validator that fetches whatever the user submits:

# VULNERABLE — fetches any URL, follows redirects, no destination checks
import requests

def validate_webhook(url: str):
    resp = requests.get(url, timeout=5)   # attacker sends http://169.254.169.254/...
    return resp.status_code == 200

The fix combines scheme allow-listing, DNS resolution before the request, an explicit block of private/link-local ranges, and disabling redirects so a 302 can't smuggle the request to an internal target:

# FIXED — validate scheme, resolve host, reject private ranges, no redirects
import ipaddress, socket, requests
from urllib.parse import urlparse

ALLOWED_SCHEMES = {"https"}

def is_public_ip(host: str) -> bool:
    for family, _, _, _, sockaddr in socket.getaddrinfo(host, None):
        ip = ipaddress.ip_address(sockaddr[0])
        if ip.is_private or ip.is_loopback or ip.is_link_local or ip.is_reserved:
            return False
    return True

def validate_webhook(url: str) -> bool:
    parsed = urlparse(url)
    if parsed.scheme not in ALLOWED_SCHEMES:
        raise ValueError("scheme not allowed")
    if not is_public_ip(parsed.hostname):
        raise ValueError("destination not allowed")
    resp = requests.get(url, timeout=5, allow_redirects=False)
    return resp.status_code == 200

Note the ordering: you resolve and validate the host, then make the request. Validating the string alone loses to DNS rebinding, where the name passes your check and then re-resolves to an internal IP on the actual connection. The robust pattern is to resolve once, confirm the address is public, and connect to that pinned address.

Prevention checklist

  • Deny by default. Maintain an allow-list of destination hosts or domains rather than trying to block every dangerous one.
  • Block internal ranges at the application layer: loopback, RFC 1918, link-local (169.254.0.0/16), and cloud metadata IPs — after DNS resolution.
  • Restrict schemes to https (and http only if you truly need it). Reject file://, gopher://, dict://, ftp://.
  • Disable automatic redirect following, or re-validate the destination on every hop.
  • Enforce IMDSv2 on AWS and set a metadata hop limit of 1 so containers can't reach it. Apply the equivalent hardening on GCP and Azure.
  • Segment the network so a compromised fetch service cannot route to databases, admin planes, or the control plane.
  • Send outbound fetches through an egress proxy that enforces destination policy centrally, instead of trusting every service to get it right.
  • Log and alert on outbound requests to internal or metadata addresses — that traffic is almost always malicious.

How Safeguard helps

SSRF is a runtime behavior, so it needs runtime evidence. Safeguard's DAST engine actively probes URL-handling endpoints with encoded IPs, redirect chains, and metadata payloads to confirm whether a request-forging sink is genuinely exploitable — not just theoretically present. At the code layer, Griffin AI code review traces user-controlled input into outbound request calls (requests.get, fetch, HttpClient, curl) and flags fetches that skip destination validation, follow redirects, or accept arbitrary schemes. When a vulnerable dependency introduces the sink, Safeguard's software composition analysis maps it against your dependency graph, and Safeguard's auto-fix can open a pull request that adds destination validation or pins a patched version. Running scans locally before you push is a one-liner with the Safeguard CLI, so SSRF gets caught in development rather than in a breach post-mortem.

Curious how our runtime and code analysis stack up against SAST-first tools? See the Safeguard vs Checkmarx comparison or browse the full product comparison hub.

Create a free account at app.safeguard.sh/register and point it at a repository to see SSRF sinks surfaced in minutes, or read the integration guides at docs.safeguard.sh.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.