Detection rules › Kusto
Detect credential add to Connect Sync Application
This detection specifically flags credentials being added to the Connect Sync Application in Entra ID, a technique known to have persistance from On-premise AD DS to Entra ID. It tries to look at both certificate, client secret, and federated credentials being added, and tries to remove legitimate renewal processes. Since the legitimate renewal process first adds a new certificate only te remove the old one short after, we by default allow for a maximum of 1 minute between the certificate create and delete events.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Persistence | |
| Privilege Escalation | |
| Stealth | |
| Defense Impairment | |
| Credential Access |
References
Telemetry coverage
| Platform | Record / event type |
|---|---|
| Azure | any: Entra ID audit event (any operation) |
Rules detecting the same action
These rules filter on the same operation.
- [Entra ID] Application Assigned Administrator Permissions Immediately After Obtaining Role Management Permissions (Kusto)
- [Entra ID] Application Granted Administrative Permission to Assign Microsoft Entra ID Roles (Kusto)
- Added Credentials to Existing Application (Sigma)
- Added Owner To Application (Sigma)
- Admin promotion after Role Management Application Permission Grant (Kusto)
- App Granted Microsoft Permissions (Sigma)
- App Granted Privileged Delegated Or App Permissions (Sigma)
- Application AppID Uri Configuration Changes (Sigma)
Rule body
// Flag adding credential that does not look like a renewal
let base = materialize (
AuditLogs
// Search events happening on the Sync Account
| extend AppName = tostring(TargetResources[0].displayName)
| where AppName startswith "ConnectSyncProvisioning_"
// Only get cretificate or secret changes
| where OperationName has_any ("Add service principal", "Certificates and secrets management", "Update application")
// Expand the target resources and modified properties, and only use events ralted to KeyDescription
| mv-expand TargetResources
| extend ModifiedProperties = TargetResources.modifiedProperties
| mv-expand ModifiedProperties
| where ModifiedProperties.displayName == "KeyDescription"
// Save the old and new values of the secrets on the application
| extend OldValue = tostring(ModifiedProperties.oldValue),
NewValue = tostring(ModifiedProperties.newValue)
// Save the old and new credential names in an array
| extend OldCredentialNames = extract_all(@"DisplayName=([^\]]+)", dynamic([1]), OldValue),
NewCredentialNames = extract_all(@"DisplayName=([^\]]+)", dynamic([1]), NewValue)
);
let newCredsAdd = (
base
// Flag when there are more new credentials than old ones
| where array_length(OldCredentialNames) < array_length(NewCredentialNames)
| project-rename NewCredAddTimeGenerated = TimeGenerated
);
let credRemove = (
base
// Get events where credentials are being removed
| where array_length(OldCredentialNames) > array_length(NewCredentialNames)
| project-rename CredRemoveTimeGenerated = TimeGenerated
);
// Only flag when credentials are added without another being removed for the same application within a small time window
// This excludes a normal renewal process
newCredsAdd
| join kind=leftouter credRemove on AppName
| where CredRemoveTimeGenerated - NewCredAddTimeGenerated > 1m
| project NewCredAddTimeGenerated, CredRemoveTimeGenerated, OperationName, AdditionalDetails, InitiatedBy, AppName, ModifiedProperties, OldCredentialNames, NewCredentialNames
Stages and Predicates
let base and let newCredsAdd are inlined into the numbered stages below.
Let binding: credRemove
let credRemove = (
base
| where array_length(OldCredentialNames) > array_length(NewCredentialNames)
| project-rename CredRemoveTimeGenerated = TimeGenerated
);
Stages 1 to 12 define let newCredsAdd (the rule's main pipeline source); stages 13 to 15 run on it.
Stage 1: source
AuditLogs
Stage 2: extend
| extend AppName = tostring(TargetResources[0].displayName)
Stage 3: where
| where AppName startswith "ConnectSyncProvisioning_"
Stage 4: where
| where OperationName has_any ("Add service principal", "Certificates and secrets management", "Update application")
Stage 5: mv-expand
| mv-expand TargetResources
Stage 6: extend
| extend ModifiedProperties = TargetResources.modifiedProperties
Stage 7: mv-expand
| mv-expand ModifiedProperties
Stage 8: where
| where ModifiedProperties.displayName == "KeyDescription"
Stage 9: extend
| extend OldValue = tostring(ModifiedProperties.oldValue),
NewValue = tostring(ModifiedProperties.newValue)
Stage 10: extend
| extend OldCredentialNames = extract_all(@"DisplayName=([^\]]+)", dynamic([1]), OldValue),
NewCredentialNames = extract_all(@"DisplayName=([^\]]+)", dynamic([1]), NewValue)
Stage 11: where
| where array_length(OldCredentialNames) < array_length(NewCredentialNames)
Stage 12: project-rename
| project-rename NewCredAddTimeGenerated = TimeGenerated
Stage 13: join
newCredsAdd
| join kind=leftouter credRemove on AppName
Stage 14: where CredRemoveTimeGenerated - NewCredAddTimeGenerated > 1m
| where CredRemoveTimeGenerated - NewCredAddTimeGenerated > 1m
Stage 15: project
| project NewCredAddTimeGenerated, CredRemoveTimeGenerated, OperationName, AdditionalDetails, InitiatedBy, AppName, ModifiedProperties, OldCredentialNames, NewCredentialNames
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AppName | starts_with |
| field:"AppName" kind:starts_with value:"ConnectSyncProvisioning_" |
OldCredentialNames | cross_field_compare |
| field:"OldCredentialNames" kind:cross_field_compare value:"NewCredentialNames" |
OperationName | match |
| field:"OperationName" kind:match |
displayName | eq |
| field:"displayName" kind:eq value:"KeyDescription" |
Output fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AdditionalDetails | project |
AppName | project |
CredRemoveTimeGenerated | project |
InitiatedBy | project |
ModifiedProperties | project |
NewCredAddTimeGenerated | project |
NewCredentialNames | project |
OldCredentialNames | project |
OperationName | project |