Skip to content

Lesson 4 of 11 · 8 min read

Server-Side Request Forgery

Free lessonReviewed By the AISecurityInterviews editorial team
Reading position
In this lesson

TL;DR: In SSRF the server makes a request to a destination the attacker chose, using the server's network access: internal APIs, databases with HTTP interfaces, admin panels, and the cloud metadata service that hands out credentials. URL string checks fail against alternative IP encodings, DNS tricks and redirects. Defend by allowlisting destinations where possible, otherwise by resolving the host, rejecting non-public addresses, and connecting to the address you checked, re-validating every redirect, and sending fetches through an egress proxy in a network segment with no internal access. Agent browsing and fetch tools need all of this.

Before you read

Pick an answer before you read further. The lesson below explains it, and guessing first, right or wrong, helps the explanation stay with you.

  1. 1A link-preview feature fetches any URL a user posts and shows the page title. Why is that dangerous on a cloud server?

The server as an unwitting proxy

Many features fetch URLs: link previews, webhooks, PDF and image importers, "import from URL", integrations, and now LLM tools that browse or fetch documents. Each runs inside your network with access the requester lacks. If the requester controls the destination, the server becomes a confused deputy for network access.

Targets attackers aim for:

  • Cloud metadata services that return instance details and temporary credentials.
  • Internal APIs and admin panels that trust anything on the internal network.
  • Datastores with HTTP interfaces (search engines, caches, container and orchestration APIs).
  • Localhost services on the same machine.

When the response is not returned to the attacker, the SSRF is blind, but it can still trigger actions on internal services or reveal information through timing and errors.

Why string checks fail

Blocking URLs that contain localhost or 169.254.169.254 misses:

BypassExample
Alternative IP encodingsDecimal 2130706433 or hex 0x7f000001 for 127.0.0.1
IPv6 forms[::1], IPv4-mapped IPv6 addresses
DNS names that resolve internallyAn attacker's domain with an A record of 10.0.0.5
DNS rebindingA name that resolves to a public IP at check time and an internal one at fetch time
RedirectsA public URL that responds with a redirect to an internal address

The only reliable check is on the IP address the connection actually goes to.

Defences in order of strength

  1. Allowlist destinations. If the feature only needs a few partner domains, allow exactly those and nothing else.
  2. Isolate the fetcher. Run outbound fetches through an egress proxy, or in a network segment that cannot reach internal ranges or the metadata service at all. This holds even if validation code has a bug.
  3. Validate the resolved address, then pin it. Resolve the host, reject any non-public address, and connect to the exact address you validated, so a second DNS lookup cannot change the answer.
  4. Handle redirects. Disable them, or re-run the full validation on every hop.
  5. Harden the metadata service. AWS IMDSv2 requires a session token obtained with a PUT request and limits hop count, which blocks most SSRF paths to credentials; other clouds require specific headers.
  6. Limit what comes back. Return only what the feature needs (a title, a thumbnail), not raw responses or detailed errors.

Here is the address check from step 3, using Python's ipaddress module:

import ipaddress
import socket
from urllib.parse import urlsplit

ALLOWED_SCHEMES = {"http", "https"}


def resolve_public(url: str) -> tuple[str, str]:
    """Return (host, ip) if every address the host resolves to is public."""
    parts = urlsplit(url)
    if parts.scheme not in ALLOWED_SCHEMES or not parts.hostname:
        raise ValueError("scheme or host not allowed")
    port = parts.port or (443 if parts.scheme == "https" else 80)
    infos = socket.getaddrinfo(parts.hostname, port, proto=socket.IPPROTO_TCP)
    ips = {info[4][0] for info in infos}
    for ip in ips:
        addr = ipaddress.ip_address(ip)
        if not addr.is_global:  # private, loopback, link-local, reserved...
            raise ValueError(f"{parts.hostname} resolves to non-public {ip}")
    # Connect to this exact IP (sending the original Host header), so a
    # second DNS lookup cannot return a different, internal address.
    return parts.hostname, sorted(ips)[0]

