Skip to content

Commit

Permalink
New attack technique: Usage of SSM StartSession on multiple instances (
Browse files Browse the repository at this point in the history
…#477)

* add aws ssm-start-session attack technique

* Add reference for real-world StartSession ussage

* Map to exfiltration instead of lateral movement

* Minor code change

* Regenerate docs and small code change to terminate sessions

* Fix error 400 TargetNotConnected

* Refactor to reuse existing code waiting for instances to be available in SSM

* formatting

---------

Co-authored-by: Christophe Tafani-Dereeper <[email protected]>
  • Loading branch information
adanalvarez and christophetd authored Feb 9, 2024
1 parent c86741b commit 904c352
Show file tree
Hide file tree
Showing 9 changed files with 367 additions and 34 deletions.
61 changes: 61 additions & 0 deletions docs/attack-techniques/AWS/aws.execution.ssm-start-session.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
---
title: Usage of ssm:StartSession on multiple instances
---

# Usage of ssm:StartSession 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) StartSession to gain unauthorized interactive access to 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>:

- Initiates a connection to the EC2 for a Session Manager session.

References:

- https://awstip.com/responding-to-an-attack-in-aws-9048a1a551ac (evidence of usage in the wild)
- https://hackingthe.cloud/aws/post_exploitation/run_shell_commands_on_ec2/#session-manager


## Instructions

```bash title="Detonate with Stratus Red Team"
stratus detonate aws.execution.ssm-start-session
```
## Detection


Identify, through CloudTrail's <code>StartSession</code> event, when a user is starting an interactive session to multiple EC2 instances. Sample event:

```
{
"eventSource": "ssm.amazonaws.com",
"eventName": "StartSession",
"requestParameters": {
"target": "i-123456"
},
"responseElements": {
"sessionId": "...",
"tokenValue": "Value hidden due to security reasons.",
"streamUrl": "wss://ssmmessages.eu-west-1.amazonaws.com/v1/data-channel/..."
},
}
```


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:StartSession on multiple instances](./aws.execution.ssm-start-session.md)


## Exfiltration

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: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 |
| [Exfiltrate EBS Snapshot by Sharing It](./AWS/aws.exfiltration.ec2-share-ebs-snapshot.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-start-session
name: Usage of ssm:StartSession on multiple instances
isSlow: true
mitreAttackTactics:
- Execution
platform: AWS
isIdempotent: true
Exfiltration:
- id: aws.exfiltration.ec2-security-group-open-port-22-ingress
name: Open Ingress Port 22 on a Security Group
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/ec2"
"github.com/aws/aws-sdk-go-v2/service/ssm"
"github.com/aws/aws-sdk-go-v2/service/ssm/types"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/datadog/stratus-red-team/v2/internal/utils"
"github.com/datadog/stratus-red-team/v2/pkg/stratus"
Expand Down Expand Up @@ -61,7 +60,7 @@ func detonate(params map[string]string, providers stratus.CloudProviders) error
instanceId := params["instance_id"]
instanceRoleName := params["instance_role_name"]

if err := waitForInstanceToRegisterInSSM(ssmClient, instanceId); err != nil {
if err := utils.WaitForInstanceToRegisterInSSM(ssmClient, instanceId); err != nil {
return err
}

Expand Down Expand Up @@ -117,31 +116,3 @@ func detonate(params map[string]string, providers stratus.CloudProviders) error
}
return nil
}

// waitForInstanceToRegisterInSSM waits for an instance to be registered in SSM
// may be slow (60+ seconds)
func waitForInstanceToRegisterInSSM(ssmClient *ssm.Client, instanceId string) error {
log.Println("Waiting for instance " + instanceId + " to show up in AWS SSM")
for {
result, err := ssmClient.DescribeInstanceInformation(context.Background(), &ssm.DescribeInstanceInformationInput{
Filters: []types.InstanceInformationStringFilter{
{Key: aws.String("InstanceIds"), Values: []string{instanceId}},
},
})

if err != nil {
return err
}

// When the instance isn't registered in SSM yet, it returns an empty array
// If the result we get back contains 1 instance and it has the right status,
// we're good to go!
instances := result.InstanceInformationList
if len(instances) == 1 && instances[0].PingStatus == types.PingStatusOnline {
log.Println("Instance " + instanceId + " is ready to go in SSM")
return nil
}

time.Sleep(1 * time.Second)
}
}
105 changes: 105 additions & 0 deletions v2/internal/attacktechniques/aws/execution/ssm-start-session/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package aws

import (
"context"
_ "embed"
"fmt"
"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"
)

//go:embed main.tf
var tf []byte

func init() {
const codeBlock = "```"
stratus.GetRegistry().RegisterAttackTechnique(&stratus.AttackTechnique{
ID: "aws.execution.ssm-start-session",
FriendlyName: "Usage of ssm:StartSession on multiple instances",
IsSlow: true,
Description: `
Simulates an attacker utilizing AWS Systems Manager (SSM) StartSession to gain unauthorized interactive access to multiple EC2 instances.
Warm-up:
- Create multiple EC2 instances and a VPC (takes a few minutes).
Detonation:
- Initiates a connection to the EC2 for a Session Manager session.
References:
- https://awstip.com/responding-to-an-attack-in-aws-9048a1a551ac (evidence of usage in the wild)
- https://hackingthe.cloud/aws/post_exploitation/run_shell_commands_on_ec2/#session-manager
`,
Detection: `
Identify, through CloudTrail's <code>StartSession</code> event, when a user is starting an interactive session to multiple EC2 instances. Sample event:
` + codeBlock + `
{
"eventSource": "ssm.amazonaws.com",
"eventName": "StartSession",
"requestParameters": {
"target": "i-123456"
},
"responseElements": {
"sessionId": "...",
"tokenValue": "Value hidden due to security reasons.",
"streamUrl": "wss://ssmmessages.eu-west-1.amazonaws.com/v1/data-channel/..."
},
}
` + codeBlock + `
`,
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("Starting SSM sessions on each instance...")

for _, instanceID := range instanceIDs {
session, err := ssmClient.StartSession(context.Background(), &ssm.StartSessionInput{
Target: &instanceID,
})
if err != nil {
return fmt.Errorf("failed to start session with instance %s: %v", instanceID, err)
}
fmt.Printf("\tSession started on instance %s\n", instanceID)

// Attempt to terminate the session to not leave it hanging
_, err = ssmClient.TerminateSession(context.Background(), &ssm.TerminateSessionInput{
SessionId: session.SessionId,
})
if err != nil {
return fmt.Errorf("failed to terminate SSM session with instance %s: %v", instanceID, err)
}
}

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-start-session/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-start-session-lateral-movement"
}

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-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"
_ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/aws/exfiltration/ec2-share-ebs-snapshot"
Expand Down
Loading

0 comments on commit 904c352

Please sign in to comment.