In on-prem environments, attackers escalate from a user shell to local admin to Domain Admin. In the cloud, the equivalent journey is from a low-privileged identity (a developer's leaked access key, a compromised CI token, a misconfigured Lambda role) to "owner of the account". The mechanics are different — there is no kernel exploit involved — but the goal is the same: keep iterating until you can do anything. Cloud IAM is rich, fast-moving, and full of legitimate-looking permissions that combine into account takeover. Understanding those chains, and how to close them off, is now a core skill for anyone running production workloads in AWS, Azure, or GCP.
Why cloud IAM is uniquely dangerous
Cloud IAM has properties that traditional access control did not.
- Identities are everywhere – Every workload, function, container, and pipeline has an identity. AWS roles, Azure managed identities, GCP service accounts. There are often more non-human identities than human ones, and they are rarely reviewed.
- Permissions compose – A policy may look harmless in isolation. The danger is when several "small" permissions combine: e.g.
iam:PassRole+lambda:CreateFunction+lambda:InvokeFunctionlets you run arbitrary code as a more privileged role. - APIs are public – The control plane is reachable from anywhere on the internet with the right credential. A stolen long-lived access key works just as well from a coffee shop as from inside your VPC.
- Auditing is centralised but huge – CloudTrail / Azure Activity Log / GCP Audit Logs see almost everything, but the volume is overwhelming without tuned detections.
- Defaults are generous – Wildcards in inline policies, broad service control policies, and convenience roles like
AdministratorAccessare common because they "just work" for development.
The result is that most cloud breaches do not look like exploits; they look like an attacker patiently using the API the way it was designed to be used, just with the wrong identity.
Common AWS escalation chains
AWS has been studied the longest, and the canonical privilege-escalation paths are well documented (Rhino Security Labs published the original "21 paths" list; Pacu automates many of them). The patterns to know:
iam:PassRole+ compute – If you can pass a more privileged role to a service you can control (EC2, Lambda, ECS, Glue, SageMaker), you can run code as that role.iam:PassRole+lambda:CreateFunction+lambda:InvokeFunctionis the classic chain.- Policy editing –
iam:CreatePolicyVersion,iam:SetDefaultPolicyVersion,iam:AttachUserPolicy,iam:PutUserPolicy,iam:PutRolePolicy. Any of these on a permissive scope lets you grant yourself more. - AssumeRole abuse – Trust policies that trust
*, broad organisations, or overly wide service principals let attackers assume privileged roles.sts:AssumeRoleto a role withAdministratorAccessis the goal. - CloudFormation, CodeBuild, and other "by-proxy" services – If you can create or update a stack/pipeline that runs as a privileged role, you have effectively inherited that role.
- Access key creation –
iam:CreateAccessKeyfor another user, especially a more privileged one, gives you long-lived credentials. - SSM and EC2 access –
ssm:SendCommandor instance connect on an EC2 instance with an attached privileged role yields code execution as that role.
Tools like pmapper, cloudsplaining, and iamspy build a graph of these relationships across your account and highlight the shortest path from any identity to admin.
Azure and GCP have their own flavours
Azure's RBAC plus Entra ID adds a second axis. Common Azure escalation patterns include abusing Microsoft.Authorization/roleAssignments/write (assigning yourself a higher role), abuse of "Owner" on a subscription via a forgotten guest user, and chained attacks through managed identities on VMs or Logic Apps. At the directory level, application registrations with high Graph API permissions (RoleManagement.ReadWrite.Directory, Application.ReadWrite.All) are catastrophic if compromised — they let you mint credentials and grant any role. Service principals with stale client secrets in code are a perennial source of breaches.
GCP centres on service accounts and the iam.serviceAccountTokenCreator and iam.serviceAccountUser roles. If you hold serviceAccountTokenCreator on a privileged service account, you can mint a token for it via generateAccessToken and act as that identity. iam.serviceAccountKeys.create is similar but worse (long-lived JSON keys). cloudfunctions.functions.create and cloudbuild.builds.create enable PassRole-style abuse against compute services. The Editor primitive role is essentially admin and is still common in older projects.
The vendor-specific details vary, but the shape is the same in all three: a privilege that describes itself as narrow combines with another privilege to grant the holder full control.
Hardening: principles that actually work
A few principles, applied consistently, eliminate most realistic escalation paths.
- No wildcards on dangerous actions – Especially
iam:,sts:AssumeRole,kms:,secretsmanager:. Scope by resource, by tag, or by condition (aws:SourceVpc,aws:SourceIp,aws:PrincipalOrgID). - Use SCPs / management groups / org policies as guardrails – Deny risky actions at the organisation level even if an individual account's policies are lax. Block creation of access keys at the org level; require MFA; restrict cross-account
AssumeRoleto known principals. - Short-lived credentials everywhere – Federate human access through SSO (IAM Identity Center, Entra ID, Workload Identity Federation). Eliminate long-lived IAM users and JSON service account keys. Workloads use instance/pod identity, not embedded keys.
- No
iam:PassRoleto broad scopes – Restrict which roles a principal can pass, with conditions, and never allowiam:PassRoleon*. - Separation of duties between application and platform – Application identities should not be able to modify IAM, KMS keys they don't own, CloudTrail, or organisational settings, even by accident.
- Permissions boundaries / Azure custom roles / GCP conditions – Use boundary mechanisms so that even if a developer attaches a broader policy, the effective permissions stay capped.
- Tag-based access control – Apply tag-based conditions so identities can only act on resources they own.
- Continuous IAM review – Use IAM Access Analyzer, CloudSplaining, Prowler, ScoutSuite, or commercial CSPM tools to flag overly permissive policies and external sharing. Treat findings like vulnerabilities, with SLAs.
Detection and response
You will not catch every misuse at the prevention layer, so log and alert on the high-signal events. CloudTrail (especially management events), Azure Activity Log + Entra audit, and GCP Audit Logs should be centralised, retained for at least a year, and fed into a SIEM.
- High-value API calls –
CreateAccessKey,AttachUserPolicy,PutRolePolicy,UpdateAssumeRolePolicy,PassRole,CreatePolicyVersion, role assignment changes in Azure,setIamPolicyin GCP. - Use of root or break-glass accounts – Should be exceptionally rare and always alert immediately.
- Cross-account or external principal access – New trust policies, new external sharing on S3/RDS snapshots/KMS keys.
- Identity creation and key minting – New IAM users, new access keys, new service principal secrets, new service account keys.
- GuardDuty / Defender for Cloud / SCC findings – Native services already detect a meaningful subset of escalation behaviours; route them into the same SIEM as a baseline.
Pair detections with a tested response playbook: rotate or disable the compromised identity, revoke active sessions (aws sts deactivate-mfa-device and iam:DeleteAccessKey, Entra revokeSignInSessions, GCP key disable), invalidate the credential's blast radius (snapshot what it touched), and review CloudTrail for the full action history. Cloud IAM compromise is rarely "loud", but with the right logging and a written response, you can usually contain it before the attacker reaches the data plane.