Query Details
// This rule detects when the authentication credentials for an Application or Service Principal are modified. If a threat actor obtains access to an account with sufficient privileges and adds an alternate authentication credential, the threat actor can now authenticate as the Application or Service Principal.
//
// Ref: https://docs.microsoft.com/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow
// Ref: https://docs.microsoft.com/azure/active-directory/reports-monitoring/reference-audit-activities.
let _BenignApps = toscalar(
_GetWatchlist('Activity-ExpectedSignificantActivity')
| where Activity == "AppServiceCredentialModification" and Notes has "[App]"
| summarize make_list(ActorId)
);
let query_frequency = 1h;
let query_period = 1d;
AuditLogs
| where TimeGenerated > ago(query_period)
// Capture "Add application", "Add service principal", "Add service principal credentials", and "Update application - Certificates and secrets management" events
| where OperationName has_any ("Add application", "Add service principal", "Certificates and secrets management")
| where Result == "success"
| mv-apply ModifiedProperty = TargetResources[0].modifiedProperties on (
summarize BagToUnpack = make_bag(pack(tostring(ModifiedProperty.displayName), pack("oldValue", ModifiedProperty.oldValue, "newValue", ModifiedProperty.newValue))))
| evaluate bag_unpack(BagToUnpack, columnsConflict='replace_source')
| extend
// Information about the actor
ActorIdentity = Identity,
// Information about the actor, if it was a user
ActorUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName),
ActorUserIPAddress = tostring(InitiatedBy.user.ipAddress),
//ActorUserRoles = tostring(InitiatedBy.user.roles),
ActorUserId = tostring(InitiatedBy.user.id),
// Information about the actor, if it was an application
ActorAppName = tostring(InitiatedBy.app.displayName),
ActorAppId = tostring(InitiatedBy.app.appId),
ActorAppServicePrincipalName = tostring(InitiatedBy.app.servicePrincipalName),
ActorAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId),
// Information about the target object
TargetType = tostring(TargetResources[0].type),
TargetDisplayName = tostring(TargetResources[0].displayName),
TargetId = tostring(TargetResources[0].id)
| as _KeyCredentialEvents
| join kind=leftouter (
_KeyCredentialEvents
| summarize OperationNames = make_set(OperationName) by TargetId
| extend EventType = iff(OperationNames has "Add application", "First access credential added to", "")
| project EventType, TargetId
)
on TargetId
| where not(OperationName == "Add application")
| extend KeyDescription = column_ifexists("KeyDescription", dynamic([]))
| where isnotempty(KeyDescription)
| as _KeyCredentialEventsExtended
| where TimeGenerated > ago(query_frequency)
| where not(ActorAppServicePrincipalId in (_BenignApps))
| join kind=leftouter (
_KeyCredentialEventsExtended
| summarize arg_min(TimeGenerated, CorrelationId) by TargetId
| project MinCorrelationId = CorrelationId, TargetId
)
on TargetId
| extend AddedCredentials = set_difference(todynamic(tostring(KeyDescription.newValue)), todynamic(tostring(KeyDescription.oldValue)))
| extend EventType = iff(MinCorrelationId == CorrelationId and isnotempty(EventType),
EventType,
case(
OperationName == "Add service principal", "First access credential added to",
array_length(AddedCredentials) == 0, "Removed access credential from",
KeyDescription.oldValue == "[]", "Renewed access credential added to",
"Alternative access credential added to"
))
| mv-expand AddedCredentials = iff(array_length(AddedCredentials) != 0, AddedCredentials, dynamic([""])) to typeof(string)
| parse AddedCredentials with "[KeyIdentifier=" KeyIdentifier: string ",KeyType=" KeyType: string ",KeyUsage=" KeyUsage: string ",DisplayName=" KeyDisplayName: string "]"
| extend
AlertName = strcat(EventType, " Application or Service Principal"),
AlertSeverity = case(
not(IsWorkingTime(TimeGenerated)) and isnotempty(ActorUserPrincipalName), "High",
EventType startswith "First", "High",
"Medium"
)
| project
TimeGenerated,
OperationName,
ActorIdentity,
ActorUserPrincipalName,
ActorAppName,
TargetType,
TargetDisplayName,
KeyDisplayName,
KeyType,
KeyUsage,
KeyIdentifier,
KeyDescription,
ActorUserIPAddress,
ActorUserId,
ActorAppId,
ActorAppServicePrincipalName,
ActorAppServicePrincipalId,
TargetId,
InitiatedBy,
TargetResources,
AdditionalDetails,
CorrelationId,
AlertName,
AlertSeverity
This query detects when the authentication credentials for an Application or Service Principal are modified. It looks for events such as adding an application, adding a service principal, managing certificates and secrets, etc. It filters for successful events and extracts information about the actor and the target object. It then joins the events with additional information and identifies the type of event (e.g., first access credential added, removed access credential, renewed access credential, alternative access credential added). Finally, it creates alerts with relevant information such as the time of the event, operation name, actor information, target information, key details, and alert severity.

Jose Sebastián Canós
Released: January 3, 2023
Tables
Keywords
Operators