Tested against loopback, metadata, private ranges, [::1], and the decimal and hex encodings of 127.0.0.1, it rejects all of them and accepts a public address. It is one layer: pair it with network isolation, because validation code is where bypasses are found.

Metadata services across clouds

Every major cloud gives instances a link-local metadata endpoint that hands out the instance's role credentials, which makes it the classic SSRF target. Each provider hardened it against the same observation, spelled out in AWS's security blog when IMDSv2 launched: most SSRF primitives can only issue a GET to a URL, without custom headers, and misconfigured reverse proxies add an X-Forwarded-For header to what they relay.

ProviderWhat a request must includeWhat is rejected
AWS IMDSv2A session token from PUT /latest/api/token (TTL 1 to 21,600 seconds), sent back in X-aws-ec2-metadata-tokenRequests without a valid token when tokens are required; token PUTs carrying X-Forwarded-For; token responses beyond the hop limit (default 1)
Google CloudThe header Metadata-Flavor: GoogleRequests without it, and requests with X-Forwarded-For
AzureThe header Metadata: trueRequests without it, and requests with X-Forwarded-For

AWS also offers an account-level default that makes new instances IMDSv2-only (since March 2024), and an IPv6 metadata address on Nitro instances, which is one more reason string blocklists of 169.254.169.254 fail.

Worked example: which SSRF primitive reaches credentials?

PrimitiveAWS IMDSv1AWS IMDSv2 requiredGoogle CloudAzure
URL-only GET (a link preview fetcher)CredentialsBlocked: no PUT, no tokenBlocked: no headerBlocked: no header
Open reverse proxy that adds X-Forwarded-ForCredentialsBlocked: XFF on the PUTBlocked: XFFBlocked: XFF
HTTP client whose method and headers the attacker controls (for example a model-driven HTTP tool)CredentialsCredentials, within the hop limitCredentialsCredentials
LESSON · SERVER-SIDE REQUEST FORGERY
Three fetchers, one metadata service

URL-only GET fetcher

Attacker supplies a URL

GET to metadata

no token, no header

Refused

Outcome, safe: IMDSv2 and headers stop it

The primitive cannot do a PUT or set headers.

Model-driven HTTP tool

Injected text steers the model

PUT for token, then GET

method and headers chosen

Role credentials returned

Outcome, unsafe: Hardening is bypassed

A capable client satisfies every metadata check.

Same tool, isolated network

Injected text steers the model

No route to metadata

egress tier, public only

Connection fails

Outcome, safe: Contained by the network

Works even when the client is fully capable.

1/3

The common primitive

Most SSRF bugs can only send a GET to a URL. Token and header requirements defeat them.

The first two rows are why metadata hardening was worth doing: it kills the common primitives. The third row is why it is not enough for agents. A tool that lets the model choose the method and headers is a fully capable HTTP client, and only network isolation of the fetcher (no route to the metadata address at all) plus a minimal role covers it.

The 2019 Capital One breach is the reference case for the combination: a server-side request forgery through a misconfigured web application firewall reached the metadata service of the time, the role it returned could list and read many storage buckets, and nothing restricted where the data could be sent. Teach it as three failures (SSRF, an over-privileged role, no egress control), not as one.

Webhooks and blind SSRF

Webhooks invert the usual feature: the customer chooses a URL and your servers call it, forever, on every event. Treat the delivery system as hostile-input infrastructure:

  • Deliver from a separate egress tier whose resolver and firewall only reach the public internet, never internal ranges or metadata, and re-check the resolved address on every delivery, not just at registration (a DNS record can change after you validated it).
  • Never return the target's response body to the customer, and keep error messages generic; status codes and timing are enough to map an internal network if you echo them.
  • Sign payloads with an HMAC per customer so receivers can verify them, and cap redirects, response size and time.

Blind SSRF, where the attacker never sees a response, is still serious: a request alone can trigger internal state changes on services that act on GETs, and differences in timing or error codes leak which internal hosts and ports exist.

SSRF from the browser to localhost

