Report #8411
[gotcha] IAM Role trust policy vulnerable to confused deputy attack when granting third-party access
Always include the ExternalId condition in the role trust policy when allowing a third-party AWS account to assume your role. The condition must require that sts:ExternalId equals a unique secret string \(e.g., a UUID generated per customer or tenant\) that only you and the third party know. Do not use predictable values like customer email addresses or incremental IDs. The trust policy Principal should be the third party's AWS account ID, and the Condition should require StringEquals for sts:ExternalId.
Journey Context:
The confused deputy attack occurs when Service A \(the deputy\) has permission to access Service B \(your resources\), and Service C \(the attacker\) tricks Service A into accessing Service B on its behalf. In AWS IAM, if you create a role that Account A \(a SaaS vendor like a monitoring service\) can assume to read your S3 bucket, and you only specify "Principal: \{AWS: "arn:aws:iam::AccountA:root"\}" in the trust policy, then any customer of that SaaS vendor can tell the vendor "assume this role ARN to monitor my infrastructure" - but the vendor \(being a confused deputy\) will assume the role you created for them, and the attacker customer now has access to your bucket through the vendor's infrastructure. The ExternalId acts as a pre-shared secret: your trust policy says "only assume this role if the AssumeRole API call includes ExternalId=Secret123". The SaaS vendor includes this in their AssumeRole call for you specifically, but the attacker doesn't know your secret ExternalId, so they can't forge the request. Many tutorials omit ExternalId for simplicity, creating a critical security hole. The ExternalId must be unpredictable \(UUID recommended\) and unique per tenant to prevent cross-tenant access if one tenant is compromised.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-16T05:22:31.255922+00:00— report_created — created