Adversaries may add new credentials to applications or service principals to maintain persistence and escalate privileges within the Azure environment. SOC teams should proactively hunt for this behavior as it aligns with known Nobelium tactics and can indicate unauthorized access or lateral movement.
KQL Query
// New access credential added to application or service principal
let auditLookback = 1d;
CloudAppEvents
| where Timestamp > ago(auditLookback)
| where ActionType in ("Add service principal.", "Add service principal credentials.", "Update application Certificates and secrets management ")
| extend RawEventData = parse_json(RawEventData)
| where RawEventData.ResultStatus =~ "success"
// Select only users or applications initiating the credential changes
| extend ActorDetails = RawEventData.Actor
| mvexpand ActorDetails
| where ActorDetails has "@"
| extend targetDetails = parse_json(ActivityObjects[1])
| extend targetId = targetDetails.Id
| extend targetType = targetDetails.Type
| extend targetDisplayName = targetDetails.Name
| extend keyEvents = RawEventData.ModifiedProperties
| where keyEvents has "KeyIdentifier=" and keyEvents has "KeyUsage=Verify"
| mvexpand keyEvents
| where keyEvents.Name =~ "KeyDescription"
| parse keyEvents.NewValue with * "KeyIdentifier=" keyIdentifier:string ",KeyType=" keyType:string ",KeyUsage=" keyUsage:string ",DisplayName=" keyDisplayName:string "]" *
| parse keyEvents.OldValue with * "KeyIdentifier=" keyIdentifierOld:string ",KeyType" *
| where keyEvents.OldValue == "[]" or keyIdentifier != keyIdentifierOld
| where keyUsage == "Verify"
| project-away keyEvents
| project Timestamp, ActionType, InitiatingUserOrApp=AccountDisplayName, InitiatingIPAddress=IPAddress, UserAgent, targetDisplayName, targetId, targetType, keyDisplayName, keyType, keyUsage, keyIdentifier
id: bf246545-504c-4bea-a6a2-3b6e7f65b202
name: NewAppOrServicePrincipalCredential[Nobelium]
description: |
This query will find when a new credential is added to an application or service principal.
The Nobelium activity group was able to gain sufficient access to add credentials to existing applications with mail read permissions. They used that access to exfiltrate email.
See Customer Guidance on Recent Nation-State Cyber Attacks for more on the Nobelium campaign (formerly known as Solorigate).
Additional information on OAuth Credential Grants can be found in RFC 6749 Section 4.4 or Microsoft Entra ID audit activity reference.
For further information on AuditLogs please see Microsoft Entra ID audit activity reference.
This query was inspired by an Azure Sentinel detection.
References:
https://msrc-blog.microsoft.com/2020/12/13/customer-guidance-on-recent-nation-state-cyber-attacks/
https://docs.microsoft.com/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow
https://docs.microsoft.com/azure/active-directory/reports-monitoring/reference-audit-activities
https://github.com/Azure/Azure-Sentinel/blob/master/Detections/AuditLogs/NewAppOrServicePrincipalCredential.yaml
requiredDataConnectors:
- connectorId: MicrosoftThreatProtection
dataTypes:
- CloudAppEvents
tactics:
- Persistence
tags:
- Nobelium
query: |
// New access credential added to application or service principal
let auditLookback = 1d;
CloudAppEvents
| where Timestamp > ago(auditLookback)
| where ActionType in ("Add service principal.", "Add service principal credentials.", "Update application Certificates and secrets management ")
| extend RawEventData = parse_json(RawEventData)
| where RawEventData.ResultStatus =~ "success"
// Select only users or applications initiating the credential changes
| extend ActorDetails = RawEventData.Actor
| mvexpand ActorDetails
| where ActorDetails has "@"
| extend targetDetails = parse_json(ActivityObjects[1])
| extend targetId = targetDetails.Id
| extend targetType = targetDetails.Type
| extend targetDisplayName = targetDetails.Name
| extend keyEvents = RawEventData.ModifiedProperties
| where keyEvents has "KeyIdentifier=" and keyEvents has "KeyUsage=Verify"
| mvexpand keyEvents
| where keyEvents.Name =~ "KeyDescription"
| parse keyEvents.NewValue with * "KeyIdentifier=" keyIdentifier:string ",KeyType=" keyType:string ",KeyUsage=" keyUsage:string ",DisplayName=" keyDisplayName:string "]" *
| parse keyEvents.OldValue with * "KeyIdentifier=" keyIdentifierOld:string ",KeyType" *
| where keyEvents.OldValue == "[]" or keyIdentifier != keyIdentifierOld
| where keyUsage == "Verify"
| project-away keyEvents
| project Timestamp, ActionType, InitiatingUserOrApp=AccountDisplayName, InitiatingIPAddress=IPAddress, UserAgent, targetDisplayName, targetId, targetType, keyDisplayName, keyType, keyUsage, keyIdentifier
| Sentinel Table | Notes |
|---|---|
CloudAppEvents | Ensure this data connector is enabled |
Scenario: Scheduled Job Credential Rotation
Description: A scheduled job runs nightly to rotate credentials for a database application, which is a legitimate security practice.
Filter/Exclusion: where CredentialRotationJobName = "DatabaseCredentialRotator" or where JobName contains "rotate"
Scenario: Admin Task to Add Temporary Service Principal
Description: An administrator adds a temporary service principal for a third-party tool (e.g., Azure DevOps) during a deployment.
Filter/Exclusion: where ServicePrincipalName contains "AzureDevOps-Dev" or where ServicePrincipalName contains "Temp"
Scenario: New Application Deployment with Credential Setup
Description: A new application (e.g., Jenkins CI/CD) is deployed, and its service principal is created with a new credential as part of the setup.
Filter/Exclusion: where ApplicationName contains "Jenkins" or where ApplicationName contains "CI/CD"
Scenario: User-Initiated Credential Creation for a Valid Tool
Description: A user creates a new service principal for a legitimate tool like Grafana or Prometheus to monitor system metrics.
Filter/Exclusion: where ServicePrincipalName contains "Grafana" or where ServicePrincipalName contains "Prometheus"
Scenario: Credential Addition for a Cloud Provider Integration
Description: A new credential is added for a cloud provider integration (e.g., AWS IAM role) as part of a hybrid cloud setup.
Filter/Exclusion: where ServicePrincipalName contains "AWS" or where ServicePrincipalName contains "CloudProvider"