Securing EC2 Instance Metadata in AWS

·




What is the Instance Metadata Service (IMDS)?

The AWS Instance Metadata Service (IMDS) is an Elastic Compute Cloud (EC2) service that provides helpful information about your instances, which can be used for configuration and management. The IMDS provides details such as security groups associated with the instance, hostname information and the EC2 instance ID. Furthermore, if the instance has an IAM role attached the IAM credentials of this role are also available via cURL to the IMDS API endpoint. Currently AWS maintains two versions of the IMDS, the later of which (IMDSv2) introduced security enhancements in response to a security incident1 2.


Why should you care about securing it?

While the IMDS is a useful resource for administration/management of your EC2 instances, it is also extremely useful in the hands of a malicious actor. Publicly accessible EC2 instances running IMDS version 1 are vulnerable to possible Server Side Request Forgery (SSRF) attacks. If you leverage IAM roles with your EC2 instances, security credentials are attached to the instance and accessible via the IMDS. Depending on their scope, these credentials can be used to steal additional information or escalate privilege further. If you don’t use IAM roles with your EC2 instances, you should still consider reconnaissance activity that can be performed via the IMDS on version 1.


IMDS Version 2

In November 2019, AWS release version 2 of the IMDS. This update introduced security enhancements to the service. These updates include:

  1. An authentication token must first be requested from the IMDS and included in any subsequent requests for metadata.

  2. A new HTTP header was introduced for the token retrieval X-aws-ec2-metadata-token-ttl-seconds. This header delineates the time to live on the token before expiry.

  3. The HTTP method was updated from a GET to a PUT for the API token request. This change was implemented based on AWS research for PUT request support on Web Application Firewalls (WAFs). The research outcome was that the majority of open-source WAF services do not support PUT requests.

  4. A second header X-aws-ec2-metadata-token was introduced to hold the token required for retrieving the metadata.

  5. Finally, a hop count (HTTPPutResponseHopLimit) was introduced for the IMDS metadata packet which enables AWS to drop it after a certain number of hops to prevent sensitive data being exfiltrated externally.

IMDS version 1 vs version 2 in practice

From a programmatic/command line perspective, how do the two versions differ? As mentioned, for version 2 you must now request the token prior to any subsequent calls to the IMDS and add the relevant headers to your requests. See below for a comparison of an IMDSv1 vs v2 API call.

IMDS version 1 API request

[daniel@ip-172-31-6-175 ~]$ curl -s "http://169.254.169.254/latest/meta-data/iam/security-credentials/"
Daniel_Cremin_Demo_Instance

--------------------------------
 
[daniel@ip-172-31-6-175 ~]$ curl -s "http://169.254.169.254/latest/meta-data/iam/security-credentials/Daniel_Cremin_Demo_Instance"
{
  "Code" : "Success",
  "LastUpdated" : "2023-04-10T21:04:33Z",
  "Type" : "AWS-HMAC",
  "AccessKeyId" : "<ACCESS-KEY-ID",
  "SecretAccessKey" : "<SECRET-ACCESS-KEY>",
  "Token" : "<TOKEN>",
  "Expiration" : "2022-04-11T03:04:33Z"
}

IMDS version 2 API request

[daniel@ip-172-31-6-175 ~]$ IMDSV2_TOKEN=$(curl -s -X PUT -H "X-aws-ec2-metadata-token-ttl-seconds: 300"  http://169.254.169.254/latest/api/token)
 
--------------------------------
  
[daniel@ip-172-31-6-175 ~]$ curl -H "X-aws-ec2-metadata-token: $IMDSV2_TOKEN" http://169.254.169.254/latest/meta-data/iam/security-credentials/
Daniel_Cremin_Demo_Instance
 
--------------------------------
 
[daniel@ip-172-31-6-175 ~]$ curl -H "X-aws-ec2-metadata-token: $IMDSV2_TOKEN" http://169.254.169.254/latest/meta-data/iam/security-credentials/Daniel_Cremin_Demo_Instance
{
  "Code" : "Success",
  "LastUpdated" : "2023-04-10T21:06:30Z",
  "Type" : "AWS-HMAC",
  "AccessKeyId" : "<ACCESS-KEY-ID",
  "SecretAccessKey" : "<SECRET-ACCESS-KEY>",
  "Token" : "<TOKEN>",
  "Expiration" : "2022-04-11T03:06:30Z"
}

What should I do next?

First a foremost, if you’re getting started with AWS – you should leverage Service Control Policies (SCPs) via AWS Organisations to block IMDS version 1 usage entirely from all of your accounts. See below for a SCP to rule them all for blocking IMDS version 1 at the organisation level. If you’ve already started your AWS journey, you should also consider the SCP but you likely have a bit of pre-work to do which we’ll discuss.

If you don’t need the IMDS, you should disable it entirely.


Identifying IMDS version 1 usage in your accounts

If you’re working with existing AWS infrastructure, implementation of an SCP may not be possible straight away, as to do so may have a downstream impact on running EC2 workloads. Here is some pre-work you can do prior to migration/blocking efforts:

  • Communicate the migration plan with your internal teams so they understand the impact and timeline. It is often difficult to ascertain how/if teams are using the service, they will need to update their codebases if they are making version 1 calls to the IMDS to include the token & additional headers.

  • AWS recently introduced a CloudWatch metric MetadataNoToken which can be used to identify calls to the IMDS via version 1. You can track this metric to 0 for confidence all services on your EC2 have been migrated.
  • Check your internal codebases, documentation, Slack/communication channels for references to IMDS usage. This is painful, but it can unearth use cases that may not be discovered otherwise.
  • You can use the AWS CLI to identify the IMDS version of your EC2 instances:
aws ec2 describe-instances --filters "Name=metadata-options.http-tokens,Values=optional" --query "Reservations[*].Instances[*].[InstanceId]" --output text
  • Finally, if your organisation uses AWS Config you can aggregate the IMDS version data of your EC2 instances across your organisation. Here is a sample query that will provide those details:
SELECT
  resourceId,
  accountId,
  availabilityZone,
  configuration.state.name,
  configuration.metadataOptions.httpTokens,
  configuration.metadataOptions.httpEndpoint,
  configuration.metadataOptions.httpPutResponseHopLimit
WHERE
  resourceType = 'AWS::EC2::Instance'

Migration & Deployment of EC2’s to IMDSv2

AWS provides several ways to both migrate and deploy EC2 instances on IMDSv2. The approach here is up to you, the outcome will be the same. First we’ll discuss migrating your instances from IMDSv1 to IMDSv2 and then consciously deploying instances with the security enhanced version. Updating the IMDS version can be done via the 2 options below while the instances are running.

If you are running containerised workloads, you should consider the hop count which is described in this context here.

1. AWS Systems Manager – Change Management

The Systems Manager automation workflow/document uses the Systems Manager Agent (SSM Agent) to enforce IMDSv2, configure the HTTPPutResponseHopLimit and allow/deny access to the metadata. See this AWS Knowledge Center article for up to date details on using Systems Manager to enforce IMDSv2. The benefit of this approach is execution against a single instance, target group of instances, multiple accounts and regions. Note: This approach requires instances to have the SSM agent installed.

2. Programmatically migrating to IMDSv2

Where you are enabling programmatically, you want to ensure that your instances have a HttpTokens value of Required for IMDSv2. You can also set the hop count via these APIs.

  • Details on updating via the modify-instance-metadata-options API/CLI can be found here & here.

  • Migration via boto3 (Python library) is available here.

3. Deploying EC2’s on IMDSv2

If you’re deploying an EC2 for the first time, you can bake in IMDSv2 as part of the creation process. For brevity, we’ll discuss three options here CloudFormation, Terraform and Amazon Machine Images (AMIs).

4. CloudFormation

Here is a sample CloudFormation template/launch template for IMDSv2:


AWSTemplateFormatVersion: 2010-09-09
 
Resources:
  EC2LaunchTemplate:
    Type: AWS::EC2::LaunchTemplate
    Properties:
      LaunchTemplateData:
        ImageId: <ami-id>
        InstanceType: <ec2-instance-type>
        MetadataOptions:
          HttpTokens: required
          HttpPutResponseHopLimit: 1
 
  IMDSv2EC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      LaunchTemplate:
        LaunchTemplateId: !Ref 'EC2LaunchTemplate'
        Version: !GetAtt 'EC2LaunchTemplate.LatestVersionNumber'

--------------------------------
Launch Template
--------------------------------

AWSTemplateFormatVersion: 2010-09-09
 
Resources:
  IMDSv2EC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      LaunchTemplate:
        LaunchTemplateId: <launch-template-template-id>
        Version: <launch-template-version>

5. Terraform

resource "aws_instance" "IMDSv2-EC2-Instance" {
 
  ami                    = var.ec2_ami_identifier
  instance_type          = var.ec2_instance_type
  iam_instance_profile   = var.ec2_iam_instance_profile
 
  metadata_options {
    http_endpoint               = "enabled"
    http_tokens                 = "required"
    http_put_response_hop_limit = 1
  }
}

6. AMI Configuration

AWS recently introduced the imds-support parameter for AMIs. This parameter allows you to enforce IMDSv2 on AMI creation/registration. If your organisation manages base images centrally, this is extremely useful to ensure IMDS is in use organisation wide. See details for adding IMDS support to your AMIs here.

7. AWS Console Configuration

If you are using the AWS Management Console to deploy an EC2 instance, you can also configure the instance to use IMDSv2. The settings for IMDS are found in the the Advanced Details section of the launch configuration.

EC2 Instances → Launch an instance → Advanced Details


Blocking IMDSv1

You’ve migrated all of your EC2 instances to IMDSv2, congrats! Now you’ll want to ensure that future instances are not deployed on version 1 or the modify-instance-metadata-options CLI command isn’t used to switch the EC2 back to IMDSv1 after creation. This is where Service Control Policies (SCPs) come in.

A SCP is an organisation policy that you can use to manage permissions in your AWS organisation. They allow you to centrally manage (allow/deny) access to specific resources, APIs and even AWS regions. If you’re not using AWS Organisations you should add it your cloud security roadmap. Organisations offers considerably more control over your member accounts and eases scaling your AWS footprint when required.

SCP to block IMDSv1 at the organisation level

As the saying goes, with great power comes great responsibility. SCPs are a powerful tool, but you need to test them throughly before applying them to production. You should always test your SCPs on a subset of accounts/a test organisation before rolling them out on a wider scale.

The SCPs detailed below should be fully tested to ensure that they do not have a negative impact in your AWS environment. Ensure that you have completely migrated any EC2 instances to IMDSv2 before considering these SCPs.

You can apply these as individual SCPs or a single policy with multiple SIDs, for simplicity I’ve broken them down separately and also included a single merged policy.

Enforce EC2 roles to use IMDSv2

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "EnforceEC2RoleIMDSv2",
            "Effect": "Deny",
            "Action": "*",
            "Resource": "*",
            "Condition": {
                "NumericLessThan": {
                    "ec2:RoleDelivery": "2.0"
                }
            }
        }
    ]
}

