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

Create lambda function #122

Closed
wants to merge 1 commit into from
Closed
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
89 changes: 89 additions & 0 deletions ttps/cloud/aws/lambda/create-lambda-function/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Create AWS Lambda Function

![Meta TTP](https://img.shields.io/badge/Meta_TTP-blue)

This TTP is used to create a new Lambda function in AWS. It uses the AWS CLI to create a new function with the specified name.
A temprory IAM role, S3 bucket are created to hold the lambda function code.
If a function with given name exists, no new function is created.
If a function does not exist a new function is created. It is also deleted during cleanup.
A public API gateway is created to also invoke this function. This enables use to invoke the function from the internet.
`--no-cleanup` options should be explicitly specified if we do not want the new function created to be deleted.


## Arguments

- **region**: Name of the AWS region to use with TTPForge.
- **lambda_function_name**: The name of the new Lambda function to be created.

## Steps

1. Set up necessary cloud environment variables.
2. Create a temprory IAM role to be assumed by the lambda function.
3. Create a S3 bucket with a random string name
4. Zip and upload lambad code from local device to the newly created S3 bucket.
5. Create a new Lambda function if no existing function is found with given function name.
6. Create a API which is publicly accessible and integrate the newly created lambda function with it.
6. Invoke the newly created Lambda function by accessing the API URL
7. By default during the cleanup, delete the recently created Lambda function, S3 bucket, IAM role and the API gateway.

## Manual Reproduction Steps

```
# Set necessary env variables

# Create new temp IAM role
aws iam create-role --role-name "ROLE-NAME" --assume-role-policy-document file://trust-policy.json

# Create a new S3 bucket to host lambda function
aws s3 mb s3://BUCKET_NAME

# Upload lambda function zip file to S3 bucket
aws s3 cp lambda-function.zip s3://BUCKET_NAME

# Create a new function
aws lambda create-function --function-name "FUNCTION_NAME" --runtime "RUNTIME" --handler "HANDLER" --role "ROLE_ARN" --code "S3Bucket=$BUCKET_NAME,S3Key=lambda-function.zip"

# Create a new REST API
rest_api_id=$(aws apigateway create-rest-api --name "FUNCTION_NAME"-api --query 'id' --output text)

# Get the root resource id
root_resource_id=$(aws apigateway get-resources --rest-api-id $rest_api_id --query 'items[?path==`/`].id' --output text)

# Create a GET method for the new resource
aws apigateway put-method --rest-api-id $rest_api_id --resource-id $root_resource_id --http-method GET --authorization-type "NONE" > /dev/null 2>&1

# Add necessary permissions to the new resource method
lambda_func_arn=$(aws lambda get-function --function-name "FUNCTION_NAME" --query 'Configuration.FunctionArn' --output text)
account_id=$(aws sts get-caller-identity --query Account --output text)
account_region=$(aws configure get region)
source_arn="arn:aws:execute-api:$account_region:$account_id:$rest_api_id/*/*/"

aws lambda add-permission --function-name $lambda_func_arn --source-arn $source_arn --principal apigateway.amazonaws.com --statement-id apigateway-test1-"FUNCTION_NAME" --action lambda:InvokeFunction > /dev/null 2>&1


# Connect the GET method to the Lambda function (define integration)
lambda_invocation_uri="arn:aws:apigateway:$account_region:lambda:path/2015-03-31/functions/$lambda_func_arn/invocations"
aws apigateway put-integration --rest-api-id $rest_api_id --resource-id $root_resource_id --http-method GET --type AWS --integration-http-method POST --uri $lambda_invocation_uri > /dev/null 2>&1


# Specify an INTEGRATION RESPONSE for a method response code of 200 and allow Passthrough content handling
aws apigateway put-integration-response --rest-api-id $rest_api_id --resource-id $root_resource_id --http-method GET --status-code 200 > /dev/null 2>&1

# Specify a METHOD response for HTTP status code 200
aws apigateway put-method-response --rest-api-id $rest_api_id --resource-id $root_resource_id --http-method GET --status-code 200 > /dev/null 2>&1

# Deploy the API
aws apigateway create-deployment --rest-api-id $rest_api_id --stage-name prod > /dev/null 2>&1

# Get the invoke URL and curl it
invoke_url="https://$rest_api_id.execute-api.$account_region.amazonaws.com/prod"
curl $invoke_url

```

## MITRE ATT&CK Mapping

- **Tactics**:
- TA0003 Persistence
- **Techniques**:
- T1098 Account Manipulation
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
---
api_version: 2.0
uuid: 4b783ca9-7cf7-49c4-a74f-26c753a4b1c2
name: create_lambda_function
description: |
This TTP is used to create a new Lambda function in AWS. It uses the AWS CLI to create a new function with the specified name and runtime.
If a function with given name exists, nothing is done and the TTP is closed.
If a function does not exist a new function is created. It is also deleted during cleanup.
A public API gateway is also created to invoke this function. This enables use to invoke the function from the internet.
`--no-cleanup` options should be explicitly specified if we do not want the new function created to be deleted.

args:
- name: lambda_function_name
description: The name of the new Lambda function to be created.
default: ttpforge-lambda-function
- name: region
description: Name of the AWS region to use with TTPForge
default: us-east-1

mitre:
tactics:
- TA0003 Persistence
techniques:
- T1098 Account Manipulation

steps:
- name: aws-connector
description: This step invokes the verifies aws creds are present and aws cli is available.
ttp: //helpers/cloud/aws/validate-aws-env-configured.yaml
args:
region: "{{ .Args.region }}"

- name: create_temp_iam_role
description: Create a temp IAM role that will be used by the lambda function.
inline: |
echo "Creating temp IAM role..."

role_name="{{.Args.lambda_function_name}}-role"
aws iam create-role --role-name $role_name --assume-role-policy-document file://trust-policy.json
echo "Temp IAM role created: $role_name"

cleanup:
inline: |
echo "Deleting temp IAM role..."

role_name="{{.Args.lambda_function_name}}-role"
aws iam delete-role --role-name $role_name

- name: create_s3_bucket
description: Create a S3 bucket with a random string name.
inline: |
echo "Creating S3 bucket..."

bucket_suffix=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 10 | head -n 1)
bucket_name="{{.Args.lambda_function_name}}-${bucket_suffix}"

