What Are Azure App Registration Risks?
App registrations in Azure Entra ID allow applications to authenticate and access Microsoft Graph, Azure resources, and other APIs. They are the identity layer for every custom application, automation script, integration, and third-party SaaS tool connected to your Microsoft 365 environment.
App registrations accumulate over time just like user accounts. Shadow IT creates them without governance. Developers register apps for quick testing and never remove them. Third-party integrations request more permissions than needed. The result is an environment full of applications holding privileged access to mailboxes, SharePoint, Teams data, and Azure resources — often with credentials (client secrets) that never expire and no one monitors.
Compromising an app registration with high Microsoft Graph permissions is equivalent to compromising a privileged user account — often with fewer detection signals.
How It Works
App registrations authenticate to Entra ID using one of two methods:
- Client secrets — passwords for the app, often with long expiry dates or no expiry
- Certificates — X.509 certificates used for app authentication
Once authenticated, the app acts according to its API permissions. Two permission types exist:
- Delegated permissions — app acts on behalf of a signed-in user
- Application permissions — app acts as itself, without a user context
Application permissions are the dangerous ones. An app with Mail.ReadWrite application permission can read and write every mailbox in the tenant without any user interaction. An app with Directory.ReadWrite.All can modify any object in Entra ID — including creating new admin accounts.
When these permissions are granted to poorly secured or forgotten app registrations, attackers who obtain the client secret gain persistent, privileged API access to the entire tenant.
The Attack Chain
Step 1 - Enumerate App Registrations and Permissions
Connect-MgGraph -Scopes "Application.Read.All"
# List all app registrations with their permissions
Get-MgApplication | ForEach-Object {
$app = $_
$sp = Get-MgServicePrincipal -Filter "appId eq '$($app.AppId)'"
$appRoles = Get-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $sp.Id
[PSCustomObject]@{
AppName = $app.DisplayName
AppId = $app.AppId
Created = $app.CreatedDateTime
SecretExpiry = ($app.PasswordCredentials | Sort-Object EndDateTime | Select-Object -Last 1).EndDateTime
Permissions = ($appRoles.AppRoleId | ForEach-Object { $_.ToString() }) -join ", "
}
} | Where-Object {$_.Permissions -ne ""}
Step 2 - Target Apps With High Permissions and Weak Secrets
Priority targets:
- Apps with
Mail.ReadWrite,Files.ReadWrite.All, orDirectory.ReadWrite.Allapplication permissions - Apps with client secrets expiring in years or never
- Apps with secrets that have not been rotated in 12+ months
Step 3 - Extract or Brute-Force Client Secret
Client secrets are often stored insecurely — in code repositories, CI/CD pipelines, configuration files, or developer machines. Attackers scan GitHub and public repos for leaked Azure app credentials.
# Scan GitHub for leaked Azure client secrets
# Pattern: "client_secret" + Azure tenant domain in same file
# Tools: truffleHog, git-secrets, GitHub Advanced Security
Step 4 - Authenticate as the App and Exfiltrate
import msal
app = msal.ConfidentialClientApplication(
client_id="APP_CLIENT_ID",
client_credential="STOLEN_SECRET",
authority="https://login.microsoftonline.com/TENANT_ID"
)
result = app.acquire_token_for_client(scopes=["https://graph.microsoft.com/.default"])
token = result["access_token"]
# With Mail.ReadWrite.All — dump all mailboxes
import requests
r = requests.get("https://graph.microsoft.com/v1.0/users", headers={"Authorization": f"Bearer {token}"})
Detection
Microsoft Entra Audit Logs
| Event | What to Look For |
|---|---|
| Add application | New app registrations — especially outside change windows |
| Add password credential | New client secret added to existing app |
| Add service principal | New enterprise app added to tenant |
| Consent to application | User or admin consent to third-party app permissions |
SIEM Detection Query (Elastic KQL)
azure.auditlogs.operation_name: "Add password credential to service principal" AND
NOT azure.auditlogs.properties.initiated_by.user.roles: "*Admin*"
# Detect app authentication from unusual IPs
azure.signinlogs.properties.app_display_name: (* AND NOT "Microsoft*") AND
azure.signinlogs.properties.user_type: "servicePrincipal" AND
azure.signinlogs.properties.risk_level_during_sign_in: ("high" OR "medium")
💡 Tip: Enable Microsoft Graph activity logs and route them to your SIEM. App-based API access — especially bulk mailbox reads or user enumeration — is a strong data exfiltration indicator.
Remediation
💡 Quick Win: Run the PowerShell audit above and identify any app with Directory.ReadWrite.All or Mail.ReadWrite application permissions. Each one is a potential full-tenant compromise vector.
1. Audit and Remove Unused App Registrations
# Find apps with no sign-in activity in 90+ days
$cutoff = (Get-Date).AddDays(-90)
Get-MgApplication | Where-Object {$_.CreatedDateTime -lt $cutoff} | ForEach-Object {
$sp = Get-MgServicePrincipal -Filter "appId eq '$($_.AppId)'"
$lastSignIn = (Get-MgServicePrincipalSignInActivity -ServicePrincipalId $sp.Id).LastSignInDateTime
if ($lastSignIn -lt $cutoff -or $lastSignIn -eq $null) {
[PSCustomObject]@{App=$_.DisplayName; LastSignIn=$lastSignIn}
}
}
2. Rotate Expiring and Old Client Secrets
# Find client secrets expiring within 30 days or already expired
Get-MgApplication | ForEach-Object {
$_.PasswordCredentials | Where-Object {$_.EndDateTime -lt (Get-Date).AddDays(30)} |
Select-Object @{N="App";E={$_.DisplayName}}, EndDateTime
}
# Rotate via: Add-MgApplicationPassword / Remove-MgApplicationPassword
3. Replace Secrets With Certificates
Client secrets are more easily leaked than certificates. Migrate high-privilege apps to certificate-based authentication:
# Generate a self-signed certificate
$cert = New-SelfSignedCertificate -Subject "CN=AppName" -CertStoreLocation "Cert:\CurrentUser\My" `
-KeyExportPolicy Exportable -KeySpec Signature -NotAfter (Get-Date).AddYears(2)
# Upload certificate to app registration
$certData = [Convert]::ToBase64String($cert.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Cert))
$keyCredential = @{Type="AsymmetricX509Cert"; Usage="Verify"; Key=[Convert]::FromBase64String($certData)}
Update-MgApplication -ApplicationId $appId -KeyCredentials @($keyCredential)
4. Apply Least Privilege to App Permissions
Review every app registration and replace broad permissions with scoped alternatives:
- Replace
Mail.ReadWritewithMail.Readif write is not needed - Replace
Directory.ReadWrite.Allwith specific read permissions - Use delegated permissions instead of application permissions where user context is available
How EtcSec Detects This
EtcSec audits all app registrations and service principals in your Azure tenant.
The Applications category checks include: apps with overly broad application permissions (Directory.ReadWrite.All, Mail.ReadWrite.All, Files.ReadWrite.All), apps with expired or expiring client secrets, apps with no recent sign-in activity, and apps with admin consent granted to sensitive permissions without review.
ℹ️ Note: EtcSec audits all Azure app registrations and permissions automatically. Run a free audit to discover over-privileged and forgotten app registrations in your tenant.
Related articles: Azure Privileged Access | Azure Identity Security


