Every EC2 instance can ask a link-local web server at 169.254.169.254 who it is, which subnet it sits in, and, most importantly, for a set of temporary credentials for the IAM role attached to it. That endpoint is the Instance Metadata Service (IMDS). It is also the single most abused pivot in AWS compromise stories, because any process on the instance, including a web application tricked into making an HTTP request on an attacker’s behalf, can call it.
The AWS Certified Security - Specialty (SCS-C02) exam expects you to know the difference between IMDSv1 and IMDSv2, why the second version defeats server-side request forgery (SSRF), what the hop limit actually does, how to enforce IMDSv2 across an account or organization, and how to detect and contain credentials that have already leaked. Those questions span the Infrastructure Security domain (20%), the Identity and Access Management domain (16%), and the Threat Detection and Incident Response domain (14%). The existing infrastructure security guide covers the network layers a packet crosses, and the IAM deep dive covers roles and instance profiles. This guide fills the gap between them: the metadata endpoint itself.
What the Instance Metadata Service Actually Serves
IMDS is a local HTTP service available only from inside the instance, at http://169.254.169.254 over IPv4 and http://[fd00:ec2::254] over IPv6 on Nitro instances in IPv6-enabled subnets. It is not reachable from the network, and it does not authenticate the caller. Anything that can open a socket on the instance can read it.
The paths the exam cares about:
| Path | What it returns | Why it matters for security |
|---|---|---|
/latest/meta-data/instance-id, /placement/..., /network/... | Identity and placement details | Low sensitivity, but useful for attacker reconnaissance |
/latest/meta-data/iam/security-credentials/<role-name> | Temporary AccessKeyId, SecretAccessKey and Token for the instance profile role | The prize. Whoever reads this becomes the instance’s IAM role |
/latest/user-data | The user data script passed at launch | Frequently contains hard-coded secrets, which is a mistake the exam will punish |
/latest/dynamic/instance-identity/document and /signature | A signed identity document | Used by Nitro attestation flows and license checks |
/latest/meta-data/tags/instance/<key> | Instance tags, only if instance metadata tags are enabled | Convenient, but treat tags as readable by every process on the box |
Two conclusions follow. First, the role credentials served by IMDS are the same credentials the AWS SDK on the instance uses, so stealing them gives the attacker everything the application is allowed to do. Second, user data is not a secret store. If a question shows a database password in user data, the correct remediation is Secrets Manager or Parameter Store, as the Secrets Manager vs Parameter Store guide explains, never “encrypt the user data”.
IMDSv1: Request and Response
Version 1 is a plain GET. No headers, no handshake:
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/
# -> app-server-role
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/app-server-role
# -> {"AccessKeyId":"ASIA...","SecretAccessKey":"...","Token":"...","Expiration":"..."}
The simplicity is the problem. Any vulnerability that lets an attacker make the application issue a GET to a URL of their choosing becomes a credential theft primitive. The canonical case is SSRF: a web application accepts a URL parameter (an image proxy, a webhook tester, a PDF renderer, a misconfigured reverse proxy) and fetches it server-side. Point that parameter at 169.254.169.254 and the application obligingly returns the role credentials to the attacker’s browser. A widely reported 2019 breach of a large financial institution followed exactly this path: SSRF through a web application firewall misconfiguration, credentials read from IMDSv1, then bulk S3 reads with the stolen role.
Other IMDSv1 attack paths the exam alludes to:
- Open reverse proxies that forward arbitrary
Hostheaders to internal addresses. - Container escape or shared-node abuse, where a compromised container on an EC2 host reads the host’s role credentials.
- Unrestricted outbound URL fetchers in CI runners, link-preview services and import jobs.
IMDSv2: Session Tokens and Why They Defeat SSRF
Version 2 turns the stateless GET into a session-oriented exchange. You first obtain a token with a PUT, then present that token as a header on every GET:
TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" \
-H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
http://169.254.169.254/latest/meta-data/iam/security-credentials/app-server-role
Three design choices do the security work:
- The token request is a PUT. Most SSRF primitives can only issue GETs. A URL-fetching library given a URL will GET it; it will not PUT with a custom header. That single detail removes the majority of SSRF-based credential theft.
- The PUT is rejected if it carries an
X-Forwarded-Forheader. Reverse proxies and load balancers add that header automatically, so a request that has been proxied through a misconfigured layer never gets a token. - The PUT response has an IP TTL (hop limit) of 1 by default. The packet carrying the token is discarded by the first router it meets, so a token cannot be delivered to a process one network hop away, such as a container on a bridge network or a host beyond a NAT.
The token lifetime is between 1 second and 21,600 seconds (6 hours), set by the X-aws-ec2-metadata-token-ttl-seconds header. Tokens are bound to the instance that issued them. All current AWS SDKs and the CLI use IMDSv2 automatically and fall back to v1 only when v2 is unavailable, so a correctly patched application needs no code changes.
IMDSv1 and IMDSv2 both remain available on an instance whose metadata option HttpTokens is optional. The exam phrase for “the instance accepts both” is IMDSv2 optional; the phrase for “only the token flow works” is IMDSv2 required.
The Hop Limit, Explained Properly
The HttpPutResponseHopLimit option (range 1 to 64) controls the IP TTL on the token response. Candidates often memorize “set hop limit to 1” without understanding when that breaks things.
- Hop limit 1 is the default and the most restrictive. Processes running directly on the instance’s network namespace get tokens. Anything that reaches IMDS through an extra routing hop does not.
- Containers in Docker bridge mode (the classic ECS EC2 launch type with
bridgenetwork mode) sit behind thedocker0bridge, which decrements the TTL. With hop limit 1 they cannot obtain a token, so they silently fall back to IMDSv1. If you also setHttpTokenstorequired, those containers lose metadata access entirely. The documented fix is hop limit 2 for container hosts. - Kubernetes pods on EKS using the VPC CNI route their IMDS traffic through the node. The EKS security best practice is to keep the hop limit at 1 precisely so pods cannot reach IMDS, and to give pods their own identities through IAM Roles for Service Accounts (IRSA) or EKS Pod Identity instead of inheriting the node role.
The exam scenario is usually a two-liner: “After enforcing IMDSv2, containerized applications on EC2 can no longer retrieve credentials. What is the least-privilege fix?” The answer is to raise the hop limit to 2 on the container instances, not to re-enable IMDSv1.
For ECS specifically, note two complementary controls. Task role credentials are not delivered through IMDS at all; the ECS agent serves them from a different link-local address, 169.254.170.2, scoped to the task. And the agent setting ECS_AWSVPC_BLOCK_IMDS=true blocks tasks in awsvpc network mode from reaching the host’s IMDS, so a compromised task cannot escalate to the container instance role.
Enforcing IMDSv2: Every Lever, From One Instance to the Organization
The exam wants you to know not only that IMDSv2 can be required, but where that requirement can be expressed. Think of it as five layers.
1. Per instance: instance metadata options
aws ec2 modify-instance-metadata-options \
--instance-id i-0123456789abcdef0 \
--http-tokens required \
--http-put-response-hop-limit 1 \
--http-endpoint enabled
--http-endpoint disabled turns IMDS off completely. That is the strongest setting, but it breaks the SSM Agent, the CloudWatch agent, and any SDK call that relies on the instance role, so it is only appropriate for instances that need no AWS credentials at all. You can also apply the same options at launch with --metadata-options on run-instances.
2. Per fleet: launch templates and Auto Scaling
Set MetadataOptions in the launch template so every instance an Auto Scaling group or EC2 Fleet creates is IMDSv2-only from its first second:
"MetadataOptions": {
"HttpTokens": "required",
"HttpPutResponseHopLimit": 1,
"HttpEndpoint": "enabled",
"InstanceMetadataTags": "disabled"
}
3. Per AMI: the IMDS support attribute
When you register an AMI, you can stamp it with --imds-support v2.0. Instances launched from that AMI default to HttpTokens: required and a hop limit of 2, without anyone having to remember a launch template setting. Amazon Linux 2023 AMIs already launch IMDSv2-only, and AWS has moved its Quick Start AMIs to the same default. An exam distractor that claims “Amazon Linux 2023 requires you to enable IMDSv2 manually” is wrong.
4. Per Region and account: instance metadata defaults
Since 2024 you can set an account-level default per Region:
aws ec2 modify-instance-metadata-defaults \
--http-tokens required \
--http-put-response-hop-limit 2 \
--region us-east-1
Any new launch that does not specify its own metadata options inherits these. It does not change running instances, and an explicit per-instance or launch-template value still wins. Use it as the safety net under layers 1 to 3.
5. Organization-wide: IAM condition keys in SCPs
The hard guarantee comes from policy. Three EC2 condition keys describe the metadata options on a RunInstances or ModifyInstanceMetadataOptions call: ec2:MetadataHttpTokens, ec2:MetadataHttpPutResponseHopLimit and ec2:MetadataHttpEndpoint. A service control policy can refuse any launch that is not IMDSv2-only:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "RequireIMDSv2OnLaunch",
"Effect": "Deny",
"Action": "ec2:RunInstances",
"Resource": "arn:aws:ec2:*:*:instance/*",
"Condition": {
"StringNotEquals": { "ec2:MetadataHttpTokens": "required" }
}
},
{
"Sid": "BlockDowngradeToIMDSv1",
"Effect": "Deny",
"Action": "ec2:ModifyInstanceMetadataOptions",
"Resource": "*",
"Condition": {
"StringEquals": { "ec2:MetadataHttpTokens": "optional" }
}
}
]
}
The second statement matters because modify-instance-metadata-options can downgrade an instance back to optional; without it, an attacker with EC2 write permissions reopens the v1 door. The multi-account governance guide covers where SCPs sit in the policy evaluation chain.
A fourth key works on the consumer side rather than the launch side. ec2:RoleDelivery is present whenever a request is signed with credentials that came from an instance profile, with the value 1.0 if they were fetched through IMDSv1 and 2.0 if through IMDSv2. Attaching this to the role’s policy, or to an SCP, makes v1-delivered credentials useless even on instances you have not yet migrated:
{
"Effect": "Deny",
"Action": "*",
"Resource": "*",
"Condition": {
"NumericLessThan": { "ec2:RoleDelivery": "2.0" }
}
}
Because the key is absent on requests from humans, Lambda, or ECS tasks, the deny simply does not match them. That makes this a safe organization-wide control.
Finding the Stragglers Before You Flip the Switch
Requiring IMDSv2 on an instance that still has v1 callers breaks those callers. The exam phrases this as “the security team wants to enforce IMDSv2 across the fleet without causing outages. What should they do first?” The answer is measurement.
- CloudWatch metric
MetadataNoTokenin theAWS/EC2namespace counts, per instance, the IMDS calls made without a token, meaning IMDSv1 calls. An instance that reports zero over a representative window is safe to switch. The companion metricMetadataNoTokenRejectedcounts v1 calls that were refused after you required v2, which tells you what you broke. - AWS Config managed rule
ec2-imdsv2-checkflags every instance whoseHttpTokensis stilloptional, and can trigger an SSM Automation remediation that runsmodify-instance-metadata-options. - Security Hub control EC2.8 (“EC2 instances should use IMDSv2”) surfaces the same finding in the Foundational Security Best Practices standard, which is how the logging and monitoring guide recommends centralizing posture findings.
- A quick inventory from the CLI:
aws ec2 describe-instances \
--query 'Reservations[].Instances[].[InstanceId,MetadataOptions.HttpTokens,MetadataOptions.HttpPutResponseHopLimit]' \
--output table
The migration order that the exam rewards is: inventory with Config or the CLI, watch MetadataNoToken for a few days, update the SDK or agent on anything still at v1, require v2 on the quiet instances first, then lock in the result with launch templates, account defaults and an SCP.
Detecting and Containing Stolen Instance Credentials
Hardening reduces the chance of theft. The Threat Detection domain tests what you do when it happens anyway.
GuardDuty tells you the credentials left the instance
Two GuardDuty finding types exist for exactly this case:
UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration.OutsideAWS: credentials created for an EC2 instance role were used from an IP address outside AWS.UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration.InsideAWS: the same credentials were used from an EC2 instance or other AWS resource in a different account.
Both findings are high severity and both are derived from CloudTrail, so they fire within minutes of the first API call with the stolen keys. The GuardDuty deep dive explains the data sources behind each finding family, and the threat detection and incident response guide covers the automated response patterns with EventBridge and Lambda.
Revoke the session without touching the application
Temporary credentials cannot be deleted, but they can be invalidated. In the IAM console, the role’s Revoke sessions action attaches an inline policy that denies all actions for any session whose aws:TokenIssueTime is earlier than now:
{
"Effect": "Deny",
"Action": "*",
"Resource": "*",
"Condition": {
"DateLessThan": { "aws:TokenIssueTime": "2026-10-08T09:15:00Z" }
}
}
The legitimate instance refreshes its credentials from IMDS within the hour and carries on; the attacker’s copy is dead. If the instance itself is compromised, the response additionally isolates it with a quarantine security group, snapshots its volumes for forensics, and terminates it, as the incident response guide describes.
Make stolen credentials useless outside the VPC with an SCP
The most elegant containment is preventive. Two global condition keys, aws:ec2InstanceSourceVPC and aws:ec2InstanceSourcePrivateIPv4, describe the VPC and private IP of the instance that obtained the credentials. Comparing them with where the request came from (aws:SourceVpc and aws:VpcSourceIp) denies any use of instance credentials from somewhere other than the instance’s own VPC:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Action": "*",
"Resource": "*",
"Condition": {
"StringNotEquals": { "aws:ec2InstanceSourceVPC": "${aws:SourceVpc}" },
"Null": { "ec2:SourceInstanceARN": "false" },
"BoolIfExists": { "aws:ViaAWSService": "false" }
}
},
{
"Effect": "Deny",
"Action": "*",
"Resource": "*",
"Condition": {
"StringNotEquals": { "aws:ec2InstanceSourcePrivateIPv4": "${aws:VpcSourceIp}" },
"Null": { "ec2:SourceInstanceARN": "false" },
"BoolIfExists": { "aws:ViaAWSService": "false" }
}
}
]
}
Read it slowly, because the exam will. The Null check on ec2:SourceInstanceARN limits the statement to requests made with instance credentials, so users and Lambda are unaffected. The aws:ViaAWSService exception keeps services that act on the instance’s behalf (CloudFormation, for example) working. And aws:SourceVpc only exists when the request arrives through a VPC endpoint, which means this control assumes your workloads reach AWS APIs through VPC endpoints rather than over the public internet. That dependency is itself a classic exam cue: “the policy denies all requests from the instance” usually means a missing VPC endpoint.
How the Pieces Fit: A Decision Table
| Scenario in the question | Correct control |
|---|---|
| Web app vulnerable to SSRF could leak role credentials | Require IMDSv2 (HttpTokens: required) |
| Containers on EC2 in bridge mode lost credentials after enforcement | Raise HttpPutResponseHopLimit to 2 |
| Pods on EKS must not inherit the node role | Keep hop limit 1; use IRSA or Pod Identity |
| ECS tasks must not reach the container instance’s IMDS | ECS_AWSVPC_BLOCK_IMDS=true and task roles |
| Guarantee every future launch is IMDSv2-only | SCP denying RunInstances unless ec2:MetadataHttpTokens = required |
| Stop anyone downgrading an instance to IMDSv1 | SCP denying ModifyInstanceMetadataOptions with optional |
| Make v1-delivered credentials unusable immediately | Deny when ec2:RoleDelivery < 2.0 |
| Find which instances still make IMDSv1 calls | MetadataNoToken CloudWatch metric |
| Report non-compliant instances continuously | Config rule ec2-imdsv2-check, Security Hub EC2.8 |
| Alert when instance credentials are used from outside AWS | GuardDuty InstanceCredentialExfiltration.OutsideAWS |
| Invalidate stolen temporary credentials without rotating the role | Revoke sessions (aws:TokenIssueTime deny) |
| Prevent stolen instance credentials from working outside the VPC | SCP with aws:ec2InstanceSourceVPC and aws:ec2InstanceSourcePrivateIPv4 |
| Instance needs no AWS credentials at all | HttpEndpoint: disabled |
| Secrets found in user data | Move to Secrets Manager or Parameter Store; never rely on user data |
Common Mistakes Candidates Make
- Confusing the hop limit with a network ACL. The hop limit is an IP TTL on the token response, not a firewall rule. It cannot be “allowed” by a security group.
- Thinking IMDSv2 encrypts metadata. It does not. Traffic to
169.254.169.254is plain HTTP on a link-local path that never leaves the instance. The protection is the session handshake, not encryption. - Assuming the SDK needs code changes. Modern SDKs and the CLI negotiate IMDSv2 on their own. The usual fix for a “legacy application breaks” scenario is to update the SDK, not to rewrite credential handling.
- Choosing to disable IMDS when the instance uses SSM. Session Manager, patching and inventory all depend on the SSM Agent, which needs the instance role from IMDS.
- Treating Nitro Enclaves as the answer to SSRF. Enclaves isolate a specific piece of sensitive processing, as the Nitro Enclaves guide explains; they do not change how the parent instance’s IMDS behaves.
- Forgetting that enforcement is per Region. Instance metadata defaults are a Regional setting. “We set the account default in us-east-1 but new instances in eu-west-1 still accept IMDSv1” is a plausible question.
Practice the Scenario Shapes
IMDS questions are rarely labelled as such. They arrive disguised as “an application proxy returned unexpected JSON”, “GuardDuty raised a high-severity finding referencing an instance role”, or “containers stopped working after a security change”. Recognizing the metadata service underneath each of those takes repetition. Sailor.sh’s AWS Certified Security - Specialty (SCS-C02) Mock Exam Bundle includes eight full-length mock exams whose infrastructure security and incident response scenarios are written in that indirect style, with explanations that walk through why the hop limit, the SCP or the revoke-sessions policy is the least-privilege answer. Pair it with the SCS-C02 practice questions and the 30-day study plan to place this topic within the broader exam map.
Frequently Asked Questions
Is IMDSv1 going away?
No announcement has retired IMDSv1, but AWS has steadily moved defaults toward v2: Amazon Linux 2023 and current Quick Start AMIs launch with IMDSv2 required, account-level defaults can require it, and the SDKs prefer it. The exam treats IMDSv2 as the baseline and IMDSv1 as a legacy setting to migrate away from.
Does requiring IMDSv2 stop all SSRF attacks?
It stops SSRF-based credential theft through IMDS, which is the attack the exam cares about, because the token request needs a PUT with a custom header and rejects proxied requests. It does not fix the SSRF vulnerability itself; the application still needs input validation, an allow-list of fetchable hosts, and ideally WAF rules in front of it.
Why is the default hop limit 1 and not 0?
A TTL of 1 means the packet survives exactly one delivery, from the metadata service to the instance’s own network stack. A TTL of 0 would never be delivered. Raising the limit to 2 lets the token cross one additional hop, such as a container bridge, which is why container hosts use 2.
Can I require IMDSv2 on running instances without a restart?
Yes. modify-instance-metadata-options --http-tokens required takes effect immediately, with no reboot. The change is visible in the MetadataNoTokenRejected metric if any process was still using v1.
What is the difference between ec2:RoleDelivery and ec2:MetadataHttpTokens?
ec2:MetadataHttpTokens describes the configuration being set on an instance at launch or modification time, so it belongs in policies about RunInstances and ModifyInstanceMetadataOptions. ec2:RoleDelivery describes how the credentials in the current request were obtained, so it belongs in policies that gate what those credentials can do. The first prevents v1 from being enabled; the second makes v1-delivered credentials powerless.
Do Lambda functions or Fargate tasks use IMDS?
No. Lambda receives its execution role credentials through environment variables inside the execution environment, and ECS tasks (on EC2 or Fargate) receive task role credentials from the container credential endpoint at 169.254.170.2. Only EC2 instances, and workloads that share their network namespace, talk to 169.254.169.254.
Which is better for stopping stolen-credential use: revoking sessions or the VPC-source SCP?
They solve different moments. Revoking sessions is a reactive incident-response step after you know credentials leaked. The aws:ec2InstanceSourceVPC SCP is preventive: stolen credentials fail from the attacker’s location before any damage is done. Mature environments use both, plus GuardDuty to tell them when the SCP has just saved them.
Conclusion
The Instance Metadata Service is where EC2’s identity lives, and IMDSv1 handed that identity to anyone who could make the instance perform a GET. IMDSv2 closes that door with a PUT-based token handshake, an X-Forwarded-For rejection and a hop limit that keeps tokens on the box. For the SCS-C02 exam, know the handshake, know what the hop limit does to containers, and be able to name every enforcement layer from modify-instance-metadata-options through launch templates, AMI attributes and account defaults up to SCPs with ec2:MetadataHttpTokens and ec2:RoleDelivery. Then close the loop on detection and containment: MetadataNoToken to find stragglers, Config and Security Hub to keep the fleet honest, GuardDuty’s exfiltration findings to spot a theft, revoke-sessions to kill it, and the VPC-source SCP to make the next theft pointless.
Review the SCS-C02 exam guide for where this topic sits among the six domains, and bring the scenarios above to a timed mock exam before test day.