Skip to content

Commit

Permalink
New technique: Usage of ssm:SendCommand on multiple instances (#482)
Browse files Browse the repository at this point in the history
* New attack technique: Usage of ssm:SendCommand on multiple instances (closes #480)

* terraform fmt

* Avoid using log.Print(fmt.Sprintf(...))
  • Loading branch information
christophetd authored Feb 9, 2024
1 parent 093f472 commit ee9c8de
Show file tree
Hide file tree
Showing 7 changed files with 330 additions and 0 deletions.
67 changes: 67 additions & 0 deletions docs/attack-techniques/AWS/aws.execution.ssm-send-command.md
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.


2 changes: 2 additions & 0 deletions docs/attack-techniques/AWS/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)


Expand Down
1 change: 1 addition & 0 deletions docs/attack-techniques/list.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
7 changes: 7 additions & 0 deletions docs/index.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
123 changes: 123 additions & 0 deletions v2/internal/attacktechniques/aws/execution/ssm-send-command/main.go
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 v2/internal/attacktechniques/aws/execution/ssm-send-command/main.tf
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
}
1 change: 1 addition & 0 deletions v2/internal/attacktechniques/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
_ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/aws/discovery/ec2-get-user-data"
_ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/aws/execution/ec2-launch-unusual-instances"
_ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/aws/execution/ec2-user-data"
_ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/aws/execution/ssm-send-command"
_ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/aws/execution/ssm-start-session"
_ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/aws/exfiltration/ec2-security-group-open-port-22-ingress"
_ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/aws/exfiltration/ec2-share-ami"
Expand Down

0 comments on commit ee9c8de

Please sign in to comment.