Detection rules › Kusto
Dataverse - New user agent type that was not used with Office 365
Identifies users accessing Dynamics with a User Agent that has not been seen in any Office 365 workloads in the last 14 days.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Telemetry coverage
| Platform | Record / event type |
|---|---|
| Microsoft 365 | any: Dataverse and model-driven apps audit (catch-all) |
Rule body
id: 094b3c0a-1f63-42f7-9535-c8c7b7198328
kind: Scheduled
name: Dataverse - New user agent type that was not used with Office 365
description: Identifies users accessing Dynamics with a User Agent that has not been
seen in any Office 365 workloads in the last 14 days.
severity: Low
status: Available
requiredDataConnectors:
- connectorId: Dataverse
dataTypes:
- DataverseActivity
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1190
- T1133
query: |
let query_lookback = 14d;
let query_frequency = 1h;
let known_useragents = dynamic([
// Enter known user agents to exclude.
// example:
// "Agent1", "Agent2", "Agent3"
]);
DataverseActivity
| where TimeGenerated > ago(query_frequency)
| where not (UserId has_any ("@onmicrosoft.com", "@microsoft.com", "Unknown"))
| where isnotempty(UserAgent)
| where UserAgent !in~ (known_useragents)
| where UserAgent !hasprefix "azure-logic-apps" and UserAgent !hasprefix "PowerApps"
| join kind = leftanti (
OfficeActivity
| where TimeGenerated between(ago(query_lookback) .. ago(query_frequency))
| where isnotempty(UserAgent)
| summarize by UserAgent)
on UserAgent
// Exclude user agents with a render agent to reduce noise.
| join kind = leftanti(
DataverseActivity
| where TimeGenerated > ago(query_frequency)
| where UserAgent has_any ("Gecko", "WebKit", "Presto", "Trident", "EdgeHTML", "Blink"))
on UserAgent
| summarize
FirstSeen = min(TimeGenerated),
LatestIP = arg_max(ClientIp, TimeGenerated)
by UserAgent, UserId, InstanceUrl
| extend
AccountName = tostring(split(UserId, '@')[0]),
UPNSuffix = tostring(split(UserId, '@')[1]),
CloudAppId = int(32780)
| project
FirstSeen,
UserId,
UserAgent,
LatestIP,
InstanceUrl,
CloudAppId,
AccountName,
UPNSuffix
eventGroupingSettings:
aggregationKind: AlertPerResult
entityMappings:
- entityType: CloudApplication
fieldMappings:
- identifier: AppId
columnName: CloudAppId
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: LatestIP
version: 3.2.0
Stages and Predicates
Parameters
let query_lookback = 14d;
let query_frequency = 1h;
Let binding: known_useragents
let known_useragents = dynamic([
]);
Stage 1: source
DataverseActivity
Stage 2: where
| where TimeGenerated > ago(query_frequency)
Stage 3: where
| where not (UserId has_any ("@onmicrosoft.com", "@microsoft.com", "Unknown"))
Stage 4: where
| where isnotempty(UserAgent)
Stage 5: where
| where UserAgent !in~ (known_useragents)
Stage 6: where
| where UserAgent !hasprefix "azure-logic-apps" and UserAgent !hasprefix "PowerApps"
Stage 7: join (negated)
| join kind = leftanti (
OfficeActivity
| where TimeGenerated between(ago(query_lookback) .. ago(query_frequency))
| where isnotempty(UserAgent)
| summarize by UserAgent)
on UserAgent
Stage 8: join (negated)
| join kind = leftanti(
DataverseActivity
| where TimeGenerated > ago(query_frequency)
| where UserAgent has_any ("Gecko", "WebKit", "Presto", "Trident", "EdgeHTML", "Blink"))
on UserAgent
Stage 9: summarize
| summarize
FirstSeen = min(TimeGenerated),
LatestIP = arg_max(ClientIp, TimeGenerated)
by UserAgent, UserId, InstanceUrl
Stage 10: extend
| extend
AccountName = tostring(split(UserId, '@')[0]),
UPNSuffix = tostring(split(UserId, '@')[1]),
CloudAppId = int(32780)
Stage 11: project
| project
FirstSeen,
UserId,
UserAgent,
LatestIP,
InstanceUrl,
CloudAppId,
AccountName,
UPNSuffix
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
UserId | match | @onmicrosoft.com, @microsoft.com, Unknown | excludes:UserId field:"UserId" value:"@onmicrosoft.com" field:"UserId" value:"@microsoft.com" field:"UserId" value:"Unknown" |
UserAgent | eq | [] | excludes:UserAgent field:"UserAgent" value:"[]" |
UserAgent | starts_with | PowerApps | excludes:UserAgent field:"UserAgent" value:"PowerApps" |
UserAgent | starts_with | azure-logic-apps | excludes:UserAgent field:"UserAgent" value:"azure-logic-apps" |
UserAgent | is_not_null | excludes:UserAgent | |
UserAgent | match | Gecko, WebKit, Presto, Trident, EdgeHTML, Blink | excludes:UserAgent |
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
UserAgent | is_not_null | field:"aws::userAgent" kind:is_not_null |
Output fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountName | project |
CloudAppId | project |
FirstSeen | project |
InstanceUrl | project |
LatestIP | project |
UPNSuffix | project |
UserAgent | project |
UserId | project |