Skip to content

AWS storage stack automation Lambda role has overly broad IAM/control-plane permissions #189

Description

@changli376

Summary

The CloudFormation template deployment/aws-python-storage-stack-automation/storage_stack_lifecycle.yaml creates Lambda execution roles with overly broad IAM/control-plane permissions.

The storagelambdarole role is allowed to perform high-impact actions such as:

  • iam:PassRole
  • cloudformation:CreateStack
  • lambda:CreateFunction
  • lambda:AddPermission
  • s3:CreateBucket
  • sqs:CreateQueue
  • sns:CreateTopic

These permissions are granted with broad resource scope, effectively allowing the automation Lambda to create or modify additional AWS control-plane resources beyond the minimum required for S3 bucket onboarding.

Affected Component

File:

deployment/aws-python-storage-stack-automation/storage_stack_lifecycle.yaml

Affected resources:

storagelambdarole
removelambdarole

Tested deployed role names:

serverless-storage-stack-lifestyle-us-east-1-storage
serverless-storage-stack-lifestyle-us-east-1-removestorage

Security Impact

If an attacker can trigger the automation Lambda, influence its execution path, or obtain the Lambda execution role credentials, the role can be abused as a control-plane pivot.

Potential impact includes:

  • Creating new Lambda functions
  • Passing IAM roles to newly created functions or services
  • Creating CloudFormation stacks
  • Creating S3 buckets, SQS queues, and SNS topics
  • Modifying the serverless scanning infrastructure
  • Expanding privileges through iam:PassRole + lambda:CreateFunction or cloudformation:CreateStack

This creates a permission chaining risk and violates least privilege.

Severity

High

Suggested CVSS v3.1:

CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
Score: 8.8

Evidence

The issue was verified with AWS IAM policy simulation in AWS CloudShell after deploying the template in a test account.

storagelambdarole simulation

Command:

ROLE_ARN=$(aws iam get-role \
  --role-name serverless-storage-stack-lifestyle-us-east-1-storage \
  --query 'Role.Arn' \
  --output text)

aws iam simulate-principal-policy \
  --policy-source-arn "$ROLE_ARN" \
  --action-names \
    iam:PassRole \
    cloudformation:CreateStack \
    lambda:CreateFunction \
    lambda:AddPermission \
    s3:CreateBucket \
    sqs:CreateQueue \
    sns:CreateTopic \
  --resource-arns "*" \
  --query 'EvaluationResults[*].[EvalActionName,EvalDecision]' \
  --output table

Result:

+-----------------------------+---------+
| iam:PassRole                | allowed |
| cloudformation:CreateStack  | allowed |
| lambda:CreateFunction       | allowed |
| lambda:AddPermission        | allowed |
| s3:CreateBucket             | allowed |
| sqs:CreateQueue             | allowed |
| sns:CreateTopic             | allowed |
+-----------------------------+---------+

This confirms that the storage automation Lambda role can perform multiple high-impact control-plane actions.

removelambdarole simulation

Command:

REMOVE_ROLE_ARN=$(aws iam get-role \
  --role-name serverless-storage-stack-lifestyle-us-east-1-removestorage \
  --query 'Role.Arn' \
  --output text)

aws iam simulate-principal-policy \
  --policy-source-arn "$REMOVE_ROLE_ARN" \
  --action-names \
    iam:PassRole \
    cloudformation:CreateStack \
    lambda:CreateFunction \
    lambda:AddPermission \
    s3:CreateBucket \
    sqs:CreateQueue \
    sns:CreateTopic \
  --resource-arns "*" \
  --query 'EvaluationResults[*].[EvalActionName,EvalDecision]' \
  --output table

Result:

+-----------------------------+--------------+
| iam:PassRole                | allowed      |
| cloudformation:CreateStack  | implicitDeny |
| lambda:CreateFunction       | implicitDeny |
| lambda:AddPermission        | implicitDeny |
| s3:CreateBucket             | allowed      |
| sqs:CreateQueue             | implicitDeny |
| sns:CreateTopic             | implicitDeny |
+-----------------------------+--------------+

Although the remove role has fewer allowed actions than the storage role, it still allows sensitive actions such as iam:PassRole and s3:CreateBucket.

Expected Behavior

Lambda execution roles should follow least privilege and only be allowed to manage the specific resources required by the storage stack automation workflow.

High-impact actions such as iam:PassRole, lambda:CreateFunction, and cloudformation:CreateStack should be restricted to explicit ARNs, expected naming patterns, and/or protected by permission boundaries and conditions.

Actual Behavior

The generated Lambda role allows broad control-plane actions against Resource: "*", including IAM, Lambda, CloudFormation, S3, SQS, and SNS actions.

Recommended Fix

  1. Restrict iam:PassRole to only the exact role ARNs required by the storage stack.
  2. Add iam:PassedToService conditions where applicable, for example limiting pass-role usage to CloudFormation or Lambda only when required.
  3. Restrict cloudformation:CreateStack to expected stack name patterns and use a constrained CloudFormation execution role.
  4. Restrict lambda:CreateFunction, lambda:AddPermission, S3, SQS, and SNS permissions to resources created by this automation stack only.
  5. Add a permissions boundary to roles created by this template.
  6. Avoid Resource: "*" for control-plane permissions unless absolutely required.
  7. Consider separating deployment-time permissions from runtime Lambda execution permissions.

Minimal Example of Safer Direction

Instead of allowing:

Action:
  - iam:PassRole
Resource: "*"

use a restricted role ARN and condition, for example:

Action:
  - iam:PassRole
Resource:
  - arn:aws:iam::<account-id>:role/<expected-role-prefix>-*
Condition:
  StringEquals:
    iam:PassedToService:
      - lambda.amazonaws.com
      - cloudformation.amazonaws.com

The exact ARNs and conditions should be adjusted to match the intended deployment model.

Reproduction Notes

The verification used IAM simulation only. No real exploit resources, Lambda functions, stacks, queues, topics, or buckets were created during validation.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions