Elastic Compute Cloud (EC2) was the third service released by Amazon under its “Web Services” banner. This was late into 2007 when AWS was rapidly expanding and innovating solutions for its end users. One such service released within EC2 was the Instance Metadata Service (IMDS)— a handy service exposed to each compute instance which can be used to retrieve an instance’s configuration data.
How is this service helpful?
Often, you’d want to securely access information about your instance’s configuration for launching resources or deciding your service’s logics. Manual efforts by the UI become exhaustive as you scale this beyond a few instances. The service, exposed in the form of a link-local IP address to the instance, can be used to query information via scripts, CLI or the SDK, and used to perform a range of operations. You can query information from the following categories about your instance(s):
- Host: Instance ID, AMI, AMI Launch Index, etc.
- Network: Public/Private IPs, Subnet, Security Groups, etc.
- User-data: Custom bootstrap scripts which execute at instance start-up
- So many more available here!
Querying the IMDS
IMDS is exposed via a link-local IP address, 169.254.169.254, to the instance and behaves like a REST interface accepting requests and generating responses. Now mind you, these requests never leave the network — i.e., these requests never go through the typical flow of going outbound from your security groups, NACLs, or the Internet Gateway. Instead, they’re resolved by the AWS hypervisor (upon which your instance is hosted) which returns information requested by your specific URL.
So, how does a sample query look like? Here’s an example:
If you run this from your instance, you’ll be returned with a list of categories (I mentioned before) which will further need to be appended to the URL to retrieve data. Here’s another example:
If you’d like to use IPv6 (which needs to be specifically enabled during Instance launch or via
modify-instance-metadata-options calls) to reach out to the service, you can use the following link-local address:
To what extent can I really retrieve information from this service? Let’s see. Digging through the list of categories — you’ll come across Security Credentials.
Using this endpoint, you can retrieve (and I quote):
“…the temporary security credentials associated with the role”
You can go so far as to retrieve credentials for a role associated with an instance. If it were a highly privileged role assigned to that instance, re-use of the role could lead to devastating outcomes!
Something’s Clearly Wrong Here…
There’s no form of authentication, verification, or accountability as to who’s making these requests. We’ve seen compromises in the past where EC2 instances were prone to SSRF attacks and the actors were able to query the metadata service from vulnerable applications. Practically leaving credentials open for anyone to steal.
Once you’ve got all three — the AccessKey, the SecretAccessKey, and the Token — you can do pretty much anything with the credentials. Fire up your CLI or the SDK and run whatever’s possible through the privileges assigned to the credentials.
Say Hello to IMDSv2
IMDSv2 offers protection against SSRF and three other issues with its predecessor. It does so by enforcing session authentications such that the user has to first acquire a token and then pass it in subsequent requests to retrieve the desired data.
SSRF is mitigated by only allowing the PUT method for requests to get session authentication tokens. Most applications either use a GET or POST method in their backend API calls . Same goes for WAFs which allow POST requests to go through but rarely allow PUT requests. Since the method never aligns, the vulnerability can’t be exploited.
Here’s how the requests look now; first we generate a token with a valid time to live value (maximum 6 hours — 21600 seconds):
curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 10000"
Then, we re-use the acquired token to send out a request to the IMDS: (assuming I’ve stored the token in the $TOKEN variable)
curl -H “X-aws-ec2-metadata-token: $TOKEN” http://169.254.169.254/latest/meta-data/instance-id
To further improve the security of the secret token, the token is never stored by the service itself. This way, whenever the calling process exits or the session is terminated, the session token is terminated as well. It also restricts the token to the subject instance. Though there’s no limit to the number of sessions, calling processes, or requests per session.
PS: PUT requests with an
X-Forwarded-For header are immediately rejected which I believe is a decent measure for forwarded requests.
Migrating to IMDSv2
Luckily, migration isn’t a pain. Both versions of the metadata service are available to instances and enabled by default. It’s up to the tenant to disable either of the two or both the versions on their instances.
There’s also a helpful CloudWatch metric — MetadataNoToken — which can help keep track of what instances are still using the older version of the service.
You can easily disable IMDSv1 at the launch of your instance. While launching it, take a look at the Advanced Details section and look for the Metadata Version parameter. By selecting V2 Only you can ensure your instance only allows the upgraded version of the metadata service.
What if you’ve already launched the instance with IMDSv1 enabled? Access the instance and run the following
modify-instance-metadata-options call via the CLI to require tokens on all HTTP requests to the IMDS endpoint (replace ‘x’ with your instance ID):
aws ec2 modify-instance-metadata-options --instance-id X --http-tokens required --http-endpoint enabled
Using SCPs or IAM policies, you can also restrict the launch of instances with IMDSv1 enabled (or it being enabled at all if there’s no legitimate use-case).
Response Hop Limits
By default, the PUT request to retrieve the token can only go through a single hop to reach the metadata service. Any increment and the request gets rejected. However, this is concerning for containerized applications which have to communicate to IMDS via the host itself. In such cases, the response hop limit can be increased using the same
aws ec2 modify-instance-metadata-options --instance-id X --http-put-response-hop-limit 5 --http-endpoint enabled
Disable or upgrade the service? Put up controls? Secure yourself! But hey, due diligence comes first. Check to see if any legitimate application is dependent on the older version of the IMDS. If so, they first need to be made compatible with the IMDSv2. Once compatible, it’s time to get to work 😉