Series · 3 partsWhen Access Checks FailPart 3 · You are here
  1. 1The Pipeline Has No Secret. It Still Has a Cloud Identity.
  2. 2The Signature Is Valid. The Token Still Belongs Somewhere Else.
  3. 3The Request Stayed Server-Side. The Credential Did Not.You are here

SSRF in 60 seconds

An SSRF finding is usually demonstrated with a URL and reported with a sentence: “the server can make arbitrary outbound requests.” That sentence describes a capability and says almost nothing about impact.

The server was already allowed to make requests. The security question is what those requests inherit: internal routing, trusted source addresses, ambient credentials, proxy exceptions, redirect handling, and access to link-local control planes. SSRF becomes serious when an untrusted input crosses one of those boundaries and the resulting request is treated as the server rather than as the caller.

The URL is only the first edge. The route is the finding.

Four decisions happen after validation

Most application code validates a string, then hands it to a network stack that performs several additional transformations:

  1. The parser resolves scheme, authority, user information, host, port, and path.
  2. DNS converts the accepted hostname into one or more addresses.
  3. The HTTP client follows redirects, possibly to a new scheme or address.
  4. A proxy, service mesh, or host route decides where the request actually goes.

If policy runs only before those transformations, it authorizes a name and the network connects to an address. The two can disagree for the same reasons a reverse proxy and application can disagree about a path.

A blocklist for 127.0.0.1 does not express the intended policy. The intended policy is usually “this feature may fetch public HTTPS resources from these destinations, must not follow a redirect outside that set, and must never reach private, loopback, link-local, or control-plane ranges after every DNS resolution.”

That is an allowlist plus a connection-time address check, not a collection of forbidden strings.

THE INPUT IS A URL — THE SECURITY DECISION IS THE RESOLVED ROUTE URL input parse once scheme + host DNS all addresses check results redirect new target revalidate egress ROUTE POLICY last boundary dest ? IMPACT IS DECIDED BY WHAT THE SERVER CAN REACH AND WHO IT BECOMES public canary proves outbound request only capability evidence internal service crosses network trust boundary evidence metadata identity request inherits workload trust identity-impact boundary
SSRF impact grows at each boundary the request crosses. A public canary proves the primitive; an internal route proves network trust; metadata or a control-plane identity proves that the server-side request can become a credentialed principal.

Start with a canary, not an internal target

The first test should answer whether the feature performs a server-side request and what that request looks like. Use an assessment-controlled HTTPS endpoint with a unique per-request path. Record source address, method, headers, DNS queries, redirect behavior, and timing.

https://canary.example.test/ssrf/<engagement>/<request-id>

That observation separates server-side fetches from client-side navigation and exposes hidden behavior without touching an internal service. A request arriving from the server’s egress address is positive evidence. No arrival is not automatically a negative result: the application may queue work, restrict schemes, resolve through a proxy, or fetch only certain content types. The canary should mirror the expected response shape before the test expands.

Next, change one property at a time:

  • a redirect from one allowed canary hostname to a second controlled hostname;
  • a hostname resolving to multiple public canary addresses;
  • a slow response to identify timeout and retry behavior;
  • a non-HTTPS scheme to confirm it is rejected before connection;
  • an allowed hostname that redirects toward a prohibited address, which must be blocked without contacting that destination.

These tests map the client’s state machine. They do not require probing real internal hosts.

Validate after DNS and after every redirect

The application cannot decide whether a destination is public from the hostname string alone. It has to resolve the name, inspect every returned address, and bind the connection to an allowed result without letting a later lookup silently change the answer.

Redirects restart that process. A 302 is not permission to inherit the original trust decision. The new URL needs the same scheme, host, port, DNS, and address policy as the first. If the client changes method or forwards sensitive headers across hosts, those behaviors are part of the finding too.

The safest fetcher is deliberately boring:

parse canonical HTTPS URL
  -> compare hostname and port with explicit allowlist
  -> resolve all addresses
  -> reject private, loopback, link-local, multicast, and reserved results
  -> connect to the validated address
  -> disable redirects, or re-run the entire decision for each hop
  -> enforce response-size, content-type, and time limits

