Detection rules › Kusto
Dataverse - TI map URL to DataverseActivity
Identifies a match in DataverseActivity from any URL IOC from Microsoft Sentinel Threat Intelligence.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Initial Access | T1566 Phishing |
| Execution | T1204 User Execution, T1574 Hijack Execution Flow |
| Initial Access | T1456 Drive-By Compromise, T1474 Supply Chain Compromise |
| Initial Access | T0819 Exploit Public-Facing Application, T0862 Supply Chain Compromise, T0865 Spearphishing Attachment |
| Execution | T0863 User Execution |
| Persistence | T0873 Project File Infection |
Rule body kusto
id: d88a0e22-3b6a-40c2-af28-c064b44d03b7
kind: Scheduled
name: Dataverse - TI map URL to DataverseActivity
description: Identifies a match in DataverseActivity from any URL IOC from Microsoft
Sentinel Threat Intelligence.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: Dataverse
dataTypes:
- DataverseActivity
- connectorId: ThreatIntelligence
dataTypes:
- ThreatIntelligenceIndicator
- connectorId: ThreatIntelligenceTaxii
dataTypes:
- ThreatIntelligenceIndicator
- connectorId: MicrosoftDefenderThreatIntelligence
dataTypes:
- ThreatIntelligenceIndicator
- connectorId: ThreatIntelligence
dataTypes:
- ThreatIntelligenceIndicator
- connectorId: ThreatIntelligenceTaxii
dataTypes:
- ThreatIntelligenceIndicator
- connectorId: MicrosoftDefenderThreatIntelligence
dataTypes:
- ThreatIntelligenceIndicator
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
- Execution
- Persistence
relevantTechniques:
- T1566
- T1456
- T1474
- T0819
- T0865
- T0862
- T0863
- T1204
- T1574
- T0873
query: |
let dt_lookBack = 1h;
let ioc_lookBack = 14d;
ThreatIntelligenceIndicator
| where TimeGenerated >= ago(ioc_lookBack) and ExpirationDateTime > now()
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
| where Active == true
| where isnotempty(Url)
| join kind=innerunique (
DataverseActivity
| where TimeGenerated >= ago(dt_lookBack)
| where Message in ("Create", "Update")
| where isnotempty(Fields) and Fields has "http"
| extend
ExtractedUrls = extract_all("(http[s]?://(?:[a-zA-Z\\.-]|[0-9])+)", tostring(Fields)),
DataverseActivity_TimeGenerated = TimeGenerated
| mv-expand Url = ExtractedUrls
| project
DataverseActivity_TimeGenerated,
tostring(Url),
UserId,
ClientIp,
InstanceUrl,
EntityName
)
on Url
| where DataverseActivity_TimeGenerated < ExpirationDateTime
| summarize DataverseActivity_TimeGenerated = arg_max(DataverseActivity_TimeGenerated, *) by IndicatorId, Url
| extend
CloudAppId = int(32780),
AccountName = tostring(split(UserId, '@')[0]),
UPNSuffix = tostring(split(UserId, '@')[1])
| project
DataverseActivity_TimeGenerated,
Description,
ActivityGroupNames,
IndicatorId,
ThreatType,
ExpirationDateTime,
ConfidenceScore,
UserId,
ClientIp,
InstanceUrl,
CloudAppId,
AccountName,
UPNSuffix,
Url
eventGroupingSettings:
aggregationKind: AlertPerResult
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: ClientIp
- entityType: URL
fieldMappings:
- identifier: Url
columnName: Url
- entityType: CloudApplication
fieldMappings:
- identifier: AppId
columnName: CloudAppId
- identifier: InstanceName
columnName: InstanceUrl
alertDetailsOverride:
alertDisplayNameFormat: Dataverse - TI match on URL in {{InstanceUrl}}
alertDescriptionFormat: Malicous IP {{Url}} was found in {{InstanceUrl}}. Associated
user is {{UserId}}
version: 3.2.0
Stages and Predicates
Parameters
let dt_lookBack = 1h;
let ioc_lookBack = 14d;
Stage 1: source
ThreatIntelligenceIndicator
Stage 2: where
| where TimeGenerated >= ago(ioc_lookBack) and ExpirationDateTime > now()
Stage 3: summarize
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
Stage 4: where
| where Active == true
Stage 5: where
| where isnotempty(Url)
Stage 6: join
| join kind=innerunique (
DataverseActivity
| where TimeGenerated >= ago(dt_lookBack)
| where Message in ("Create", "Update")
| where isnotempty(Fields) and Fields has "http"
| extend
ExtractedUrls = extract_all("(http[s]?://(?:[a-zA-Z\\.-]|[0-9])+)", tostring(Fields)),
DataverseActivity_TimeGenerated = TimeGenerated
| mv-expand Url = ExtractedUrls
| project
DataverseActivity_TimeGenerated,
tostring(Url),
UserId,
ClientIp,
InstanceUrl,
EntityName
)
on Url
Stage 7: where
| where DataverseActivity_TimeGenerated < ExpirationDateTime
Stage 8: summarize
| summarize DataverseActivity_TimeGenerated = arg_max(DataverseActivity_TimeGenerated, *) by IndicatorId, Url
Stage 9: extend
| extend
CloudAppId = int(32780),
AccountName = tostring(split(UserId, '@')[0]),
UPNSuffix = tostring(split(UserId, '@')[1])
Stage 10: project
| project
DataverseActivity_TimeGenerated,
Description,
ActivityGroupNames,
IndicatorId,
ThreatType,
ExpirationDateTime,
ConfidenceScore,
UserId,
ClientIp,
InstanceUrl,
CloudAppId,
AccountName,
UPNSuffix,
Url
Indicators
Each row is a field, operator, and value that the rule matches. The corpus column counts how many other rules in the catalog look for the same combination: high numbers point to widely-used, community-vetted indicators. Blank or 1 shows that the indicator is specific to this rule.
Output fields
Fields the rule emits when it matches. Chronicle authors list these in the outcome block; they appear on the detection and $risk_score drives alerting. Sentinel / Defender XDR rules build them up through project / summarize / extend stages. Sentinel maps these into alert fields via entityMappings and customDetails; Defender XDR custom detections surface them as alert fields directly.
| Field | Source |
|---|---|
AccountName | project |
ActivityGroupNames | project |
ClientIp | project |
CloudAppId | project |
ConfidenceScore | project |
DataverseActivity_TimeGenerated | project |
Description | project |
ExpirationDateTime | project |
IndicatorId | project |
InstanceUrl | project |
ThreatType | project |
UPNSuffix | project |
Url | project |
UserId | project |