Query Details
id: 8a790672-0ad9-403a-a80d-f67bd0b7fea6
name: Added Credential to privileged workload by lower or non-privileged user (WorkloadIdentityInfo)
version: 1.0.0
kind: Scheduled
description: This will alert when a unprivileged or lower privileged user adds a new credential to an Application or Service Principal object. Monitor these changes to avoid account take over (for persistent access) and breach of tiering model. If a threat actor obtains access to the workload identity with sufficient privileges and adds the alternate authentication material triggering this event, the threat actor can now authenticate as the Application or Service Principal using this credential.
severity: Medium
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
- PrivilegeEscalation
- DefenseEvasion
relevantTechniques:
- T1078
- T1550
query: |+
// Exclude automated rotation of certificates for Managed Identities by Azure Back-end / IDMS
let ExcludedActor = dynamic (["Managed Service Identity", "Windows Azure Service Management API"]);
AuditLogs
// Captures "Add service principal", "Add service principal credentials", and "Update application - Certificates and secrets management" events
| where OperationName has_any ("Add service principal", "Certificates and secrets management", "Update application")
| where Result =~ "success"
| where parse_json(tostring(InitiatedBy.app)).displayName !in~ (ExcludedActor) and parse_json(tostring(InitiatedBy.app)).appId != "null"
| extend InitiatingUserOrAppId = iff(isnotempty(InitiatedBy.user.id), tostring(InitiatedBy.user.id), tostring(InitiatedBy.app.id))
| extend InitiatingIpAddress = iff(isnotempty(InitiatedBy.user.ipAddress), tostring(InitiatedBy.user.ipAddress), tostring(InitiatedBy.app.ipAddress))
| join kind=leftouter (UnifiedIdentityInfo
| project
ObjectId,
InitiatingUserOrAppClassification = Classification,
InitiatingUserOrAppName = ObjectDisplayName,
InitiatingUserOrAppDirectoryRoles = EntraIdRoles,
InitiatingUserOrAppGraphRoles = AppRoles)
on $left.InitiatingUserOrAppId == $right.ObjectId
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type =~ "Application" or TargetResource.type =~ "ServicePrincipal"
| extend
TargetName = tostring(TargetResource.displayName),
ResourceId = tostring(TargetResource.id),
WorkloadIdentityObjectType = tostring(TargetResource.type),
keyEvents = TargetResource.modifiedProperties
)
| mv-apply Property = keyEvents on
(
where Property.displayName =~ "KeyDescription" or Property.displayName =~ "FederatedIdentityCredentials"
| extend
new_value_set = parse_json(tostring(Property.newValue)),
old_value_set = parse_json(tostring(Property.oldValue))
)
| extend diff = set_difference(new_value_set, old_value_set)
| where isnotempty(diff)
| parse diff with * "KeyIdentifier=" keyIdentifier: string ",KeyType=" keyType: string ",KeyUsage=" keyUsage: string ",DisplayName=" keyDisplayName: string "]" *
| where keyUsage =~ "Verify" or isnotempty(parse_json(tostring(diff[0].Audiences))[0])
| mv-apply AdditionalDetail = AdditionalDetails on
(
where AdditionalDetail.key =~ "User-Agent"
| extend UserAgent = tostring(AdditionalDetail.value)
)
| mv-apply AdditionalDetail = AdditionalDetails on
(
where AdditionalDetail.key =~ "AppId"
| extend ApplicationId = tostring(AdditionalDetail.value)
)
| join kind=leftouter (PrivilegedWorkloadIdentityInfo
| extend WorkloadIdClassification = EnterpriseAccessModelTiering
| extend WorkloadIdentityObjectId = ServicePrincipalObjectId)
on ApplicationId
| extend CredentialName = iff(isnotempty(keyDisplayName), keyDisplayName, diff[0].Name)
| extend CredentialIdentifier = iff(isnotempty(keyIdentifier), keyIdentifier, diff[0].Subject)
| extend CredentialType = iff(isnotempty(keyType), keyType, keyEvents[0].displayName)
| extend CredentialUsage = iff(isnotempty(keyUsage), keyUsage, tostring(diff[0].Audiences))
// Use Target Name if Workload Identity Name is unknown
| extend WorkloadIdentityName = iff(isempty(WorkloadIdentityName), TargetName, WorkloadIdentityName)
// Compare Classification of Application with Actor to detect "Tiering" breach, Allowlist all Control Plane roles
| where parse_json(tostring(parse_json(InitiatingUserOrAppClassification))) !contains WorkloadIdClassification and (parse_json(tostring(parse_json(InitiatingUserOrAppClassification))) !contains "ControlPlane")
// Optional: Exclude all Apps with Unclassified Permissions
//| where WorkloadIdClassification != "Unclassified"
| extend Severity = iff(WorkloadIdClassification contains "ControlPlane", "High", "Medium")
suppressionEnabled: false
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 5h
matchingMethod: AllEntities
groupByEntities:
- Account
- IP
- CloudApplication
groupByAlertDetails: []
groupByCustomDetails: []
eventGroupingSettings:
aggregationKind: AlertPerResult
alertDetailsOverride:
alertDisplayNameFormat: Added {{CredentialType}} to workload with {{WorkloadIdClassification}} privileges by {{InitiatingUserOrAppClassification}} user
alertDescriptionFormat: '{{InitiatingUserOrAppName}} has added a credential for {{WorkloadIdentityName}} on {{WorkloadIdClassification}}. Verify this change to avoid account take over (for persistent access) and breach of tiering model. If a threat actor obtains access to the workload identity with sufficient privileges and adds the alternate authentication material triggering this event, the threat actor can now authenticate as the Application or Service Principal using this credential.'
alertSeverityColumnName: Severity
alertDynamicProperties: []
customDetails:
WorkloadIdentityName: WorkloadIdentityName
WorkloadIdentityType: WorkloadIdentityType
ServicePrincipalId: ServicePrincipalObjectId
ApplicationId: ServicePrincipalObjectId
IsFirstPartyApps: IsFirstPartyApp
PrivilegedAccess: WorkloadIdClassification
EntraDirectoryRoles: EntraIdRoles
MSGraphRoles: AppRolePermissions
CredentialIdentifier: CredentialIdentifier
CredentialType: CredentialType
CredentialUsage: CredentialUsage
entityMappings:
- entityType: Account
fieldMappings:
- identifier: AadUserId
columnName: InitiatingUserOrAppId
- entityType: CloudApplication
fieldMappings:
- identifier: AppId
columnName: ApplicationId
- entityType: CloudApplication
fieldMappings:
- identifier: Name
columnName: WorkloadIdentityName
- entityType: IP
fieldMappings:
- identifier: Address
columnName: InitiatingIpAddress
suppressionDuration: 5h
This query is designed to detect when an unprivileged or lower privileged user adds a new credential to an Application or Service Principal object. It helps monitor these changes to prevent account takeovers and breaches of the tiering model. The query looks for specific events related to adding service principals and managing certificates and secrets. It filters out automated rotations of certificates for Managed Identities by Azure Back-end / IDMS. It also joins with other tables to gather additional information about the user or application that initiated the change and the target resource. The query then applies various filters and transformations to extract relevant details about the added credential. Finally, it generates an alert with a formatted display name and description, including information about the user, the credential, and the target resource. The alert severity is determined based on the classification of the target resource. The query also includes incident configuration and grouping settings for managing and grouping related alerts.

Thomas Naunheim
Released: November 19, 2023
Tables
Keywords
Operators