SSRF Vulnerabilities in Cloud Environments: Blocking Metadata Endpoint Abuse
Your application fetches a URL on behalf of a user — a preview generator, a webhook validator, a PDF renderer. That single feature, if left unguarded, can hand an attacker your cloud provider's IAM credentials within a single HTTP request. Server-Side Request Forgery (SSRF) in cloud environments is not a theoretical threat; it is one of the most reliable paths from a low-privileged web vulnerability to full account compromise.
What You'll Learn
- How SSRF attacks reach cloud metadata endpoints and what data they expose
- Why default cloud configurations amplify the risk
- How to enforce IMDSv2 on AWS (and equivalent controls on GCP and Azure)
- Network-layer and application-layer controls that stop metadata requests dead
- The pitfalls teams repeatedly fall into when they think they're protected
What Makes Cloud Metadata Endpoints a Prime SSRF Target
Every major cloud provider exposes an internal HTTP endpoint that running instances can query to learn about themselves. On AWS, that endpoint lives at http://169.254.169.254. GCP uses the same IP but requires a specific header. Azure uses http://169.254.169.254/metadata/instance. These addresses are link-local — they are only reachable from inside the instance itself, not from the public internet.
The problem is that "reachable from the instance" and "reachable by your application process" are the same thing. If your web server can be coaxed into making an outbound HTTP request to an attacker-controlled destination, it can equally be coaxed into making one to 169.254.169.254. The metadata service returns instance identity, user data scripts, and — most critically — temporary IAM credentials for any attached instance profile or service account.
Those credentials carry whatever permissions the role was granted. In a misconfigured environment, that can mean read access to every S3 bucket, the ability to spin up new instances, or write access to secrets in AWS Secrets Manager.
How a Cloud Metadata SSRF Attack Actually Works
The attack path is straightforward. An attacker identifies a feature in your application that accepts a URL and makes a server-side HTTP request to it. Common examples include:
- URL preview or link unfurling (Slack-style)
- Webhook URL validation that fires a test request
- PDF or screenshot generation from a user-supplied URL
- Image proxies that fetch remote images on behalf of the frontend
- XML/JSON parsers that follow external entity or reference URLs
The attacker supplies http://169.254.169.254/latest/meta-data/iam/security-credentials/ as the URL. Your server fetches it, gets back a role name, then fetches http://169.254.169.254/latest/meta-data/iam/security-credentials/<role-name> and receives a JSON blob containing AccessKeyId, SecretAccessKey, and Token. The attacker now has a fully functional, rotating credential set that is valid until the token expires — typically one hour.
The Anatomy of a Real Attack Request
To make this concrete, here is what the exchange looks like from the attacker's perspective against an unprotected image-proxy endpoint:
# Step 1 — discover what roles are attached
curl 'https://your-app.com/proxy?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/'
# Response body: "MyAppInstanceRole"
# Step 2 — retrieve the credentials for that role
curl 'https://your-app.com/proxy?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/MyAppInstanceRole'
# Response body (JSON):
# {
# "Code": "Success",
# "AccessKeyId": "ASIA...",
# "SecretAccessKey": "wJalr...",
# "Token": "IQoJb3...",
# "Expiration": "2024-06-01T12:00:00Z"
# }
Two requests. No authentication bypass, no memory corruption, no exploit kit. The application fetched the data itself and returned it in the response body. The attacker configures aws configure with those values and starts enumerating your environment.
Why Default Cloud Configurations Make This Worse
AWS's original metadata service (IMDSv1) requires zero authentication. Any process on the instance — including your web server — can call it over plain HTTP with no token, no signature, nothing. This was a pragmatic design choice when EC2 launched, but it turns every SSRF vulnerability into an immediate credential-theft primitive.
GCP's metadata server requires the header Metadata-Flavor: Google, which raises the bar slightly — a naive SSRF that just follows a redirect won't automatically include custom headers. But if the attacker controls the application code path even partially, they can instruct the request to include that header.
Azure's IMDS requires the query parameter api-version and the header Metadata: true, creating a similar mild barrier. None of these defaults are strong enough to stop a determined attacker who understands the endpoint.
Defense Layer 1: Enforce IMDSv2 on Every Instance
AWS's IMDSv2 (Instance Metadata Service version 2) is the single highest-impact control you can apply. It introduces a session-oriented flow: the client must first make a PUT request to retrieve a short-lived session token, then include that token as a header on subsequent GET requests. A basic SSRF using a GET to the metadata IP will receive a 401 and no credentials.
# Enforce IMDSv2 on a running instance (hop-limit of 1 blocks container-in-container relay)
aws ec2 modify-instance-metadata-options \
--instance-id i-0abc12345def67890 \
--http-tokens required \
--http-put-response-hop-limit 1 \
--http-endpoint enabled
The --http-put-response-hop-limit 1 flag is important. It means the TTL on the IMDSv2 session token drops to zero after one network hop, preventing an attacker from relaying the metadata request through a container to the host's IMDS.
You should also enforce IMDSv2 at the account level via a Service Control Policy (SCP) if you run AWS Organizations, and enforce it in your Terraform or CloudFormation instance launch configurations so new instances are born compliant:
{
"MetadataOptions": {
"HttpTokens": "required",
"HttpPutResponseHopLimit": 1,
"HttpEndpoint": "enabled"
}
}
For GCP, enable the --no-metadata-from-concealed-attributes and ensure your workloads use Workload Identity rather than relying on the metadata server for credentials where possible. On Azure, prefer Managed Identities with the most restrictive scope you can justify.
Defense Layer 2: Block Metadata IPs at the Network Layer
IMDSv2 is a strong control but it is not your only line of defense. Defense in depth means adding network-layer restrictions so that even if IMDSv2 is misconfigured or bypassed, outbound requests to the metadata address never leave your application process successfully.
In a container-based environment (EKS, GKE, AKS), you can use network policies or iptables rules to prevent pods from reaching the metadata IP entirely, unless the specific system component that legitimately needs it is explicitly allowed:
# Block application pods from reaching AWS IMDS (run on node or via DaemonSet)
iptables -I OUTPUT -d 169.254.169.254 -j DROP
# Allow only the node agent (e.g. aws-node) — add exceptions before the DROP rule
iptables -I OUTPUT -d 169.254.169.254 -m owner --uid-owner 0 -j ACCEPT
In a VPC environment, Security Groups do not apply to link-local traffic, so iptables or host-based firewalls are the right tool. For Lambda functions, the metadata endpoint is handled differently (Lambda injects credentials via environment variables), so you don't need this rule — but you should audit whether your Lambda functions make outbound HTTP calls with user-supplied URLs at all.
Defense Layer 3: Validate and Sanitize URL Inputs in Your Application
Network controls are a safety net. Your application code should never pass user-supplied URLs to an HTTP client without validation. The rules are strict:
- Parse the URL before fetching it. Reject anything that resolves to a private, link-local, or loopback address after DNS resolution.
- Resolve DNS yourself, check the IP, then connect to the IP directly. Do not resolve in validation and then re-resolve in the HTTP client — DNS rebinding can return a different IP on the second lookup.
- Whitelist allowed schemes. Reject anything that isn't
https://. Drophttp://,file://,ftp://,gopher://, and anything else. - Enforce a hostname allowlist if the set of valid external hosts is known and bounded.
- Disable redirects, or validate the redirect destination with the same rules before following it.
Here is a Python example using ipaddress and httpx that blocks private and link-local destinations:
import ipaddress
import socket
import httpx
from urllib.parse import urlparse
BLOCKED_NETWORKS = [
ipaddress.ip_network("169.254.0.0/16"), # link-local (IMDS)
ipaddress.ip_network("10.0.0.0/8"),
ipaddress.ip_network("172.16.0.0/12"),
ipaddress.ip_network("192.168.0.0/16"),
ipaddress.ip_network("127.0.0.0/8"),
ipaddress.ip_network("::1/128"),
ipaddress.ip_network("fc00::/7"),
]
def is_safe_url(url: str) -> bool:
parsed = urlparse(url)
if parsed.scheme not in ("https",):
return False
hostname = parsed.hostname
if not hostname:
return False
try:
# Resolve once; use the resolved IP for the actual connection
resolved_ip = ipaddress.ip_address(socket.gethostbyname(hostname))
except (socket.gaierror, ValueError):
return False
for network in BLOCKED_NETWORKS:
if resolved_ip in network:
return False
return True
def safe_fetch(url: str) -> httpx.Response:
if not is_safe_url(url):
raise ValueError(f"Blocked URL: {url}")
# Disable redirects; validate each hop manually if you need them
return httpx.get(url, follow_redirects=False, timeout=5)
Note the gethostbyname call: this is still susceptible to DNS rebinding in high-volume scenarios. For production hardening, resolve the IP yourself, pass it directly to the connection layer (bypassing a second DNS lookup), or use a purpose-built library like ssrfcheck that pins the socket to the resolved address. This pattern is also related to how attackers exploit trust assumptions at the authentication boundary — the same class of issue covered in the context of JWT validation mistakes that let attackers forge tokens, where the server trusts data it shouldn't without independent verification.
Defense Layer 4: WAF Rules and Egress Filtering
A Web Application Firewall gives you a centralized place to block requests that contain obvious SSRF payloads before they reach your application code. AWS WAF, Cloudflare WAF, and most commercial offerings include managed rule groups for SSRF that match common patterns like 169.254, metadata, and URL-encoded variants.
Create a custom rule that blocks request parameters containing 169.254.169.254 or its IPv6 equivalent fd00:ec2::254. Also cover common bypass techniques: decimal IP notation (2852039166), hex notation (0xa9fea9fe), and URL encoding (%31%36%39%2e%32%35%34). Attackers actively try these to sidestep naive string-matching filters.
Egress filtering at the VPC level adds another layer. Use VPC endpoints for AWS services (S3, Secrets Manager, SSM) so that legitimate service calls never leave the VPC and don't depend on the metadata service for credentials. Route all other outbound internet traffic through a NAT Gateway or a proxy that you control, and log every outbound connection. An alert on any instance making a direct connection to 169.254.169.254 that isn't your node agent is a high-confidence signal of SSRF exploitation in progress.
Common Pitfalls That Leave You Exposed
Validating before resolving, not after. Checking whether a hostname is a known private IP before DNS resolution is useless against DNS rebinding. Always resolve first, check the resulting IP, then connect to that IP explicitly.
Blocking only the AWS IP and forgetting cloud-specific variants. GCP uses 169.254.169.254 too, but also exposes metadata.google.internal as a hostname. Azure has 169.254.169.254 and historically used 168.63.129.16 for DHCP/DNS. Your blocklist needs all of them.
Trusting the X-Forwarded-For or Host header in SSRF defenses. These are attacker-controlled. Your outbound URL validation must operate on the URL being fetched, not on anything the attacker sends in request headers.
Applying IMDSv2 only to new instances. Old instances launched before you enforced the policy remain on IMDSv1. Audit regularly with AWS Config's ec2-imdsv2-check managed rule, which flags any instance not requiring IMDSv2.
Forgetting serverless and container workloads. Lambda, ECS tasks, and Fargate all have their own credential delivery mechanisms. Each service has its own metadata-like endpoint; confirm you understand how credentials reach each workload type in your environment.
Next Steps
SSRF is rarely a standalone problem. Once you've addressed the immediate metadata endpoint risk, these are the concrete actions to take:
- Run an IMDSv2 audit right now. Use the AWS Config managed rule or a one-liner with the CLI to list every EC2 instance where
HttpTokensis notrequired. Remediate them in a maintenance window this week, not next quarter. - Grep your codebase for HTTP client calls that accept user-supplied URLs. Search for
requests.get,fetch,curl_exec,HttpClient, and similar patterns. Every one of them is a candidate SSRF sink. - Add SSRF test cases to your security regression suite. Include
http://169.254.169.254, DNS rebinding payloads, and decimal/hex IP variants. A test that fails is a vulnerability caught before deployment. - Enable VPC Flow Logs and create an alert on outbound connections to
169.254.0.0/16from anything other than your node agents. This is your detection layer if prevention fails. - Review IAM role permissions attached to every instance profile. The blast radius of a successful SSRF credential theft is determined by the permissions on the role. Scope each role to the minimum actions it actually needs — least-privilege is your last line of defense.
Frequently Asked Questions
Can SSRF attacks steal credentials from AWS Lambda functions the same way they do from EC2?
Lambda does not use the EC2 metadata endpoint — it injects credentials directly as environment variables. However, Lambda functions that make outbound HTTP calls with user-supplied URLs can still be exploited to reach other internal services, so input validation is still essential.
Does enforcing IMDSv2 break existing applications that use the instance metadata service?
It can, if your application or any SDK it uses still calls the metadata service with the old IMDSv1 flow. Modern AWS SDK versions switched to IMDSv2 by default, but you should test in a staging environment first and check CloudWatch metrics for IMDSv1 call counts before enforcing on production instances.
What are the most common bypass techniques attackers use to get around SSRF filters?
Common bypasses include using decimal or hexadecimal representations of the IP address (e.g. 2852039166 for 169.254.169.254), URL encoding, IPv6 notation, DNS rebinding to return a private IP on the second lookup, and open redirects on trusted domains that forward the request internally.
How do I detect an ongoing SSRF attack against the metadata endpoint in my cloud environment?
Enable VPC Flow Logs and create an alert on any traffic destined for 169.254.0.0/16 from unexpected source instances. Also monitor your CloudTrail logs for API calls made with credentials whose source IP is your own application server — that is a strong indicator of stolen instance profile credentials being used.
Is SSRF only a risk in web applications, or can it affect backend services and internal APIs too?
Any service that makes outbound HTTP requests based on data it receives — whether that data comes from end users, message queues, or other microservices — is a potential SSRF target. Internal APIs are often less hardened than public-facing ones, so an attacker who can influence what URLs a backend service fetches may have an easier time exploiting it.
📤 Share this article
Sign in to saveComments (0)
No comments yet. Be the first!