🔒 SSRF Deep Dive: Turning Your Backend Into a Network Proxy (and How to Stop It)

2/15/2026

🕵️ Server-Side Request Forgery (SSRF)

🚨

SSRF is a trust inversion. The attacker doesn’t “hack the network” directly — they trick your server into making requests on their behalf, using your server’s credentials, network location, and implicit trust.

SSRF happens when an application fetches a remote resource (URL, IP, host, webhook, image, PDF, “import from URL”, etc.) and an attacker can influence where it fetches from.

This is especially dangerous because servers often have access to things attackers don’t:


🧠 SSRF in One Picture (Mental Model) ✅

Think of the application as a request relay:

The “relay” is the vulnerability.


📊 Types of SSRF at a Glance

TypeWhat the attacker getsTypical signals
🔎 Basic SSRFServer requests attacker-controlled hostOutbound requests to unusual domains, webhook endpoints, URL import features
🕳️ Blind SSRFServer requests happen, but attacker can’t see responsesDNS callbacks, timing differences, side-channel effects
🧭 Internal SSRFAccess to internal IPs / hostnamesRequests to RFC1918 ranges, localhost, internal DNS zones
☁️ Metadata SSRFCloud identity/credentials via metadata endpointRequests to link-local addresses; short-lived credential artifacts
🧬 SSRF → Lateral MovementPivot into internal APIs / admin panelsBurst of internal HTTP calls, unusual host headers, unexpected ports
💡

Key insight: SSRF is not “just fetching URLs.” It’s about where your server is allowed to go and what it can reach that a user cannot.


⏱️ A Typical SSRF Kill Chain (Conceptual)

1) SSRF Primitive Found

Discovery

A feature fetches a URL (image fetcher, webhook tester, PDF renderer, link preview, import-from-URL) and accepts attacker input.

2) Reachability Probing

Exploration

Attacker infers internal reachability via response content (non-blind) or via DNS/timing side channels (blind).

3) Target Selection

Targeting

Internal admin endpoints, internal APIs, service meshes, cloud metadata endpoints, or partner allowlisted services.

4) Bypass Filtering

Evasion

Attacker tries URL parsing tricks, redirects, DNS rebinding, IPv6/IPv4 encoding, or alternate host representations.

5) Impact Realization

Abuse

Data exfiltration, credential theft (metadata), internal API actions, or chaining into RCE via vulnerable internal services.

⚠️

This timeline is conceptual. In real assessments, steps may reorder or collapse. The key is that SSRF is often a pivot primitive that becomes high-impact when chained.


🧩 Where SSRF Hides in Real Apps (Red Team Lens)

SSRF most often shows up in features that “helpfully” fetch content:

The “fetch” might be explicit (a visible URL input) or implicit (URL inside JSON, a redirect chain, a stored configuration).


🔥 Why SSRF Becomes Critical: Trust + Reach

✅ SSRF gives an attacker your server’s:

✅ SSRF often becomes “Credential Theft” in cloud

Many cloud platforms provide metadata services reachable only from the instance. If the app can be tricked into querying metadata endpoints, it may leak short-lived credentials.

🚨

Defenders: Treat outbound access to link-local metadata endpoints as a high-severity signal. SSRF + metadata is one of the most consistently high-impact SSRF outcomes.


🧱 Anatomy of a Vulnerable Pattern (Safe, Illustrative)

Many SSRF issues start with “just fetch the URL” logic, without strict validation:

vulnerable-fetch.js
// Illustrative example (do not use as-is)
import fetch from "node-fetch";

export async function fetchPreview(userUrl) {
// ❌ Dangerous: userUrl can point to internal hosts, localhost, or metadata endpoints
const res = await fetch(userUrl, { redirect: "follow" });
const text = await res.text();
return text.slice(0, 2000);
}

What makes this risky?


🧨 SSRF Variants & What to Look For

1) 🔎 Non-blind SSRF (Response Visible)

You see the fetched content in the UI or API response.

High risk if:

2) 🕳️ Blind SSRF (No Response)

You can’t see the response, but you can confirm requests via:

💡

Blue team tip: DNS telemetry (resolver logs) is often your best “truth source” for blind SSRF discovery.

3) 🧭 Internal SSRF (Private Networks)

The server can reach private address space that users cannot. Defenders should assume internal services are not hardened like public ones.

4) ☁️ Metadata SSRF (Cloud)

