This is the story nobody wants to acknowledge in a postmortem: there were no 0-days, no sophisticated social engineering, no “APTs”. There was a leaked credential, excessive permissions, and late detection. The result was operational control of the environment in under an hour, with enough persistence to survive password changes.
The scenario is corporate and common: several teams deploy in cloud, there is automation, there are old service accounts “because they work”, and the SOC receives too many alerts to investigate every IAM anomaly. The full chain fits in a short shift.
Minute 0–10: the leaked credential becomes real access
The entry point was not a human user, but an automation credential: an access key exposed in a repository (private, but cloned outside the perimeter). As soon as the key ends up in the attacker’s hands, the first move is not to “attack”, it is to check whether it’s alive: an identity call and a couple of listings to measure surface area.
In AWS, the typical start is sts:GetCallerIdentity and, if it responds, enumerate effective permissions with cheap calls: list buckets, describe instances, read parameters, see container repos. You don’t need an aggressive scanner; with 20–30 well-chosen requests, you draw the map. In an enterprise, this behavior can be easily camouflaged among legitimate pipelines if you don’t have a baseline for origin and frequency.
- Operational signal: unusual geographic origin and ASN.
If your CI access comes from known ranges (NAT, runners, corporate IPs) and suddenly you see signed calls from a residential ASN or a different cloud, that is a clear shot. Many organizations only alert on “console login”; here, there is no console.
- Operational signal: early enumeration pattern.
A compromised access key usually starts with discovery calls (List*, Describe*, Get*) to services that the original pipeline does not touch. If you are not logging and aggregating CloudTrail (or equivalent) with detections on sequences, the attacker gets free minutes.
Minute 10–25: escalation via “utility” permissions, nobody reviewed
Escalation rarely comes from direct “Admin”; it usually comes from wildcard permissions justified for speed: iam:PassRole to run jobs, lambda:UpdateFunctionCode permissions for hotfixes, or the ability to edit container tasks. With that, the attacker does not need to break IAM: they need to find a service that can execute code under a more privileged role.
A realistic path: the leaked credential belongs to a CI user/role with permissions to update a Lambda function and run it, and there is also a Lambda execution role with broad permissions (for example, access to Secrets Manager/Parameter Store and the ability to assume internal roles). The attacker uploads a minimal payload that exfiltrates secrets and, in the same flow, attempts sts:AssumeRole to a more privileged role, or extracts temporary credentials from where the runtime exposes them.
In enterprises, this happens because “only the platform team touches the Lambda” or because the execution role was created with a broad policy to avoid incidents. That trade-off (less friction today) gets paid in escalation tomorrow.
- Typical consequence: theft of integration secrets.
With access to secrets, the attacker not only progresses within the account; they can also jump to SaaS (Git, monitoring, transactional email) or to other accounts/tenants if there are shared keys. In many incidents, the main impact is not the workloads: it is the “integration graph” that opens up.
- Operational signal: code/config changes to functions or tasks outside the window.
A Lambda/ECS/Batch update outside a deployment, from a principal that normally only does builds, is a strong anomaly. If change control lives in Git but permissions allow hot changes, you are giving a side door that is hard to audit.
Minute 25–45: persistence that survives credential rotation
Once the attacker obtains a role with capability over IAM, the objective changes: stop depending on the leaked credential (which can be revoked) and create “legitimate” persistence. In cloud environments, persistence is usually IAM + automation: a new “integration” user, an additional access key on an existing user, or a trust relationship (trust policy) that allows assuming a role from another account controlled by the attacker.
The method that most often slips through reviews is the trust policy. There is no need to create users; it is enough to modify the trust relationship of a role used in production to allow sts:AssumeRole from an external principal. Operationally, the account will keep working and teams will see “nothing went down”, but a stable entry already exists.
Example of a dangerous trust policy (AWS) that introduces persistence:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::111122223333:root"},
"Action": "sts:AssumeRole"
}
]
}
In a corporate account, that 111122223333 can be an external “test” account nobody remembers, or directly an attacker’s account. If you don’t have drift control and alerts on changes to AssumeRolePolicyDocument, this goes unnoticed.
How to do it in practice (minimum validation):
- Verify recent changes to roles (CloudTrail).
Look for events such as UpdateAssumeRolePolicy, PutRolePolicy, AttachRolePolicy, CreateAccessKey and UpdateLoginProfile. It’s not “having logs”: it’s having queries and alerts for these verbs with context (principal executing it, IP, user-agent, time).
- Review trust policies on critical roles.
Audit roles with privileges (for example, those that touch IAM, KMS, organizations, networks). Any cross-account Principal must be justified, documented, and preferably constrained with Condition (such as aws:PrincipalArn, sts:ExternalId if applicable, or tag-based restrictions).
Minute 45–60: operational control and ability to impact the business
With persistence established, the attacker is no longer “inside”: they are in a position to operate. The next realistic step is not always to delete resources; many times it is to prepare monetization or extortion: export data, create snapshots, copy objects to an external bucket, or disable controls to move without noise. If there is access to KMS or keys used by services, the blast radius multiplies.
In organizations with multiple accounts, a common path is to abuse cross-account access roles (for example, central read roles, deployment roles, audit roles) to pivot. You don’t need global admin if you can assume a role in the logs account or in the network account: from there you can degrade visibility or open lateral paths.
- Operational signal: “legal” exfiltration using export APIs.
You won’t see “mass download” if the attacker uses native mechanisms: selective GetObject, server-side copies, scheduled exports, shared snapshots. That’s why alerts on sensitive actions matter, not only on traffic volume.
- Typical consequence: the team rotates credentials… and the attacker comes back.
This is the most expensive failure in response: the leaked access key is revoked, but the IAM changes that already left backdoors are ignored. After 24–48 hours, “mysterious” activity appears and a second compromise is assumed, when in reality it is the same persistent access.
Recommendations for corporate environments
This chain ends in under an hour because each link rests on real operational decisions: static credentials in automation, broad permissions to avoid friction, and lack of specific detection on IAM. The attacker does not need sophistication when the account lets them execute code with a useful role and modify trust relationships.
The key signals were available from minute 1: anomalous origins, out-of-pattern enumeration, code/config changes in runtimes and, above all, IAM modifications (policies, access keys and trust policies). In a mature corporate environment, those signals are treated as high-criticality events with rapid response, because they indicate persistence capability.
Operationally, what makes the difference is validating and protecting the “control plane”: logging and centralizing audit, alerting on sensitive IAM verbs, limiting PassRole and runtime changes to controlled pipelines, and reviewing trust policies of critical roles as if they were firewall rules. If today you can’t answer with certainty “which roles changed their trust policy this week and by whom”, you are one leaked credential away from repeating this story.
Interested in Cloud Security?
Technical analysis, hands-on labs and real-world cloud security insights.