Build1 publisher2 min readPublished
Scoping iam:AttachUserPolicy to the caller's own ARN hands out AdministratorAccess
A policy that lets users read and manage their own IAM profile usually carries iam:AttachUserPolicy scoped to ${aws:username}. That scope is enough to attach AdministratorAccess to yourself in a single call.
The Engineer · Build desk

What happened
- Spencer Gietzen at Rhino Security Labs published a catalogue of 24 IAM privilege escalation techniques in 2018, and the first one is a single attach-user-policy call against the caller's own username.
- AdministratorAccess grants Action: * on Resource: *, so the single call finishes the escalation and there is no second prerequisite for the caller to satisfy.
- Its companion Z3 program encodes four policy ARNs, treating ReadOnlyAccess as the intended attachment and AdministratorAccess, IAMFullAccess and PowerUserAccess as dangerous.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- decision Teams that want IAM self-service now have to split the statement: the two read actions can stay on ${aws:username}, and the attach action has to move behind something that approves the policy ARN.
- exposure A leaked access key belonging to any user holding this policy is a full-account compromise rather than a scoped one, because the key's holder can promote it before doing anything else.
- constraint The SCP remedy is only as complete as the account's inventory of custom managed policies granting admin, so the fix depends on work nobody can do from the AWS managed catalogue alone.
The call takes two arguments that do different jobs. `--user-name` names the resource the IAM statement authorises. `--policy-arn` names the permission being handed over, and the self-management statement places no condition on it [9]. Scope `Resource` to `arn:aws:iam::111122223333:user/${aws:username}` and you have authorised exactly one target: the caller [3].
Two of the three actions in that statement are reads, `iam:GetUser` and `iam:ListAttachedUserPolicies`, and the third is the write that carries the finding [4]. The dev.to post describes the intent plainly: users should be able to read their own profile, list what is attached to them, and, in the writing developer's view, attach the policies their work needs [5].
Most of the 24 techniques in the Rhino Security Labs catalogue need a chain, where one action sets up the privilege and a second exercises it [6]. Self-attach needs no chain. `AdministratorAccess` grants `Action: *` on `Resource: *`, so step two is whatever the attacker wants [7].
Detection is either an alarm on `iam:AttachUserPolicy` events wired from CloudTrail, EventBridge and SNS, or a daily diff of IAM policy that catches the new attachment [8]. Both run after the call has already succeeded. A once-a-day diff leaves up to 24 hours between the attach and the finding [10].
Stave's engine precomputes the verdict by walking each user's attached policies and setting a boolean, `escalation.attach_user_policy_self.present` [11]. The control, `CTL.IAM.ESCALATE.ATTACHUSERPOLICY.001`, has two leaf clauses and both are required: identity kind equals user, and that boolean is true [12]. Severity is critical, on the reasoning that there is no graduated state between holding self-attach and being admin [13]. CEL fires on the boolean; Z3 reads the statements to name which managed policy closes the escalation [14].
The companion Z3 program encodes four policy ARNs and marks three of them dangerous: `AdministratorAccess`, `IAMFullAccess` and `PowerUserAccess`, with `ReadOnlyAccess` as the intended, benign case [15][16]. That is a model of the AWS managed catalogue, not a measurement of your account. It transfers only if no custom managed policy in the account grants admin, and the post concedes the gap: where custom admin-granting policies exist, the SCP has to be broader [17].
The rule the post states is narrow enough to hand to a policy linter. A user must not have `iam:AttachUserPolicy` whose `Resource` includes its own ARN [18].
What to watch
- Whether the Z3 model moves past its four hardcoded ARNs and reads the account's own custom managed policies for Action: * grants.
- Whether a role and group variant appears, since the control's first leaf clause requires identity.kind to equal user and so skips AttachRolePolicy and AttachGroupPolicy.