SSRF (Server-Side Request Forgery) Deep Dive
SSRF happens when an application lets an attacker influence where the server makes a network request. The attacker can trick the server into requesting internal systems (databases, admin panels, cloud metadata) that the attacker cannot reach directly.
Practical note: SSRF shows up in âhelpfulâ features â link previews, imports, webhooks â and it often becomes an internal network scanner before anyone notices.
Why SSRF exists (root cause)
Modern apps routinely fetch URLs: webhooks, link previews, image imports, PDF converters, OAuth metadata, remote config, and integrations. SSRF appears when the app treats a user-controlled destination as data but the server treats it as instructions for routing.
- Powerful server network position: servers often have access to internal subnets, service meshes, and cloud metadata endpoints.
- Ambiguous URL parsing: URL syntax is complex; naive allow/deny checks are easy to bypass conceptually.
- Protocol confusion: âURLâ might mean http(s), redirects, DNS aliases, or even non-HTTP handlers depending on libraries.
- Implicit trust in internal services: internal endpoints may assume âif you can reach me, youâre trustedâ.
Mental model (experienced-level)
Think of SSRF as a routing pipeline:
- Source: where the destination comes from (URL param, JSON field, webhook config, import feature).
- Normalization: parsing, decoding, redirects, DNS resolution, and proxy behavior.
- Decision: allow/deny logic (host allowlist, scheme restrictions, IP blocks, port policy).
- Request execution: the HTTP client, its redirect rules, DNS caching, and timeout behavior.
- Response handling: do we return the body to the user? store it? parse it? (this decides data leakage vs blind SSRF).
Common SSRF shapes (what you see in real apps)
1) Fetch/preview features
Plain: âPreview this link / import this imageâ. Deep: server fetches user-supplied URLs and may follow redirects.
2) Webhooks & callbacks
Plain: âSend events to this URLâ. Deep: outbound requests can be aimed at internal systems or metadata.
3) Integrations / connectors
Plain: âConnect to service Xâ. Deep: misconfigured âservice URLâ fields become SSRF if not constrained.
4) Blind SSRF
Plain: server makes a request but you donât see the response. Deep: still dangerous if it can hit internal control endpoints or trigger side effects.
Vulnerable vs secure code patterns (Node.js)
Vulnerable pattern (minimal)
// Node/Express (concept example)
app.post("/preview", async (req, res) => {
const url = String(req.body.url || "");
// â Vulnerable: server fetches an attacker-influenced destination
const r = await fetch(url, { redirect: "follow" });
const text = await r.text();
res.json({ ok: true, preview: text.slice(0, 500) });
}); Fixed defensive pattern (allowlist + resolution checks + safe client)
import dns from "node:dns/promises";
import net from "node:net";
// Minimal helpers (conceptual; tune for your environment)
const ALLOWED_HOSTS = new Set(["example.com", "api.example.com"]); // prefer explicit host allowlist
const ALLOWED_SCHEMES = new Set(["https:"]); // prefer https-only
const ALLOWED_PORTS = new Set([443]); // keep tight
const MAX_REDIRECTS = 0; // safest default: don't follow redirects automatically
function isPrivateIp(ip) {
// Covers common private ranges + loopback + link-local (IPv4). Expand for IPv6 in real code.
const parts = ip.split(".").map(Number);
if (parts.length !== 4 || parts.some(n => Number.isNaN(n) || n < 0 || n > 255)) return false;
const [a,b] = parts;
if (a === 10) return true;
if (a === 127) return true;
if (a === 169 && b === 254) return true;
if (a === 172 && b >= 16 && b <= 31) return true;
if (a === 192 && b === 168) return true;
return false;
}
async function validateDestination(raw) {
let u;
try { u = new URL(raw); } catch { throw new Error("Invalid URL"); }
if (!ALLOWED_SCHEMES.has(u.protocol)) throw new Error("Blocked scheme");
if (u.username || u.password) throw new Error("Blocked credentials in URL");
if (!u.hostname) throw new Error("Missing host");
// Enforce explicit host allowlist
const host = u.hostname.toLowerCase();
if (!ALLOWED_HOSTS.has(host)) throw new Error("Host not allowed");
// Enforce explicit ports (URL without port implies protocol default)
const port = u.port ? Number(u.port) : (u.protocol === "https:" ? 443 : 80);
if (!ALLOWED_PORTS.has(port)) throw new Error("Port not allowed");
// Resolve and block private IPs (validate the final resolved destination)
const addrs = await dns.lookup(host, { all: true, verbatim: true });
for (const a of addrs) {
if (a.family === 4 && isPrivateIp(a.address)) throw new Error("Resolved to private IP");
// For IPv6, add: block ::1, fc00::/7, fe80::/10, etc.
}
return u.toString();
}
app.post("/preview", async (req, res) => {
try {
const raw = String(req.body.url || "");
const safeUrl = await validateDestination(raw);
// Use strict client behavior: no redirects, short timeouts, size limits, no proxy surprises
const ctrl = new AbortController();
const t = setTimeout(() => ctrl.abort(), 3000);
const r = await fetch(safeUrl, { redirect: "manual", signal: ctrl.signal });
clearTimeout(t);
// Optional: enforce content-type allowlist and response size caps before reading
const ct = (r.headers.get("content-type") || "").toLowerCase();
if (!ct.includes("text/html") && !ct.includes("application/json")) {
return res.status(415).json({ ok: false, error: "Unsupported content type" });
}
const text = await r.text();
res.json({ ok: true, preview: text.slice(0, 500) });
} catch (e) {
res.status(400).json({ ok: false, error: String(e.message || e) });
}
}); Detection workflow (how to validate it)
Start by mapping the feature, then validate the controls rather than âtrying payloadsâ.
Step A â Find outbound request surfaces
- Link previews, image imports, âfetch from URLâ, PDF generation, webhooks
- SSO/OAuth config URLs, âmetadata URLâ fields, connectors
- Any place users can configure a callback or integration endpoint
Step B â Determine SSRF class
- Response-visible SSRF: the app returns fetched content to the user
- Blind SSRF: no content returned, but a request is made (still dangerous)
Step C â Validate controls
- Strict allowlist for host/scheme/port (prefer âknown-good onlyâ)
- DNS resolution checks + private IP blocks
- Redirect policy (prefer no redirects or re-validate each hop)
- Timeouts, size limits, and safe proxy configuration
- Network egress controls (firewalls, service mesh policies) as defense-in-depth
Safe validation (defensive verification only)
- Baseline behavior: confirm the feature makes outbound requests and document inputs that control destination.
- Observe controls: check for allowlists, scheme/port restrictions, redirect handling, and error messages.
- Confirm resolution behavior: verify that destination validation occurs after parsing and DNS resolution (and again after redirects if used).
- Limit scope: test only against safe, owned, or explicitly permitted endpoints (e.g., internal staging endpoints you control).
- Evidence: capture request logs, server-side traces, and policy decisions (blocked vs allowed) to support remediation.
Exploitation progression (attacker mindset)
Conceptual only (no step-by-step). Attackers usually move from âcan I steer the serverâs request?â to âcan I reach something the server can access?â by exploring parsing, redirects, DNS, and egress policy gaps.
Phase 1: Find a controllable request
- Identify features that fetch URLs or call webhook destinations.
- Determine whether responses are visible (data leak) or blind (side effects).
Phase 2: Test constraints
- See if scheme/host/port are constrained; evaluate redirect behavior.
- Evaluate how DNS is handled (caching, re-resolution, allowlist enforcement).
Phase 3: Attempt internal reachability (concept)
- Attackers look for internal services that trust the network, or metadata endpoints in cloud environments.
- They also look for endpoints with side effects (triggering actions) even if response is not visible.
Phase 4: Chain with app behavior
- Use SSRF as a pivot: accessing internal APIs, abusing trust, or extracting configuration that enables further compromise.
Tricky edge cases & bypass logic (conceptual)
- Redirect chains: allowlisted host redirects to a disallowed host; fix is no redirects or re-validate every hop.
- DNS rebinding: a host resolves to a public IP at validation time but later resolves to a private IP; fix involves re-checking resolution and pinning the resolved IP for the request where feasible.
- URL parsing confusion: differences between âstring checksâ and actual URL parser behavior; always parse with a standard URL parser before applying rules.
- Proxy / NO_PROXY surprises: outbound proxies may allow internal routing unless carefully configured.
- IPv6 coverage gaps: blocking only IPv4 private ranges leaves gaps; policies must cover loopback, link-local, and private IPv6 ranges.
- Credentialed URLs: user:pass@host can confuse naive checks; block embedded credentials.
- Alternate encodings: encoded host/port segments can bypass naive substring filters; normalize before decisions.
- Non-HTTP protocols: libraries or runtimes may support more than http/https; restrict schemes explicitly.
Confidence levels (low / medium / high)
| Confidence | What you observed | What you can claim |
|---|---|---|
| Low | Suspected outbound fetch, but destination control unclear or inconsistent | âPotential SSRF surface; requires confirmation of controllable destination and control evaluationâ |
| Medium | Repeatable destination influence, but tight allowlist/egress controls limit risk | âLikely SSRF class issue; controls reduce exploitability but design review recommendedâ |
| High | Repeatable influence with ability to reach non-allowed/internal destinations (or major side effects) during validation | âConfirmed SSRF with clear root cause and actionable remediation guidanceâ |
Fixes that hold in production
1) Eliminate arbitrary URLs (best)
- Use IDs or pre-registered destinations instead of âfetch any URLâ.
- For webhooks, require verification and restrict destinations (tenant-owned allowlists).
2) Strict allowlist: scheme + host + port
- Prefer explicit host allowlists (or exact subdomain rules) over blocklists.
- Restrict to
httpsand a minimal set of ports.
3) Validate final destination
- Parse with a real URL parser, normalize, then validate.
- Resolve DNS and block private/loopback/link-local ranges (IPv4 + IPv6).
- Handle redirects safely: disable or re-validate each hop.
4) Harden the HTTP client
- Short timeouts, low max response size, strict content-type handling.
- Ensure proxy settings donât accidentally enable internal routing.
5) Defense-in-depth: network egress controls
- Firewall/service mesh policies to restrict outbound to known services.
- Separate networks for sensitive services; do not rely on âinternal-onlyâ as the primary control.
Interview-ready summaries (60-second + 2-minute)
60-second answer
SSRF is when user input influences where the server makes a network request. Because servers can reach internal services and cloud metadata, SSRF can become high impact. I look for URL-fetch features and validate controls: strict scheme/host/port allowlists, DNS resolution checks to block private ranges, safe redirect handling, client hardening, and egress firewalling as defense-in-depth.
2-minute answer
I treat SSRF as a trust-boundary bug between user-controlled destinations and the serverâs privileged network position. I start by mapping the feature: where destination input comes from, how URLs are parsed/normalized, whether redirects are followed, and whether the response is exposed (data leak) or itâs blind SSRF (side effects). Then I validate controls at the right layer: parse with a proper URL parser, enforce strict allowlists for scheme/host/port, resolve DNS and block private/loopback/link-local ranges (IPv4 and IPv6), and either disable redirects or re-validate each hop. Finally I harden the HTTP client (timeouts, size limits) and add network egress controls so even if app logic regresses, the network blocks unsafe routes.
Checklist
- No âfetch any URLâ unless absolutely required; prefer IDs or pre-registered destinations.
- Strict allowlist for scheme/host/port (prefer https-only and minimal ports).
- URL is parsed and normalized before policy checks.
- DNS resolution is checked; private/loopback/link-local ranges blocked (IPv4 + IPv6).
- Redirects disabled or every hop is re-validated.
- HTTP client hardened (timeouts, size limits, content-type policy, proxy config).
- Network egress controls restrict outbound to known destinations.
- Logs/metrics exist for blocked outbound attempts (helps detect abuse and tune rules).
Remediation playbook
- Contain: disable/limit the URL-fetch feature or constrain it to known-good destinations immediately.
- Fix root cause: implement strict allowlists and validate the final resolved destination (DNS + redirects).
- Harden client: timeouts, response size caps, and safe redirect policy; remove proxy surprises.
- Network controls: enforce outbound egress rules to block private networks and metadata ranges.
- Regression prevention: centralize fetch logic, add tests for policy enforcement, and review all URL-fetch call sites.
- Verify: re-test the original flow and search for other âserver fetches URLâ surfaces across the app.
Interview Questions & Answers (Easy â Hard)
Easy
- What is SSRF?
A: Plain: when the server is tricked into making a request to a destination influenced by an attacker. Deep: itâs a trust-boundary failureâuser input controls server routing; the server can reach internal services the attacker canât. - Why is SSRF risky?
A: Plain: because the server has more network access. Deep: SSRF can reach internal control planes, metadata services, or âinternal-onlyâ APIs and trigger side effects or leak data depending on response exposure. - Whatâs blind SSRF?
A: Plain: the server makes a request but you canât see the response. Deep: still dangerous if it can trigger internal actions or reach privileged endpoints; detection relies on server-side logs/telemetry. - Best primary defense?
A: Plain: donât allow arbitrary URLs. Deep: use strict allowlists for destinations (scheme/host/port), validate resolved IPs, and handle redirects safely. - Allowlist vs blocklist?
A: Plain: allowlist is safer. Deep: blocklists miss edge cases (IPv6, encodings, parser quirks). Allowlists define âknown-goodâ destinations and reduce ambiguity. - What does âvalidate final destinationâ mean?
A: Plain: check where it really goes, not what the string looks like. Deep: parse URL, resolve DNS, and re-check after redirects; block private/loopback/link-local ranges.
Medium
- Scenario: A âlink previewâ feature fetches a URL and returns HTML snippet. What risks do you assess?
A: Plain: the server might fetch internal resources. Deep: I assess if destination is controllable, redirect behavior, DNS resolution checks, whether response is returned (data leak), and whether egress controls exist. - Scenario: Webhooks let customers configure callback URLs. How do you secure it?
A: Plain: restrict where callbacks can go. Deep: per-tenant destination allowlists, https-only, fixed ports, validation after DNS, safe redirect policy, plus outbound egress rules and auditing. - Follow-up: Why do redirects matter for SSRF?
A: Plain: redirects can change the destination. Deep: if you only validate the initial URL, a redirect can send the request elsewhere; safest is no redirects or validate every hop. - Scenario: The app blocks âlocalhostâ and private ranges using string checks. Is that enough?
A: Plain: no. Deep: string checks are brittle; you must parse the URL, resolve DNS, and check the actual IP ranges (including IPv6) and re-check if redirects occur. - Follow-up: What do you look at in the HTTP client?
A: Plain: how it behaves with redirects/timeouts. Deep: redirect policy, proxy settings, DNS caching, timeouts, response size caps, and whether non-http schemes are possible. - Scenario: Response is not returned to user. How can SSRF still be impactful?
A: Plain: it can trigger actions. Deep: blind SSRF can hit internal endpoints with side effects, interact with trusted internal APIs, or create observable behavior via logs/metrics. - Follow-up: How do you prioritize fixes?
A: Plain: remove arbitrary destinations first. Deep: eliminate or constrain the feature (IDs/known hosts), add final destination validation and redirect/DNS safety, harden the client, then enforce egress controls.
Hard
- Scenario: You use a host allowlist, but DNS rebinding is a concern. What do you do?
A: Plain: ensure the request goes to the validated IP. Deep: validate DNS resolution, block private ranges, and where feasible pin the resolved IP for the request; combine with egress firewalling so private ranges are blocked regardless of DNS tricks. - Follow-up: Why do you still need network egress controls if app allowlists exist?
A: Plain: mistakes happen. Deep: egress controls are defense-in-depth: if code regresses or a bypass is found, the network still blocks internal destinations and metadata ranges. - Scenario: Multi-tenant SaaS allows âcustom integration base URLâ. How can that become a tenant-to-tenant risk?
A: Plain: one tenant could point requests at another tenantâs internal endpoints. Deep: if shared infrastructure or internal APIs are reachable, SSRF can become lateral movement; enforce per-tenant isolation, strict allowlists, and separate networks. - Scenario: The feature must fetch user-provided URLs for business reasons. What âsecure architectureâ do you propose?
A: Plain: isolate the fetcher. Deep: run fetching in a hardened egress-restricted service or sandbox with strict outbound ACLs, minimal credentials, tight timeouts, and validated destinations; treat it as an untrusted âfetch workerâ. - Follow-up: Whatâs the most common âexperienced missâ in SSRF fixes?
A: Plain: validating the wrong thing. Deep: validating only the input string, not the parsed/resolved destination; forgetting IPv6; following redirects without re-validation; and assuming âinternal-onlyâ services are safe. - Scenario: You discover SSRF in an internal admin tool. Is it lower severity?
A: Plain: not necessarily. Deep: admin tools often run in privileged networks and may access sensitive internal systems; SSRF there can be higher impact due to reach and trust. Scope and network position matter. - Follow-up: How do you communicate SSRF risk to non-security stakeholders?
A: Plain: âusers can make our servers call places they shouldnât.â Deep: I explain server reach and internal trust, show how allowlists + egress controls reduce blast radius, and propose an incremental fix plan with minimal product disruption.