CSRF vs SSRF for systems analyst interviews

Train for your next tech interview
1,500+ real interview questions across engineering, product, design, and data — with worked solutions.
Join the waitlist

Why interviewers ask about CSRF and SSRF

Picture this: a Stripe systems analyst loop, second round, and the architect slides a whiteboard prompt across the table — "design the file-upload service; here is how we authenticate; walk me through the threats." If you reach for SQL injection and stop there, you have just told them you read the OWASP Top-10 headline and nothing under it. The two attacks that catch underprepared SA candidates are CSRF (cross-site request forgery) and SSRF (server-side request forgery), because the names look similar but the threat models could not be further apart.

CSRF is a client-side trust problem: the browser is tricked into sending a request the user never intended. SSRF is a server-side trust problem: the server is tricked into fetching a URL it should never touch. Same prefix, completely different blast radius. The Capital One breach in 2019 cost $190M in settlements because an SSRF chain pulled AWS metadata credentials. The 2008 Gmail filter-change CSRF compromised celebrity inboxes. Interviewers want to see that you can tell the architectural fix from the patch.

A senior SA at Meta, Snowflake, or Airbnb is expected to weave both defenses into a sequence diagram without being prompted — token storage, SameSite cookie attributes, allowlists for outbound URLs, metadata IP blocks. If you can do that in seven minutes on the whiteboard, you have just cleared the security bar that filters maybe 60% of mid-level SA candidates out at FAANG-tier loops.

CSRF: how the attack works

Cross-Site Request Forgery assumes the victim is already authenticated to bank.com with a cookie-based session. The attacker hosts a page on evil.com that quietly submits a form to the bank from the victim's browser. The browser attaches the bank's cookies automatically — that is the whole trick. No password theft, no XSS, just abuse of ambient authority.

<!-- hosted on evil.com -->
<form action="https://bank.com/transfer" method="POST">
  <input name="to" value="attacker_account">
  <input name="amount" value="10000">
</form>
<script>document.forms[0].submit()</script>

The victim opens evil.com, the form auto-submits, the request lands at bank.com with the session cookie attached, and the transfer goes through. The bank's server sees a perfectly valid authenticated request because, from its perspective, it is one.

Load-bearing rule: CSRF needs three preconditions — the victim is authenticated, auth rides on cookies (or HTTP Basic), and the endpoint mutates state without an extra confirmation step. Break any one and the attack fails.

Defending against CSRF

The canonical defense is the CSRF token — a per-session or per-form secret rendered into the HTML and verified on the server. The same-origin policy prevents evil.com from reading the token off bank.com, so the attacker cannot forge a valid request.

<form action="/transfer" method="POST">
  <input type="hidden" name="csrf_token" value="abc123xyz">
  <input name="to" value="...">
  <input name="amount" value="...">
</form>

The second layer is the SameSite cookie attribute. Since Chrome 80 in 2020, cookies default to SameSite=Lax, which already neutralizes most naive CSRF. The three values matter at interview depth:

SameSite value Cross-site navigation Cross-site iframe / form POST When to use
Strict Cookie blocked Cookie blocked Banking, admin consoles, anything mutating money
Lax Cookie sent Cookie blocked Default for most logged-in apps
None Cookie sent Cookie sent (requires Secure) Embedded widgets, federated SSO

A third defense — custom request headers like X-Requested-With: XMLHttpRequest — works because CORS forces a preflight on custom headers, and a plain HTML form cannot trigger preflight. The server simply rejects any state-changing request missing the header. Finally, Origin and Referer checks add a cheap belt-and-suspenders layer; they are spoofable in edge cases but excellent as a deny-by-default signal.

The honest truth is that modern frameworks — Django, Rails, Next.js Server Actions — bake CSRF tokens in by default, so the failure mode in 2026 is usually a custom JSON API that forgot to opt in.

SSRF: how the attack works

Server-Side Request Forgery flips the trust direction. The application accepts a URL from the user and fetches it server-side — for avatars, webhooks, link previews, PDF generation, OAuth callbacks. The attacker supplies a URL pointing at the internal network instead.

# vulnerable handler
url = request.params['avatar_url']  # untrusted input
response = requests.get(url)
save_image(response.content)

