Assigning ownership to your application and service principal objects in Entra ID might seem like good governance, but it introduces significant security and privilege risks.
When assigned ownership, users can manage the credentials and modify high-impact settings on these objects – all without being assigned any administrator roles. These are privileged actions that pose significant risk, whether through intentional misuse or account compromise.
This article highlights the risks and explains why you should avoid assigning ownership to your application and service principal objects in Entra ID.
As a brief recap:
In Entra ID, an application usually consists of two related objects: the application object and the service principal object – ignoring multi-tenant and managed identity scenarios for simplicity.
The application object acts as a blueprint that specifies the application’s configuration, capabilities, and behavior. The service principal object is the representation of that blueprint within a specific tenant – the identity of the application used for most interactions.
When you assign someone as an owner of an Entra ID application object, you give them control over key parts of the app’s configuration, including credentials, permissions, and authentication behavior.
Owners can:
Adding a client credential allows the owner to authenticate as the application itself using the client credentials grant flow. This means they can obtain access tokens including all the application permissions the application’s service principal is granted consent for in the tenant.
In other words, those permissions operate entirely under the application’s identity, without any interactive user sign-in. That design makes sense for legitimate service-to-service communication, but in the wrong hands, it can also enable privilege escalation or unauthorized access far beyond a user’s normal role.
For example, in the screenshot below, Megan – the marketing manager – is assigned ownership of a highly privileged Entra ID application object that has the Directory.ReadWrite.All application permission consented.
Figure1 : Application object ownership in the Entra admin portal.
Figure3 : Addition of a client secret to the application object in the Entra admin center.
With that ownership in place, Megan – or anyone who compromises her account – could add a new client credential, request an access token, and gain directory-wide access by leveraging that Directory.ReadWrite.All permission. Even though Megan isn’t a Global Administrator, she (and any attacker) effectively has the same level of control through the app’s permissions.
With Megan being a typical business user exposed to common attack vectors, her account is far easier to compromise than a dedicated administrator account – a risk that should not be underestimated.
So, should you simply avoid assigning ownership on application objects with high privilege application permissions consented? Unfortunately, it doesn’t stop there.
Even without high-privilege application permissions consented, assigning ownership on application objects still exposes multiple lateral movement paths as owners can also:
Each of these actions creates real opportunities for lateral movement, impersonation, and privilege escalation.
Ownership of service principal objects also introduces significant risk. Service principal object owners can:
A lesser-known risk is that service principal owners can also add client credentials directly to the service principal object – effectively the same as adding client credentials to the application object, with the crucial difference that these credentials aren’t visible in the Entra admin portal, making detection less likely.
To mitigate this risk, Microsoft introduced the App Instance Property Lock feature, which prevents modification of sensitive properties (see the screenshot below) on service principal objects and is enabled by default for all applications created after March 2024.
Unfortunately, this feature is managed on the application object and as you could have probably guessed, an application object owner can disable the feature for the application or unlock any of the properties.
This means assigning ownership to service principal objects can be just as risky as granting ownership to application objects. In both cases, a compromised owner account can be leveraged for privilege escalation or lateral movement.
Figure 4: App instance property lock configuration with property dropdown.
Unfortunately, not really. While blocking (read) access to the Entra admin center using the setting below prevents owners from performing these actions in the admin center, it does not prevent them from performing these actions through other interfaces such as PowerShell modules. Conversely, it also defeats the purpose of assigning ownership in the first place, since owners can no longer manage the objects through the admin center.
Previously, users could still manage application and service principal objects they owned in the Entra admin center (or Azure portal) if they had a direct link to the object. Microsoft has since tightened this behavior, fully blocking access, even when the direct URL is known.
Figure 5: User setting in Entra ID to restrict access to the Entra admin center for non-admin users (i.e.: users not assigned any administrator roles).
Even with the above Entra admin center restriction in place, owners can still interact with owned objects through other interfaces – which is one of the hidden risks of assigning ownership. Depending on the interface, access can sometimes be restricted by configuring the associated service principal (for example, by requiring assignment).
For instance:
|
Interface |
Service principal displayname |
AppId |
|
Microsoft Graph Explorer (aka.ms/ge) |
Graph Explorer |
de8bc8b5-d9f9-48b1-a8ad-b748da725064 |
|
Microsoft Graph PowerShell SDK |
Microsoft Graph Command Line Tools |
14d82eec-204b-4c2f-b7e8-296a70dab67e |
|
legacy AzureAD (Preview) PowerShell module |
Azure Active Directory PowerShell |
1b730954-1685-4b74-9bfd-dac224a7b894 |
|
Microsoft Graph |
Microsoft Graph |
00000003-0000-0000-c000-000000000000 |
Ideally, from a security perspective, you’d want to:
As you cannot directly filter on owned application and service principal objects in the Entra ID admin center, I provided some PowerShell code below that identifies owned application and service principal objects and exports it to a CSV. Alternatively, tools like ENow App Governance Accelerator can quickly identify objects with/without ownership.
# Connect to Microsoft Graph PowerShell
Connect-MgGraph -Scopes Application.Read.All -ContextScope Process
# Function to get all objects from a paged Microsoft Graph endpoint
# This is needed because Get-Mg* cmdlets do not return owners with all properties (only id)
function Get-MgGraphPaged {
param(
[Parameter(Mandatory = $true)][string]$Endpoint
)
$URI = "https://graph.microsoft.com/v1.0/$Endpoint"
$Accumulator = @()
do {
$Response = Invoke-MgGraphRequest -Method GET -Uri $URI -ContentType
'application/json'
if ($null -ne $Response.value) {
$Accumulator += $Response.value
}
else {
$Accumulator += $Response
}
$Next = $Response.'@odata.nextLink'
if (-not $Next) { $Next = $Response.'@odata.nextlink' }
$URI = $Next
} while ($URI)
return $Accumulator
}
# Get all service principal objects with their owners expanded
try {
$ServicePrincipalObjects = Get-MgGraphPaged -Endpoint
"servicePrincipals?`$expand=owners"
Write-Host "Successfully retrieved $($ServicePrincipalObjects.count) service principal objects and their owners"
}
catch {
Throw "Error retrieving service principal objects: $_"
}
# Get all application objects with their owners expanded
try {
$ApplicationObjects = Get-MgGraphPaged -Endpoint
"applications?`$expand=owners"
Write-Host "Successfully retrieved $($ApplicationObjects.count)
application objects and their owners"
}
catch {
Throw "Error retrieving application objects: $_"
}
# Define an array to hold the results
$Results = @()
# Combine both service principals and applications into a single collection for processing
$Combined = @(
$ServicePrincipalObjects |
Where-Object { $_.owners } |
ForEach-Object { [PSCustomObject]@{ Type = 'ServicePrincipal'; Object = $_ } }
$ApplicationObjects |
Where-Object { $_.owners } |
ForEach-Object { [PSCustomObject]@{ Type = 'Application'; Object = $_ } }
)
# Process each object to extract required properties and resolve owner display names
foreach ($Item in $Combined) {
$Object = $Item.Object
if (-not $script:OwnerCache) { $script:OwnerCache = @{} }
$Owners = (@($Object.owners) | ForEach-Object {
$Id = $_.id
$DisplayName = $_.displayName
if ([string]::IsNullOrEmpty($DisplayName)) {
if ($script:OwnerCache.ContainsKey($Id)) {
$DisplayName = $script:OwnerCache[$Id]
}
else {
try {
$Resp = Invoke-MgGraphRequest -Method GET -Uri
"https://graph.microsoft.com/v1.0/directoryObjects/$Id`?$select=displayName" -
ContentType 'application/json'
$DisplayName = $Resp.displayName
}
catch {
$DisplayName = $null
}
if ([string]::IsNullOrEmpty($DisplayName)) { $DisplayName
= $Id }
$script:OwnerCache[$Id] = $DisplayName
}
}
"$DisplayName ($Id)"
}) -join '; '
$Results += [PSCustomObject]@{
ObjectType = $Item.Type
AppId = $Object.appId
ObjectId = $Object.id
DisplayName = $Object.displayName
Owners = $Owners
}
}
# Export results to CSV
$Results | Export-Csv -Path ".\ApplicationOwners_ServicePrincipalOwners.csv" -
NoTypeInformation -Encoding UTF8
Write-Host "Exported $($Results.count) entries to
ApplicationOwners_ServicePrincipalOwners.csv"
In Part 2, we’ll explore safer alternatives to assigning ownership on application and service principal objects in Entra ID. We’ll also look at common scenarios where ownership is assigned today, such as managing credential expiry, and provide practical guidance on how to handle these cases securely.
Bookmark this article or subscribe to the AppGov Score blog to be notified when it’s published.
Stay tuned!