The same confused-deputy pattern runs inside the user's browser. A web page can make the browser send requests to localhost or a private address, and developer tools increasingly run local HTTP servers: notebook servers, model runners, MCP inspectors and proxies. If such a service has no authentication and accepts requests from any origin, any website the developer visits can drive it.

CVE-2025-49596 in the MCP Inspector is the agent-era example. Its proxy listened on all interfaces on port 6277 without authentication, so a malicious page could reach it, through the browser's handling of 0.0.0.0 or through DNS rebinding, and launch commands. Version 0.14.1 fixed it by adding a session token and checking the Origin and Host headers. Browsers are adding a backstop: Chrome 142 introduced Local Network Access, which asks the user's permission before a public site can send requests to local or loopback addresses. Treat that as defence in depth; the service itself must authenticate, bind to loopback only, and check Origin and Host.

Agents and fetch tools

LESSON · SERVER-SIDE REQUEST FORGERY
An injected page turns a browsing tool into SSRF

Web page

Says: fetch an internal URL

Agent

Has a fetch tool

Metadata service

Cloud credentials

Controls

Egress proxy

  • Public addresses only

Also in the picture

Internal admin API

1/2

Fetch tool with direct network access

Injected text asks the agent to fetch an internal address. The tool runs inside the network and reaches the metadata service and an admin API.

A browsing or fetch tool exists to request URLs chosen by the model, and the model's choices can be steered by any text it reads. That makes the tool an SSRF primitive by design. It needs every defence above, enforced in the tool or the network rather than in the prompt, plus the reverse concern: the same tool is an exfiltration channel if it can put private data into a URL.

Misconceptions interviewers listen for

  • "IMDSv2 fixes SSRF." It stops the common primitives against one target. Internal APIs, other metadata paths and fully capable HTTP clients remain.
  • "Block 169.254.169.254 and localhost by string." Alternative encodings, IPv6, DNS names, redirects and rebinding all bypass it.
  • "Blind SSRF is low severity." Requests alone can change internal state and map the network.
  • "Services on localhost are safe from the web." Browsers can reach them; authentication and Origin checks are required.
  • "OWASP dropped SSRF from the Top 10." It was folded into A01 Broken Access Control in the 2025 edition, alongside CSRF, because both are a server or browser acting with authority the requester lacks.

What if · interview follow-ups

Hold your answer as the constraints change

Your product has a link-preview feature, customer-configured webhooks and an AI assistant with a fetch tool, all running on cloud instances. The interviewer probes the server-side request paths one at a time.

  1. Start here

    What if the link-preview fetcher follows redirects?

Primary references

References for the mechanisms discussed above, accessed 6 November 2026:

How this shows up in interviews

"What is SSRF, and why do LLM fetch tools bring it back?" is a frequent question for AI security roles. Strong answers name the metadata service and internal APIs as targets, explain why string filters fail, describe resolve-validate-pin plus network isolation, and mention IMDSv2. Then they connect it to agents: the model chooses the URL, injection chooses for the model, so the tool must enforce destination rules itself.

Say it in 30 seconds: "SSRF is the server fetching a URL an attacker influenced, from inside our network, so it can hit internal APIs or the cloud metadata service for credentials. String filters fail against encodings, DNS tricks and redirects. I allowlist destinations where I can; otherwise resolve, reject non-public IPs, connect to the checked IP, re-validate redirects, and run fetches through an egress proxy that can't reach internal ranges. Agent fetch tools are SSRF by design, so the tool enforces this, not the prompt."

Check yourself

Say or write your answer first, then reveal. Explaining it from memory, in your own words, is the skill an interviewer is checking.

  1. 1Your validation resolves the hostname, confirms the IP is public, then passes the original URL to an HTTP library, which resolves it again. What attack does this allow?

  2. 2Why is network isolation of the fetcher worth having even with perfect validation code?

  3. 3Why do Google Cloud and Azure reject metadata requests that carry an X-Forwarded-For header?

  4. 4A webhook URL was validated as public when the customer registered it. Why is that not enough?

Practise it as an interviewer asks it

Related lessons