AWS IAM in Depth
One over-broad policy statement is how a read-only key becomes admin.
What you will learn
- -Read an IAM policy and name the exact actions it grants
- -Assume a role with the CLI and understand what sts:AssumeRole really does
- -Spot the classic privilege escalation paths hiding in a set of permissions
Almost every AWS compromise ends the same way: someone had permissions they should not have, and an attacker found the one action that turned a small foothold into full . is where all of that is decided. It is a engine, and a policy is just that says which identities may call which actions on which resources. Read the JSON well and you can see the whole attack path before you run anything.
A policy statement has four parts that matter. Effect is Allow or Deny. Action is the API call, like s3:GetObject or iam:CreateAccessKey. Resource is the ARN it applies to. Condition is optional extra rules. A * in Action or Resource is the thing you hunt for, because it means "everything."
{
"Effect": "Allow",
"Action": "iam:*",
"Resource": "*"
}
That block on a user you control is game over. It says this can perform any IAM action on any resource, which includes creating a new admin user or attaching AdministratorAccess to itself.
Roles and assume-role
A role is an identity with no long-term or key. Nobody in as a role. Instead, an allowed principal calls sts:AssumeRole and STS hands back that expire, usually in an hour. Roles are how EC2 instances, functions, and cross-account access all work.
$aws sts assume-role --role-arn arn:aws:iam::123456789012:role/deploy --role-session-name test$export AWS_ACCESS_KEY_ID=... AWS_SECRET_ACCESS_KEY=... AWS_SESSION_TOKEN=...$aws sts get-caller-identityThe gate on assume-role is the role's trust policy, a separate document that names who is allowed to assume it. A trust policy with "Principal": {"AWS": "*"} and no condition lets anyone in any account assume that role. That misconfiguration is rare in production and pure gold when you find it.
The privilege escalation paths
Rhino Security published the canonical list of IAM privesc methods, and they still work because they are features, not bugs. You do not need admin to become admin. You need one of a couple dozen specific permissions on the wrong resource.
The cleanest is iam:CreateAccessKey on another user. If you can create an for the admin user, you just log in as admin.
$aws iam create-access-key --user-name adminiam:AttachUserPolicy is just as good: attach AdministratorAccess to yourself and you are done. iam:PutUserPolicy lets you write an inline policy granting whatever you want. iam:PassRole combined with a service that runs code, like lambda:CreateFunction or ec2:RunInstances, lets you launch a resource carrying a high-privilege role and read its . iam:CreatePolicyVersion lets you quietly swap a policy's active version for one you write.
The fastest way to check for all of these at once is Rhino's enumerate-iam tool, which brute-forces which API calls your key can make.
$python3 enumerate-iam.py --access-key AKIA... --secret-key ...IAM privesc is real on real infrastructure. Run these only inside an account you own or a CloudGoat scenario built to be attacked. Creating an access key or attaching a policy in a account without written is unauthorized and traceable in CloudTrail to you.
Defenders should read this list as a deny list. No human user needs iam:*. Scope IAM permissions to specific resources, keep PassRole locked to named roles, and alarm on iam:CreateAccessKey and iam:AttachUserPolicy in CloudTrail, because a legitimate admin does those rarely and an attacker does them immediately.
Your key can call lambda:CreateFunction and iam:PassRole, and there is a Lambda execution role with AdministratorAccess. Why is that a full account takeover?