
Server-Side Request Forgery (SSRF) is a vulnerability where an attacker coerces a server into making requests to a destination the attacker controls or chooses, and it earned its own slot, A10:2021, in the OWASP Top 10 after a string of breaches. The 2019 Capital One incident, which exposed data on roughly 100 million people, started with SSRF against AWS instance metadata.
This post explains how SSRF works, the difference between basic and blind variants, the payloads that matter (cloud metadata, gopher, dict, DNS rebinding), the tooling to confirm it, and the controls that actually stop it. Every technique here maps to WSTG-INPV-19, the OWASP test for SSRF.
SSRF is a flaw where a web application fetches a remote resource using a URL you supply, without properly validating where that URL points. Because the request originates from the server, it inherits the server's network position, so you can reach internal services, cloud metadata endpoints, and admin panels that are firewalled off from the public internet.
Any feature that takes a URL is a candidate: webhook configuration, PDF or image generators that render a page, link previews, document import-from-URL, and proxy or fetch endpoints. The application thinks it is fetching something benign; you point it at http://169.254.169.254/ or http://127.0.0.1:6379/ instead.
Find every parameter that causes the server to make an outbound request, then swap the value for a destination you control and watch whether the server connects. Start by setting the URL to a Burp Collaborator host or your own listener and confirming the inbound hit.
Once you have a confirmed fetch, pivot to internal targets. Common first moves are localhost and the cloud metadata service:
# Reach back to your own listener to confirm the fetch
url=http://YOUR-COLLABORATOR.oastify.com/
# Probe localhost services and the cloud metadata endpoint
url=http://127.0.0.1:80/
url=http://localhost:8080/admin
url=http://169.254.169.254/latest/meta-data/If responses differ by internal port (open vs closed, different status, different timing), you have a working SSRF you can use to map the internal network. This is core WSTG-INPV-19 territory and a staple of any thorough web application pentesting checklist.
On cloud hosts, the link-local address 169.254.169.254 serves an instance metadata service that can hand out temporary IAM credentials, which turns a read-only SSRF into a path to the cloud account. On AWS with the legacy IMDSv1, a single GET can return role credentials.
# AWS IMDSv1 credential theft via SSRF
GET http://169.254.169.254/latest/meta-data/iam/security-credentials/
# returns the role name, e.g. s3-access-role
GET http://169.254.169.254/latest/meta-data/iam/security-credentials/s3-access-role
# returns AccessKeyId, SecretAccessKey, TokenGCP and Azure expose equivalent endpoints (GCP requires the Metadata-Flavor: Google header, which raises the bar slightly). IMDSv2 requires a PUT to obtain a session token first, which most SSRF primitives cannot do, so enforcing IMDSv2 is one of the strongest single mitigations. The same primitive is a key check in any cloud penetration test.
Blind SSRF is when the server makes the request but the response is never reflected back to you, so you confirm it out-of-band instead of by reading the body. You point the vulnerable parameter at a server you control and watch for the inbound DNS lookup or HTTP request.
Burp Collaborator is the standard tool: it gives you a unique hostname and reports any DNS or HTTP interaction. A DNS-only hit (no HTTP) still proves the server resolved your name, which is enough to confirm blind SSRF even when egress filtering blocks the actual connection. From there you escalate by probing internal hosts and inferring results from response timing or error differences.
Naive SSRF defenses block the string "127.0.0.1" or "169.254.169.254", which is trivial to evade because there are many ways to write the same address and many protocols to reach the same service. Treat any blocklist as bypassable.
http://2130706433/ (decimal), http://0x7f000001/ (hex), http://127.1/, or http://[::1]/ for IPv6 localhost.gopher://127.0.0.1:6379/_ to send raw bytes to Redis, or dict://127.0.0.1:11211/ to talk to memcached.Stop SSRF with positive validation and network controls, not blocklists. Validate that the user-supplied destination resolves to an explicit allowlist of hosts and resolves after any redirects, and reject anything in link-local (169.254.0.0/16), loopback, or private RFC 1918 ranges.
Then add defense in depth: disable URL schemes you do not need (drop gopher, dict, file), require IMDSv2 on every cloud instance so a bare GET cannot mint credentials, and put outbound egress filtering on the application's network so even a successful SSRF has nowhere to go. SSRF maps to A10:2021, so it should be a fixed line item in your penetration testing test cases.