-
Notifications
You must be signed in to change notification settings - Fork 219
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
New technique: Usage of ssm:SendCommand on multiple instances (#482)
* New attack technique: Usage of ssm:SendCommand on multiple instances (closes #480) * terraform fmt * Avoid using log.Print(fmt.Sprintf(...))
- Loading branch information
1 parent
093f472
commit ee9c8de
Showing
7 changed files
with
330 additions
and
0 deletions.
There are no files selected for viewing
67 changes: 67 additions & 0 deletions
67
docs/attack-techniques/AWS/aws.execution.ssm-send-command.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
--- | ||
title: Usage of ssm:SendCommand on multiple instances | ||
--- | ||
|
||
# Usage of ssm:SendCommand on multiple instances | ||
|
||
<span class="smallcaps w3-badge w3-orange w3-round w3-text-sand" title="This attack technique might be slow to warm up or detonate">slow</span> | ||
<span class="smallcaps w3-badge w3-blue w3-round w3-text-white" title="This attack technique can be detonated multiple times">idempotent</span> | ||
|
||
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. | ||
|
||
<span style="font-variant: small-caps;">Warm-up</span>: | ||
|
||
- Create multiple EC2 instances and a VPC (takes a few minutes). | ||
|
||
<span style="font-variant: small-caps;">Detonation</span>: | ||
|
||
- Runs <code>ssm:SendCommand</code> on several EC2 instances, to execute the command <code>echo "id=$(id), hostname=$(hostname)"</code> 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 <code>SendCommand</code> event, especially when <code>requestParameters.instanceIds</code> 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 <code>ssm:SendCommand</code> on several instances, an attacker may use one call per instance to execute commands on. In that case, the <code>SendCommand</code> event will be emitted for each call. | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
123 changes: 123 additions & 0 deletions
123
v2/internal/attacktechniques/aws/execution/ssm-send-command/main.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 <code>ssm:SendCommand</code> on several EC2 instances, to execute the command <code>` + commandToExecute + `</code> 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 <code>SendCommand</code> event, especially when <code>requestParameters.instanceIds</code> 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 <code>ssm:SendCommand</code> on several instances, an attacker may use one call per instance to execute commands on. In that case, the <code>SendCommand</code> 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 | ||
} |
129 changes: 129 additions & 0 deletions
129
v2/internal/attacktechniques/aws/execution/ssm-send-command/main.tf
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 = <<EOF | ||
{ | ||
"Version": "2012-10-17", | ||
"Statement": [ | ||
{ | ||
"Action": "sts:AssumeRole", | ||
"Principal": { | ||
"Service": "ec2.amazonaws.com" | ||
}, | ||
"Effect": "Allow", | ||
"Sid": "" | ||
} | ||
] | ||
} | ||
EOF | ||
} | ||
|
||
resource "aws_iam_role_policy_attachment" "rolepolicy" { | ||
role = aws_iam_role.instance-role.name | ||
policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" | ||
} | ||
|
||
resource "aws_iam_instance_profile" "instance" { | ||
name = "${local.resource_prefix}-instance" | ||
role = aws_iam_role.instance-role.name | ||
} | ||
|
||
resource "aws_instance" "instance" { | ||
count = var.instance_count | ||
ami = data.aws_ami.amazon-2.id | ||
instance_type = "t3.micro" | ||
iam_instance_profile = aws_iam_instance_profile.instance.name | ||
|
||
network_interface { | ||
device_index = 0 | ||
network_interface_id = aws_network_interface.iface[count.index].id | ||
} | ||
|
||
tags = { | ||
Name = "${local.resource_prefix}-instance-${count.index}" | ||
} | ||
} | ||
|
||
output "instance_ids" { | ||
value = aws_instance.instance[*].id | ||
} | ||
|
||
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]) | ||
])) | ||
} | ||
|
||
output "instance_role_name" { | ||
value = aws_iam_role.instance-role.name | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters