Back to Checks
aws

IAM service role prevents cross-service confused deputy attack

iam_role_cross_service_confused_deputy_prevention
Service: iamSeverity: high
OfficialBuilt-inVerified
Categories:identity-accesstrust-boundaries
Resource Type:AwsIamRole

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 Cloud
Run
prowler aws --checks iam_role_cross_service_confused_deputy_prevention

Recommendation

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 deputy

Other

1In the AWS console, go to IAM > Roles
2Open <example_resource_name> and select the Trust relationships tab
3Click Edit trust policy
4In the statement for Principal Service "<service>.amazonaws.com", add a Condition block:
5- StringEquals: aws:SourceAccount = <ACCOUNT_ID>
6Save changes
7Re-run the check to confirm the role now prevents cross-service confused deputy attacks

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 findings