Block creation of instances on IMDSv1

This API prevents creation of instances running IMDSv1, existing instances are started via ec2:StartInstances therefore are not covered by this SCP. Blocking start instances could have a negative impact on production pre-migration.

{
    "Version": "2012-10-17",
    "Statement": {
        "Sid": "RequireImdsV2",
        "Effect": "Deny",
        "Action": "ec2:RunInstances",
        "Resource": "arn:aws:ec2:*:*:instance/*",
        "Condition": {
            "StringNotEquals": {
                "ec2:MetadataHttpTokens": "required"
            }
        }
    }
}

Deny access to the IMDS CLI/API endpoint for configuration

{
    "Version": "2012-10-17",
    "Statement": {
        "Sid": "BlockAccessToImdsApi",
        "Effect": "Deny",
        "Action": "ec2:ModifyInstanceMetadataOptions",
        "Resource": "*"
    }
}

Set the HTTP PUT hop count

Note: Consider containerised workloads for this one.

{
    "Version": "2012-10-17",
    "Statement": {
        "Sid": "SetImdsHopCountLimit",
        "Effect": "Deny",
        "Action": "ec2:RunInstances",
        "Resource": "arn:aws:ec2:*:*:instance/*",
        "Condition": {
            "NumericGreaterThan": {"ec2:MetadataHttpPutResponseHopLimit": "1"}
        }
    }
}

Merged Policy

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "EnforceEC2RoleIMDSv2",
      "Effect": "Deny",
      "Action": "*",
      "Resource": "*",
      "Condition": {
        "NumericLessThan": {
          "ec2:RoleDelivery": "2.0"
        }
      }
    },
    {
      "Sid": "RequireImdsV2",
      "Effect": "Deny",
      "Action": "ec2:RunInstances",
      "Resource": "arn:aws:ec2:*:*:instance/*",
      "Condition": {
        "StringNotEquals": {
          "ec2:MetadataHttpTokens": "required"
        }
      }
    },
    {
      "Sid": "BlockAccessToImdsApi",
      "Effect": "Deny",
      "Action": "ec2:ModifyInstanceMetadataOptions",
      "Resource": "*"
    },
    {
      "Sid": "SetImdsHopCountLimit",
      "Effect": "Deny",
      "Action": "ec2:RunInstances",
      "Resource": "arn:aws:ec2:*:*:instance/*",
      "Condition": {
        "NumericGreaterThan": {
          "ec2:MetadataHttpPutResponseHopLimit": "1"
        }
      }
    }
  ]
}

IMDS in GCP & Azure

The metadata service is not unique to AWS, both Google Cloud Platform (GCP) and Azure offer their own metadata endpoints. However, as of the time of writing both GCP and Azure’s current implementations have SSRF protection baked in by default.


Future considerations

As of the time of writing, IMDSv1 and its security shortcomings are not under the scope of security audits from what I’ve seen. However, it is possible and likely that will change. Therefore, it is worthwhile getting ahead of this via migration and subsequent blocking of IMDSv1 from your organisation.

From an adversarial perspective, MITRE are tracking exploitation of the IMDS under technique ID T1552 in their ATT&CK framework3. Thus, it is likely malicious actors leveraging IMDSv1’s security shortcomings via SSRF or other methods will continue. With this in mind, you should plan and execute your migration strategy as soon as possible.


Fin.

Thanks for taking the time to read this post. I hope it has been useful in understanding the need for IMDS version 2 and the potential impact of non-migration. If you have any feedback/corrections please feel free to reach out to me via the email below or on LinkedIn. I’d love to hear from you!

-Dan


Reference Material