privilege escalation in Azure AD

It should be clear that -effectively- a user has the same permissions as the object he has control of – but sometimes things are new or complex or both and then the simplest rules vaporize in our heads.

NOTE: What we will discuss here, is not a bug but a misconfiguration

Azure AD applications are such a confusing thing for example. The main problem here is that an Azure AD app in a given tenant consists out of two parts – A security principal (found under ‘Enterprise applications’) and the application object itself (found under ‘App registrations’). Read more about this here – this is how it looks in the azure portal:

Confusing these objects and misconfiguring them could lead to a privilege escalation in case of an attack.

Security Principals & Applications

If you register an app in Azure AD via the portal, both, an application object, and a security principal are created. If you register a multi-tenant application, this application can be used also in other tenants – therefore another security principal must be created in those tenants.

Both objects can be managed from multiple Azure AD roles – with ‘application administrators’ leading the way. Those roles can manage all application objects and security principals in a tenant. If you want to define granularly who can manage which app, you can assign owners:

As you can see, the application object and the security principal could have owners. On both objects, you can configure different aspects:

On the application object, you mainly configure ‘API permissions’ and ‘Certificates & secrets’. In ‘API permissions’, you manage the permissions your application has on other applications e.g., an application that needs to interact with user mailboxes might need ‘read all mail’ in ‘Exchange Online’ via Graph.

In ‘Certificates & secrets’ you can add new ‘credentials’ to your app. For example, if you are using PowerShell to authenticate to Azure AD using the AppID (to e.g. access Microsoft Graph), you would specify the secret of your app in the PowerShell script.

Azure AD applications can have two different kinds of ‘API permissions’, delegated permissions & application permission, e.g., here for Graph:

As you can read in the description above, for delegated permission, in addition to the API permission, the user using your app, needs the appropriate permission to access this API, whereas with application permissions he doesn’t.

On the security principal you mainly manage which users have access to this application. This is exactly the task, IT administrators often want to outsource to application owners. And this is where things get dangerous.

Abusing Misconfigurations

If the Marketing Lead of your company needs a new application for his team, IT might not know who must have access to this application and they don’t want to change access settings to this app for the rest of their lifes. In such cases, the application management is delegated to the user – the application owner. As mentioned, this could be done through an Azure AD role or -more granularly- through the ‘owner’ property of the security principal object.

If an owner’s account is compromised, the attacker has the same permissions as the user:

  • If the user is then an ‘owner’ of the security principal object, the attacker could manage access to this application – which might not make things worse since the compromised account itself probably already has access to this app anyway.
  • But, if the user account is ‘owner’ of the application object, there is a chance for the attacker to escalate privileges.

Possible Attacks

As an owner of the application object, the attacker could add any API permission to this application. However, these permissions would not be effective, since most of the important permissions need an admin consent, which cannot be given by the application owner.

Ok, so no new permissions – but if the application already has broad permissions, the attacker could abuse those. Since she cannot get the value of an existing secret …

… she has to find another way to connect to the app. Therefore, she could create a new secret:

$startDate = Get-Date
$endDate = $startDate.AddYears(1)
$NewAppSecret = New-AzureADApplicationPasswordCredential -ObjectId f224eafb-5a7a-4ff4-b9f4-ec0b4085717e -CustomKeyIdentifier "NewAppSecret" -StartDate $startDate -EndDate $endDate

By creating a new secret and adding it to the app, an owner of the ‘application object‘ can gain access to the application and all its API permissions:

Let’s summarize that: if a user account has been assigned ownership of an application object, an attacker that has compromised this user, can easily abuse API permissions of the application he owns by generating a new secret and then impersonating the app (at least if ‘application permissions’ have been assigned, rather than ‘delegate permissions’).

From a blue team perspective, you now need two things:

1. A report of all the application owners and the associated permissions of the app
2. Some alerting when new misconfigurations in applications occur

From a red team perspective, such a report is interesting as well.

1. The Report

I have written a short PowerShell script that creates such a report:

In the table above we have a registered application called ‘MTP-API’ that has an admin owner and an owner with a normal user account ‘’. We can also see on which APIs this app has either application- or delegate permissions on (read more about the different permission types here).

Here is a peek into the script:

Find the script on GitHub:

2. Alerting

Azure auditing is logging the activities regarding applications. If you connect the Azure Active Directory data connector with your Sentinel instance and check the ‘Azure Active Directory Audit logs’ checkbox on the data connector settings, you can query events related to our topic:

Another interesting query searches for changes to an application:

| where OperationName == "Update application"

With that you can monitor modifications to an application e.g., when permissions has been added:

This would not tell you per se the name of the permission. I have created a gist that lists AAD permission names and IDs. With the following KQL query, we are pulling in the information from this gist:

let aadAppPermissions = (externaldata(permissionID:string,permissionName:string)
with (format="csv"));
| where OperationName == "Update application"
| extend EntitlementId_ = tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(TargetResources[0].modifiedProperties))[0].newValue))[0].RequiredAppPermissions))[1].EntitlementId)
| join aadAppPermissions on $left.EntitlementId_ == $right.permissionID


Which then results in the information we want:

With these queries, you can create analytic rules to get notified when changes happen to your applications.


Create admin accounts for users that need to manage objects that have higher privileges than their user accounts and protect those admin accounts. Check out the report script and run it against your environment. Also, you might want to setup alerting and hunting in Azure Sentinel as described.

Special thanks to Azure AD Guru Stephan Waelde for reviewing this article upon release!


  • Pingback: Sign in with App ID and App Secret – Azure AD Stuff

  • Was trying to implement the Sentinel rule:

    let aadAppPermissions = (externaldata(permissionID:string,permissionName:string)
    with (format=”csv”));
    | where OperationName == “Update application”
    | extend EntitlementId_ = tostring(parse_json(tostring(parse_json(tostring(parse_json(tostring(TargetResources[0].modifiedProperties))[0].newValue))[0].RequiredAppPermissions))[1].EntitlementId)
    | join aadAppPermissions on $left.EntitlementId_ == $right.permissionID

    To test it I was adding a new Client Secret. It does not show any results if I execute the command as described, but if I only execute

    | where OperationName == “Update application”

    the task is shown.
    But there is no EntitlementId listed.

    Any idea what is going on?

    Liked by 1 person

  • Hey Patrick – run your query that returns a result. Check the entitlementId of the result and then search of it on my gist above. If you don’t find it – let me know, I will update it (in this case give me as much info about the result as possible). Contact me via thanks!

    Liked by 1 person

  • Hello Jan,

    Thank you for this informative blob.

    How did you obtain the permission names and IDs? Is there a way to obtain all of them and not only for Microsoft Graph? I think the gist is not up to date with all permissions anymore since publishing date.

    Thanks in advance.


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.