IMDSv2 enforcement: coming to a region near you!

On March 25, AWS released a new feature that helps enforcing IMDSv2 at the region level by default for newly-launched instances. This represents a long-awaited and feature, that still has some quirks.

A short introduction to IMDSv2

IMDSv2 is a security mechanism that AWS released back in November 2019, following the high-profile Capital One breach. This incident was caused by an SSRF-like vulnerability and caused US Senator Ron Wyden to write a letter to AWS asking which protections were available to customers. I reproduced a few extracts from this letter below.

A number of security experts have publicly speculated that the Capital One hacker exploited a SSRF vulnerability, a flaw about which experts have been warning for years. To the best of Amazon’s knowledge, was a SSRF attack used to gain access to Capital One’s customer data?

During the past two years, how many of Amazon’s customers were compromised through SSRF attacks against their Amazon cloud computing servers?

What guidance, if any, has Amazon provided to its cloud computing customers about the potential for SSRF attacks, particularly against Amazon’s metadata service (…)?

Interestingly, “Abusing the AWS metadata service using SSRF vulnerabilities” is one of my first security-focused blog posts that I wrote in 2017, before I even started working in security. Jon Hencinski and Scott Piper have also warned about this in 2018 and 2019:

IMDSv2—when enforced—protects against SSRF vulnerabilities that would otherwise allow an attacker to exfiltrate credentials from the Instance Metadata Service (IMDS).

Exploiting an SSRF vulnerability in an AWS environment

Past research from late 2022 and late 2021 has shown that such vulnerabilities were one of the most common causes for publicly-documented cloud security incidents. IMDSv2 is therefore regarded as a critlcal cloud security control to enforce in AWS environments.

For a more in-depth explanation of IMDSv2, see the excellent Securing the EC2 Instance Metadata Service by the just as great Nick Frichette (who happens to be a co-worker). I also discuss it in my talk “Fantastic AWS Hacks and Where to Find Them“.

How it started

Until now, enforcing IMDSv2 was relatively painful.

At first, you had to enforce it at the instance level by setting the metadata_options.http_tokens="required" option on the instance itself, or on the associated EC2 launch template or launch configuration. Although you could restrict the ability to use ec2:RunInstances to only IMDSv2-enforced instances using the ec2:MetadataHttpTokens condition key, this was still a very much manual process and a classic example of an insecure default.

In October 2022, AWS released a new feature which allows us to state “by default, enforce IMDSv2 on all instances start from this specific AMI“.

aws ec2 register-image \
    --name my-image \
    --root-device-name /dev/xvda \
    --block-device-mappings DeviceName=/dev/xvda,Ebs={SnapshotId=snap-0123456789example} \
    --architecture x86_64 \
    --imds-support v2.0

This handily enabled them to roll out Amazon Linux 2023 with AMIs that enforce IMDSv2 by default in March 2023. As Terraform support wasn’t included with the release, I took the opportunity to send my first (small) contribution to the Terraform AWS provider.

In November 2023, the AWS Console UI started to enforce IMDSv2 by default in the very popular “Quick Start” EC2 screen.

Now, in March 2024, AWS has introduced this new feature that allows to enforce IMDSv2 by default for all new instances in a region!

But enough history—let’s have a look at how this feature works.

Meet GetInstanceMetadataDefaults and ModifyInstanceMetadataDefaults

These 2 new API calls popped up in my RSS feed on March 25, quickly followed by the official announcement. As their name suggest, these two bad boys allow you to set default EC2 instance metadata options at the region level. Not really at the “account level” as the documentation suggests, since it’s a regional setting.

client.modify_instance_metadata_defaults(
    HttpTokens='optional'|'required'|'no-preference',
    HttpPutResponseHopLimit=123,
    HttpEndpoint='disabled'|'enabled'|'no-preference',
    InstanceMetadataTags='disabled'|'enabled'|'no-preference',
    DryRun=True|False
)

The important piece here is HttpTokens. Setting it to required means “enforcing IMDSv2”. Consequently, the piece of code below is what you want to run in all your accounts and regions:

import boto3

ec2_client = boto3.client('ec2')
ec2_client.modify_instance_metadata_defaults(HttpTokens='required')

At the time of writing, the AWS CLI does not support these API calls yet, so you’ll have to use good old Python or another SDK.

Update: Support in the AWS CLI was released as part of versions 1.32.70 (released March 25th) and 2.15.33 (released March 27th):

aws ec2 modify-instance-metadata-defaults --http-tokens required

If you’ve followed the previous “historical” section, you’ve probably noticed that we can now have IMDS settings at the region, AMI and instance level. How do these interact with each other? AWS has put up a very clear documentation on that which can be summarized as “instance first (if set), region setting second (if set), AMI setting third (if set), default to not enforcing IMDSv2“.

Implementing Terraform support

Any cloud feature without IaC support is arguably helpful for practitioners, many of whom deal with plethora of regions and accounts every day.

As a heavy Terraform user, I made sure to track the ask in an issue, quickly followed by a leap of faith to implement this new Terraform resource by myself, using the helpful HashiCorp contributor guide.

After a few hours of painfully debugging Go code and fighting against CI, the pull request was ready. It has now been merged and released in v5.43.0. You can now use the aws_ec2_instance_metadata_defaults resource:

resource "aws_ec2_instance_metadata_defaults" "imdsv2" {
  http_tokens                 = "required" # non-default
  instance_metadata_tags      = "disabled"
  http_endpoint               = "enabled"
  http_put_response_hop_limit = 1
}

Wrapping up

This new feature is highly useful for practitioners fighting to enforce IMDSv2 every day. It’s a great way to enable secure defaults in an environment—but still leaves us with insecure defaults by default. This is likely to change soon, per AWS announcement:

Mid-2024 – Newly released Amazon EC2 instance types will use IMDSv2 only by default. For transition support, you will still be able to enable/turn on IMDSv1 at launch or after launch on an instance live without the need for a restart or stop/start.

It’s also important to remember that this feature is about default settings. Using it to enforce IMDSv2 by default doesn’t prevent anyone from starting up instances that allow for IMDSv1 usage. For this purpose, you can use an SCP or explicit deny using the ec2:MetadataHttpTokens condition key.

Stay tuned, and thanks for reading!

Leave a Reply

Your email address will not be published. Required fields are marked *