Skip to main content

Lambda Function Not Publicly Accessible

Overview

This check examines AWS Lambda function resource-based policies for overly permissive access. It flags functions where the policy contains a wildcard (*) or empty Principal that allows anyone on the internet to invoke the function.

Risk

A publicly accessible Lambda function creates serious security vulnerabilities:

  • Unauthorized code execution: Anyone can invoke your function, running code under your function's IAM role
  • Data exposure: Attackers may access backend systems (databases, APIs, internal services) through your function
  • Data manipulation: Malicious invocations could modify data or trigger unintended side effects
  • Cost explosion: Attackers can flood your function with invocations, causing throttling and unexpected AWS charges
  • Compliance violations: Public Lambda functions may violate regulatory requirements (PCI-DSS, HIPAA, SOC 2)

Remediation Steps

Prerequisites

  • AWS account access with permissions to modify Lambda function policies
  • Knowledge of which AWS services or accounts should legitimately invoke your function
Required IAM permissions

You will need the following permissions:

  • lambda:GetPolicy - View the function's resource-based policy
  • lambda:RemovePermission - Remove permissive policy statements
  • lambda:AddPermission - Add properly scoped permissions

AWS Console Method

Step 1: Review the current policy

  1. Sign in to the AWS Management Console
  2. Navigate to Lambda > Functions
  3. Select the flagged function
  4. Click the Configuration tab
  5. Click Permissions in the left sidebar
  6. Scroll down to Resource-based policy statements
  7. Look for statements where Principal is set to * (these are the problematic ones)
  8. Note down the Statement ID (Sid) for each overly permissive statement

Step 2: Remove the public permission

  1. In the Resource-based policy statements section, find the statement with Principal *
  2. Click the statement to expand it
  3. Click Delete (or the trash icon)
  4. Confirm the deletion when prompted

Step 3: Add properly scoped permissions (if needed)

If a legitimate AWS service or account needs to invoke this function, add a new permission with a specific principal:

  1. In the Resource-based policy statements section, click Add permissions
  2. Choose the appropriate option:
    • AWS service - For services like S3, SNS, API Gateway, etc.
    • AWS account - For cross-account access
  3. Configure the permission:
    • Service: Select the AWS service (e.g., sns.amazonaws.com)
    • Statement ID: Enter a descriptive name (e.g., allow-sns-invoke)
    • Principal: The service or account ARN
    • Action: lambda:InvokeFunction
  4. For service principals, add conditions to restrict access:
    • Source ARN: The ARN of the specific resource (e.g., an SNS topic ARN)
    • Source account: Your AWS account ID
  5. Click Save
AWS CLI (optional)

View the current policy:

aws lambda get-policy \
--function-name <your-function-name> \
--region us-east-1

This returns a JSON policy. Look for statements where Principal is "*" or {"AWS": "*"} and note the Sid value.

Remove the public permission:

aws lambda remove-permission \
--function-name <your-function-name> \
--statement-id <statement-id-from-policy> \
--region us-east-1

Replace <statement-id-from-policy> with the Sid value you noted (e.g., public-access or AllowPublicInvoke).

Add a properly scoped permission (example for SNS):

aws lambda add-permission \
--function-name <your-function-name> \
--statement-id allow-sns-invoke \
--action lambda:InvokeFunction \
--principal sns.amazonaws.com \
--source-arn arn:aws:sns:us-east-1:<account-id>:<topic-name> \
--source-account <account-id> \
--region us-east-1

Add a properly scoped permission (example for specific AWS account):

aws lambda add-permission \
--function-name <your-function-name> \
--statement-id allow-account-invoke \
--action lambda:InvokeFunction \
--principal arn:aws:iam::<trusted-account-id>:root \
--region us-east-1
CloudFormation (optional)

Use AWS::Lambda::Permission resources to define explicit, scoped permissions. Do not use * as the principal.

AWSTemplateFormatVersion: '2010-09-09'
Description: Lambda function with properly scoped invocation permissions

Parameters:
FunctionName:
Type: String
Description: Name of the Lambda function
SNSTopicArn:
Type: String
Description: ARN of the SNS topic allowed to invoke the function

Resources:
MyLambdaFunction:
Type: AWS::Lambda::Function
Properties:
FunctionName: !Ref FunctionName
Runtime: python3.12
Handler: index.lambda_handler
Role: !GetAtt LambdaExecutionRole.Arn
Code:
ZipFile: |
def lambda_handler(event, context):
return {'statusCode': 200, 'body': 'Hello'}

LambdaExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

# Properly scoped permission for SNS
SNSInvokePermission:
Type: AWS::Lambda::Permission
Properties:
FunctionName: !Ref MyLambdaFunction
Action: lambda:InvokeFunction
Principal: sns.amazonaws.com
SourceArn: !Ref SNSTopicArn
SourceAccount: !Ref AWS::AccountId