aws s3 mb s3://$bucket_name
echo "S3 bucket created: $bucket_name"
cleanup:
inline: |
echo "Deleting S3 bucket..."
bucket_name=$(aws s3 ls | grep {{.Args.lambda_function_name}} | awk '{print $3}')
echo $bucket_name

aws s3 rb s3://$bucket_name --force

- name: upload_lambda_code
description: Upload the lambda code to the newly created S3 bucket.
inline: |
echo "Uploading lambda code to S3 bucket..."

bucket_name=$(aws s3 ls | grep {{.Args.lambda_function_name}} | awk '{print $3}')
echo $bucket_name

zip -r lambda-function.zip lambda-function.py
aws s3 cp lambda-function.zip s3://$bucket_name

echo "Lambda code uploaded to S3 bucket: $bucket_name"

cleanup:
inline: |
echo "Deleting created lambda function zip file..."
rm -rf lambda-function.zip

- name: create_lambda_function
description: Check if the specified Lambda function exists. If not create a new one.
inline: |
echo "Checking if function {{.Args.lambda_function_name}} exists..."
role_name="{{.Args.lambda_function_name}}-role"
role_arn=$(aws iam get-role --role-name $role_name --query 'Role.Arn' --output text)
bucket_name=$(aws s3 ls | grep {{.Args.lambda_function_name}} | awk '{print $3}')

function_exists=$(aws lambda list-functions --query 'Functions[?FunctionName==`{{.Args.lambda_function_name}}`]' 2>&1)
if [ -n "$function_exists" ] && [ "$function_exists" = "[]" ]; then
echo - "Function does not exist. Creating a new function..."
aws lambda create-function --function-name {{.Args.lambda_function_name}} --runtime "python3.9" --handler lambda-function.lambda_handler --role $role_arn --code S3Bucket=$bucket_name,S3Key=lambda-function.zip
echo "New Lambda function created: {{.Args.lambda_function_name}}"
sleep 5
else
echo "Function {{.Args.lambda_function_name}} already exists."
fi
cleanup:
inline: |
echo "Deleting Lambda function..."
aws lambda delete-function --function-name {{.Args.lambda_function_name}}
echo "Lambda function deleted: {{.Args.lambda_function_name}}"