The attacker passes url=http://localhost:8080/admin and gets the admin panel response piped back through the application. Or url=http://169.254.169.254/latest/meta-data/iam/security-credentials/ and walks away with AWS IAM credentials scoped to the EC2 instance's role.

What SSRF buys an attacker is broad: read internal admin services, exfiltrate cloud metadata (AWS, GCP, Azure all have variants), port-scan the private subnet, bypass perimeter firewalls because the request originates inside the trust boundary, and in some clients read file:///etc/passwd directly. The Capital One case is the textbook example — a misconfigured WAF made an outbound request that hit the metadata service and returned credentials with s3:ListBucket permissions on millions of customer records.

Defending against SSRF

The single most effective control is an outbound allowlist, not a blocklist. Decide which domains your service is allowed to fetch — images.cdn.example.com, webhooks.partner.com — and reject everything else.

from urllib.parse import urlparse

ALLOWED_DOMAINS = {'images.example.com', 'cdn.example.com'}

parsed = urlparse(url)
if parsed.hostname not in ALLOWED_DOMAINS:
    raise SecurityError('domain not allowed')

A domain allowlist alone is insufficient because of DNS rebinding: the attacker registers attacker.com, points its DNS at a public IP for the allowlist check, then flips the record to 169.254.169.254 between the check and the actual fetch. The fix is to resolve the hostname yourself, verify the IP is public, and then connect to that IP directly — closing the TOCTOU (time-of-check vs time-of-use) race.

import ipaddress, socket

ip = socket.gethostbyname(parsed.hostname)
if ipaddress.ip_address(ip).is_private:
    raise SecurityError('private IP blocked')

Other controls stack on top: disable HTTP redirects (allow_redirects=False) so a 302 cannot escape the allowlist, forbid non-HTTP schemes (file://, gopher://, dict://), explicitly block cloud metadata IPs (169.254.169.254, metadata.google.internal, 100.100.100.200 for Alibaba), and enforce network segmentation so the worker pod simply cannot route to the admin subnet even if the application logic fails.

Gotcha: blocking RFC 1918 ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16) without also blocking the link-local range 169.254.0.0/16 leaves the cloud metadata endpoint wide open. That is exactly the Capital One footgun.

Train for your next tech interview
1,500+ real interview questions across engineering, product, design, and data — with worked solutions.
Join the waitlist

IDOR — the cousin you should mention

Insecure Direct Object Reference is not CSRF or SSRF, but interviewers love to chain it in because the fix lives in the same authorization layer. A user changes /api/orders/123 to /api/orders/124 and reads someone else's order because the endpoint trusts the URL parameter without checking ownership.

The defense is server-side: every endpoint enforces WHERE order.user_id = current_user.id, not just on the UI route. Switching from incrementing integers to UUIDs makes enumeration harder but does not replace the authorization check — guessable IDs are a discoverability problem, missing authorization is a vulnerability. Some teams add per-user indirect references (mapping 1, 2, 3 to opaque per-session tokens), which is belt-and-suspenders for high-sensitivity resources.

CSRF vs SSRF at a glance

Dimension CSRF SSRF
Who initiates the bad request Victim's browser Application server
Trust being abused Ambient session cookie Outbound network access
Primary defense CSRF token + SameSite=Lax URL allowlist + private-IP check
Real-world example 2008 Gmail filter hijack 2019 Capital One — $190M loss
Where to draw it on a sequence diagram Between browser and API Between API and internal services / metadata
OWASP Top-10 (2021) A01 Broken Access Control adjacency A10 Server-Side Request Forgery

Common pitfalls

The most common interview-killer on CSRF is protecting only the login form. Login is the one endpoint where CSRF is least dangerous — the attacker already needs the user's credentials. The state-changing endpoints (POST /transfer, DELETE /account, PUT /settings) are where tokens actually matter, and they are exactly where lazy implementations skip them. If an interviewer asks "which endpoints need CSRF protection," the answer is every mutating request that uses cookie auth, not just login.

A close second is shipping SameSite=None without Secure. Modern browsers reject the combination outright, so the cookie ends up not being set at all and developers either revert to no SameSite (worse) or panic-set Strict and break their SSO. The correct pairing is always SameSite=None; Secure; HttpOnly for cross-site contexts, and SameSite=Lax as the default everywhere else.