# Example: Properly scoped permission for API Gateway
# APIGatewayInvokePermission:
# Type: AWS::Lambda::Permission
# Properties:
# FunctionName: !Ref MyLambdaFunction
# Action: lambda:InvokeFunction
# Principal: apigateway.amazonaws.com
# SourceArn: !Sub "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:<api-id>/*/*/*"

Outputs:
FunctionArn:
Description: ARN of the Lambda function
Value: !GetAtt MyLambdaFunction.Arn

Deploy the template:

aws cloudformation deploy \
--template-file lambda-permissions.yaml \
--stack-name lambda-secure-permissions \
--parameter-overrides \
FunctionName=my-secure-function \
SNSTopicArn=arn:aws:sns:us-east-1:123456789012:my-topic \
--capabilities CAPABILITY_IAM \
--region us-east-1

Important: Never use Principal: "*" in AWS::Lambda::Permission resources.

Terraform (optional)

Use aws_lambda_permission resources with specific principals and source constraints.

provider "aws" {
region = "us-east-1"
}

# Lambda function
resource "aws_lambda_function" "my_function" {
filename = "function.zip"
function_name = "my-secure-function"
role = aws_iam_role.lambda_role.arn
handler = "index.lambda_handler"
runtime = "python3.12"
source_code_hash = filebase64sha256("function.zip")
}

# IAM role for Lambda
resource "aws_iam_role" "lambda_role" {
name = "my-lambda-execution-role"

assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "lambda.amazonaws.com"
}
}]
})
}

resource "aws_iam_role_policy_attachment" "lambda_basic" {
role = aws_iam_role.lambda_role.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}

# Properly scoped permission for SNS
resource "aws_lambda_permission" "sns_invoke" {
statement_id = "AllowSNSInvoke"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.my_function.function_name
principal = "sns.amazonaws.com"
source_arn = aws_sns_topic.my_topic.arn
source_account = data.aws_caller_identity.current.account_id
}

# Example SNS topic that can invoke the function
resource "aws_sns_topic" "my_topic" {
name = "my-lambda-trigger-topic"
}

resource "aws_sns_topic_subscription" "lambda_subscription" {
topic_arn = aws_sns_topic.my_topic.arn
protocol = "lambda"
endpoint = aws_lambda_function.my_function.arn
}

# Get current account ID for source_account constraint
data "aws_caller_identity" "current" {}

# Example: Properly scoped permission for API Gateway
# resource "aws_lambda_permission" "api_gateway_invoke" {
# statement_id = "AllowAPIGatewayInvoke"
# action = "lambda:InvokeFunction"
# function_name = aws_lambda_function.my_function.function_name
# principal = "apigateway.amazonaws.com"
# source_arn = "${aws_api_gateway_rest_api.my_api.execution_arn}/*/*/*"
# }

# Example: Properly scoped permission for cross-account access
# resource "aws_lambda_permission" "cross_account_invoke" {
# statement_id = "AllowCrossAccountInvoke"
# action = "lambda:InvokeFunction"
# function_name = aws_lambda_function.my_function.function_name
# principal = "arn:aws:iam::123456789012:root" # Specific account
# }

Important: Never use principal = "*" in aws_lambda_permission resources.

Verification

After completing the remediation:

  1. Go to Lambda > Functions > your function in the AWS Console
  2. Click Configuration > Permissions
  3. Review Resource-based policy statements and confirm no statement has Principal *
  4. Verify any remaining permissions have specific principals (service names or account ARNs)
  5. Test your function to ensure legitimate callers can still invoke it
  6. Re-run the Prowler check to confirm the issue is resolved
CLI verification commands

Verify the policy no longer has public access:

aws lambda get-policy \
--function-name <your-function-name> \
--region us-east-1 \
--query 'Policy' \
--output text | python3 -m json.tool

Look through the output and confirm:

  • No statement has "Principal": "*"
  • No statement has "Principal": {"AWS": "*"}
  • All principals are specific service names (e.g., sns.amazonaws.com) or account ARNs

If the function has no resource-based policy (which is secure), you will see an error:

An error occurred (ResourceNotFoundException): The resource you requested does not exist.

This is expected and means the function has no external invocation permissions.

Additional Resources

Notes

  • Assess before removing: Before removing a public permission, investigate why it was added. Removing it may break integrations.
  • Use source conditions: When granting access to AWS services, always use SourceArn and SourceAccount conditions to restrict which specific resources can invoke your function.
  • API Gateway considerations: If your Lambda is fronted by API Gateway, the API Gateway permission should specify the API's execution ARN, not a wildcard.
  • Cross-account access: For cross-account invocations, specify the exact account ID or IAM role ARN rather than using wildcards.
  • Function URLs: If you need public HTTP access to your Lambda, consider using Lambda function URLs with proper authentication (IAM or NONE with your own auth logic) rather than a public resource policy.
  • Audit regularly: Periodically review Lambda permissions using IAM Access Analyzer to detect unintended public access.