Cloud Lateral Movement: Post-Compromise Techniques in AWS and Azure
In September 2023, Scattered Spider compromised MGM Resorts without exploiting a single CVE. The group called the help desk, convinced an agent to reset an Okta admin credential, then used legitimate cloud APIs to reach MGM's Microsoft Entra ID (formerly Azure AD) and AWS. MGM's declared losses exceeded $100 million. The weapon was not a zero-day. It was sts:GetCallerIdentity.
Cloud lateral movement is not about exploiting vulnerabilities. It is about calling API endpoints for which you already have permission. An attacker with any IAM credential starts by enumerating permissions, then uses those permissions to reach more credentials, higher privileges, or sensitive data. Organizations that structure their defense exclusively around CVEs miss this dynamic entirely.
This post covers the six stages of the post-compromise kill-chain that repeat across the largest cloud incidents of the last three years. Previous posts in this series cover initial access via SSRF and direct exposure via misconfiguration. This one covers what happens after the attacker has valid credentials.
The First API Call: sts:GetCallerIdentity Reveals Everything
Every attacker with a stolen AWS credential starts with sts:GetCallerIdentity. The call returns the account ID, user ARN, and the currently assumed identity in a single JSON response. By default, it requires no IAM permissions to succeed — an attacker can call it with a credential stripped of every policy — and does not trigger GuardDuty findings. It does appear in CloudTrail under the sts.amazonaws.com event source, but without a specific detection rule it is indistinguishable from a legitimate SDK or CLI call.
That single-line response reveals what was compromised and which account it belongs to, enough for a complete target fingerprint. The Pacu module iam__enum_users_roles_policies_groups starts from GetCallerIdentity before any escalation attempt, because the account ID is required to construct role ARNs for enumeration. The pattern is consistent across groups with different motivations: identify exactly what you have before deciding where to go.
In the Cloudflare Thanksgiving 2023 incident, a nation-state actor used four credentials stolen via the Okta breach to immediately enumerate 36 Jira tickets, 202 wiki pages, and download 76 source code repositories. The group actively searched for terms like "remote access", "secret", and "token", a pattern of deliberate reconnaissance before any lateral movement. In Azure, the equivalent is az account show or a GET call to https://management.azure.com/subscriptions. Also not flagged by Defender for Cloud by default.
sts:AssumeRole: The Trust Policy Is a Map
Trust policies are readable before any privileged action. A compromised credential can enumerate every role it is authorized to assume, mapping the entire reachable attack surface without triggering any security finding.
iam:ListRoles and iam:GetRole expose the trust policies of every role in the account. Both are read operations that rarely trigger anomaly findings in default GuardDuty configurations. Rhino Security Labs documented 21 IAM escalation methods, and most use variants of sts:AssumeRole or iam:PassRole. The Sigma rule aws_sts_assumerole_misuse available on Detection.FYI fires when an AssumeRole call comes from an account different from the account where the assumed role is defined.
The most common compromise path in multi-account environments: low-privilege role A assumes role B (a CI/CD deployment role) that has a trust policy allowing it to assume role C in the production account with AdministratorAccess. Full production compromise via three API calls. If the compromised credential has iam:UpdateAssumeRolePolicy, the attacker adds their own principal to any role in the account without modifying any identity policy, leaving virtually no evidence of a new entity being created.
Instance Profiles: Every EC2 Is a Credential Dispenser
An EC2 with an attached IAM role exposes temporary credentials to any code running on it via the Instance Metadata Service. Code execution on a VM is functionally equivalent to credential theft, even with no SSRF vulnerability present.
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/<role-name>
The response includes AccessKeyId, SecretAccessKey, and Token with the role's full permissions, rotate approximately every hour and are automatically refreshed by the EC2 service (role-assumed credentials via sts:AssumeRole can be configured for sessions up to 12 hours). IMDSv2 requires a session token in the request, closing the unauthenticated SSRF vector, but does not protect against code running directly on the instance. The Capital One 2019 incident exploited IMDSv1 via SSRF, exposing 100 million records; with IMDSv2, the SSRF vector closes, but the code execution path remains identical.
In Azure, the pattern is identical via managed identity: any process on the VM can request a token with the identity's permissions through the IMDS at http://169.254.169.254/metadata/identity/oauth2/token. Hunters Security documented the full attack path: VM compromise, managed identity token via IMDS, call to the Azure Resource Manager API, access to Key Vault or Storage Account, and eventually to resources in other subscriptions when the identity has cross-subscription permissions. DEV-0537 (Lapsus$), documented by Microsoft MSTIC in March 2022, created attacker-controlled VMs inside target Azure subscriptions to collect managed identity tokens at scale.
Cross-Account Pivoting: When the Trust Graph Crosses Org Boundaries
Multi-account AWS organizations create a trust graph that security teams rarely map with full precision. A single compromised service account can traverse three account boundaries with five API calls when trust policies are misconfigured.
The most documented pattern: a credential from the development account assumes a shared deployment role in the tooling account (account B), which has a trust policy allowing it to assume the administrator role in the production account (account C) with AdministratorAccess. A wildcard (*) as the principal in a trust policy grants assumption rights to any principal in the source account, including the lowest-privilege ones. NVISO Labs demonstrated that external enumeration is possible via trust policy modification: error messages returned by the API reveal whether users and roles exist in the target account, without requiring direct account access.
AWS Organizations SCPs restrict which actions can be executed in member accounts, but they do not prevent role assumption when the trust policy on the target role explicitly authorizes the principal. This distinction is the most common misunderstanding security teams have about the defense model in multi-account organizations: SCPs deny actions, not assumption flows that the target's trust policies directly authorize.
PoLP limits the blast radius from a single compromised credential but does not eliminate the trust graph reachable through sts:AssumeRole chains. Even a tightly scoped service account can traverse roles it was never intended to reach when trust policies accumulate across teams over months of normal operations. The attack surface is not the permissions attached to the credential — it is every role the credential can reach transitively.
Enumerating Permissions Without Triggering GuardDuty: iam:SimulatePrincipalPolicy
Attackers can map all effective permissions without triggering any GuardDuty finding. The IAM policy simulator evaluates permissions in the control plane and does not generate CloudTrail events for simulation results, creating a structural blind spot in default detection controls.
iam:SimulatePrincipalPolicy accepts a list of actions and a resource ARN, and returns allowed or denied for each action without making the actual API calls. It is not classified as a sensitive operation for GuardDuty anomaly detection and does not appear in the documented finding types, partly because legitimate CSPM tools and internal audit platforms also use the API. Frichetten's research (aws_stealth_perm_enum) documents additional IAM enumeration techniques that avoid CloudTrail logging entirely, exploiting calls that return informative errors without generating auditable events.
The Pacu module iam__enum_permissions uses SimulatePrincipalPolicy as its first attempt, falling back to brute-force enumeration of read operations only when the permission is unavailable for the compromised principal. CISA advisory AA23-320A on Scattered Spider documents that the group conducted extensive reconnaissance of cloud environments before any privilege escalation, a pattern consistent with systematic permission mapping. The organization learns what the attacker could do after they have already done it.
Each of these technique stages leaves artifacts in the control plane — the question is whether the detection stack is looking for the sequence rather than the individual event.
Detecting the Chain in CloudTrail: Sequence Matters More Than Events
Cloud lateral movement leaves a detectable signature in CloudTrail, but the signal is in the sequence and timing of calls within a time window. No isolated event triggers a high-confidence finding. The sequence pattern is the operational indicator.
The key detection sequence: GetCallerIdentity (T+0s) followed by ListRoles (T+5s), AssumeRole (T+30s), and CreateAccessKey (T+60s). A new persistent credential is live at the end of that cycle. GuardDuty PrivilegeEscalation:IAMUser/AdministrativePermissions fires when an identity adds permissions directly to itself, capturing explicit escalation, but not role assumption chains that modify no policy.
In Azure, Defender for Cloud UEBA triggers a medium-severity finding when a managed identity token request comes from a new VM or a geographic region different from the identity's historical pattern. In the Cloudflare 2023 incident, the attempted lateral movement to the console server in São Paulo was blocked by access controls, and that failed attempt was the signal that generated detection: failed lateral movement is as informative as successful movement. The operational recommendation: alert on AssumeRole calls where the assumed role's account ID differs from the caller's account ID, especially from principals that have never made cross-account assumptions. For sequence correlation at volume, any SIEM with CloudTrail ingestion can implement the cross-account AssumeRole alert as an event correlation rule.
The blast radius of any credential is determined by what it can assume, not just what it can do directly. Before the next IAM audit, run iam:SimulatePrincipalPolicy on the lowest-privilege service account and trace every role reachable via sts:AssumeRole chains. That map is what an attacker sees in the first two minutes after compromise.
Top comments (0)