eWPTX study materials
eWPTX study resources

The Blind SSRF That Only Speaks DNS

eWPTX · Put your knowledge to work in context.

All case studies

The Blind SSRF That Only Speaks DNS

During a white-box review of a Node.js invoice app you find an endpoint POST /api/invoices/preview that fetches a user-supplied templateUrl. The fetch helper blocks RFC1918 literals and localhost by regex on the raw string, then calls axios.get(url, {maxRedirects: 0}). No allowlist exists. You control an authoritative DNS server at attacker.example and can log every query. The app sits in a VPC; you cannot see its egress. Direct fetches to http://169.254.169.254/latest/meta-data/ are rejected by the regex. You have 90 minutes before the engagement window closes and the client wants evidence of impact, not just a theory.

1. Outline the sequence you would run to convert this into demonstrable impact, and state exactly what each step proves.

Read the worked answer
  1. Prove the fetch is server-side and uncached. Point templateUrl at http://probe1.attacker.example/x. A DNS hit from a VPC resolver IP (not your workstation) proves server-side egress and gives you the resolver address - useful later for rebinding.
  2. Confirm the regex is string-based, not resolved. Submit http://[email protected]/ and http://169.254.169.254.attacker.example/. The first is a userinfo trick; the second is a hostname the regex won't match. DNS logs on the second prove the app resolves arbitrary names.
  3. Defeat the literal check with DNS rebinding. Serve probe2.attacker.example with TTL 0, first answering a public IP, then 169.254.169.254. Since maxRedirects: 0 only stops HTTP redirects, not DNS, the second resolution reaches the metadata service. Capture the response body in the invoice preview.
  4. Escalate. With IMDSv1 reachable, request /latest/meta-data/iam/security-credentials/ and pull temporary creds. Each step proves progressively more: (1) SSRF exists, (2) filter is bypassable, (3) internal reachability, (4) credential theft. Stop at the step the client's scope defines as sufficient impact and screenshot the DNS logs as the audit trail.

2. The client says 'just block 169.254.169.254 and ship it.' Write the two-sentence pushback you would give, grounded in what you observed.

Read the worked answer

Blocking that one literal does nothing here: the regex already blocks it and I bypassed it with a hostname (169.254.169.254.attacker.example) and a userinfo trick, so any string-based denylist will fall to DNS rebinding within minutes. The durable fix is resolve-then-connect with an allowlist of template hosts, rejecting any resolved IP in RFC1918/link-local/loopback space at connect time, plus IMDSv2 enforcement - otherwise the same class of bug returns on the next endpoint. Frame it as 'the control you're proposing is the one I already defeated,' not as a lecture.