- name: create_api_and_invoke
description: Invoke the newly created Lambda function and display the output. This is a public API, that means anyone on the internet can invoke this lambda function
inline: |
echo "Creating REST API Gateway..."

# Create a new REST API
rest_api_id=$(aws apigateway create-rest-api --name {{.Args.lambda_function_name}}-api --query 'id' --output text)

# Get the root resource id
root_resource_id=$(aws apigateway get-resources --rest-api-id $rest_api_id --query 'items[?path==`/`].id' --output text)

# Create a GET method for the new resource
aws apigateway put-method --rest-api-id $rest_api_id --resource-id $root_resource_id --http-method GET --authorization-type "NONE" > /dev/null 2>&1

# Add necessary permissions to the new resource method
lambda_func_arn=$(aws lambda get-function --function-name {{.Args.lambda_function_name}} --query 'Configuration.FunctionArn' --output text)
account_id=$(aws sts get-caller-identity --query Account --output text)
account_region=$(aws configure get region)
statement_id=apigateway-$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 10 | head -n 1)-{{.Args.lambda_function_name}}
source_arn="arn:aws:execute-api:$account_region:$account_id:$rest_api_id/*/GET/"

aws lambda add-permission --function-name $lambda_func_arn --source-arn $source_arn \
--principal apigateway.amazonaws.com --statement-id $statement_id --action lambda:InvokeFunction > /dev/null 2>&1


# Connect the GET method to the Lambda function (define integration)
lambda_invocation_uri="arn:aws:apigateway:$account_region:lambda:path/2015-03-31/functions/$lambda_func_arn/invocations"
aws apigateway put-integration --rest-api-id $rest_api_id --resource-id $root_resource_id \
--http-method GET --type AWS --integration-http-method POST --uri $lambda_invocation_uri > /dev/null 2>&1


# Specify an INTEGRATION RESPONSE for a method response code of 200 and allow Passthrough content handling
aws apigateway put-integration-response --rest-api-id $rest_api_id --resource-id $root_resource_id \
--http-method GET --status-code 200 --response-templates '{"application/json":"$input.path('\'$.body\'')"}' > /dev/null 2>&1

# Specify a METHOD response for HTTP status code 200
aws apigateway put-method-response --rest-api-id $rest_api_id --resource-id $root_resource_id \
--http-method GET --status-code 200 > /dev/null 2>&1

# Deploy the API
aws apigateway create-deployment --rest-api-id $rest_api_id --stage-name prod > /dev/null 2>&1

# Get the invoke URL and curl it
invoke_url="https://$rest_api_id.execute-api.$account_region.amazonaws.com/prod"
echo "Invoke URL: $invoke_url"
sleep 10
curl $invoke_url

cleanup:
inline: |
echo "Deleting REST API Gateway..."
rest_api_id=$(aws apigateway get-rest-apis --query 'items[?name==`{{.Args.lambda_function_name}}-api`].id' --output text)
aws apigateway delete-rest-api --rest-api-id $rest_api_id
echo "REST API Gateway deleted: $rest_api_id"
25 changes: 25 additions & 0 deletions ttps/cloud/aws/lambda/create-lambda-function/lambda-function.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import json
import logging

import boto3

logger = logging.getLogger()
logger.setLevel(logging.INFO)


def lambda_handler(event, context):
sts = boto3.client("sts")
sts_response = sts.get_caller_identity()
print("STS Response: ", sts_response)

logger.info(f"CloudWatch logs group: {context.log_group_name}")

formatted_sts_response = json.dumps(sts_response, indent=4)
return {
"statusCode": 200,
"body": formatted_sts_response,
"headers": {
"Content-Type": "text/plain",
},
"isBase64Encoded": False,
}
13 changes: 13 additions & 0 deletions ttps/cloud/aws/lambda/create-lambda-function/trust-policy.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}
Loading