On SSRF, the trap is a domain-only allowlist with redirects enabled. The allowlist passes the initial check, the server follows a 302 to an internal IP, and the metadata service responds. The fix is twofold — disable redirects on the HTTP client and re-validate the destination on every hop if you must follow them. Treat each redirect as a new untrusted URL.

Another SSRF mistake is blocking RFC 1918 ranges but forgetting 169.254.0.0/16. Cloud metadata services live there for a reason — the link-local range is technically not "private" in the RFC sense. Your blocklist needs to be the union of RFC 1918, the metadata IPs for every cloud you might run on, loopback (127.0.0.0/8), and IPv6 equivalents (::1, fc00::/7, fe80::/10). Most homegrown allowlists miss at least two of those.

The last pitfall is trusting authorization checks in the UI layer. The mobile app hides the "delete user" button for non-admins, so the team assumes the API is safe. It is not. Every endpoint must re-check authorization server-side, because a curl one-liner does not care what your React component renders. This is where IDOR creeps in, and it is the most common finding in any first-pass pentest of a young product.

If you want to drill OWASP-style threat-modeling prompts the way real SA loops at Stripe and Snowflake structure them, NAILDD is launching with hundreds of interview problems covering CSRF, SSRF, and the full Top-10 chain.

FAQ

Does CSRF still apply to JSON APIs that don't use cookies?

If your API authenticates with Authorization: Bearer <token> and the token lives in localStorage or sessionStorage, then no — the browser does not attach those automatically on a cross-site request, so the core CSRF precondition fails. The moment you store the token in a cookie (even an HttpOnly one), you are back on the hook and need either CSRF tokens or SameSite=Strict. The trade-off is real: HttpOnly cookies survive XSS better than localStorage, so the right answer is "cookies plus CSRF defense," not "localStorage so I don't have to think about it."

Is SameSite=Lax enough on its own?

For 90% of consumer apps, yes — Lax blocks the most common CSRF vector (cross-site form POST) while still allowing top-level navigation, so SSO and "click a link in email to confirm" still work. For payments, admin consoles, or anything where a single forged request causes irreversible damage, layer CSRF tokens on top. The defense-in-depth answer at the interview is: Lax by default, Strict for mutating money endpoints, plus tokens as a second line.

How is SSRF different from a regular open-redirect bug?

Open redirect tricks the client into navigating somewhere malicious — the server returns a 302 with an attacker-controlled Location. SSRF tricks the server into fetching somewhere malicious. Open redirect is mostly a phishing aid; SSRF is a path to internal compromise. They can chain — an open redirect in an allowlisted domain is a bypass for an SSRF allowlist that follows redirects. Mention the chain in the interview if it comes up.

Why is the AWS metadata endpoint at 169.254.169.254 specifically?

The link-local range 169.254.0.0/16 is reserved by RFC 3927 for host-local communication without a DHCP server. Cloud providers chose addresses in that range for the metadata service so it is reachable from every instance without routing or DNS, and so it never collides with VPC ranges customers configure. AWS picked 169.254.169.254, GCP exposes the same address plus metadata.google.internal, Azure uses 169.254.169.254 as well. AWS IMDSv2 added a session-token requirement specifically to make SSRF exploitation harder — interview-worthy detail.

Should I mention OWASP Top-10 explicitly in the interview?

Yes, but anchor it. Saying "OWASP A10 is Server-Side Request Forgery, added in the 2021 list because of breaches like Capital One" is far better than reciting the full Top-10 from memory. Interviewers want to see that you read the underlying writeups, not the headline poster. One concrete CVE or breach story per category is worth more than a memorized list.

Where do CSRF and SSRF sit on a C4 diagram?

CSRF lives at the container boundary between the browser and the API gateway — it is a story about what the browser is allowed to send. SSRF lives at the container boundary between an application service and downstream services or the internet — it is a story about what the service is allowed to fetch. If you can draw both arrows on the same C4 container diagram and label the controls (CSRF token, allowlist, IMDSv2), you have already given a senior-level answer.