An attacker who grants an extra role or policy to an identity they control turns a single stolen credential into standing administrative access that a password reset does not remove. MITRE ATT&CK tracks this as T1098.003 Additional Cloud Roles, a sub-technique of T1098 Account Manipulation in the Persistence (TA0003) and Privilege Escalation (TA0004) tactics, on IaaS, identity provider, Office Suite and SaaS platforms. The parent guide lists all seven account manipulation sub-techniques at overview depth; this page goes further for role grants only: the exact audit events per platform for AWS IAM, Microsoft Entra ID and Azure RBAC, and Google Cloud IAM, the quieter variants such as policy versions, eligible assignments and root-scope elevation, how to baseline who may grant roles, and how to find and remove every added grant during an incident. It is written for detection engineers, cloud identity administrators and incident responders, and it describes no attack procedures. See the MITRE ATT&CK T1098 Account Manipulation Guide.
Key takeaways
- The grant is the persistence. Once a role or policy sits on an identity the attacker controls, every new session inherits it, so containment means removing the grant, not just the session.
- Look beyond "add member to role". ATT&CK's own AWS example uses CreatePolicyVersion, which changes what an already attached policy allows without any new attachment; Entra eligible assignments and Azure root-scope elevation are similarly quiet.
- Every platform logs it, in different places. CloudTrail for IAM API calls, the Entra audit log for directory and application roles, the Azure activity log for RBAC, and Google Cloud Admin Activity logs (always on) for SetIamPolicy with binding deltas.
- Few identities should ever grant roles. A short, named list of role-granting identities, enforced by permissions and just-in-time approval, makes every other grant a high-confidence alert.
- Response is a reconciliation. Compare current role state with a known good baseline across every account, subscription and project the attacker could reach, then remove the difference and check that removal stuck.
What T1098.003 covers
ATT&CK v20 (sub-technique version 2.5) describes T1098.003 as adding roles or permissions to an adversary-controlled cloud account to keep persistent access to a tenant, for example by updating IAM policies or adding a new global administrator in Microsoft 365. It notes that the change often follows T1136 Create Account or the compromise of an existing account (T1078.004 Cloud Accounts), and that roles may be granted to accounts outside the victim tenant, so the external identity can act inside the tenant without any new local account. That last variant is easy to miss: a reviewer scanning for new users finds nothing.
The sub-technique is distinct from its siblings. Adding a secret or key to an existing identity is T1098.001 (see the MITRE ATT&CK T1098.001 Account Manipulation guide); Kubernetes RoleBindings are T1098.006; changes to federation trust are T1484.002 Trust Modification. In real intrusions these appear together: a credential is added to a service principal, then that principal is given a role.
ATT&CK's procedure examples show the range: during the SolarWinds compromise (C0024), APT29 granted company administrator privileges to a newly created service principal; LAPSUS$ added the global admin role to accounts it created; Scattered Spider assigned user access admin roles to gain Tenant Root Group management in Azure; and Storm-0501 used Microsoft.Authorization/elevateAccess/action and Microsoft.Authorization/roleAssignments/write to gain User Access Administrator and Owner over Azure subscriptions.
Detection: the audit events per platform
MITRE's detection strategy DET0277 Role Addition to Cloud Accounts has three analytics: IaaS (CloudTrail AttachUserPolicy, CreatePolicyVersion, PutRolePolicy), identity provider (Entra "Add member to role" and "Add app role assignment") and Office Suite (Microsoft 365 "Add member to role"). The table extends that list to the events a complete detection needs.
| Platform | Change type | Audit event or operation | Log source and notes |
|---|---|---|---|
| AWS IAM | Managed policy attached | AttachUserPolicy, AttachRolePolicy, AttachGroupPolicy | CloudTrail management events, eventSource iam.amazonaws.com; check the policy ARN for AdministratorAccess or broad custom policies |
| AWS IAM | Inline policy written | PutUserPolicy, PutRolePolicy, PutGroupPolicy | CloudTrail; the policy document is in requestParameters, so parse it for "*" actions |
| AWS IAM | Existing policy rewritten | CreatePolicyVersion, SetDefaultPolicyVersion | CloudTrail; no new attachment appears, only a new default version of a policy already attached |
| AWS IAM | Group membership or trust | AddUserToGroup, UpdateAssumeRolePolicy | CloudTrail; a trust policy edit that adds an external account principal is the cross-tenant variant |
| Microsoft Entra ID | Directory role | Add member to role, Add eligible member to role, Add scoped member to role | Entra audit log, category RoleManagement |
| Microsoft Entra ID | Application permissions | Add app role assignment to service principal, Add delegated permission grant | Entra audit log, category ApplicationManagement |
| Microsoft Entra ID | Control of a privileged app | Add owner to service principal, Add owner to application | Entra audit log, ApplicationManagement; an owner can then add credentials (T1098.001) |
| Azure RBAC | Role assignment | Microsoft.Authorization/roleAssignments/write | Azure activity log; watch Owner, User Access Administrator and Contributor at management group or subscription scope |
| Azure RBAC | Root-scope elevation | Microsoft.Authorization/elevateAccess/action | Entra audit log, Service "Azure RBAC (Elevated Access)", and the Azure activity log |
| Google Cloud IAM | Allow policy binding | SetIamPolicy with serviceData.policyDelta.bindingDeltas (action ADD, role, member) | Admin Activity audit log, always on; logged against the resource whose policy changed (organisation, folder, project or individual resource) |
| Google Cloud IAM | Custom role widened | google.iam.admin.v1.CreateRole, google.iam.admin.v1.UpdateRole | Admin Activity audit log; an UpdateRole that adds permissions changes every existing binding to that role |
Practical notes that decide whether these events are usable:
- Keep the trail: CloudTrail is on when an account is created, but the Event history view keeps only recent events; create an organisation trail to central storage for anything longer. See the Cloud Logging and Audit Trail Guide.
- Collect at the right scope: Google Cloud writes the SetIamPolicy entry where the policy lives, so a binding added at folder or organisation level is not in any project's logs. Route organisation and folder audit logs to the same sink as projects.
- Join the two Microsoft planes: directory roles and app role assignments live in the Entra audit log, while Azure resource roles live in the activity log. A single investigation needs both.
- Use the provider's own detections as a second layer: Microsoft lists Defender for Resource Manager alerts "Suspicious Azure role assignment detected (Preview)" and "Suspicious elevate access operation" for this pattern, and Microsoft Sentinel offers an "Azure RBAC (Elevate Access)" analytics template.
The quiet variants defenders miss
- Policy versions in AWS: IAM stores up to five versions of a customer managed policy, and the default version is the one in effect for every user, group and role it is attached to. A new default version widens access silently. AWS states that to stop someone changing the default version you must deny both
iam:CreatePolicyVersionandiam:SetDefaultPolicyVersion. During response, list all versions: a broad non-default version left behind can be made default again later. - Eligible, not active, Entra roles: "Add eligible member to role" gives no access until activation, so role membership reports that show only active members miss it. The attacker activates when needed.
- Application roles instead of directory roles: an app role assignment such as a Microsoft Graph application permission gives tenant-wide data access with no human in any admin role. In the Microsoft Midnight Blizzard Breach, an OAuth application was granted the Exchange Online full_access_as_app role.
- Root-scope elevation in Azure: a Global Administrator who turns on "Access management for Azure resources" receives User Access Administrator at root scope (
/) over every subscription and management group. Microsoft documents that this assignment cannot be removed from the Access control (IAM) page and that deactivating a PIM role does not switch the toggle off. - Custom role edits in Google Cloud: UpdateRole changes the permissions behind every existing binding to that role, so no new binding appears.
- Grants to external principals: a trust policy or role binding naming an identity from another tenant, account or domain. Compare principals with your list of owned accounts and tenants.
Baselining who may grant roles
DET0277 leaves tuning to you through mutable elements: the role scope, the acting identity, the time between initial access and the change, the expected user-to-role mapping, an admin role threshold, and whether a role came from outside the trusted tenant list. Turn those into a short baseline per platform.
- List the granters. Name every identity allowed to change role state: the IaC pipeline roles, the PIM approvers, the identity governance connector and a small number of break-glass accounts. See the Break-Glass Account Guide.
- Record the normal path. For each granter note source (pipeline runner network, privileged access workstation), method (Terraform, PIM portal, governance connector) and the roles it normally assigns.
- Define the crown jewels. Roles that alert on any grant: AWS AdministratorAccess and any policy with
"Action": "*"; Entra Global Administrator, Privileged Role Administrator, Application Administrator and high-impact Graph application permissions; Azure Owner and User Access Administrator; Google Cloud roles/owner, roles/editor and roles/resourcemanager.organizationAdmin. - Snapshot the state. Export role assignments, attached and inline policies, policy versions and IAM bindings daily, so response has a known good state to compare against.
With the baseline in place, three rules give high precision: any crown-jewel grant by anyone; any grant by an identity not on the granter list; and any grant within a short window after a risky or first-seen sign-in by the actor. ATT&CK's PolicyChangeTimeWindow element covers the last one.
Prevention: shrinking who can grant
| ATT&CK mitigation | AWS | Microsoft Entra ID and Azure | Google Cloud |
|---|---|---|---|
| M1018 User Account Management: low-privileged accounts must not add permissions or update IAM policies | Remove iam:Attach*, iam:Put*Policy, iam:CreatePolicyVersion and iam:SetDefaultPolicyVersion from workload and developer roles; use permissions boundaries on delegated admin roles | Keep Privileged Role Administrator and User Access Administrator to a few named admins; restrict who can own applications | Keep resourcemanager.*.setIamPolicy and iam.roles.update to a few identities; avoid roles/owner and roles/editor for workloads |
| M1026 Privileged Account Management: least privilege, PIM with two or more approvals | Grant admin through short-lived role sessions from a central identity source rather than attached admin policies on users | PIM role settings: "Require approval to activate", "On activation, require multifactor authentication", short "Activation maximum duration", and expiry instead of "Allow permanent eligible assignment" | Time-bound grants with IAM Conditions and approval in the change process for organisation-level bindings |
| M1032 Multi-factor Authentication | MFA for every human who can reach IAM write actions | Phishing-resistant MFA for all role holders; Microsoft's Storm-0501 guidance is that every privileged user has an MFA method already registered | 2-Step Verification enforced for administrators |
Two design choices matter more than any single setting. First, keep privileged directory roles on cloud-native accounts, not identities synchronised from on-premises; Microsoft makes this recommendation after Storm-0501 found a synchronised non-human account holding Global Administrator with no MFA registered, a pattern also described on the Storm-0501 Hybrid Cloud Attacks page. Second, make the IaC pipeline the only routine granter, so a console or CLI grant by a human is itself the anomaly. See the Cloud PAM and CIEM Guide and the AWS IAM Permissions Boundaries Guide.
PIM approval needs care: Microsoft warns that if all Privileged Role Administrators and Global Administrators are eligible, approval is required and no approvers are configured, you can lock yourself out of the tenant. Configure named approvers and emergency access accounts before enforcing.
Small and large estates: a small organisation with one cloud and one tenant gets most value from the provider's built-in role assignment and elevation alerts plus just-in-time admin roles. A large multi-account estate needs a central log sink, the granter allowlist and daily role-state snapshots, because precision and response both depend on the baseline.
Rollout and testing
- Confirm the logs arrive. In a sandbox account, subscription and project, make one benign grant of each type in the detection table and confirm each event reaches the SIEM with the actor, target and role parsed.
- Run alerts in report-only mode for two to four weeks. Tune the granter allowlist by identity, source and method, not by identity alone, so a stolen pipeline credential used from elsewhere still fires.
- Remove standing grant rights. Start with tenant and organisation level roles, then subscriptions and accounts. Keep the old assignments documented so you can restore them if a business process breaks.
- Apply preventive policies in audit mode first. Deny statements, permissions boundaries and PIM approval settings can be piloted on one account, subscription or role; roll back by removing the deny or relaxing the setting.
- Promote to paging for crown-jewel grants and grants by non-granters, then exercise the response by planting one benign grant per platform and timing its removal.
Responding to a suspected role addition
- Scope the actor. From the alert, list every role-changing event by the same actor and by any identity it touched, across all accounts, subscriptions and projects, for the full log retention.
- Reconcile against the baseline. Compare current assignments, attached and inline policies, every stored policy version, eligible as well as active Entra assignments, app role assignments, application owners, Azure role assignments at every scope, Google Cloud bindings at organisation, folder, project and resource level, and custom role definitions. Treat every difference as suspect until a named owner explains it.
- Remove the grants. Detach or delete policies and inline statements, delete broad non-default policy versions, remove role assignments and bindings, restore custom role definitions, and switch off Azure elevated access. Remove application ownership added during the window.
- Remove the footholds the grants protected. Check for credentials added in the same window (T1098.001), new accounts (T1136.003) and changed trust settings (T1484.002); revoke sessions and tokens for the affected identities.
- Verify. Re-run the reconciliation after 24 hours and again after a week; a reappearing grant means a foothold remains.
- Preserve evidence before cleanup: export the relevant CloudTrail, Entra audit, Azure activity and Google Cloud audit entries and the role-state snapshots to case storage.
Measuring whether it works
- Coverage: percentage of the change types in the detection table with a live, tested rule (target 100 per cent for crown-jewel roles).
- Granter count: number of identities able to grant crown-jewel roles, per platform, trending down.
- Standing privilege: share of privileged role holders on just-in-time rather than permanent assignment.
- Unexplained grants: grants per month made outside the pipeline or PIM, each closed with a named reason.
- Time to remove: median time from a confirmed malicious grant to verified removal.
Common failure patterns
- Watching attachments only: rules on AttachUserPolicy miss CreatePolicyVersion and Google Cloud UpdateRole, which widen access with no new grant.
- Reviewing active roles only: eligible Entra assignments and stored policy versions survive the review and are used later.
- Project-only log collection: organisation and folder level bindings in Google Cloud, and management group assignments in Azure, are logged outside the project or subscription the team watches.
- Trusting the granter blindly: allowlisting the pipeline role without checking where the call came from means a stolen pipeline credential grants freely.
- Leaving elevation on: Azure root-scope User Access Administrator left switched on after an admin task is standing access over every subscription.
- Cleaning the user, not the grant: resetting a password and revoking sessions leaves the role in place for the next credential the attacker holds.
Framework mapping
| Framework | Relevant items |
|---|---|
| MITRE ATT&CK v20 | T1098.003 Additional Cloud Roles; DET0277; mitigations M1018, M1026, M1032; related T1078.004 Cloud Accounts, T1136.003 Cloud Account, T1098.001 Additional Cloud Credentials, T1484.002 Trust Modification |
| NIST SP 800-53 Rev. 5 | AC-2 Account Management; AC-2(7) Privileged User Accounts; AC-6(5) Privileged Accounts; AC-6(7) Review of User Privileges; AC-6(9) Log Use of Privileged Functions; AU-6 Audit Record Review, Analysis, and Reporting; AU-12 Audit Record Generation; SI-4 System Monitoring |
| NIST CSF 2.0 | PR.AA-05 access permissions managed and reviewed; DE.CM-03 personnel activity and technology usage monitored; DE.AE-02 potentially adverse events analysed; RS.MI-01 incidents contained |
| ISO/IEC 27001:2022 Annex A | 5.18 Access rights; 8.2 Privileged access rights; 8.15 Logging; 8.16 Monitoring activities |
| CIS Controls v8.1 | Control 5 Account Management; Control 6 Access Control Management; Control 8 Audit Log Management |
Practitioner checklist
- Alert on every event in the detection table, including CreatePolicyVersion, SetDefaultPolicyVersion, UpdateRole and elevateAccess.
- Collect Google Cloud organisation and folder audit logs and Azure management group activity in the same sink as projects and subscriptions.
- Write down the granter list per platform and alert on any grant outside it.
- Deny iam:CreatePolicyVersion and iam:SetDefaultPolicyVersion to every role that does not manage policies.
- Put privileged Entra and Azure roles in PIM with approval, MFA on activation and expiring eligible assignments, with named approvers configured first.
- Keep privileged directory roles on cloud-native accounts, not synchronised identities.
- Snapshot role state daily and keep the snapshots with your audit logs.
- Add role reconciliation, including policy versions, eligible roles and app role assignments, to every cloud identity incident runbook.
Standards and references
- MITRE ATT&CK: Account Manipulation: Additional Cloud Roles (T1098.003)
- AWS Documentation: Versioning IAM policies
- AWS Documentation: Logging IAM and AWS STS API calls with AWS CloudTrail
- Microsoft Learn: Microsoft Entra audit log activity reference
- Microsoft Learn: Elevate access to manage all Azure subscriptions and management groups
- Microsoft Learn: Configure Microsoft Entra role settings in Privileged Identity Management
- Google Cloud: IAM audit logging
- Microsoft Threat Intelligence: Storm-0501's evolving techniques lead to cloud-based ransomware
Related NHI Mgmt Group resources: MITRE ATT&CK T1098 Account Manipulation Guide · Azure RBAC and Service Principals Guide · Google Cloud IAM and Service Accounts Guide · AWS IAM Policies and Roles Guide · MITRE ATT&CK T1555.006 Credentials from Password Stores