IAM Service Role Cross-Service Confused Deputy Prevention
Overview
This check verifies that IAM service roles have trust policies that prevent the "confused deputy" attack. A confused deputy attack happens when an AWS service that you trust (like Lambda or S3) is tricked into acting on behalf of an attacker.
In plain terms: your IAM role says "I trust Lambda to assume me" -- but without proper conditions, any Lambda function in any AWS account could potentially assume your role.
Risk
Severity: High
Without proper protections, attackers can exploit this vulnerability to:
- Steal data by assuming your role and accessing your resources
- Modify resources in your account without authorization
- Move laterally through your cloud environment
- Escalate privileges by chaining role assumptions
This affects both the confidentiality and integrity of your AWS environment.
Remediation Steps
Prerequisites
You need:
- Access to the AWS Console with IAM permissions, OR
- AWS CLI configured with appropriate credentials
AWS CLI setup (if not already configured)
# Install AWS CLI (if needed)
# macOS: brew install awscli
# Linux: pip install awscli
# Configure credentials
aws configure
# Enter your Access Key ID, Secret Access Key, and region (us-east-1)
AWS Console Method
-
Open the IAM Console
- Go to IAM Roles
-
Find the affected role
- Search for the role name flagged by Prowler
- Click on the role name to open its details
-
Edit the Trust Policy
- Select the Trust relationships tab
- Click Edit trust policy
-
Add the Condition block
- Find the statement that allows the AWS service (e.g.,
lambda.amazonaws.com) - Add a
Conditionblock that restricts access to your account
Before (insecure):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}After (secure):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"aws:SourceAccount": "123456789012"
}
}
}
]
}Replace
123456789012with your actual AWS account ID. - Find the statement that allows the AWS service (e.g.,
-
Save the policy
- Click Update policy
AWS CLI (optional)
Step 1: Get the current trust policy
aws iam get-role \
--role-name MyServiceRole \
--query 'Role.AssumeRolePolicyDocument' \
--region us-east-1 \
> trust-policy.json
Step 2: Edit the policy to add conditions
Open trust-policy.json and add the Condition block as shown above.
Step 3: Update the role's trust policy
aws iam update-assume-role-policy \
--role-name MyServiceRole \
--policy-document file://trust-policy.json \
--region us-east-1
Using SourceArn for even tighter control
For maximum security, use aws:SourceArn to restrict to a specific resource:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"aws:SourceAccount": "123456789012"
},
"ArnEquals": {
"aws:SourceArn": "arn:aws:lambda:us-east-1:123456789012:function:MyFunction"
}
}
}
]
}
CloudFormation (optional)
AWSTemplateFormatVersion: '2010-09-09'
Description: IAM Role with Confused Deputy Prevention
Parameters:
SourceAccountId:
Type: String
Description: The AWS account ID that is allowed to assume this role
AllowedPattern: '[0-9]{12}'
Resources:
ServiceRole:
Type: AWS::IAM::Role
Properties:
RoleName: MySecureServiceRole
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
Condition:
StringEquals:
aws:SourceAccount: !Ref SourceAccountId
Outputs:
RoleArn:
Description: ARN of the created role
Value: !GetAtt ServiceRole.Arn
Deploy with:
aws cloudformation deploy \
--template-file template.yaml \
--stack-name secure-service-role \
--parameter-overrides SourceAccountId=123456789012 \
--capabilities CAPABILITY_NAMED_IAM \
--region us-east-1
Terraform (optional)
variable "source_account_id" {
description = "The AWS account ID allowed to assume this role"
type = string
validation {
condition = can(regex("^[0-9]{12}$", var.source_account_id))
error_message = "Account ID must be a 12-digit number."
}
}
variable "source_arn" {
description = "The ARN of the resource allowed to assume this role (optional)"
type = string
default = null
}
data "aws_iam_policy_document" "trust_policy" {
statement {
effect = "Allow"
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["lambda.amazonaws.com"]
}
condition {
test = "StringEquals"
variable = "aws:SourceAccount"
values = [var.source_account_id]
}
dynamic "condition" {
for_each = var.source_arn != null ? [var.source_arn] : []
content {
test = "ArnEquals"
variable = "aws:SourceArn"
values = [condition.value]
}
}
}
}
resource "aws_iam_role" "service_role" {
name = "MySecureServiceRole"
assume_role_policy = data.aws_iam_policy_document.trust_policy.json
}
output "role_arn" {
description = "ARN of the created role"
value = aws_iam_role.service_role.arn
}
Apply with:
terraform init
terraform apply -var="source_account_id=123456789012"
Verification
After making changes, verify the fix:
-
In the AWS Console:
- Go to IAM > Roles > Your Role > Trust relationships
- Confirm the
Conditionblock is present withaws:SourceAccountoraws:SourceArn
-
Re-run Prowler to confirm the check now passes:
Prowler verification command
prowler aws -c iam_role_cross_service_confused_deputy_prevention \
--filter-role-name MyServiceRole \
--region us-east-1
Additional Resources
Notes
-
Which condition key to use?
aws:SourceAccount-- Restricts by account ID. Use when multiple resources in your account may use the role.aws:SourceArn-- Restricts to a specific resource ARN. Use for maximum security when you know exactly which resource needs the role.- You can use both together for defense in depth.
-
Not all services support these conditions. Check AWS documentation for your specific service. Most commonly used services (Lambda, S3, SNS, SQS, etc.) do support them.
-
Service-linked roles are managed by AWS and cannot be modified. These roles already have appropriate restrictions built in.
-
Test before applying to production. Changing trust policies can break applications if conditions are too restrictive. Test in a non-production environment first.