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..0c620936 --- /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.Printf("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..ad4f5697 --- /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 = <