An outbound proxy or service-specific fetch broker makes this easier to enforce consistently. Application code is bad at reproducing network policy in every feature that accepts a URL.

Metadata is an identity boundary, not a special URL

On an EC2 instance, the Instance Metadata Service is available through a link-local address. Applications sometimes treat blocking that literal address as the SSRF fix. The durable control is stronger: require IMDSv2, constrain metadata reachability to workloads that need it, and give the attached role only the permissions the workload requires.

IMDSv2 uses a session-oriented token. A client first sends a PUT to request a token and then presents that token in subsequent metadata GET requests. This blocks many simple SSRF primitives that can issue only a fixed GET or cannot set the required header. It does not turn arbitrary server-side request capability into a safe design. A sufficiently capable primitive, local proxy, or compromised process may still satisfy the exchange.

The hop limit is another boundary. It controls how far the metadata token response may travel, which matters when container networking adds a hop. The correct value depends on the deployment topology; setting it from a generic hardening checklist without testing the workload can either break credential delivery or leave a broader route than intended.

For an authorised validation, use a dedicated test instance with a lab role that grants no resource access. Prove only whether the application can reach an inert metadata path such as instance metadata information under the configured IMDS mode. Do not retrieve or store role credentials. The finding is stronger when it proves the boundary without creating a new credential exposure in the evidence package.

Egress decides whether one bug becomes a platform path

Application validation is the first control. Network egress is the independent negative control. A workload that needs to fetch avatars from one image service should not have a general route to every RFC1918 segment, orchestration API, metadata endpoint, and management plane.

That separation matters because URL validation code changes frequently. Egress policy can keep a parser regression from becoming an identity compromise. Conversely, a perfect application allowlist should not be used as evidence that the network boundary is safe; each control should be tested with the other assumed to fail.

Evidence matrix

SignalWhat it provesNegative controlDefender verification
Unique canary request arrives from server-side infrastructureThe application performs an outbound request and reveals its fetch behaviorUse an unsupported scheme or disallowed hostname and require no canary contactCorrelate application request ID, DNS, proxy, and egress logs
Redirect to a second controlled public host is revalidated or blockedRedirect handling does not inherit the first destination’s trust blindlyRedirect toward a prohibited test address and confirm no connection attempt leaves the hostTest every redirect hop for scheme, host, port, resolved address, method, and forwarded headers
All DNS answers are checked and the connection stays bound to an allowed resultPolicy applies to addresses rather than only the original hostnameReturn a mixed allowed/prohibited answer set and require rejectionInstrument resolver results and destination IP at connection time
IMDSv2-required test instance rejects a simple headerless metadata fetchThe metadata boundary resists basic GET-only SSRFConfirm a legitimate SDK on the same workload still obtains its intended identityInventory HttpTokens, hop limit, metadata exposure, and attached role permissions
Egress policy denies private, link-local, and control-plane routesA parser failure does not automatically become an internal pivotAttempt an approved canary connection in each denied class without touching production servicesValidate route tables, proxies, service-mesh policy, and host firewall from the workload identity

The pattern I keep seeing

The application team fixes the input string. The network team owns egress. The cloud team owns the workload role. Each closes the piece visible from its layer, and the request path between them remains undocumented.

That is how a finding alternates between “High” and “Low” depending on who reads it. One person sees a URL fetcher. Another assumes metadata credentials. Neither has evidence for the whole chain.

What to hand the defenders

The resolved route. Record the canonical URL, every DNS answer, redirect hop, destination address, proxy path, and the exact trust boundary crossed.

Capability separated from impact. A public canary proves server-side fetching. Internal reachability proves a network boundary failure. Metadata or control-plane access must be validated independently and safely before claiming identity impact.

Two control owners. Fix the fetcher with an allowlist and post-resolution validation; fix the platform with egress restrictions, IMDSv2, and least-privilege workload identity. Either control can regress. The pair is what makes the route survivable.

The request may never leave the server. The trust it carries can cross half the environment.

Sources & freshness

How current is this note?

Sources checkedJanuary 29, 2026

The latest source-review, content-update, or publication date is shown.

ReviewAuthor review complete

The author completed a technical review. This does not, by itself, claim lab reproduction.