From 993e2f539dcade17fb6b2e59bc016717423f2f93 Mon Sep 17 00:00:00 2001 From: Christophe Tafani-Dereeper Date: Fri, 9 Feb 2024 13:48:42 +0100 Subject: [PATCH 1/3] New attack technique: Usage of ssm:SendCommand on multiple instances (closes #480) --- .../AWS/aws.execution.ssm-send-command.md | 67 +++++++++ docs/attack-techniques/AWS/index.md | 2 + docs/attack-techniques/list.md | 1 + docs/index.yaml | 7 + .../aws/execution/ssm-send-command/main.go | 123 +++++++++++++++++ .../aws/execution/ssm-send-command/main.tf | 129 ++++++++++++++++++ v2/internal/attacktechniques/main.go | 1 + 7 files changed, 330 insertions(+) create mode 100755 docs/attack-techniques/AWS/aws.execution.ssm-send-command.md create mode 100644 v2/internal/attacktechniques/aws/execution/ssm-send-command/main.go create mode 100644 v2/internal/attacktechniques/aws/execution/ssm-send-command/main.tf diff --git a/docs/attack-techniques/AWS/aws.execution.ssm-send-command.md b/docs/attack-techniques/AWS/aws.execution.ssm-send-command.md new file mode 100755 index 00000000..c9d9cbdc --- /dev/null +++ b/docs/attack-techniques/AWS/aws.execution.ssm-send-command.md @@ -0,0 +1,67 @@ +--- +title: Usage of ssm:SendCommand on multiple instances +--- + +# Usage of ssm:SendCommand on multiple instances + + slow + idempotent + +Platform: AWS + +## MITRE ATT&CK Tactics + + +- Execution + +## Description + + +Simulates an attacker utilizing AWS Systems Manager (SSM) to execute commands through SendCommand on multiple EC2 instances. + +Warm-up: + +- Create multiple EC2 instances and a VPC (takes a few minutes). + +Detonation: + +- Runs ssm:SendCommand on several EC2 instances, to execute the command echo "id=$(id), hostname=$(hostname)" on each of them. + +References: + +- https://hackingthe.cloud/aws/post_exploitation/run_shell_commands_on_ec2/#send-command +- https://www.chrisfarris.com/post/aws-ir/ +- https://www.invictus-ir.com/news/aws-cloudtrail-cheat-sheet +- https://securitycafe.ro/2023/01/17/aws-post-explitation-with-ssm-sendcommand/ + + +## Instructions + +```bash title="Detonate with Stratus Red Team" +stratus detonate aws.execution.ssm-send-command +``` +## Detection + + +Identify, through CloudTrail's SendCommand event, especially when requestParameters.instanceIds contains several instances. Sample event: + +```json +{ + "eventSource": "ssm.amazonaws.com", + "eventName": "SendCommand", + "requestParameters": { + "instanceIds": [ + "i-0f364762ca43f9661", + "i-0a86d1f61db2b9b5d", + "i-08a69bfbe21c67e70" + ], + "documentName": "AWS-RunShellScript", + "parameters": "HIDDEN_DUE_TO_SECURITY_REASONS", + "interactive": false + } +} +``` + +While this technique uses a single call to ssm:SendCommand on several instances, an attacker may use one call per instance to execute commands on. In that case, the SendCommand event will be emitted for each call. + + diff --git a/docs/attack-techniques/AWS/index.md b/docs/attack-techniques/AWS/index.md index 488cba53..47a7741c 100755 --- a/docs/attack-techniques/AWS/index.md +++ b/docs/attack-techniques/AWS/index.md @@ -47,6 +47,8 @@ Note that some Stratus attack techniques may correspond to more than a single AT - [Execute Commands on EC2 Instance via User Data](./aws.execution.ec2-user-data.md) +- [Usage of ssm:SendCommand on multiple instances](./aws.execution.ssm-send-command.md) + - [Usage of ssm:StartSession on multiple instances](./aws.execution.ssm-start-session.md) diff --git a/docs/attack-techniques/list.md b/docs/attack-techniques/list.md index a9a3e4e0..8ff5a5ba 100755 --- a/docs/attack-techniques/list.md +++ b/docs/attack-techniques/list.md @@ -25,6 +25,7 @@ This page contains the list of all Stratus Attack Techniques. | [Download EC2 Instance User Data](./AWS/aws.discovery.ec2-download-user-data.md) | [AWS](./AWS/index.md) | Discovery | | [Launch Unusual EC2 instances](./AWS/aws.execution.ec2-launch-unusual-instances.md) | [AWS](./AWS/index.md) | Execution | | [Execute Commands on EC2 Instance via User Data](./AWS/aws.execution.ec2-user-data.md) | [AWS](./AWS/index.md) | Execution, Privilege Escalation | +| [Usage of ssm:SendCommand on multiple instances](./AWS/aws.execution.ssm-send-command.md) | [AWS](./AWS/index.md) | Execution | | [Usage of ssm:StartSession on multiple instances](./AWS/aws.execution.ssm-start-session.md) | [AWS](./AWS/index.md) | Execution | | [Open Ingress Port 22 on a Security Group](./AWS/aws.exfiltration.ec2-security-group-open-port-22-ingress.md) | [AWS](./AWS/index.md) | Exfiltration | | [Exfiltrate an AMI by Sharing It](./AWS/aws.exfiltration.ec2-share-ami.md) | [AWS](./AWS/index.md) | Exfiltration | diff --git a/docs/index.yaml b/docs/index.yaml index c5436e45..edde566c 100644 --- a/docs/index.yaml +++ b/docs/index.yaml @@ -116,6 +116,13 @@ AWS: - Privilege Escalation platform: AWS isIdempotent: true + - id: aws.execution.ssm-send-command + name: Usage of ssm:SendCommand on multiple instances + isSlow: true + mitreAttackTactics: + - Execution + platform: AWS + isIdempotent: true - id: aws.execution.ssm-start-session name: Usage of ssm:StartSession on multiple instances isSlow: true diff --git a/v2/internal/attacktechniques/aws/execution/ssm-send-command/main.go b/v2/internal/attacktechniques/aws/execution/ssm-send-command/main.go new file mode 100644 index 00000000..7d00289f --- /dev/null +++ b/v2/internal/attacktechniques/aws/execution/ssm-send-command/main.go @@ -0,0 +1,123 @@ +package aws + +import ( + "context" + _ "embed" + "fmt" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/ssm" + "github.com/datadog/stratus-red-team/v2/internal/utils" + "github.com/datadog/stratus-red-team/v2/pkg/stratus" + "github.com/datadog/stratus-red-team/v2/pkg/stratus/mitreattack" + "log" + "strings" + "time" +) + +//go:embed main.tf +var tf []byte + +const commandToExecute = `echo "id=$(id), hostname=$(hostname)"` + +func init() { + const codeBlock = "```" + stratus.GetRegistry().RegisterAttackTechnique(&stratus.AttackTechnique{ + ID: "aws.execution.ssm-send-command", + FriendlyName: "Usage of ssm:SendCommand on multiple instances", + IsSlow: true, + Description: ` +Simulates an attacker utilizing AWS Systems Manager (SSM) to execute commands through SendCommand on multiple EC2 instances. + +Warm-up: + +- Create multiple EC2 instances and a VPC (takes a few minutes). + +Detonation: + +- Runs ssm:SendCommand on several EC2 instances, to execute the command ` + commandToExecute + ` on each of them. + +References: + +- https://hackingthe.cloud/aws/post_exploitation/run_shell_commands_on_ec2/#send-command +- https://www.chrisfarris.com/post/aws-ir/ +- https://www.invictus-ir.com/news/aws-cloudtrail-cheat-sheet +- https://securitycafe.ro/2023/01/17/aws-post-explitation-with-ssm-sendcommand/ +`, + Detection: ` +Identify, through CloudTrail's SendCommand event, especially when requestParameters.instanceIds contains several instances. Sample event: + +` + codeBlock + `json +{ + "eventSource": "ssm.amazonaws.com", + "eventName": "SendCommand", + "requestParameters": { + "instanceIds": [ + "i-0f364762ca43f9661", + "i-0a86d1f61db2b9b5d", + "i-08a69bfbe21c67e70" + ], + "documentName": "AWS-RunShellScript", + "parameters": "HIDDEN_DUE_TO_SECURITY_REASONS", + "interactive": false + } +} +` + codeBlock + ` + +While this technique uses a single call to ssm:SendCommand on several instances, an attacker may use one call per instance to execute commands on. In that case, the SendCommand event will be emitted for each call. +`, + Platform: stratus.AWS, + PrerequisitesTerraformCode: tf, + IsIdempotent: true, + MitreAttackTactics: []mitreattack.Tactic{mitreattack.Execution}, + Detonate: detonate, + }) +} + +func detonate(params map[string]string, providers stratus.CloudProviders) error { + ssmClient := ssm.NewFromConfig(providers.AWS().GetConnection()) + instanceIDs := getInstanceIds(params) + + if err := utils.WaitForInstancesToRegisterInSSM(ssmClient, instanceIDs); err != nil { + return fmt.Errorf("failed to wait for instances to register in SSM: %v", err) + } + + log.Println("Instances are ready and registered in SSM!") + log.Println("Executing command '" + commandToExecute + "' through ssm:SendCommand on all instances...") + + result, err := ssmClient.SendCommand(context.Background(), &ssm.SendCommandInput{ + InstanceIds: instanceIDs, + DocumentName: aws.String("AWS-RunShellScript"), + Parameters: map[string][]string{ + "commands": {commandToExecute}, + }, + }) + if err != nil { + return fmt.Errorf("failed to send command to instances: %v", err) + } + + commandId := result.Command.CommandId + log.Println("Command sent successfully. Command ID: " + *commandId) + log.Println("Waiting for command outputs") + + for _, instanceID := range instanceIDs { + result, err := ssm.NewCommandExecutedWaiter(ssmClient).WaitForOutput(context.Background(), &ssm.GetCommandInvocationInput{ + InstanceId: &instanceID, + CommandId: commandId, + }, 2*time.Minute) + if err != nil { + return fmt.Errorf("failed to execute command on instance %s: %v", instanceID, err) + } + log.Print(fmt.Sprintf("Successfully executed on instance %s. Output: %s", instanceID, *result.StandardOutputContent)) + } + + return nil +} + +func getInstanceIds(params map[string]string) []string { + instanceIds := strings.Split(params["instance_ids"], ",") + // iterate over instanceIds and remove \n, \r, spaces and " from each instanceId + for i, instanceId := range instanceIds { + instanceIds[i] = strings.Trim(instanceId, " \"\n\r") + } + return instanceIds +} diff --git a/v2/internal/attacktechniques/aws/execution/ssm-send-command/main.tf b/v2/internal/attacktechniques/aws/execution/ssm-send-command/main.tf new file mode 100644 index 00000000..e3a856f2 --- /dev/null +++ b/v2/internal/attacktechniques/aws/execution/ssm-send-command/main.tf @@ -0,0 +1,129 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 4.0" + } + } +} + +provider "aws" { + skip_region_validation = true + skip_credentials_validation = true + default_tags { + tags = { + StratusRedTeam = true + } + } +} + +locals { + resource_prefix = "stratus-red-team-ssm-send-command-execution" +} + +variable "instance_count" { + description = "Number of instances to create" + default = 3 +} + +data "aws_availability_zones" "available" { + state = "available" +} + +module "vpc" { + source = "terraform-aws-modules/vpc/aws" + version = "~> 3.0" + + name = "${local.resource_prefix}-vpc" + 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" { + count = var.instance_count + subnet_id = module.vpc.private_subnets[0] + + private_ips = [format("10.0.1.%d", count.index + 10)] +} + +resource "aws_iam_role" "instance-role" { + name = "${local.resource_prefix}-role" + path = "/" + + assume_role_policy = < Date: Fri, 9 Feb 2024 13:50:46 +0100 Subject: [PATCH 2/3] terraform fmt --- .../attacktechniques/aws/execution/ssm-send-command/main.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/v2/internal/attacktechniques/aws/execution/ssm-send-command/main.tf b/v2/internal/attacktechniques/aws/execution/ssm-send-command/main.tf index e3a856f2..ad4f5697 100644 --- a/v2/internal/attacktechniques/aws/execution/ssm-send-command/main.tf +++ b/v2/internal/attacktechniques/aws/execution/ssm-send-command/main.tf @@ -119,8 +119,8 @@ output "instance_ids" { output "display" { value = format("Instances ready: \n%s", join("\n", [ - for i in aws_instance.instance : - format(" %s in %s", i.id, data.aws_availability_zones.available.names[0]) + for i in aws_instance.instance : + format(" %s in %s", i.id, data.aws_availability_zones.available.names[0]) ])) } From 23f5047b91b5f19f878c6fa02c30fe3520604df8 Mon Sep 17 00:00:00 2001 From: Christophe Tafani-Dereeper Date: Fri, 9 Feb 2024 13:57:47 +0100 Subject: [PATCH 3/3] Avoid using log.Print(fmt.Sprintf(...)) --- .../attacktechniques/aws/execution/ssm-send-command/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/v2/internal/attacktechniques/aws/execution/ssm-send-command/main.go b/v2/internal/attacktechniques/aws/execution/ssm-send-command/main.go index 7d00289f..0c620936 100644 --- a/v2/internal/attacktechniques/aws/execution/ssm-send-command/main.go +++ b/v2/internal/attacktechniques/aws/execution/ssm-send-command/main.go @@ -107,7 +107,7 @@ func detonate(params map[string]string, providers stratus.CloudProviders) error if err != nil { return fmt.Errorf("failed to execute command on instance %s: %v", instanceID, err) } - log.Print(fmt.Sprintf("Successfully executed on instance %s. Output: %s", instanceID, *result.StandardOutputContent)) + log.Printf("Successfully executed on instance %s. Output: %s", instanceID, *result.StandardOutputContent) } return nil