From fd82e0ad359569a349bc343c36048333c8ad93d4 Mon Sep 17 00:00:00 2001 From: ryananicholson Date: Fri, 17 Jan 2025 17:14:34 -0500 Subject: [PATCH] New Technique: T1651 (#3031) Co-authored-by: Carrie Roberts Co-authored-by: Bhavin Patel --- atomics/T1651/T1651.yaml | 57 ++++++++++ atomics/T1651/src/T1651-1/AWSSSMAttack.ps1 | 126 +++++++++++++++++++++ atomics/T1651/src/T1651-1/compute.tf | 30 +++++ atomics/T1651/src/T1651-1/main.tf | 23 ++++ atomics/T1651/src/T1651-1/network.tf | 38 +++++++ atomics/T1651/src/T1651-1/output.tf | 3 + atomics/T1651/src/T1651-1/security.tf | 31 +++++ atomics/T1651/src/T1651-1/variables.tf | 14 +++ 8 files changed, 322 insertions(+) create mode 100644 atomics/T1651/T1651.yaml create mode 100644 atomics/T1651/src/T1651-1/AWSSSMAttack.ps1 create mode 100644 atomics/T1651/src/T1651-1/compute.tf create mode 100644 atomics/T1651/src/T1651-1/main.tf create mode 100644 atomics/T1651/src/T1651-1/network.tf create mode 100644 atomics/T1651/src/T1651-1/output.tf create mode 100644 atomics/T1651/src/T1651-1/security.tf create mode 100644 atomics/T1651/src/T1651-1/variables.tf diff --git a/atomics/T1651/T1651.yaml b/atomics/T1651/T1651.yaml new file mode 100644 index 00000000..701cb111 --- /dev/null +++ b/atomics/T1651/T1651.yaml @@ -0,0 +1,57 @@ +attack_technique: T1651 +display_name: Cloud Administration Command +atomic_tests: +- name: AWS Run Command (and Control) + description: | + This test simulates an adversary using the AWS Run Command service to execute commands on EC2 instances. + supported_platforms: + - iaas:aws + input_arguments: + access_key: + description: AWS Access Key + type: string + default: "" + secret_key: + description: AWS Secret Key + type: string + default: "" + session_token: + description: AWS Session Token + type: string + default: "" + profile: + description: AWS profile + type: string + default: "" + region: + description: AWS region to deploy the EC2 instance + type: string + default: us-east-2 + dependency_executor_name: powershell + dependencies: + - description: | + The AWS PowerShell module must be installed. + prereq_command: | + try {if (Get-InstalledModule -Name AWSPowerShell -ErrorAction SilentlyContinue) {exit 0} else {exit 1}} catch {exit 1} + get_prereq_command: | + Install-Module -Name AWSPowerShell -Force + - description: | + Terraform must be installed. + prereq_command: | + terraform --version + get_prereq_command: | + Write-Host "Terraform is required. Download it from https://www.terraform.io/downloads.html" + executor: + command: | + Import-Module "PathToAtomicsFolder/T1651/src/T1651-1/AWSSSMAttack.ps1" -Force + $access_key = "#{access_key}" + $secret_key = "#{secret_key}" + $session_token = "#{session_token}" + $aws_profile = "#{profile}" + $region = "#{region}" + Set-AWSAuthentication -AccessKey $access_key -SecretKey $secret_key -SessionToken $session_token -AWSProfile $aws_profile -AWSRegion $region + Invoke-Terraform -TerraformCommand init -TerraformDirectory "PathToAtomicsFolder/T1651/src/T1651-1" + Invoke-Terraform -TerraformCommand apply -TerraformDirectory "PathToAtomicsFolder/T1651/src/T1651-1" -TerraformVariables @("profile=T1651-1", "region=$region") + Invoke-SSMAttack -AWSProfile "T1651-1" -TerraformDirectory "PathToAtomicsFolder/T1651/src/T1651-1" + Invoke-Terraform -TerraformCommand destroy -TerraformDirectory "PathToAtomicsFolder/T1651/src/T1651-1" -TerraformVariables @("profile=T1651-1", "region=$region") + name: powershell diff --git a/atomics/T1651/src/T1651-1/AWSSSMAttack.ps1 b/atomics/T1651/src/T1651-1/AWSSSMAttack.ps1 new file mode 100644 index 00000000..7b1b8965 --- /dev/null +++ b/atomics/T1651/src/T1651-1/AWSSSMAttack.ps1 @@ -0,0 +1,126 @@ +Import-Module AWSPowerShell + +function Set-AWSAuthentication { + param ( + [string]$AccessKey, + [string]$SecretKey, + [string]$SessionToken, + [string]$AWSProfile, + [string]$AWSRegion + ) + if ($SessionToken -eq "" -and $AWSProfile -eq "") { + Set-AWSCredential -AccessKey $AccessKey -SecretKey $SecretKey -StoreAs "T1651-1" + } + elseif ($SessionToken -ne "" -and $AWSProfile -ne "") { + Set-AWSCredential -AccessKey $AccessKey -SecretKey $SecretKey -SessionToken $SessionToken -StoreAs "T1651-1" + } + elseif ($AWSProfile -ne "") { + Set-AWSCredential -ProfileName $AWSProfile -StoreAs "T1651-1" + } + + try { + Get-STSCallerIdentity -ProfileName "T1651-1" | Out-Null + } + catch { + Write-Host "ERROR: Failed to authenticate to AWS. Please check your credentials and try again." + exit 1 + } + Set-DefaultAWSRegion -Region $AWSRegion +} + +function Invoke-Terraform { + param ( + [string]$TerraformCommand, + [string]$TerraformDirectory, + [string[]]$TerraformVariables + ) + + $currentPath = Resolve-Path . + + if (-not (Test-Path $TerraformDirectory)) { + Write-Host "ERROR: Terraform directory not found. Please check the path and try again." + exit 1 + } + + if (-not (Get-ChildItem $TerraformDirectory -Filter "*.tf")) { + Write-Host "ERROR: No Terraform files found in the directory. Please check the path and try again." + exit 1 + } + + foreach($variable in $TerraformVariables) { + $varName = $variable.Split("=")[0] + $varValue = $variable.Split("=")[1] + [Environment]::SetEnvironmentVariable("TF_VAR_$varName", $varValue, "Process") + } + + Set-Location $TerraformDirectory + + if ($TerraformCommand -eq "init") { + try { + terraform init | Out-Null + } + catch { + Write-Host "ERROR: Failed to initialize Terraform. Please check the error message and try again." + exit 1 + } + } elseif ($TerraformCommand -eq "apply") { + try { + terraform apply -auto-approve | Out-Null + } + catch { + Write-Host "ERROR: Failed to apply Terraform. Please check the error message and try again." + exit 1 + } + } elseif ($TerraformCommand -eq "destroy") { + try { + terraform destroy -auto-approve | Out-Null + } + catch { + Write-Host "ERROR: Failed to destroy Terraform. Please check the error message and try again." + exit 1 + } + } else { + Write-Host "ERROR: Invalid Terraform command. Please use 'init', 'apply', or 'destroy'." + exit 1 + } + + Set-Location $currentPath +} + +function Invoke-SSMAttack { + param ( + [string]$AWSProfile, + [string]$TerraformDirectory + ) + $currentPath = Resolve-Path . + Set-Location $TerraformDirectory + $instanceId = (terraform output -json | ConvertFrom-Json | Select-Object -ExpandProperty aws_ec2_instance_id).value + Set-Location $currentPath + foreach($i in 1..50) { + $instanceStatus = (Get-SSMInstanceAssociationsStatus -InstanceId $instanceId -ProfileName $AWSProfile).Status + if ($instanceStatus -eq "Success") { + break + } + Start-Sleep -Seconds 6 + } + $commandId = (Send-SSMCommand -DocumentName "AWS-RunShellScript" -Target @{Key="tag:AtomicTest";Values=@("T1651-1")} -Comment "Atomic Test T1651-1" -Parameters @{commands = @("cat /etc/shadow")}).CommandId + foreach ($i in 1..50) { + $commandStatus = (Get-SSMCommandInvocation -CommandId $commandId -ProfileName $AWSProfile).Status + if ($commandStatus -eq "Success") { + $instanceId = (Get-SSMCommandInvocation -CommandId $commandId)[0].InstanceId + $output = (Get-SSMCommandInvocationDetail -CommandId $commandId -InstanceId $instanceId).StandardOutputContent + break + } + elseif ($commandStatus -eq "Failed") { + Write-Host "ERROR: Failed to execute the SSM command. Please check the error message and try again." + exit 1 + } + Start-Sleep -Seconds 6 + } + if ($output -eq "") { + Write-Host "ERROR: No output received from the SSM command. Please check the error message and try again." + exit 1 + } + Write-Host "SSM Command Output:" + Write-Host $output +} diff --git a/atomics/T1651/src/T1651-1/compute.tf b/atomics/T1651/src/T1651-1/compute.tf new file mode 100644 index 00000000..f2162c8f --- /dev/null +++ b/atomics/T1651/src/T1651-1/compute.tf @@ -0,0 +1,30 @@ +data "aws_ami" "ami" { + most_recent = true + + filter { + name = "name" + values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"] + } + + filter { + name = "virtualization-type" + values = ["hvm"] + } + + owners = ["099720109477"] +} + +resource "aws_instance" "instance" { + ami = data.aws_ami.ami.id + instance_type = var.instance_type + subnet_id = aws_subnet.subnet.id + iam_instance_profile = aws_iam_instance_profile.profile.name + vpc_security_group_ids = [aws_security_group.sg.id] + user_data = < 0 ? "aws-us-gov" : length(regexall("-cn-", var.region)) > 0 ? "aws-cn" : "aws" +} diff --git a/atomics/T1651/src/T1651-1/network.tf b/atomics/T1651/src/T1651-1/network.tf new file mode 100644 index 00000000..a89eb36d --- /dev/null +++ b/atomics/T1651/src/T1651-1/network.tf @@ -0,0 +1,38 @@ +resource "aws_vpc" "vpc" { + cidr_block = "10.0.0.0/16" +} + +resource "aws_subnet" "subnet" { + vpc_id = aws_vpc.vpc.id + cidr_block = "10.0.0.0/24" + map_public_ip_on_launch = true +} + +resource "aws_internet_gateway" "igw" { + vpc_id = aws_vpc.vpc.id +} + +resource "aws_route_table" "rt" { + vpc_id = aws_vpc.vpc.id + + route { + cidr_block = "0.0.0.0/0" + gateway_id = aws_internet_gateway.igw.id + } +} + +resource "aws_route_table_association" "rta" { + subnet_id = aws_subnet.subnet.id + route_table_id = aws_route_table.rt.id +} + +resource "aws_security_group" "sg" { + vpc_id = aws_vpc.vpc.id + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } +} diff --git a/atomics/T1651/src/T1651-1/output.tf b/atomics/T1651/src/T1651-1/output.tf new file mode 100644 index 00000000..c72bd060 --- /dev/null +++ b/atomics/T1651/src/T1651-1/output.tf @@ -0,0 +1,3 @@ +output "aws_ec2_instance_id" { + value = aws_instance.instance.id +} diff --git a/atomics/T1651/src/T1651-1/security.tf b/atomics/T1651/src/T1651-1/security.tf new file mode 100644 index 00000000..f2848669 --- /dev/null +++ b/atomics/T1651/src/T1651-1/security.tf @@ -0,0 +1,31 @@ +data "aws_iam_policy" "ssm" { + arn = "arn:${local.cloud}:iam::aws:policy/AmazonSSMManagedInstanceCore" +} + +resource "aws_iam_role" "role" { + name = "T1651-1-Role" + assume_role_policy = jsonencode( + { + Version = "2012-10-17" + Statement = [ + { + Effect = "Allow" + Principal = { + Service = "ec2.amazonaws.com" + } + Action = "sts:AssumeRole" + } + ] + } + ) +} + +resource "aws_iam_role_policy_attachment" "attachment" { + role = aws_iam_role.role.name + policy_arn = data.aws_iam_policy.ssm.arn +} + +resource "aws_iam_instance_profile" "profile" { + name = "T1651-1-Profile" + role = aws_iam_role.role.name +} diff --git a/atomics/T1651/src/T1651-1/variables.tf b/atomics/T1651/src/T1651-1/variables.tf new file mode 100644 index 00000000..3771af17 --- /dev/null +++ b/atomics/T1651/src/T1651-1/variables.tf @@ -0,0 +1,14 @@ +variable "profile" { + description = "The AWS profile to use" + default = "default" +} + +variable "region" { + description = "The AWS region to deploy to" + default = "us-east-2" +} + +variable "instance_type" { + description = "The instance type to use for the EC2 instance" + default = "t2.micro" +}