From a URL Field to Cloud Keys: SSRF and the Metadata Endpoint
Server side request forgery turns a harmless looking URL input into a way to read a cloud instance's own credentials. Here is how the metadata endpoint becomes the prize, and why IMDSv2 changed the game.
Picture a perfectly ordinary feature on a website. You paste in a link to your profile picture, the site fetches that image, resizes it, and shows it back to you. Millions of applications do exactly this. Now imagine that instead of a picture link, an attacker pastes in a special internal address, and the server obediently fetches back its own cloud login keys. No password cracked, no firewall breached, just a URL field that trusted the wrong input. That is server side request forgery, and in the cloud it very often ends at a single magic address, 169.254.169.254, and the credentials of the machine you are attacking. This is how a plain URL field becomes a set of cloud keys, and what actually stops it.
Credit and scope
SSRF and the metadata endpoint attack are well established, widely documented techniques, not my original research. This post explains how they fit together so the risk is clear. Notable public cases and sources are referenced throughout.
Authorised testing only
Probe metadata endpoints and internal addresses only on systems you own or are engaged to test. Reaching for cloud credentials on infrastructure you do not control is unauthorised access.
What SSRF actually is
Let us define the term in plain language before we go anywhere near a cloud. Server side request forgery, usually shortened to SSRF, is a flaw where an attacker tricks a server into making a web request on the attacker's behalf. The word "forgery" is the key. The attacker forges, or fakes, the destination of a request that the server then sends out as if it were its own.
SSRF happens when an application fetches a URL that an attacker can influence, and the request goes out from the server rather than from the attacker's own machine. Anything that takes a URL is a candidate: a webhook (a feature that calls out to a link you configure), an image fetcher, a PDF renderer, a link preview generator, or an "import by URL" button.
Here is a tiny example of the kind of code that gets people into trouble.
# The user gives us a URL, and we fetch it for them
image_url = request.form["avatar_url"]
response = http_get(image_url) # the server makes this request
save(response.body)Nothing here checks where avatar_url actually points. The developer imagined it would always be something like a link to a photo on another website. An attacker sees a machine that will fetch any address they hand it.
The interesting part is not that the server makes a request. It is that the server makes it from inside the network, with the server's own network position and identity. Think of the server as a trusted employee inside a locked office building. You, the attacker, are stuck outside. But if you can slip that employee a note that says "go to room 200 and read me what is on the whiteboard", you get to see things you could never reach yourself. Suddenly the attacker can reach internal admin panels, databases sitting on private addresses that are not exposed to the internet, and, most usefully of all, the cloud metadata service.
Wait, what is a link local address?
That strange number 169.254.169.254 deserves a moment of explanation, because it is central to everything that follows.
Some addresses on a network are "link local", which means they only work on the local machine or the local network segment and are never routed across the wider internet. The whole range 169.254.0.0/16 is reserved for this purpose. Your laptop uses addresses like these too when it cannot get a normal one.
Cloud providers borrowed one specific address from that range, 169.254.169.254, and turned it into a private information desk that only the instance itself can talk to. An instance, in cloud terms, is just a virtual server you rent. When that server wants to know facts about itself, it asks this address. Because the address is link local, it is invisible and unreachable from the outside world, which is exactly why it feels safe to the people who built it, and exactly why SSRF is so dangerous, since an SSRF flaw lets an outsider ask questions from the inside.
The metadata endpoint is the prize
Every major cloud gives a running instance a way to ask "who am I and what am I allowed to do" over that link local address that only the instance itself can reach. This service is called the metadata endpoint, because it serves metadata, meaning data about the instance itself.
AWS http://169.254.169.254/latest/meta-data/
GCP http://metadata.google.internal/computeMetadata/v1/
Azure http://169.254.169.254/metadata/instanceOn AWS, that tree includes the temporary credentials for whatever IAM role the instance runs as. A quick translation: IAM stands for Identity and Access Management, and an IAM role is simply the set of permissions the server is allowed to use, for example "may read this storage bucket" or "may send messages to this queue". Rather than baking permanent passwords into the server, AWS hands the instance short lived keys through this endpoint.
http://169.254.169.254/latest/meta-data/iam/security-credentials/<role-name>If an application is vulnerable to SSRF and the older metadata service (IMDSv1, where IMDS means Instance Metadata Service and v1 is version one) is enabled, the attack is almost embarrassingly direct. Point the vulnerable URL field at that address and the server fetches back live access keys.
# The attacker supplies this as the "url" the server will fetch
http://169.254.169.254/latest/meta-data/iam/security-credentials/web-app-roleThe server dutifully requests that address, believing it to be just another image or webhook, and returns something like this.
{
"AccessKeyId": "ASIA...redacted",
"SecretAccessKey": "wJalr...redacted",
"Token": "IQoJb3JpZ2lu...redacted",
"Expiration": "2026-06-23T18:00:00Z"
}Those keys are temporary, but they are real. With them an attacker inherits the instance's permissions and can pivot into whatever that role can touch, often storage buckets, queues, or other accounts. "Pivot" here means to use one foothold to reach the next target, the way a burglar who finds a key on the kitchen table then tries it on every door in the house.
This is not hypothetical
The 2019 Capital One breach followed exactly this shape: an SSRF flaw reached the AWS metadata service, retrieved the instance role credentials, and those were used to read data from S3. One misused URL, tens of millions of records.
Walking through the attack step by step
It helps to see the whole chain laid out, because each link is simple on its own and only becomes devastating when strung together.
1. Attacker finds a feature that fetches a URL (an avatar import).
2. Instead of an image link, they supply the metadata address.
3. The server, sitting inside AWS, happily requests that address.
4. The metadata service hands back temporary IAM credentials.
5. The server returns that response to the attacker.
6. The attacker loads the keys into their own tools.
7. They now act as the instance, reading storage and moving sideways.Notice that at no point did the attacker exploit a memory bug or crack encryption. They simply asked a trusting server to run an errand, and the errand happened to be "fetch my secrets". This is why SSRF is often described as an abuse of trust rather than a break of it.
Common tricks to slip past weak filters
Once a developer learns about this attack, the first instinct is usually to block the metadata address by name. Attackers have a whole toolbox for defeating naive blocks, and understanding them is essential to defending properly.
- Alternative encodings of the address. The same address can be written in ways a simple string match misses, for example as a decimal number,
http://2852039166/, or with padded octets,http://0251.0376.0251.0376/. Both resolve to the same place. - Redirects. The attacker points the URL at a server they control, which then replies "the thing you want has moved to
169.254.169.254". If the fetcher follows redirects, it walks straight into the trap. - DNS rebinding and hostile DNS. The attacker uses a domain name that resolves to a harmless address when the filter checks it, then resolves to the metadata address a split second later when the actual request is made.
These tricks are the reason a defence has to inspect the real destination, not the text of the URL. We will come back to that.
Test your own URL features
If you build anything that fetches a link on behalf of a user, add a test that feeds it internal addresses like 169.254.169.254, 127.0.0.1, and a private range address, and confirm the request is refused. Catching this in your own test suite is far cheaper than reading about it in a breach report.
Why IMDSv2 changed the game
The original metadata service (IMDSv1) answered any plain GET request. A GET request is the simplest kind of web request, the same one your browser makes when you open a page. That simplicity is what makes IMDSv1 such a clean SSRF target, because most SSRF flaws can only do a simple GET and nothing fancier.
IMDSv2, version two of the service, added a deliberately awkward handshake before it will tell you anything.
1. PUT a request for a session token, with a short time to live header.
2. Receive a token.
3. Send it back as an X-aws-ec2-metadata-token header on every read.A PUT is a different type of request from a GET, and a header is an extra piece of information attached to a request. That combination breaks the typical SSRF, because a basic "fetch this URL" primitive cannot usually send a PUT and then attach a custom header on a second request. The attacker's tool can only say "go get this address", not "go do this multi step conversation".
IMDSv2 also sets a hop limit on the response packet so it will not travel beyond the instance itself. A hop limit is a counter that decreases each time a packet passes through a network device, and when it hits zero the packet is discarded. Setting it to one means the answer can reach the instance but cannot be bounced onward to anywhere else.
GCP and Azure use the same idea in a different form. They refuse to answer unless you send a specific header, Metadata-Flavor: Google on Google Cloud or Metadata: true on Azure, which a naive SSRF cannot add. The common thread across all three clouds is the same: demand something a simple forged request cannot provide.
Defending against it
Treat this as layers, because any one of them failing should not hand over your keys. Security people call this defence in depth, meaning you assume each wall might fall and put another wall behind it.
- Enforce IMDSv2 and set the hop limit to 1. This alone defeats the overwhelming majority of SSRF to metadata. Make it mandatory, not optional, across your fleet, so that no instance can quietly fall back to the older, chattier version.
- Lock down the application's outbound requests. Validate and allowlist the destinations a URL feature may reach. An allowlist is a list of permitted destinations, the opposite of a blocklist, and it is safer because anything you did not explicitly approve is refused by default. Block private and link local ranges, including
169.254.0.0/16,127.0.0.0/8, and the RFC1918 ranges (the private address blocks reserved for internal networks). - Resolve then check. Attackers bypass naive filters with redirects and DNS that resolves to internal addresses. Resolve the hostname yourself, check the resulting IP against your blocklist, and pin to it for the actual request so it cannot change out from under you between the check and the fetch.
- Least privilege on the instance role. Least privilege means giving each component only the permissions it truly needs and nothing more. If the credentials are stolen anyway, a tightly scoped role limits what they unlock, so a leaked key opens one drawer rather than the whole building.
- Egress filtering. Egress means outbound traffic. Network controls that stop instances making arbitrary outbound calls cut off both the SSRF itself and the exfiltration, the sneaking out of stolen data, that usually follows.
Defaults have moved, but check yours
Newer AWS accounts increasingly ship with IMDSv2 required by default, which is real progress. That does not mean your older instances, images, or launch templates were upgraded. Audit what your fleet actually enforces rather than assuming the modern default applies everywhere.
If you want to see another case where a single trusted input turned into a full compromise, the Log4Shell breakdown covers a flaw where merely logging attacker text was enough to run code. The lesson rhymes: input you assume is harmless rarely is.
The takeaway
SSRF is dangerous because it borrows the server's identity and network position. The server is a trusted insider, and the attacker gets to write its errands. In the cloud, that identity is a set of credentials sitting one predictable URL away at 169.254.169.254. Enforce IMDSv2 so a simple forged request can no longer complete the handshake, treat every URL your server fetches as hostile and resolve then pin the destination, and scope your instance roles with least privilege so that even a stolen key opens as few doors as possible. Do all three and the harmless looking avatar field goes back to being just an avatar field.