Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New attack technique: Usage of SSM StartSession on multiple instances #477

Merged
merged 8 commits into from
Feb 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading