From 5238b26a7afaae2bd83f45b74fe077d919850f17 Mon Sep 17 00:00:00 2001 From: Jonah Feldman Date: Thu, 5 Mar 2026 09:34:33 -0500 Subject: [PATCH 1/6] azure entra ID escalation --- ...calation.root-user-access-administrator.md | 98 ++++++++ docs/attack-techniques/azure/index.md | 5 + docs/attack-techniques/list.md | 1 + .../mitre-attack-coverage-matrices.md | 8 +- docs/index.yaml | 8 + v2/go.mod | 1 + v2/go.sum | 2 + .../root-user-access-administrator/main.go | 214 ++++++++++++++++++ v2/internal/attacktechniques/main.go | 1 + 9 files changed, 334 insertions(+), 4 deletions(-) create mode 100755 docs/attack-techniques/azure/azure.privilege-escalation.root-user-access-administrator.md create mode 100644 v2/internal/attacktechniques/azure/privilege-escalation/root-user-access-administrator/main.go diff --git a/docs/attack-techniques/azure/azure.privilege-escalation.root-user-access-administrator.md b/docs/attack-techniques/azure/azure.privilege-escalation.root-user-access-administrator.md new file mode 100755 index 000000000..3580d5988 --- /dev/null +++ b/docs/attack-techniques/azure/azure.privilege-escalation.root-user-access-administrator.md @@ -0,0 +1,98 @@ +--- +title: Elevate to User Access Administrator at Root Scope +--- + +# Elevate to User Access Administrator at Root Scope + + + + +Platform: Azure + +## Mappings + +- MITRE ATT&CK + - Privilege Escalation + + + +## Description + + +Elevates the current principal to the User Access Administrator role at root scope (/), +by abusing the "Access management for Azure resources" capability available to Global Administrators in Entra ID. + +This technique allows a Global Administrator to gain control over all Azure subscriptions and management groups +in the tenant, enabling arbitrary role assignments across all Azure resources. + +Warm-up: None + +Detonation: + +- Call the elevateAccess REST API endpoint, which assigns the User Access Administrator role at root scope (/) to the current principal + +Revert: + +- Remove the User Access Administrator role assignment at root scope (/) + +References: + +- https://learn.microsoft.com/en-us/azure/role-based-access-control/elevate-access-global-admin +- https://microsoft.github.io/Azure-Threat-Research-Matrix/PrivilegeEscalation/AZT402/AZT402/ +- https://www.mandiant.com/resources/blog/detecting-microsoft-365-azure-active-directory-backdoors + + +## Instructions + +```bash title="Detonate with Stratus Red Team" +stratus detonate azure.privilege-escalation.root-user-access-administrator +``` +## Detection + + +Identify when the elevateAccess action is called through either the Entra ID Audit Logs or the Azure Activity Log. + +Sample Entra ID Audit Log entry (sensitive fields redacted), filtering on service Azure RBAC (Elevated Access): + +```json hl_lines="1 3" +{ + "activityDisplayName": "User has elevated their access to User Access Administrator for their Azure Resources", + "category": "AzureRBACRoleManagementElevateAccess", + "loggedByService": "Azure RBAC (Elevated Access)", + "result": "success", + "additionalDetails": [ + { "key": "OperationName", "value": "Microsoft.Authorization/elevateAccess/action" }, + { "key": "Resource", "value": "/providers/Microsoft.Authorization" } + ], + "initiatedBy": { + "user": { + "id": "00000000-0000-0000-0000-000000000000", + "userPrincipalName": "user@example.com", + "ipAddress": "1.2.3.4" + } + } +} +``` + +Sample Azure Activity Log entry (sensitive fields redacted): + +```json hl_lines="2 8" +{ + "authorization": { + "action": "Microsoft.Authorization/elevateAccess/action", + "scope": "/providers/Microsoft.Authorization" + }, + "caller": "user@example.com", + "operationName": { + "value": "Microsoft.Authorization/elevateAccess/action", + "localizedValue": "Assigns the caller to User Access Administrator role" + }, + "status": { + "value": "Succeeded" + } +} +``` + +Microsoft Sentinel provides a built-in analytics rule Azure RBAC (Elevate Access) to detect this behavior using the AuditLogs table. + + diff --git a/docs/attack-techniques/azure/index.md b/docs/attack-techniques/azure/index.md index 45a565425..9fc59e672 100755 --- a/docs/attack-techniques/azure/index.md +++ b/docs/attack-techniques/azure/index.md @@ -16,6 +16,11 @@ Note that some Stratus attack techniques may correspond to more than a single AT - [Create Azure VM Bastion shareable link](./azure.persistence.create-bastion-shareable-link.md) +## Privilege Escalation + + - [Elevate to User Access Administrator at Root Scope](./azure.privilege-escalation.root-user-access-administrator.md) + + ## Exfiltration - [Export Disk Through SAS URL](./azure.exfiltration.disk-export.md) diff --git a/docs/attack-techniques/list.md b/docs/attack-techniques/list.md index 5e3685595..37e3da007 100755 --- a/docs/attack-techniques/list.md +++ b/docs/attack-techniques/list.md @@ -61,6 +61,7 @@ This page contains the list of all Stratus Attack Techniques. | [Azure ransomware via Storage Account Blob deletion](./azure/azure.impact.blob-ransomware-individual-file-deletion.md) | [Azure](./azure/index.md) | Impact | | [Delete Azure resource lock](./azure/azure.impact.resource-lock.md) | [Azure](./azure/index.md) | Impact | | [Create Azure VM Bastion shareable link](./azure/azure.persistence.create-bastion-shareable-link.md) | [Azure](./azure/index.md) | Persistence | +| [Elevate to User Access Administrator at Root Scope](./azure/azure.privilege-escalation.root-user-access-administrator.md) | [Azure](./azure/index.md) | Privilege Escalation | | [Create Admin EKS Access Entry](./EKS/eks.lateral-movement.create-access-entry.md) | [EKS](./EKS/index.md) | Lateral Movement | | [Backdoor aws-auth EKS ConfigMap](./EKS/eks.persistence.backdoor-aws-auth-configmap.md) | [EKS](./EKS/index.md) | Persistence, Privilege Escalation | | [Backdoor Entra ID application through service principal](./entra-id/entra-id.persistence.backdoor-application-sp.md) | [Entra ID](./entra-id/index.md) | Persistence, Privilege Escalation | diff --git a/docs/attack-techniques/mitre-attack-coverage-matrices.md b/docs/attack-techniques/mitre-attack-coverage-matrices.md index f85359429..d1e73b39f 100644 --- a/docs/attack-techniques/mitre-attack-coverage-matrices.md +++ b/docs/attack-techniques/mitre-attack-coverage-matrices.md @@ -43,11 +43,11 @@ This provides coverage matrices of MITRE ATT&CK tactics and techniques currently

Azure

- + - - - + + +
ExecutionPersistenceExfiltrationImpact
ExecutionPersistencePrivilege EscalationExfiltrationImpact
Execute Command on Virtual Machine using Custom Script ExtensionCreate Azure VM Bastion shareable linkExport Disk Through SAS URLAzure Blob Storage ransomware through Encryption Scope using client-managed Key Vault key
Execute Commands on Virtual Machine using Run CommandExfiltrate Azure Storage via public accessAzure ransomware via Storage Account Blob deletion
Exfiltrate Azure Storage through SAS URLDelete Azure resource lock
Execute Command on Virtual Machine using Custom Script ExtensionCreate Azure VM Bastion shareable linkElevate to User Access Administrator at Root ScopeExport Disk Through SAS URLAzure Blob Storage ransomware through Encryption Scope using client-managed Key Vault key
Execute Commands on Virtual Machine using Run CommandExfiltrate Azure Storage via public accessAzure ransomware via Storage Account Blob deletion
Exfiltrate Azure Storage through SAS URLDelete Azure resource lock
diff --git a/docs/index.yaml b/docs/index.yaml index 8c19bc7ed..047df9155 100644 --- a/docs/index.yaml +++ b/docs/index.yaml @@ -746,6 +746,14 @@ Azure: - Persistence platform: Azure isIdempotent: false + Privilege Escalation: + - id: azure.privilege-escalation.root-user-access-administrator + name: Elevate to User Access Administrator at Root Scope + isSlow: false + mitreAttackTactics: + - Privilege Escalation + platform: Azure + isIdempotent: false Entra ID: Persistence: - id: entra-id.persistence.backdoor-application-sp diff --git a/v2/go.mod b/v2/go.mod index 995d3b1af..f2c4661da 100644 --- a/v2/go.mod +++ b/v2/go.mod @@ -63,6 +63,7 @@ require ( cloud.google.com/go/compute/metadata v0.6.0 // indirect cloud.google.com/go/iam v1.3.1 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect + github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v2 v2.2.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 // indirect github.com/ProtonMail/go-crypto v1.1.0-alpha.2 // indirect diff --git a/v2/go.sum b/v2/go.sum index 0b9ae35a3..0b3794ab5 100644 --- a/v2/go.sum +++ b/v2/go.sum @@ -23,6 +23,8 @@ github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+ github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8= github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA= github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v2 v2.2.0 h1:Hp+EScFOu9HeCbeW8WU2yQPJd4gGwhMgKxWe+G6jNzw= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v2 v2.2.0/go.mod h1:/pz8dyNQe+Ey3yBp/XuYz7oqX8YDNWVpPB0hH3XWfbc= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v4 v4.2.1 h1:UPeCRD+XY7QlaGQte2EVI2iOcWvUYA2XY8w5T/8v0NQ= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v4 v4.2.1/go.mod h1:oGV6NlB0cvi1ZbYRR2UN44QHxWFyGk+iylgD0qaMXjA= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal v1.1.2 h1:mLY+pNLjCUeKhgnAJWAKhEUQM+RJQo2H1fuGSw1Ky1E= diff --git a/v2/internal/attacktechniques/azure/privilege-escalation/root-user-access-administrator/main.go b/v2/internal/attacktechniques/azure/privilege-escalation/root-user-access-administrator/main.go new file mode 100644 index 000000000..3608e1812 --- /dev/null +++ b/v2/internal/attacktechniques/azure/privilege-escalation/root-user-access-administrator/main.go @@ -0,0 +1,214 @@ +package azure + +import ( + "context" + "encoding/base64" + "encoding/json" + "fmt" + "log" + "strings" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v2" + "github.com/datadog/stratus-red-team/v2/pkg/stratus" + "github.com/datadog/stratus-red-team/v2/pkg/stratus/mitreattack" +) + +// Built-in User Access Administrator role definition ID (constant across all tenants) +const userAccessAdminRoleDefinitionID = "18d7d88d-d35e-4fb5-a5c3-7773c20a72d9" //TODO: double check this is constant across all tenants + +func init() { + const codeBlock = "```" + stratus.GetRegistry().RegisterAttackTechnique(&stratus.AttackTechnique{ + ID: "azure.privilege-escalation.root-user-access-administrator", + FriendlyName: "Elevate to User Access Administrator at Root Scope", + Platform: stratus.Azure, + MitreAttackTactics: []mitreattack.Tactic{ + mitreattack.PrivilegeEscalation, + }, + Description: ` +Elevates the current principal to the User Access Administrator role at root scope (/), +by abusing the "Access management for Azure resources" capability available to Global Administrators in Entra ID. + +This technique allows a Global Administrator to gain control over all Azure subscriptions and management groups +in the tenant, enabling arbitrary role assignments across all Azure resources. + +Warm-up: None + +Detonation: + +- Call the elevateAccess REST API endpoint, which assigns the User Access Administrator role at root scope (/) to the current principal + +Revert: + +- Remove the User Access Administrator role assignment at root scope (/) + +References: + +- https://learn.microsoft.com/en-us/azure/role-based-access-control/elevate-access-global-admin +- https://microsoft.github.io/Azure-Threat-Research-Matrix/PrivilegeEscalation/AZT402/AZT402/ +- https://www.mandiant.com/resources/blog/detecting-microsoft-365-azure-active-directory-backdoors +`, + Detection: ` +Identify when the elevateAccess action is called through either the Entra ID Audit Logs or the Azure Activity Log. + +Sample Entra ID Audit Log entry (sensitive fields redacted), filtering on service Azure RBAC (Elevated Access): + +` + codeBlock + `json hl_lines="1 3" +{ + "activityDisplayName": "User has elevated their access to User Access Administrator for their Azure Resources", + "category": "AzureRBACRoleManagementElevateAccess", + "loggedByService": "Azure RBAC (Elevated Access)", + "result": "success", + "additionalDetails": [ + { "key": "OperationName", "value": "Microsoft.Authorization/elevateAccess/action" }, + { "key": "Resource", "value": "/providers/Microsoft.Authorization" } + ], + "initiatedBy": { + "user": { + "id": "00000000-0000-0000-0000-000000000000", + "userPrincipalName": "user@example.com", + "ipAddress": "1.2.3.4" + } + } +} +` + codeBlock + ` + +Sample Azure Activity Log entry (sensitive fields redacted): + +` + codeBlock + `json hl_lines="2 8" +{ + "authorization": { + "action": "Microsoft.Authorization/elevateAccess/action", + "scope": "/providers/Microsoft.Authorization" + }, + "caller": "user@example.com", + "operationName": { + "value": "Microsoft.Authorization/elevateAccess/action", + "localizedValue": "Assigns the caller to User Access Administrator role" + }, + "status": { + "value": "Succeeded" + } +} +` + codeBlock + ` + +Microsoft Sentinel provides a built-in analytics rule Azure RBAC (Elevate Access) to detect this behavior using the AuditLogs table. +`, + IsIdempotent: false, + Detonate: detonate, + Revert: revert, + }) +} + +func detonate(_ map[string]string, providers stratus.CloudProviders) error { + ctx := context.Background() + cred := providers.Azure().GetCredentials() + clientOptions := providers.Azure().ClientOptions + + globalAdminClient, err := armauthorization.NewGlobalAdministratorClient(cred, clientOptions) + if err != nil { + return fmt.Errorf("failed to create global administrator client: %w", err) + } + + log.Println("Elevating access to User Access Administrator at root scope (/)") + _, err = globalAdminClient.ElevateAccess(ctx, nil) + if err != nil { + return fmt.Errorf("failed to call elevateAccess: %w", err) + } + + log.Println("Successfully elevated access: User Access Administrator role assigned at root scope (/)") + return nil +} + +func revert(_ map[string]string, providers stratus.CloudProviders) error { + ctx := context.Background() + cred := providers.Azure().GetCredentials() + clientOptions := providers.Azure().ClientOptions + + // Get a management token to extract the current principal's object ID + token, err := cred.GetToken(ctx, policy.TokenRequestOptions{ + Scopes: []string{"https://management.azure.com/.default"}, + }) + if err != nil { + return fmt.Errorf("failed to get access token: %w", err) + } + principalID, err := getOIDFromToken(token.Token) + if err != nil { + return fmt.Errorf("failed to extract principal object ID from access token: %w", err) + } + log.Println("Current principal object ID: " + principalID) + + roleAssignmentsClient, err := armauthorization.NewRoleAssignmentsClient("", cred, clientOptions) + if err != nil { + return fmt.Errorf("failed to create role assignments client: %w", err) + } + + // List all role assignments at root scope (principalId filter is unsupported at root scope) + // and filter by principal ID and role definition ID in Go + log.Println("Listing role assignments at root scope (/) for principal " + principalID) + pager := roleAssignmentsClient.NewListForScopePager("/", &armauthorization.RoleAssignmentsClientListForScopeOptions{ + Filter: to.Ptr("atScope()"), + }) + + for pager.More() { + page, err := pager.NextPage(ctx) + if err != nil { + return fmt.Errorf("failed to list role assignments at root scope: %w", err) + } + for _, ra := range page.Value { + if *ra.Properties.PrincipalID != principalID { + continue + } + if !strings.HasSuffix(*ra.Properties.RoleDefinitionID, userAccessAdminRoleDefinitionID) { + continue + } + log.Println("Found User Access Administrator role assignment at root scope, deleting it") + _, err := roleAssignmentsClient.DeleteByID(ctx, *ra.ID, nil) + if err != nil { + return fmt.Errorf("failed to delete role assignment %s: %w", *ra.ID, err) + } + log.Println("Successfully removed User Access Administrator role assignment at root scope") + return nil + } + } + + return fmt.Errorf("User Access Administrator role assignment at root scope not found") +} + +// getOIDFromToken extracts the object ID (oid claim) from an Azure AD JWT access token +// without validating the signature, since we already obtained the token from Azure AD ourselves. +func getOIDFromToken(tokenString string) (string, error) { + parts := strings.Split(tokenString, ".") + if len(parts) != 3 { + return "", fmt.Errorf("invalid JWT token format: expected 3 parts, got %d", len(parts)) + } + + // JWT uses base64url encoding without padding - add padding as needed + payload := parts[1] + switch len(payload) % 4 { + case 2: + payload += "==" + case 3: + payload += "=" + } + + decoded, err := base64.URLEncoding.DecodeString(payload) + if err != nil { + return "", fmt.Errorf("failed to decode JWT payload: %w", err) + } + + var claims struct { + OID string `json:"oid"` + } + if err := json.Unmarshal(decoded, &claims); err != nil { + return "", fmt.Errorf("failed to parse JWT claims: %w", err) + } + + if claims.OID == "" { + return "", fmt.Errorf("oid claim not found in JWT token") + } + + return claims.OID, nil +} diff --git a/v2/internal/attacktechniques/main.go b/v2/internal/attacktechniques/main.go index 66134db43..68ecdf430 100644 --- a/v2/internal/attacktechniques/main.go +++ b/v2/internal/attacktechniques/main.go @@ -53,6 +53,7 @@ import ( _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/azure/impact/blob-ransomware-individual-file-deletion" _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/azure/impact/resource-lock" _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/azure/persistence/create-bastion-shareable-link" + _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/azure/privilege-escalation/root-user-access-administrator" _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/eks/lateral-movement/create-access-entry" _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/eks/persistence/backdoor-aws-auth-configmap" _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/entra-id/persistence/backdoor-application" From dc827c241e216daa14382b7554e2160378753f63 Mon Sep 17 00:00:00 2001 From: Jonah Feldman Date: Thu, 5 Mar 2026 11:51:05 -0500 Subject: [PATCH 2/6] tighten docs --- .../root-user-access-administrator/main.go | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/v2/internal/attacktechniques/azure/privilege-escalation/root-user-access-administrator/main.go b/v2/internal/attacktechniques/azure/privilege-escalation/root-user-access-administrator/main.go index 3608e1812..b81065d8e 100644 --- a/v2/internal/attacktechniques/azure/privilege-escalation/root-user-access-administrator/main.go +++ b/v2/internal/attacktechniques/azure/privilege-escalation/root-user-access-administrator/main.go @@ -43,12 +43,12 @@ Detonation: Revert: - Remove the User Access Administrator role assignment at root scope (/) +If you are getting a 403 error when attempting to revert, you may need to refresh your credentials with az login References: - https://learn.microsoft.com/en-us/azure/role-based-access-control/elevate-access-global-admin - https://microsoft.github.io/Azure-Threat-Research-Matrix/PrivilegeEscalation/AZT402/AZT402/ -- https://www.mandiant.com/resources/blog/detecting-microsoft-365-azure-active-directory-backdoors `, Detection: ` Identify when the elevateAccess action is called through either the Entra ID Audit Logs or the Azure Activity Log. @@ -57,22 +57,23 @@ Sample Entra ID Audit Log entry (sensitive fields redacted), filtering on servic ` + codeBlock + `json hl_lines="1 3" { - "activityDisplayName": "User has elevated their access to User Access Administrator for their Azure Resources", - "category": "AzureRBACRoleManagementElevateAccess", - "loggedByService": "Azure RBAC (Elevated Access)", - "result": "success", - "additionalDetails": [ - { "key": "OperationName", "value": "Microsoft.Authorization/elevateAccess/action" }, - { "key": "Resource", "value": "/providers/Microsoft.Authorization" } - ], - "initiatedBy": { - "user": { + "operationName": "User has elevated their access to User Access Administrator for their Azure Resources", + "category": "AuditLogs", + "properties": { + "category": "AzureRBACRoleManagementElevateAccess", + "activityDisplayName": "User has elevated their access to User Access Administrator for their Azure Resources", + "loggedByService": "Azure RBAC (Elevated Access)", + "result": "success", + "initiatedBy": { + "user": { "id": "00000000-0000-0000-0000-000000000000", "userPrincipalName": "user@example.com", "ipAddress": "1.2.3.4" + } } } } + ` + codeBlock + ` Sample Azure Activity Log entry (sensitive fields redacted): @@ -88,6 +89,12 @@ Sample Azure Activity Log entry (sensitive fields redacted): "value": "Microsoft.Authorization/elevateAccess/action", "localizedValue": "Assigns the caller to User Access Administrator role" }, + "properties": { + "statusCode": "OK", + "eventCategory": "Administrative", + "entity": "/providers/Microsoft.Authorization", + "message": "Microsoft.Authorization/elevateAccess/action", + } "status": { "value": "Succeeded" } @@ -174,7 +181,7 @@ func revert(_ map[string]string, providers stratus.CloudProviders) error { } } - return fmt.Errorf("User Access Administrator role assignment at root scope not found") + return fmt.Errorf("user access administrator role assignment at root scope not found") } // getOIDFromToken extracts the object ID (oid claim) from an Azure AD JWT access token From 8a22f6577afc20e0adfb38b1c00963c17f73c500 Mon Sep 17 00:00:00 2001 From: Jonah Feldman Date: Thu, 5 Mar 2026 12:58:04 -0500 Subject: [PATCH 3/6] another round of doc revision --- .../root-user-access-administrator/main.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/v2/internal/attacktechniques/azure/privilege-escalation/root-user-access-administrator/main.go b/v2/internal/attacktechniques/azure/privilege-escalation/root-user-access-administrator/main.go index b81065d8e..17e086f9c 100644 --- a/v2/internal/attacktechniques/azure/privilege-escalation/root-user-access-administrator/main.go +++ b/v2/internal/attacktechniques/azure/privilege-escalation/root-user-access-administrator/main.go @@ -38,7 +38,7 @@ Warm-up: None Detonation: -- Call the elevateAccess REST API endpoint, which assigns the User Access Administrator role at root scope (/) to the current principal +- Call the elevateAccess REST API endpoint, which assigns the User Access Administrator role at root scope (/) to the current principal Revert: @@ -53,7 +53,7 @@ References: Detection: ` Identify when the elevateAccess action is called through either the Entra ID Audit Logs or the Azure Activity Log. -Sample Entra ID Audit Log entry (sensitive fields redacted), filtering on service Azure RBAC (Elevated Access): +Sample Entra ID Audit Log entry: ` + codeBlock + `json hl_lines="1 3" { @@ -76,7 +76,7 @@ Sample Entra ID Audit Log entry (sensitive fields redacted), filtering on servic ` + codeBlock + ` -Sample Azure Activity Log entry (sensitive fields redacted): +Sample Azure Activity Log entry: ` + codeBlock + `json hl_lines="2 8" { @@ -100,8 +100,6 @@ Sample Azure Activity Log entry (sensitive fields redacted): } } ` + codeBlock + ` - -Microsoft Sentinel provides a built-in analytics rule Azure RBAC (Elevate Access) to detect this behavior using the AuditLogs table. `, IsIdempotent: false, Detonate: detonate, From 7cf013813ed245564610f037968d1d0931916193 Mon Sep 17 00:00:00 2001 From: Jonah Feldman Date: Thu, 5 Mar 2026 13:02:07 -0500 Subject: [PATCH 4/6] tweaks --- .../root-user-access-administrator/main.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/v2/internal/attacktechniques/azure/privilege-escalation/root-user-access-administrator/main.go b/v2/internal/attacktechniques/azure/privilege-escalation/root-user-access-administrator/main.go index 17e086f9c..062f6dcd0 100644 --- a/v2/internal/attacktechniques/azure/privilege-escalation/root-user-access-administrator/main.go +++ b/v2/internal/attacktechniques/azure/privilege-escalation/root-user-access-administrator/main.go @@ -16,7 +16,7 @@ import ( ) // Built-in User Access Administrator role definition ID (constant across all tenants) -const userAccessAdminRoleDefinitionID = "18d7d88d-d35e-4fb5-a5c3-7773c20a72d9" //TODO: double check this is constant across all tenants +const userAccessAdminRoleDefinitionID = "18d7d88d-d35e-4fb5-a5c3-7773c20a72d9" func init() { const codeBlock = "```" @@ -150,8 +150,7 @@ func revert(_ map[string]string, providers stratus.CloudProviders) error { return fmt.Errorf("failed to create role assignments client: %w", err) } - // List all role assignments at root scope (principalId filter is unsupported at root scope) - // and filter by principal ID and role definition ID in Go + // List all role assignments at root scope and filter by principal ID and role definition ID log.Println("Listing role assignments at root scope (/) for principal " + principalID) pager := roleAssignmentsClient.NewListForScopePager("/", &armauthorization.RoleAssignmentsClientListForScopeOptions{ Filter: to.Ptr("atScope()"), @@ -182,8 +181,6 @@ func revert(_ map[string]string, providers stratus.CloudProviders) error { return fmt.Errorf("user access administrator role assignment at root scope not found") } -// getOIDFromToken extracts the object ID (oid claim) from an Azure AD JWT access token -// without validating the signature, since we already obtained the token from Azure AD ourselves. func getOIDFromToken(tokenString string) (string, error) { parts := strings.Split(tokenString, ".") if len(parts) != 3 { From d798ee5030ed122ea47bdeba0e732c8855407f6a Mon Sep 17 00:00:00 2001 From: Jonah Feldman Date: Fri, 6 Mar 2026 10:41:36 -0500 Subject: [PATCH 5/6] respond to codex feedback --- .../root-user-access-administrator/main.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/v2/internal/attacktechniques/azure/privilege-escalation/root-user-access-administrator/main.go b/v2/internal/attacktechniques/azure/privilege-escalation/root-user-access-administrator/main.go index 062f6dcd0..da1c25eb8 100644 --- a/v2/internal/attacktechniques/azure/privilege-escalation/root-user-access-administrator/main.go +++ b/v2/internal/attacktechniques/azure/privilege-escalation/root-user-access-administrator/main.go @@ -4,10 +4,12 @@ import ( "context" "encoding/base64" "encoding/json" + "errors" "fmt" "log" "strings" + "github.com/Azure/azure-sdk-for-go/sdk/azcore" "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v2" @@ -171,6 +173,10 @@ func revert(_ map[string]string, providers stratus.CloudProviders) error { log.Println("Found User Access Administrator role assignment at root scope, deleting it") _, err := roleAssignmentsClient.DeleteByID(ctx, *ra.ID, nil) if err != nil { + var respErr *azcore.ResponseError + if errors.As(err, &respErr) && respErr.StatusCode == 403 { + return fmt.Errorf("failed to delete role assignment - got 403 Forbidden. Your token may no longer reflect the elevated role assignment. Run 'az login' to refresh your credentials and try again: %w", err) + } return fmt.Errorf("failed to delete role assignment %s: %w", *ra.ID, err) } log.Println("Successfully removed User Access Administrator role assignment at root scope") From f6e34194d7137037d9cd330dfbb4513728e9b409 Mon Sep 17 00:00:00 2001 From: Christophe Tafani-Dereeper Date: Mon, 9 Mar 2026 15:29:08 +0100 Subject: [PATCH 6/6] Enhance docs --- ...calation.root-user-access-administrator.md | 43 +++++++++++-------- .../root-user-access-administrator/main.go | 6 ++- 2 files changed, 29 insertions(+), 20 deletions(-) diff --git a/docs/attack-techniques/azure/azure.privilege-escalation.root-user-access-administrator.md b/docs/attack-techniques/azure/azure.privilege-escalation.root-user-access-administrator.md index 3580d5988..ad5bae830 100755 --- a/docs/attack-techniques/azure/azure.privilege-escalation.root-user-access-administrator.md +++ b/docs/attack-techniques/azure/azure.privilege-escalation.root-user-access-administrator.md @@ -29,17 +29,19 @@ in the tenant, enabling arbitrary role assignments across all Azure resources. Detonation: -- Call the elevateAccess REST API endpoint, which assigns the User Access Administrator role at root scope (/) to the current principal +- Call the elevateAccess REST API endpoint, which assigns the User Access Administrator role at root scope (/) to the current principal Revert: - Remove the User Access Administrator role assignment at root scope (/) +If you are getting a 403 error when attempting to revert, you may need to refresh your credentials with az login References: - https://learn.microsoft.com/en-us/azure/role-based-access-control/elevate-access-global-admin - https://microsoft.github.io/Azure-Threat-Research-Matrix/PrivilegeEscalation/AZT402/AZT402/ -- https://www.mandiant.com/resources/blog/detecting-microsoft-365-azure-active-directory-backdoors +- https://www.invictus-ir.com/nieuws/the-azure-log-you-probably-didnt-know-existed +- https://permiso.io/blog/azures-apex-permissions-elevate-access-the-logs-security-teams-overlook ## Instructions @@ -52,31 +54,32 @@ stratus detonate azure.privilege-escalation.root-user-access-administrator Identify when the elevateAccess action is called through either the Entra ID Audit Logs or the Azure Activity Log. -Sample Entra ID Audit Log entry (sensitive fields redacted), filtering on service Azure RBAC (Elevated Access): +Sample Entra ID Audit Log entry: -```json hl_lines="1 3" +``` json hl_lines="2 5" { - "activityDisplayName": "User has elevated their access to User Access Administrator for their Azure Resources", - "category": "AzureRBACRoleManagementElevateAccess", - "loggedByService": "Azure RBAC (Elevated Access)", - "result": "success", - "additionalDetails": [ - { "key": "OperationName", "value": "Microsoft.Authorization/elevateAccess/action" }, - { "key": "Resource", "value": "/providers/Microsoft.Authorization" } - ], - "initiatedBy": { - "user": { + "operationName": "User has elevated their access to User Access Administrator for their Azure Resources", + "category": "AuditLogs", + "properties": { + "category": "AzureRBACRoleManagementElevateAccess", + "activityDisplayName": "User has elevated their access to User Access Administrator for their Azure Resources", + "loggedByService": "Azure RBAC (Elevated Access)", + "result": "success", + "initiatedBy": { + "user": { "id": "00000000-0000-0000-0000-000000000000", "userPrincipalName": "user@example.com", "ipAddress": "1.2.3.4" + } } } } + ``` -Sample Azure Activity Log entry (sensitive fields redacted): +Sample Azure Activity Log entry: -```json hl_lines="2 8" +``` json hl_lines="3 8" { "authorization": { "action": "Microsoft.Authorization/elevateAccess/action", @@ -87,12 +90,16 @@ Sample Azure Activity Log entry (sensitive fields redacted): "value": "Microsoft.Authorization/elevateAccess/action", "localizedValue": "Assigns the caller to User Access Administrator role" }, + "properties": { + "statusCode": "OK", + "eventCategory": "Administrative", + "entity": "/providers/Microsoft.Authorization", + "message": "Microsoft.Authorization/elevateAccess/action", + } "status": { "value": "Succeeded" } } ``` -Microsoft Sentinel provides a built-in analytics rule Azure RBAC (Elevate Access) to detect this behavior using the AuditLogs table. - diff --git a/v2/internal/attacktechniques/azure/privilege-escalation/root-user-access-administrator/main.go b/v2/internal/attacktechniques/azure/privilege-escalation/root-user-access-administrator/main.go index da1c25eb8..9fcabc194 100644 --- a/v2/internal/attacktechniques/azure/privilege-escalation/root-user-access-administrator/main.go +++ b/v2/internal/attacktechniques/azure/privilege-escalation/root-user-access-administrator/main.go @@ -51,13 +51,15 @@ References: - https://learn.microsoft.com/en-us/azure/role-based-access-control/elevate-access-global-admin - https://microsoft.github.io/Azure-Threat-Research-Matrix/PrivilegeEscalation/AZT402/AZT402/ +- https://www.invictus-ir.com/nieuws/the-azure-log-you-probably-didnt-know-existed +- https://permiso.io/blog/azures-apex-permissions-elevate-access-the-logs-security-teams-overlook `, Detection: ` Identify when the elevateAccess action is called through either the Entra ID Audit Logs or the Azure Activity Log. Sample Entra ID Audit Log entry: -` + codeBlock + `json hl_lines="1 3" +` + codeBlock + ` json hl_lines="2 5" { "operationName": "User has elevated their access to User Access Administrator for their Azure Resources", "category": "AuditLogs", @@ -80,7 +82,7 @@ Sample Entra ID Audit Log entry: Sample Azure Activity Log entry: -` + codeBlock + `json hl_lines="2 8" +` + codeBlock + ` json hl_lines="3 8" { "authorization": { "action": "Microsoft.Authorization/elevateAccess/action",