Missing sts:ExternalId Enables Confused Deputy Attacks
A shared vendor AWS account with dozens of customer cross-account roles trusts only the account ARN, not a distinct ExternalId. Any tenant holding a valid RoleArn can be redirected by the vendor's own automation into assuming a different customer's role, and CloudTrail logs it as legitimate.
Operational summary
At a glance
- Symptom
- The vendor's backend passes an incorrect or attacker-influenced RoleArn during AssumeRole, and the trust policy has no per-tenant secret to reject it.
- Likely cause
- A cross-account trust policy authorises sts:AssumeRole from a vendor account with no sts:ExternalId condition distinguishing customer tenants.
- Impact
- Every customer sharing the same vendor integration becomes reachable from a single compromised or misconfigured vendor session.
- Verification signal
- Re-run the detection test and a controlled negative-path test.
- Safe correction
- Require a unique high-entropy ExternalId per customer in the trust policy condition and validate it against CloudTrail AssumeRole events.
- Rollback or recovery
- Restore the exported configuration if the new control blocks required production traffic, then narrow the policy before redeployment.
The Trap
Cross-account IAM trust policies that authorise sts:AssumeRole from a fixed vendor or partner AWS account without a Condition block requiring sts:ExternalId. This is the textbook confused deputy setup: the trust policy correctly restricts the Principal to a known account, but places no constraint on which specific customer relationship that account is allowed to exercise.
The Default State
SaaS vendors provisioning monitoring, backup or CI/CD integrations typically generate a CloudFormation or Terraform template with a trust policy such as {"Effect":"Allow","Principal":{"AWS":"arn:aws:iam::123456789012:root"},"Action":"sts:AssumeRole"}. The vendor’s own internal service then stores every customer’s RoleArn in a shared database and calls AssumeRole on demand. Because ExternalId was treated as optional boilerplate rather than a mandatory tenant discriminator, it is either omitted entirely or set to a static, non-unique placeholder value shared across all customers.
The Blast Radius
When the vendor’s backend is itself compromised, misconfigured, or exposed to parameter injection, an attacker who can influence which RoleArn or SessionName is passed to AssumeRole can pivot from one customer’s granted permissions into another tenant’s AWS account. Because the trust policy only checks the calling principal, not a per-customer secret, the vendor’s compromised credentials become a universal skeleton key across the entire customer base. CloudTrail records these as legitimate AssumeRole calls from the authorised vendor account, so SIEM correlation rules built on principal identity alone will not flag the lateral movement. The blast radius is every customer sharing that vendor integration, not just the one initially breached.
The Lead Mechanic Fix
Mandate a unique, high-entropy ExternalId per customer relationship and enforce it in the trust policy condition block: "Condition":{"StringEquals":{"sts:ExternalId":"cust-8f21-unique-uuid"}}. The vendor must store this value alongside the RoleArn and pass it explicitly on every AssumeRole call; never derive it from predictable customer metadata such as account ID or company name. On the customer side, validate incoming vendor role assumptions with aws cloudtrail lookup-events --lookup-attributes AttributeKey=EventName,AttributeValue=AssumeRole and alert on any session where the ExternalId claim in the event does not match the provisioned value. Rotate the ExternalId on offboarding, since a stale value with an active trust relationship is a re-entry path.
Apply the safer control
Before you change production
Confirm the affected scope, export the current configuration, and test the replacement control in a non-production environment first.
Fix commands and configuration
ExternalId"Condition":{"StringEquals":{"sts:ExternalId":"cust-8f21-unique-uuid"}}AssumeRoleVerify, roll back or escalate
Verify
Re-run the detection test and a controlled negative-path test. Confirm the unsafe behaviour is blocked while approved traffic still succeeds.
Rollback
Restore the exported configuration if the new control blocks required production traffic, then narrow the policy before redeployment.
Escalate
Escalate when the blast radius is uncertain, the control cannot be tested safely, or remediation requires an outage or security exception.