IAM service role prevents cross-service confused deputy attack
IAM service role trust policies restrict AWS service principals to expected sources using global condition keys like aws:SourceArn or aws:SourceAccount, avoiding overly broad sts:AssumeRole trust relationships.
Risk
Unrestricted service-principal trust lets outsiders trigger a cross-service confused deputy, causing unintended sts:AssumeRole.
This can enable data exfiltration, unauthorized changes, and lateral movement, impacting confidentiality and integrity.
Run this check with Prowler
Run in Prowler Cloudprowler aws --checks iam_role_cross_service_confused_deputy_preventionRecommendation
Constrain service-role trust to expected callers using aws:SourceArn/aws:SourceAccount to bind service principals to specific resources or accounts. If unsupported, apply equivalent limits in resource-based policies or org-level controls. Apply least privilege and review trust relationships regularly.
Remediation
CLI
aws iam update-assume-role-policy --role-name <example_resource_name> --policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"<service>.amazonaws.com"},"Action":"sts:AssumeRole","Condition":{"StringEquals":{"aws:SourceAccount":"<ACCOUNT_ID>"}}}]}'Terraform
# IAM role trust policy with confused deputy protection
resource "aws_iam_role" "<example_resource_name>" {
name = "<example_resource_name>"
# CRITICAL: Condition restricts service to this account to prevent cross-service confused deputy
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Principal = { Service = "<service>.amazonaws.com" }
Action = "sts:AssumeRole"
Condition = {
StringEquals = { "aws:SourceAccount" = "<ACCOUNT_ID>" }
}
}
]
})
}NativeIaC
# CloudFormation: IAM role trust policy with confused deputy protection
Resources:
<example_resource_name>:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: <service>.amazonaws.com
Action: sts:AssumeRole
Condition:
StringEquals:
aws:SourceAccount: <ACCOUNT_ID> # CRITICAL: restricts the service to calls from this account to prevent cross-service confused deputyOther
Check Code
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.iam.iam_client import iam_client
from prowler.providers.aws.services.iam.lib.policy import is_policy_public
class iam_role_cross_service_confused_deputy_prevention(Check):
def execute(self) -> Check_Report_AWS:
findings = []
if iam_client.roles:
for role in iam_client.roles:
# This check should only be performed against service roles (avoid Service Linked Roles since the trust relationship cannot be changed)
if role.is_service_role and "aws-service-role" not in role.arn:
report = Check_Report_AWS(metadata=self.metadata(), resource=role)
report.region = iam_client.region
report.status = "FAIL"
report.status_extended = f"IAM Service Role {role.name} does not prevent against a cross-service confused deputy attack."
if not is_policy_public(
role.assume_role_policy,
iam_client.audited_account,
check_cross_service_confused_deputy=True,
not_allowed_actions=["sts:AssumeRole", "sts:*"],
):
report.status = "PASS"
report.status_extended = f"IAM Service Role {role.name} prevents against a cross-service confused deputy attack."
findings.append(report)
return findingsReferences
- https://docs.aws.amazon.com/IAM/latest/UserGuide/confused-deputy.html
- https://aws.amazon.com/blogs/security/how-to-set-up-least-privilege-access-to-your-encrypted-amazon-sqs-queue/
- https://docs.aws.amazon.com/IAM/latest/UserGuide/confused-deputy.html#cross-service-confused-deputy-prevention
- https://docs.aws.amazon.com/textract/latest/dg/cross-service-confused-deputy-prevention.html