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 = <