This is where “medium” SSRF becomes “critical” SSRF: temporary credentials and instance identity are often one request away if controls are missing.


🧱 Common Filter Bypasses (Conceptual, Defensive Focus)

Many defenses fail because they validate the URL as a string, not as a resolved destination. Here are bypass families (no copy-paste exploit chains):

Bypass familyWhat goes wrongHow to defend
🔁 Redirect chainsAllowed URL redirects to blocked hostValidate the final destination (and every hop), restrict redirects, or disable them
🧾 URL parser confusionDifferent components parse host/userinfo/port differentlyUse a single canonical URL parser; re-serialize; reject ambiguity
🧬 DNS tricksHostname resolves to private IP after checks or changes over timeResolve and pin IP before request; re-check after redirects; block private ranges
🔢 Alternate IP encodingsNon-standard IP formats bypass naive regex checksParse as IP objects, not strings; normalize to canonical form
🧷 IPv6/dual-stackIPv6 literal or IPv4-mapped IPv6 sneaks past IPv4-only blocksApply private-range checks for IPv4, IPv6, and IPv4-mapped IPv6
🧪 Proxy & header smugglingInternal proxies honor headers like Host/X-Forwarded-* unexpectedlyDon’t rely on Host for routing; harden internal proxies; strip risky headers
⚠️

The goal isn’t to memorize bypasses — it’s to design validation that is semantic (where does it actually connect?) rather than textual (what does the string look like?).


🧰 Defender-Grade Controls That Actually Work ✅

✅ 1) Prefer Allowlist Over Blocklist

If the feature only needs a small set of destinations (e.g., api.partner.com), enforce exact allowlists.

safe-allowlist.ts
// Illustrative allowlist pattern
const ALLOWED_HOSTS = new Set([
"api.partner.example",
"cdn.partner.example",
]);

export function isAllowedHost(hostname: string) {
return ALLOWED_HOSTS.has(hostname.toLowerCase());
}

Why it helps: you reduce the problem from “block the universe” to “permit only what you need.”


✅ 2) Canonicalize, Resolve, and Re-Validate (Before Connecting)

If you must accept arbitrary user URLs (e.g., link previewers), you need a multi-stage pipeline:

Secure default: disable redirects unless you have a strong reason to allow them.


✅ 3) Egress Network Controls (Defense-in-Depth)

Even perfect app-layer validation can fail. Backstop it with infrastructure:

💡

Best pattern: run URL fetchers in a dedicated sandbox service with minimal outbound access and no sensitive credentials.


✅ 4) Response Handling Hygiene

SSRF impact depends on what you do with the response:


🔎 Detection & Monitoring (Agesec-style Playbook) 🧠

What to log (minimum viable) ✅

What to alert on 🚨

🚨

If you see DNS queries to attacker-controlled domains right after user actions involving URL inputs, treat it as a strong blind SSRF indicator.


🧪 “Safe SSRF Lab” Thought Experiments (No Weaponization)

Here are ways to think like a red teamer without publishing exploit chains:

  1. 🔍 Map where URLs are accepted (UI fields, API params, stored configs)
  2. 🧭 Understand network boundaries (can this service reach internal networks?)
  3. 🔁 Check redirect behavior (does it follow? does it re-validate?)
  4. 🧬 Check DNS usage (does it resolve once? cache? pin IP?)
  5. 🧷 Check protocol support (only HTTP(S) or also file/gopher/etc?)
  6. 🧾 Check parser consistency (frontend vs backend vs proxy parsing)

These questions will quickly tell you if SSRF is “possible” and whether it’s “dangerous.”


🧯 Practical Hardening Checklist ✅

ControlBad default ❌Good default ✅
URL input policyAny URL acceptedStrict allowlist or constrained patterns
RedirectsFollow all redirectsDisable or validate each hop + final IP
DNS handlingResolve late / repeatedlyResolve early, pin IP, re-check on redirect
IP filteringRegex blocks some stringsSemantic checks over resolved IPs (IPv4+IPv6)
Network egressServer can reach everythingEgress rules + dedicated fetcher sandbox
Metadata accessReachable by defaultBlocked by network + hardened metadata mode
ObservabilityNo destination logsLog URL + resolved IP + redirect chain + timing

💬 Wrap-up: How to Teach Your App to Say “No” 🛡️

SSRF defenses fail when they’re:

Build the control plane around: parse → canonicalize → resolve → validate → connect (pinned) → observe.