Detection rules › Kusto
Kusto rules: 1e
Azure secure score admin MFA
#'This query searches for requiring multi-factor authentication (MFA) for all administrative roles makes it harder for attackers to access accounts. Administrative roles have higher permissions than typical users. If any of those accounts are compromised, critical devices and data is open to attack.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Impact |
Rule Body
id: 9a15c3dd-f72b-49a4-bcb7-94406395661e
name: Azure secure score admin MFA
description: |
'This query searches for requiring multi-factor authentication (MFA) for all administrative roles makes it harder for attackers to access accounts. Administrative roles have higher permissions than typical users. If any of those accounts are compromised, critical devices and data is open to attack.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: SenservaPro
dataTypes:
- SenservaPro_CL
queryFrequency: 6h
queryPeriod: 6h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Impact
relevantTechniques:
- T1529
- T1498
query: |
SenservaPro_CL
| where ControlName_s == 'AzureSecureScoreAdminMFAV2'
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: ControlName_s
- identifier: AadTenantId
columnName: TenantId
- identifier: DisplayName
columnName: TenantDisplayName_s
- entityType: SecurityGroup
fieldMappings:
- identifier: DistinguishedName
columnName: Group_s
- entityType: AzureResource
fieldMappings:
- identifier: ResourceId
columnName: SourceSystem
version: 1.0.1
kind: Scheduled
Stages and Predicates
Stage 1: source
SenservaPro_CL
Stage 2: where
| where ControlName_s == 'AzureSecureScoreAdminMFAV2'
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ControlName_s | eq |
| field:"ControlName_s" kind:eq value:"AzureSecureScoreAdminMFAV2" |
Claroty - Policy violation
#'Detects Claroty policy violation events from ClarotyEvent when EventOriginalType or EventType contains 'Policy Violation'. Use this rule to identify policy enforcement events that may indicate unauthorized discovery or prohibited network activity. This rule expects ClarotyEvent data to be available in the workspace.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Discovery |
Rule Body
id: 3b22ac47-e02c-4599-a37a-57f965de17be
name: Claroty - Policy violation
description: |
'Detects Claroty policy violation events from ClarotyEvent when EventOriginalType or EventType contains 'Policy
Violation'. Use this rule to identify policy enforcement events that may indicate unauthorized discovery or prohibited network
activity. This rule expects ClarotyEvent data to be available in the workspace.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: CefAma
dataTypes:
- CommonSecurityLog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Discovery
relevantTechniques:
- T1018
- T1135
query: |
ClarotyEvent
| where EventOriginalType has 'Policy Violation' or EventType has 'Policy Violation'
| project TimeGenerated, DstIpAddr, EventOriginalType, EventType
| extend IPCustomEntity = DstIpAddr
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPCustomEntity
alertDetailsOverride:
alertDisplayNameFormat: Claroty policy violation detected for {{IPCustomEntity}}
alertDescriptionFormat: 'Claroty reported a policy violation for {{IPCustomEntity}}. EventOriginalType: {{EventOriginalType}}.
EventType: {{EventType}}'
customDetails:
EventOriginalType: EventOriginalType
EventType: EventType
DestinationIP: DstIpAddr
version: 1.0.4
kind: Scheduled
Stages and Predicates
Stage 1: source
ClarotyEvent
Stage 2: where
| where EventOriginalType has 'Policy Violation' or EventType has 'Policy Violation'
Stage 3: project
| project TimeGenerated, DstIpAddr, EventOriginalType, EventType
Stage 4: extend
| extend IPCustomEntity = DstIpAddr
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventOriginalType | match |
| field:"EventOriginalType" kind:match value:"Policy Violation" |
EventType | match |
| field:"EventType" kind:match value:"Policy Violation" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DstIpAddr | project |
EventOriginalType | project |
EventType | project |
TimeGenerated | project |
IPCustomEntity | extend |
Cloudflare - Unexpected URI
#'Detects client requests to unusual URI.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: dcb797cd-a4cd-4306-897b-7991f71d7e27
name: Cloudflare - Unexpected URI
description: |
'Detects client requests to unusual URI.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: CloudflareDataConnector
dataTypes:
- Cloudflare
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1190
- T1133
query: |
Cloudflare
| where HttpRequestMethod =~ 'GET'
| where DstBytes != 0 or SrcBytes != 0
| where ClientRequestURI matches regex @'(127\.\d{1,3}\.\d{1,3}\.\d{1,3})|(10\.\d{1,3}\.\d{1,3}\.\d{1,3})|(172\.1[6-9]\.\d{1,3}\.\d{1,3})|(172\.2[0-9]\.\d{1,3}\.\d{1,3})|(172\.3[0-1]\.\d{1,3}\.\d{1,3})|(192\.168\.\d{1,3}\.\d{1,3})'
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIpAddr
version: 1.0.1
kind: Scheduled
Stages and Predicates
Stage 1: source
Cloudflare
Stage 2: where
| where HttpRequestMethod =~ 'GET'
Stage 3: where
| where DstBytes != 0 or SrcBytes != 0
Stage 4: where
| where ClientRequestURI matches regex @'(127\.\d{1,3}\.\d{1,3}\.\d{1,3})|(10\.\d{1,3}\.\d{1,3}\.\d{1,3})|(172\.1[6-9]\.\d{1,3}\.\d{1,3})|(172\.2[0-9]\.\d{1,3}\.\d{1,3})|(172\.3[0-1]\.\d{1,3}\.\d{1,3})|(192\.168\.\d{1,3}\.\d{1,3})'
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ClientRequestURI | regex_match |
| field:"ClientRequestURI" kind:regex_match |
DstBytes | ne |
| field:"DstBytes" kind:ne value:"0" |
HttpRequestMethod | eq |
| field:"HttpRequestMethod" kind:eq value:"GET" |
SrcBytes | ne |
| field:"SrcBytes" kind:ne value:"0" |
Create Incidents from IronDefense
#'Creates incidents based on behavioral detections from IronDefense.'
Rule Body
id: 3cf46cb9-99d5-42ee-a43c-7bd88ea394a1
name: Create Incidents from IronDefense
description: |
'Creates incidents based on behavioral detections from IronDefense.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: IronNetIronDefense
dataTypes:
- CommonSecurityLog
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
relevantTechniques:
query: |
CommonSecurityLog
| where DeviceProduct == "IronDefense"
| summarize arg_max(TimeGenerated, *) by DeviceCustomString4, DeviceCustomString2
| extend Category = coalesce(column_ifexists("DeviceEventCategory",""),extract(@'cat=([^;]+)(\;|$)', 1, AdditionalExtensions))
| extend Subcategory = extract(@'subcat=([^;]+)(\;|$)', 1, AdditionalExtensions)
| extend AlertDescription = strcat(Category, ': ', Subcategory)
| extend Status = tostring(DeviceCustomString2)
| extend IronVueUrl = replace(@'\\', @'', tostring(DeviceCustomString3))
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: DestinationIP
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIP
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: DestinationHostName
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: SourceHostName
- entityType: URL
fieldMappings:
- identifier: Url
columnName: RequestURL
customDetails:
IronDefenseStatus: Status
AnalystRating: LogSeverity
AlertCreatedTime: TimeGenerated
IronVueUrl: IronVueUrl
IronDefenseAlertId: DeviceCustomString4
AnalystSeverity: DeviceCustomString1
eventGroupingSettings:
aggregationKind: AlertPerResult
alertDetailsOverride:
alertDisplayNameFormat: |
{{Category}}: {{Subcategory}} ({{DeviceCustomString4}})
alertDescriptionFormat: |
IronDefense detected suspicious activity on {{TimeGenerated}} and categorized it as "{{AlertDescription}}". View full details in IronVue: {{IronVueUrl}}
alertSeverityColumnName: LogSeverity
version: 1.0.1
kind: Scheduled
Stages and Predicates
Stage 1: source
CommonSecurityLog
Stage 2: where
| where DeviceProduct == "IronDefense"
Stage 3: summarize
| summarize arg_max(TimeGenerated, *) by DeviceCustomString4, DeviceCustomString2
Stage 4: extend (5 consecutive steps)
| extend Category = coalesce(column_ifexists("DeviceEventCategory",""),extract(@'cat=([^;]+)(\;|$)', 1, AdditionalExtensions))
| extend Subcategory = extract(@'subcat=([^;]+)(\;|$)', 1, AdditionalExtensions)
| extend AlertDescription = strcat(Category, ': ', Subcategory)
| extend Status = tostring(DeviceCustomString2)
| extend IronVueUrl = replace(@'\\', @'', tostring(DeviceCustomString3))
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DeviceProduct | eq |
| field:"DeviceProduct" kind:eq value:"IronDefense" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DeviceCustomString2 | summarize |
DeviceCustomString4 | summarize |
Category | extend |
Subcategory | extend |
AlertDescription | extend |
Status | extend |
IronVueUrl | extend |
CYFIRMA - High severity TOR Node Network Indicators - Block Recommended Rule
#This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.
"This KQL query identifies network-based indicators from CYFIRMA intelligence that are associated with the role 'TOR'. These indicators may include IP addresses, domains, and URLs related to Tor network activity. Threat actors often use Tor for anonymous communication, command and control, data exfiltration, and evasion of network defenses."
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Reconnaissance | |
| Initial Access | |
| Persistence | |
| Command & Control | |
| Exfiltration |
Rule Body
id: fa53ac37-a646-4106-91b6-ce478a1b5323
name: CYFIRMA - High severity TOR Node Network Indicators - Block Recommended Rule
description: |
"This KQL query identifies network-based indicators from CYFIRMA intelligence that are associated with the role 'TOR'.
These indicators may include IP addresses, domains, and URLs related to Tor network activity.
Threat actors often use Tor for anonymous communication, command and control, data exfiltration, and evasion of network defenses."
version: 1.0.1
kind: Scheduled
severity: High
enabled: false
requiredDataConnectors:
- connectorId: CyfirmaCyberIntelligenceDC
dataTypes:
- CyfirmaIndicators_CL
query: |
//TOR Node Network Indicators - Block Recommended
let timeFrame= 5m;
CyfirmaIndicators_CL
| where ConfidenceScore >= 80
and TimeGenerated between (ago(timeFrame) .. now())
and pattern !contains 'file:hashes' and RecommendedActions has 'Block' and Roles has 'TOR'
| extend IPv4 = extract(@"ipv4-addr:value\s*=\s*'([^']+)'", 1, pattern)
| extend IPv6 = extract(@"ipv6-addr:value\s*=\s*'([^']+)'", 1, pattern)
| extend URL = extract(@"url:value\s*=\s*'([^']+)'", 1, pattern)
| extend Domain = extract(@"domain-name:value\s*=\s*'([^']+)'", 1, pattern)
| extend parsed = parse_json(extensions)
| extend extensionKeys = bag_keys(parsed)
| mv-expand extensionKeys
| extend extensionKeyStr = tostring(extensionKeys)
| extend ext = parsed[extensionKeyStr]
| extend props = ext.properties
| extend
extension_id = extensionKeyStr,
ASN_Owner = props.asn_owner,
ASN = props.asn,
ProviderName = 'CYFIRMA',
ProductName = 'DeCYFIR/DeTCT'
| project
IPv4,
IPv6,
URL,
Domain,
ThreatActors,
RecommendedActions,
Sources,
Roles,
Country,
IPAbuse,
name,
Description,
ConfidenceScore,
IndicatorID,
created,
modified,
valid_from,
Tags,
ThreatType,
TimeGenerated,
SecurityVendors,
ProductName,
ProviderName
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: GreaterThan
triggerThreshold: 0
suppressionDuration: 5m
suppressionEnabled: true
tactics:
- CommandAndControl
- Exfiltration
- InitialAccess
- Persistence
- Reconnaissance
relevantTechniques:
- T1090
- T1572
- T1048
- T1071
- T1189
- T1505
- T1595
- T1090.003
- T1048.002
- T1071.001
- T1505.003
- T1595.002
alertDetailsOverride:
alertDisplayNameFormat: "High-Confidence TOR Node Network Indicators - Block Recommended - {{name}} "
alertDescriptionFormat: "{{Description}} - {{name}} "
alertDynamicProperties:
- alertProperty: ProductName
value: ProductName
- alertProperty: ProviderName
value: ProviderName
customDetails:
ThreatActors: ThreatActors
Sources: Sources
RecommendedActions: RecommendedActions
Roles: Roles
Country: Country
Description: Description
ConfidenceScore: ConfidenceScore
SecurityVendors: SecurityVendors
IndicatorID: IndicatorID
Created: created
Modified: modified
ValidFrom: valid_from
Tags: Tags
ThreatType: ThreatType
TimeGenerated: TimeGenerated
IPAbuse: IPAbuse
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPv4
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPv6
- entityType: DNS
fieldMappings:
- identifier: DomainName
columnName: Domain
- entityType: URL
fieldMappings:
- identifier: Url
columnName: URL
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
eventGroupingSettings:
aggregationKind: AlertPerResult
Stages and Predicates
Parameters
let timeFrame = 5m;
Stage 1: source
CyfirmaIndicators_CL
Stage 2: where
| where ConfidenceScore >= 80
and TimeGenerated between (ago(timeFrame) .. now())
and pattern !contains 'file:hashes' and RecommendedActions has 'Block' and Roles has 'TOR'
Stage 3: extend (6 consecutive steps)
| extend IPv4 = extract(@"ipv4-addr:value\s*=\s*'([^']+)'", 1, pattern)
| extend IPv6 = extract(@"ipv6-addr:value\s*=\s*'([^']+)'", 1, pattern)
| extend URL = extract(@"url:value\s*=\s*'([^']+)'", 1, pattern)
| extend Domain = extract(@"domain-name:value\s*=\s*'([^']+)'", 1, pattern)
| extend parsed = parse_json(extensions)
| extend extensionKeys = bag_keys(parsed)
Stage 4: mv-expand
| mv-expand extensionKeys
Stage 5: extend (4 consecutive steps)
| extend extensionKeyStr = tostring(extensionKeys)
| extend ext = parsed[extensionKeyStr]
| extend props = ext.properties
| extend
extension_id = extensionKeyStr,
ASN_Owner = props.asn_owner,
ASN = props.asn,
ProviderName = 'CYFIRMA',
ProductName = 'DeCYFIR/DeTCT'
Stage 6: project
| project
IPv4,
IPv6,
URL,
Domain,
ThreatActors,
RecommendedActions,
Sources,
Roles,
Country,
IPAbuse,
name,
Description,
ConfidenceScore,
IndicatorID,
created,
modified,
valid_from,
Tags,
ThreatType,
TimeGenerated,
SecurityVendors,
ProductName,
ProviderName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ConfidenceScore | ge |
| field:"ConfidenceScore" kind:ge value:"80" |
RecommendedActions | match |
| field:"RecommendedActions" kind:match value:"Block" |
Roles | match |
| field:"Roles" kind:match value:"TOR" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
pattern | contains | file:hashes | excludes:pattern field:"pattern" value:"file:hashes" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ConfidenceScore | project |
Country | project |
Description | project |
Domain | project |
IPAbuse | project |
IPv4 | project |
IPv6 | project |
IndicatorID | project |
ProductName | project |
ProviderName | project |
RecommendedActions | project |
Roles | project |
SecurityVendors | project |
Sources | project |
Tags | project |
ThreatActors | project |
ThreatType | project |
TimeGenerated | project |
URL | project |
created | project |
modified | project |
name | project |
valid_from | project |
Detect service account login on new device
#This detection rule tries to flag suspicious logins on devices from service accounts, for which these service accounts did not login into those devices for the last 14 days. This might indicate that the service account is compromised and is being used for lateral movement into the environment. Most service accounts have a fairly static set of devices they authenticate to. Because of this, it is easier to flag deviations for service accounts compared to user accounts. However, some service accounts are known to dynamically log into devices based on observed events (susch as the MDI service accounts). Because of this some environment specific finetuning might be needed to reduce BP detections.
MITRE ATT&CK Coverage
Telemetry Coverage
Rule Body
// Get all enabled service accounts
let service_acc = (
IdentityInfo
| where Timestamp > ago(7d)
| where Type == "ServiceAccount" and IsAccountEnabled == 1
| distinct AccountName = tolower(AccountName)
);
// Get the history service account logins
let historic_events = (
DeviceLogonEvents
| where Timestamp between (ago(14d) .. ago(1h))
| where ActionType == "LogonSuccess"
| extend AccountName = tolower(AccountName)
| join kind=inner service_acc on AccountName
| summarize HistoricLogins = make_set(DeviceName) by AccountName
);
// Get the account logins done over Network
DeviceLogonEvents
| where Timestamp > ago(1h)
| where LogonType == "Network"
| where ActionType == "LogonSuccess"
| extend AccountName = tolower(AccountName)
// Join inner to only get known service account logins
| join kind=inner service_acc on AccountName
// Join inner to get a list of the historic device logins for the service accounts
| join kind=inner historic_events on AccountName
// Only get sign-ins where Device is not in the history logins
| extend HistoricLogins = tostring(HistoricLogins)
| where HistoricLogins !contains DeviceName
// Make output better
| project-away AccountName1, AccountName2
// Exclude MDI Service Account - CHANGE IF DIFFERENT FOR YOUR ORG
| where AccountName != "gsma_mdi$"
// Environment specific finetuning - begin
// Environment specific finetuning - end
// Get all enabled service accounts
let service_acc = (
IdentityInfo
| where TimeGenerated > ago(7d)
| where Type == "ServiceAccount" and IsAccountEnabled == 1
| distinct AccountName = tolower(AccountName)
);
// Get the history service account logins
let historic_events = (
DeviceLogonEvents
| where TimeGenerated between (ago(14d) .. ago(1h))
| where ActionType == "LogonSuccess"
| extend AccountName = tolower(AccountName)
| join kind=inner service_acc on AccountName
| summarize HistoricLogins = make_set(DeviceName) by AccountName
);
// Get the account logins done over Network
DeviceLogonEvents
| where TimeGenerated > ago(1h)
| where LogonType == "Network"
| where ActionType == "LogonSuccess"
| extend AccountName = tolower(AccountName)
// Join inner to only get known service account logins
| join kind=inner service_acc on AccountName
// Join inner to get a list of the historic device logins for the service accounts
| join kind=inner historic_events on AccountName
// Only get sign-ins where Device is not in the history logins
| extend HistoricLogins = tostring(HistoricLogins)
| where HistoricLogins !contains DeviceName
// Make output better
| project-away AccountName1, AccountName2
// Exclude MDI Service Account - CHANGE IF DIFFERENT FOR YOUR ORG
| where AccountName != "gsma_mdi$"
// Environment specific finetuning - begin
// Environment specific finetuning - end
Stages and Predicates
Let Binding: service_acc
let service_acc = (
IdentityInfo
| where Timestamp > ago(7d)
| where Type == "ServiceAccount" and IsAccountEnabled == 1
| distinct AccountName = tolower(AccountName)
);
Let Binding: historic_events
let historic_events = (
DeviceLogonEvents
| where Timestamp between (ago(14d) .. ago(1h))
| where ActionType == "LogonSuccess"
| extend AccountName = tolower(AccountName)
| join kind=inner service_acc on AccountName
| summarize HistoricLogins = make_set(DeviceName) by AccountName
);
Stage 1: source
let service_acc
Stage 2: source
let historic_events
Stage 3: source
DeviceLogonEvents
Stage 4: where
where Timestamp > ago(3600s)
Stage 5: where
where LogonType == "Network"
Stage 6: where
where ActionType == "LogonSuccess"
Stage 7: extend
extend AccountName
Stage 8: join
join kind=inner (service_acc) on AccountName
Stage 9: join
join kind=inner (historic_events) on AccountName
Stage 10: extend
extend HistoricLogins
Stage 11: where
where HistoricLogins !contains DeviceName
Stage 12: project-away
project-away AccountName1, AccountName2
Stage 13: where
where AccountName != "gsma_mdi$"
Stage 14: summarize aggregation inside the join branch
summarize by AccountName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AccountName | ne |
| field:"AccountName" kind:ne value:"gsma_mdi$" |
ActionType | eq |
| field:"ActionType" kind:eq value:"LogonSuccess" |
IsAccountEnabled | eq |
| field:"IsAccountEnabled" kind:eq value:"1" |
LogonType | eq |
| field:"LogonType" kind:eq value:"Network" |
Type | eq |
| field:"Type" kind:eq value:"ServiceAccount" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountName | summarize |
Guardian- Additional check JSON Policy Violation Detection
#'This alert creates an incident when Additional check JSON Policy Violation detected from the Guardian.'
Rule Body
id: 9ff28525-3c6f-47ba-806b-ffd913406989
name: Guardian- Additional check JSON Policy Violation Detection
description: |
'This alert creates an incident when Additional check JSON Policy Violation detected from the Guardian.'
severity: Informational
status: Available
requiredDataConnectors:
- connectorId: BoschAIShield
dataTypes:
- Guardian
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: |
Guardian
| where PolicyViolatedControlFeature =~ 'JSON'
| where Severity =~ 'Informational'
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: HostName
- identifier: NTDomain
columnName: NTDomain
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIP
eventGroupingSettings:
aggregationKind: SingleAlert
alertDetailsOverride:
alertDisplayNameFormat: Guardian- Additional check JSON Policy Violation detection
alertDescriptionFormat: |
This query detects Additional check JSON Policy Violation detected from the Guardian generated at {{TimeGenerated}}.\n\nPlease check the source for more information and investigate further.
alertTacticsColumnName: null
alertSeverityColumnName: Severity
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
Guardian
Stage 2: where
| where PolicyViolatedControlFeature =~ 'JSON'
Stage 3: where
| where Severity =~ 'Informational'
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
PolicyViolatedControlFeature | eq |
| field:"PolicyViolatedControlFeature" kind:eq value:"JSON" |
Severity | eq |
| field:"Severity" kind:eq value:"Informational" |
Guardian- Same Input/Output Language Detection Policy Violation Detection
#'This alert creates an incident when Same Input/Output Language Detection Policy Violation detected from the Guardian.'
Rule Body
id: b2841802-c53c-4667-be8a-9ea8771c944a
name: Guardian- Same Input/Output Language Detection Policy Violation Detection
description: |
'This alert creates an incident when Same Input/Output Language Detection Policy Violation detected from the Guardian.'
severity: Informational
status: Available
requiredDataConnectors:
- connectorId: BoschAIShield
dataTypes:
- Guardian
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: |
Guardian
| where PolicyViolatedControlFeature =~ 'Same Input/Output Language Detection'
| where Severity =~ 'Informational'
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: HostName
- identifier: NTDomain
columnName: NTDomain
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIP
eventGroupingSettings:
aggregationKind: SingleAlert
alertDetailsOverride:
alertDisplayNameFormat: Guardian- Same Input/Output Language Detection Policy Violation detection
alertDescriptionFormat: |
This query detects Same Input/Output Language Detection Policy Violation detected from the Guardian generated at {{TimeGenerated}}.\n\nPlease check the source for more information and investigate further.
alertTacticsColumnName: null
alertSeverityColumnName: Severity
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
Guardian
Stage 2: where
| where PolicyViolatedControlFeature =~ 'Same Input/Output Language Detection'
Stage 3: where
| where Severity =~ 'Informational'
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
PolicyViolatedControlFeature | eq |
| field:"PolicyViolatedControlFeature" kind:eq value:"Same Input/Output Language Detection" |
Severity | eq |
| field:"Severity" kind:eq value:"Informational" |
Hunt devices supporting MDE Containment
#This hunting query can help you finding which Defender for Endpoint enrolled devices support device containment. This is being done by looking at the client version and estimated time the version was available.
References
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Microsoft Defender for Endpoint | DeviceInfo action any: Device information |
Rule Body
// Paste your query here
// Gets the onboarded windows devices and checks containment support nuances
let onboardedWindows = DeviceInfo
| where OnboardingStatus == "Onboarded" and OSPlatform contains "Windows"
| distinct DeviceId, DeviceName, ClientVersion, OSPlatform
| parse ClientVersion with Major:int "." Minor:int "." Build:int "." Revision:int
// Reference: https://learn.microsoft.com/en-us/defender-endpoint/windows-whatsnew
| extend Date = case(
Minor >= 8760, "July-2024",
Minor >= 8750, "May-2024",
Minor >= 8735, "Feb-2024",
Minor >= 8672, "Dec-2023",
Minor >= 8560, "Sept-2023",
Minor > 8295, "May-2023",
Minor == 8295 and Revision >= 1023, "May-2023",
Minor == 8295 and Revision between (1019 .. 1023), "Jan/Feb-2023",
Minor > 8210, "Dec-2022",
Minor == 8210 and Build >= 22621 and Revision >= 1016, "Dec-2022",
Minor == 8210 and not(Build >= 22621 and Revision >= 1016), "Aug-2022",
"< Aug-2022"
)
// Containment without AH Audit supported from Nov-2022
// Containment with AH Audit supported from Mar-2023
| extend Containment = case(
Minor >= 8295, "Supported with AH Audit",
(Minor == 8210 and Build >= 22621 and Revision >= 1016) or Minor > 8210, "Supported without AH Audit",
"Unsupported"
);
// Gets onboarded non-windows devices, since containment is not supported here
let onboardedNonWindows = DeviceInfo
| where OnboardingStatus == "Onboarded" and OSPlatform !contains "Windows"
| distinct DeviceId, DeviceName, ClientVersion, OSPlatform
| extend Containment = "Unsupported";
// Get not-onboarded Servers
let notOnboardedServers = DeviceInfo
| where OnboardingStatus != "Onboarded" and DeviceType == "Server"
| distinct DeviceId, DeviceName, ClientVersion, OSPlatform
| extend Containment = "Unsupported";
// Union all and show diagram
union onboardedNonWindows, onboardedWindows, notOnboardedServers
| summarize count() by Containment
| render piechart
Stages and Predicates
Union (3 Sources)
Each leg below queries one source; the rule matches if any leg does. Sources: onboardedNonWindows, onboardedWindows, notOnboardedServers
Leg 1: onboardedNonWindows
Leg 2: onboardedWindows
Leg 3: notOnboardedServers
Applied to the Combined Result
| summarize count() by Containment | render piechart
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DeviceType | eq |
| field:"DeviceType" kind:eq value:"Server" |
OSPlatform | contains |
| field:"OSPlatform" kind:contains value:"Windows" |
OnboardingStatus | eq |
| field:"OnboardingStatus" kind:eq value:"Onboarded" |
OnboardingStatus | ne |
| field:"OnboardingStatus" kind:ne value:"Onboarded" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
OSPlatform | contains | Windows | excludes:OSPlatform field:"OSPlatform" value:"Windows" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Containment | summarize |
Lateral Movement via DCOM
#This query detects a fairly uncommon attack technique using the Windows Distributed Component Object Model (DCOM) to make a remote execution call to another computer system and gain lateral movement throughout the network. Ref: http://thenegative.zone/incident response/2017/02/04/MMC20.Application-Lateral-Movement-Analysis.html
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Lateral Movement |
Telemetry Coverage
| Provider | Record / event type |
|---|---|
| Sysmon | Event ID 1: Process creation |
Rule Body
id: 50cbf34a-4cdd-45d7-b3f5-8b53a1d0d14f
name: Lateral Movement via DCOM
description: |
'This query detects a fairly uncommon attack technique using the Windows Distributed Component Object Model (DCOM) to make a remote execution call to another computer system and gain lateral movement throughout the network.
Ref: http://thenegative.zone/incident%20response/2017/02/04/MMC20.Application-Lateral-Movement-Analysis.html'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: SecurityEvents
dataTypes:
- SecurityEvent
- connectorId: WindowsSecurityEvents
dataTypes:
- SecurityEvent
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- LateralMovement
relevantTechniques:
- T1021.003
query: |
Event
| where EventLog =~ "Microsoft-Windows-Sysmon/Operational" and EventID==1
| parse EventData with * 'CommandLine">' CommandLine "<" * 'ParentCommandLine">' ParentCommandLine "<" *
| where ParentCommandLine =~ "C:\\Windows\\System32\\svchost.exe -k DcomLaunch" and CommandLine =~ "C:\\Windows\\System32\\mmc.exe -Embedding"
| parse EventData with * 'ProcessGuid">' ProcessGuid "<" * 'Image">' Image "<" * 'Description">' Description "<" * 'CurrentDirectory">' CurrentDirectory "<" * 'User">' User "<" * 'LogonGuid">' LogonGuid "<" * 'ParentProcessGuid">' ParentProcessGuid "<" * 'ParentImage">' ParentImage "<" * 'ParentCommandLine">' ParentCommandLine "<" * 'ParentUser">' ParentUser "<" *
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by EventID, Computer, User, ParentImage, ParentProcessGuid, ParentCommandLine, ParentUser, Image, ProcessGuid, CommandLine, Description
| extend HostName = iif(Computer has '.',substring(Computer,0,indexof(Computer,'.')),Computer) , DnsDomain = iif(Computer has '.',substring(Computer,indexof(Computer,'.')+1),'')
entityMappings:
- entityType: Process
fieldMappings:
- identifier: CommandLine
columnName: CommandLine
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: Computer
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: DnsDomain
- entityType: Account
fieldMappings:
- identifier: Name
columnName: User
version: 1.0.3
kind: Scheduled
Stages and Predicates
Stage 1: source
Event
Stage 2: where
| where EventLog =~ "Microsoft-Windows-Sysmon/Operational" and EventID==1
Stage 3: parse
| parse EventData with * 'CommandLine">' CommandLine "<" * 'ParentCommandLine">' ParentCommandLine "<" *
Stage 4: where
| where ParentCommandLine =~ "C:\\Windows\\System32\\svchost.exe -k DcomLaunch" and CommandLine =~ "C:\\Windows\\System32\\mmc.exe -Embedding"
Stage 5: parse
| parse EventData with * 'ProcessGuid">' ProcessGuid "<" * 'Image">' Image "<" * 'Description">' Description "<" * 'CurrentDirectory">' CurrentDirectory "<" * 'User">' User "<" * 'LogonGuid">' LogonGuid "<" * 'ParentProcessGuid">' ParentProcessGuid "<" * 'ParentImage">' ParentImage "<" * 'ParentCommandLine">' ParentCommandLine "<" * 'ParentUser">' ParentUser "<" *
Stage 6: summarize
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by EventID, Computer, User, ParentImage, ParentProcessGuid, ParentCommandLine, ParentUser, Image, ProcessGuid, CommandLine, Description
Stage 7: extend
| extend HostName = iif(Computer has '.',substring(Computer,0,indexof(Computer,'.')),Computer) , DnsDomain = iif(Computer has '.',substring(Computer,indexof(Computer,'.')+1),'')
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
CommandLine | eq |
| field:"CommandLine" kind:eq value:"C:\Windows\System32\mmc.exe -Embedding" |
EventID | eq |
| field:"EventID" kind:eq value:"1" |
EventLog | eq |
| field:"EventLog" kind:eq value:"Microsoft-Windows-Sysmon/Operational" |
ParentCommandLine | eq |
| field:"ParentCommandLine" kind:eq value:"C:\Windows\System32\svchost.exe -k DcomLaunch" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
CommandLine | summarize |
Computer | summarize |
Description | summarize |
EndTime | summarize |
EventID | summarize |
Image | summarize |
ParentCommandLine | summarize |
ParentImage | summarize |
ParentProcessGuid | summarize |
ParentUser | summarize |
ProcessGuid | summarize |
StartTime | summarize |
User | summarize |
DnsDomain | extend |
HostName | extend |
M2131_EventLogManagementPostureChanged_EL3
#'This alert is desinged to monitor Azure policies aligned with the Maturity Model for Event Log Management (M-21-31) standard. The alert triggers when EL3 policy compliance falls below 70% within a 1 week timeframe.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Discovery |
Rule Body
id: 672bfd77-4542-4ef1-acf9-e006dcd70c51
name: M2131_EventLogManagementPostureChanged_EL3
description: |
'This alert is desinged to monitor Azure policies aligned with the Maturity Model for Event Log Management (M-21-31) standard. The alert triggers when EL3 policy compliance falls below 70% within a 1 week timeframe.'
severity: Medium
status: Available
requiredDataConnectors: []
queryFrequency: 7d
queryPeriod: 7d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Discovery
relevantTechniques:
- T1082
query: |
SecurityRecommendation
| where RecommendationDisplayName <> ""
| extend MaturityLevel=iff(RecommendationDisplayName has_any("agent","extension","retention","logs encryption","collect","retained","log profile","CloudTrail","metric","AWS","GCP","DNS","Auditing","Flow","logging","usage"), "Event Logging (EL0)",
iff(RecommendationDisplayName has_any("container registries","logic apps","Enhanced monitoring","IoT Hub","Event Hub","App Service", "Kubernetes","updates","email", "automation", "adaptive"), "Advanced Event Logging (EL3)",
iff(RecommendationDisplayName has_any("signatures","CMK","CMEK","double encryption","managed key","KMS","container","Watcher"), "Intermediate Event Logging (EL2)",
iff(RecommendationDisplayName has_any("Exploit Guard","endpoint protection","Antimalware","health","VPC","Defender","Vulnerabilities","vulnerability","diagnostic","Key","activity log alert",""), "Basic Event Logging (EL1)","Other"))))
| summarize arg_max(TimeGenerated, *) by RecommendationDisplayName, AssessedResourceId, MaturityLevel
| summarize Failed = countif(RecommendationState == "Unhealthy"), Passed = countif(RecommendationState == "Healthy"), Total = countif(RecommendationState == "Healthy" or RecommendationState == "Unhealthy") by MaturityLevel
| extend PassedControls = (Passed/todouble(Total))*100
| extend RemediationLink = strcat('https://portal.azure.com/#blade/Microsoft_Azure_Security/SecurityMenuBlade/22')
| project MaturityLevel, Total, PassedControls, Passed, Failed, RemediationLink, LastObserved=now()
| where MaturityLevel <> ''
| where MaturityLevel == "Advanced Event Logging (EL3)"
| where PassedControls < 70
//Adjust Either FailedRatePercentage or PasedRatePercentage Thresholds within Organizational Needs
| sort by PassedControls desc
| extend URLCustomEntity = RemediationLink
entityMappings:
- entityType: URL
fieldMappings:
- identifier: Url
columnName: URLCustomEntity
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
SecurityRecommendation
Stage 2: where
| where RecommendationDisplayName <> ""
Stage 3: extend
| extend MaturityLevel=iff(RecommendationDisplayName has_any("agent","extension","retention","logs encryption","collect","retained","log profile","CloudTrail","metric","AWS","GCP","DNS","Auditing","Flow","logging","usage"), "Event Logging (EL0)",
iff(RecommendationDisplayName has_any("container registries","logic apps","Enhanced monitoring","IoT Hub","Event Hub","App Service", "Kubernetes","updates","email", "automation", "adaptive"), "Advanced Event Logging (EL3)",
iff(RecommendationDisplayName has_any("signatures","CMK","CMEK","double encryption","managed key","KMS","container","Watcher"), "Intermediate Event Logging (EL2)",
iff(RecommendationDisplayName has_any("Exploit Guard","endpoint protection","Antimalware","health","VPC","Defender","Vulnerabilities","vulnerability","diagnostic","Key","activity log alert",""), "Basic Event Logging (EL1)","Other"))))
MaturityLevel =(RecommendationDisplayName has "agent" or RecommendationDisplayName has "extension" or RecommendationDisplayName has "retention" or RecommendationDisplayName has "logs encryption" or RecommendationDisplayName has "collect" or RecommendationDisplayName has "retained" or RecommendationDisplayName has "log profile" or RecommendationDisplayName has "CloudTrail" or RecommendationDisplayName has "metric" or RecommendationDisplayName has "AWS" or RecommendationDisplayName has "GCP" or RecommendationDisplayName has "DNS" or RecommendationDisplayName has "Auditing" or RecommendationDisplayName has "Flow" or RecommendationDisplayName has "logging" or RecommendationDisplayName has "usage")"Event Logging (EL0)"iff((RecommendationDisplayName has_any ("container registries", "logic apps", "Enhanced monitoring", "IoT Hub", "Event Hub", "App Service", "Kubernetes", "updates", "email", "automation", "adaptive")), "Advanced Event Logging (EL3)", iff((RecommendationDisplayName has_any ("signatures", "CMK", "CMEK", "double encryption", "managed key", "KMS", "container", "Watcher")), "Intermediate Event Logging (EL2)", iff((RecommendationDisplayName has_any ("Exploit Guard", "endpoint protection", "Antimalware", "health", "VPC", "Defender", "Vulnerabilities", "vulnerability", "diagnostic", "Key", "activity log alert", "")), "Basic Event Logging (EL1)", "Other")))Stage 4: summarize
| summarize arg_max(TimeGenerated, *) by RecommendationDisplayName, AssessedResourceId, MaturityLevel
Stage 5: summarize
| summarize Failed = countif(RecommendationState == "Unhealthy"), Passed = countif(RecommendationState == "Healthy"), Total = countif(RecommendationState == "Healthy" or RecommendationState == "Unhealthy") by MaturityLevel
Stage 6: extend
| extend PassedControls = (Passed/todouble(Total))*100
Stage 7: extend
| extend RemediationLink = strcat('https://portal.azure.com/#blade/Microsoft_Azure_Security/SecurityMenuBlade/22')
Stage 8: project
| project MaturityLevel, Total, PassedControls, Passed, Failed, RemediationLink, LastObserved=now()
Stage 9: where
| where MaturityLevel <> ''
Stage 10: where
| where MaturityLevel == "Advanced Event Logging (EL3)"
Stage 11: where
| where PassedControls < 70
Stage 12: sort
| sort by PassedControls desc
Stage 13: extend
| extend URLCustomEntity = RemediationLink
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
MaturityLevel | eq |
| field:"MaturityLevel" kind:eq value:"Advanced Event Logging (EL3)" |
PassedControls | lt |
| field:"PassedControls" kind:lt value:"70" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Failed | project |
LastObserved | project |
MaturityLevel | project |
Passed | project |
PassedControls | project |
RemediationLink | project |
Total | project |
URLCustomEntity | extend |
NRT New access credential added to Application or Service Principal
#This will alert when an admin or app owner account adds a new credential to an Application or Service Principal where a verify KeyCredential was already present for the app. If a threat actor obtains access to an account 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. Additional information on OAuth Credential Grants can be found in RFC 6749 Section 4.4 or https://docs.microsoft.com/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow For further information on AuditLogs please see https://docs.microsoft.com/azure/active-directory/reports-monitoring/reference-audit-activities.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Lateral Movement |
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
id: e42e889a-caaf-4dbb-aec6-371b37d64298
name: NRT New access credential added to Application or Service Principal
description: |
'This will alert when an admin or app owner account adds a new credential to an Application or Service Principal where a verify KeyCredential was already present for the app.
If a threat actor obtains access to an account 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.
Additional information on OAuth Credential Grants can be found in RFC 6749 Section 4.4 or https://docs.microsoft.com/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow
For further information on AuditLogs please see https://docs.microsoft.com/azure/active-directory/reports-monitoring/reference-audit-activities.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- AuditLogs
tactics:
- DefenseEvasion
relevantTechniques:
- T1550
tags:
- Solorigate
- NOBELIUM
query: |
AuditLogs
| where OperationName has_any ("Add service principal", "Certificates and secrets management") // captures "Add service principal", "Add service principal credentials", and "Update application - Certificates and secrets management" events
| where Result =~ "success"
| where tostring(InitiatedBy.user.userPrincipalName) has "@" or tostring(InitiatedBy.app.displayName) has "@"
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type =~ "Application"
| extend targetDisplayName = tostring(TargetResource.displayName),
targetId = tostring(TargetResource.id),
targetType = tostring(TargetResource.type),
keyEvents = TargetResource.modifiedProperties
)
| mv-apply Property = keyEvents on
(
where Property.displayName =~ "KeyDescription"
| extend new_value_set = parse_json(tostring(Property.newValue)),
old_value_set = parse_json(tostring(Property.oldValue))
)
| where old_value_set != "[]"
| extend diff = set_difference(new_value_set, old_value_set)
| where diff != "[]"
| parse diff with * "KeyIdentifier=" keyIdentifier:string ",KeyType=" keyType:string ",KeyUsage=" keyUsage:string ",DisplayName=" keyDisplayName:string "]" *
| where keyUsage =~ "Verify"
| mv-apply AdditionalDetail = AdditionalDetails on
(
where AdditionalDetail.key =~ "User-Agent"
| extend UserAgent = tostring(AdditionalDetail.value)
)
| extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
| extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
| extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend InitiatingIpAddress = tostring(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))
// The below line is currently commented out but Microsoft Sentinel users can modify this query to show only Application or only Service Principal events in their environment
//| where targetType =~ "Application" // or targetType =~ "ServicePrincipal"
| project-away diff, new_value_set, old_value_set
| project-reorder TimeGenerated, OperationName, InitiatingUserPrincipalName, InitiatingAadUserId, InitiatingAppName, InitiatingAppServicePrincipalId, InitiatingIpAddress, UserAgent, targetDisplayName, targetId, targetType, keyDisplayName, keyType, keyUsage, keyIdentifier, CorrelationId, TenantId
| extend InitiatedByName = tostring(split(InitiatingUserPrincipalName,'@',0)[0]), InitiatedByUPNSuffix = tostring(split(InitiatingUserPrincipalName,'@',1)[0])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: InitiatingUserPrincipalName
- identifier: Name
columnName: InitiatedByName
- identifier: UPNSuffix
columnName: InitiatedByUPNSuffix
- entityType: Account
fieldMappings:
- identifier: AadUserId
columnName: InitiatingAadUserId
- entityType: Account
fieldMappings:
- identifier: Name
columnName: InitiatingAppName
- identifier: AadUserId
columnName: InitiatingAppServicePrincipalId
- entityType: IP
fieldMappings:
- identifier: Address
columnName: InitiatingIpAddress
version: 1.0.5
kind: NRT
Stages and Predicates
Stage 1: source
AuditLogs
Stage 2: where
| where OperationName has_any ("Add service principal", "Certificates and secrets management")
Stage 3: where
| where Result =~ "success"
Stage 4: where
| where tostring(InitiatedBy.user.userPrincipalName) has "@" or tostring(InitiatedBy.app.displayName) has "@"
Stage 5: kusto:mv-apply
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type =~ "Application"
| extend targetDisplayName = tostring(TargetResource.displayName),
targetId = tostring(TargetResource.id),
targetType = tostring(TargetResource.type),
keyEvents = TargetResource.modifiedProperties
)
Stage 6: kusto:mv-apply
| mv-apply Property = keyEvents on
(
where Property.displayName =~ "KeyDescription"
| extend new_value_set = parse_json(tostring(Property.newValue)),
old_value_set = parse_json(tostring(Property.oldValue))
)
Stage 7: where
| where old_value_set != "[]"
Stage 8: extend
| extend diff = set_difference(new_value_set, old_value_set)
Stage 9: where
| where diff != "[]"
Stage 10: parse
| parse diff with * "KeyIdentifier=" keyIdentifier:string ",KeyType=" keyType:string ",KeyUsage=" keyUsage:string ",DisplayName=" keyDisplayName:string "]" *
Stage 11: where
| where keyUsage =~ "Verify"
Stage 12: kusto:mv-apply
| mv-apply AdditionalDetail = AdditionalDetails on
(
where AdditionalDetail.key =~ "User-Agent"
| extend UserAgent = tostring(AdditionalDetail.value)
)
Stage 13: extend (5 consecutive steps)
| extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
| extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
| extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend InitiatingIpAddress = tostring(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))
Stage 14: project-away
| project-away diff, new_value_set, old_value_set
Stage 15: project-reorder
| project-reorder TimeGenerated, OperationName, InitiatingUserPrincipalName, InitiatingAadUserId, InitiatingAppName, InitiatingAppServicePrincipalId, InitiatingIpAddress, UserAgent, targetDisplayName, targetId, targetType, keyDisplayName, keyType, keyUsage, keyIdentifier, CorrelationId, TenantId
Stage 16: extend
| extend InitiatedByName = tostring(split(InitiatingUserPrincipalName,'@',0)[0]), InitiatedByUPNSuffix = tostring(split(InitiatingUserPrincipalName,'@',1)[0])
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AdditionalDetail.key | eq |
| field:"AdditionalDetail.key" kind:eq value:"User-Agent" |
InitiatedBy.app.displayName | match |
| field:"InitiatedBy.app.displayName" kind:match value:"@" |
InitiatedBy.user.userPrincipalName | match |
| field:"InitiatedBy.user.userPrincipalName" kind:match value:"@" |
OperationName | match |
| field:"OperationName" kind:match |
Property.displayName | eq |
| field:"Property.displayName" kind:eq value:"KeyDescription" |
Result | eq |
| field:"Result" kind:eq value:"success" |
TargetResource.type | eq |
| field:"TargetResource.type" kind:eq value:"Application" |
diff | ne |
| field:"diff" kind:ne value:"[]" |
keyUsage | eq |
| field:"keyUsage" kind:eq value:"Verify" |
old_value_set | ne |
| field:"old_value_set" kind:ne value:"[]" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
InitiatingAppName | extend |
InitiatingAppServicePrincipalId | extend |
InitiatingUserPrincipalName | extend |
InitiatingAadUserId | extend |
InitiatingIpAddress | extend |
InitiatedByName | extend |
InitiatedByUPNSuffix | extend |
NRT User added to Microsoft Entra ID Privileged Groups
#This will alert when a user is added to any of the Privileged Groups. For further information on AuditLogs please see https://docs.microsoft.com/azure/active-directory/reports-monitoring/reference-audit-activities. For Administrator role permissions in Microsoft Entra ID please see https://docs.microsoft.com/azure/active-directory/users-groups-roles/directory-assign-admin-roles
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| Privilege Escalation |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Azure | Add eligible member to role |
| Azure | Add member to role |
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] Mass Privileged Role Change Activity Detected (Kusto)
- [Entra ID] Privilege Elevation Request Denied (Kusto)
- [Entra ID] Privileged Role Assigned to a New User (Kusto)
- [Entra ID] Privileged Role Assigned to User (Kusto)
- Account Elevated to New Role (Kusto)
- Admin promotion after Role Management Application Permission Grant (Kusto)
- App Assigned To Azure RBAC/Microsoft Entra Role (Sigma)
Rule Body
id: 70fc7201-f28e-4ba7-b9ea-c04b96701f13
name: NRT User added to Microsoft Entra ID Privileged Groups
description: |
'This will alert when a user is added to any of the Privileged Groups.
For further information on AuditLogs please see https://docs.microsoft.com/azure/active-directory/reports-monitoring/reference-audit-activities.
For Administrator role permissions in Microsoft Entra ID please see https://docs.microsoft.com/azure/active-directory/users-groups-roles/directory-assign-admin-roles'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- AuditLogs
tactics:
- Persistence
- PrivilegeEscalation
relevantTechniques:
- T1098
- T1078
tags:
- DEV-0537
query: |
let OperationList = dynamic(["Add member to role","Add eligible member to role"]);
let PrivilegedGroups = dynamic(["UserAccountAdmins","PrivilegedRoleAdmins","TenantAdmins","PrivilegedAuthenticationAdmins"]);
AuditLogs
| where Category =~ "RoleManagement"
| where OperationName in~ (OperationList)
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type =~ "User"
| extend TargetUserPrincipalName = tostring(TargetResource.userPrincipalName),
modProps = TargetResource.modifiedProperties
)
| mv-apply Property = modProps on
(
where Property.displayName =~ "Role.WellKnownObjectName"
| extend DisplayName = trim('"',tostring(Property.displayName)),
GroupName = trim('"',tostring(Property.newValue))
)
| extend InitiatingAppId = tostring(InitiatedBy.app.appId)
| extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
| extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
| extend InitiatingAppServicePrincipalName = tostring(InitiatedBy.app.servicePrincipalName)
| extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend InitiatingIpAddress = tostring(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))
| extend InitiatingUserRoles = InitiatedBy.user.roles
| where GroupName in~ (PrivilegedGroups)
// If you don't want to alert for operations from PIM, remove below filtering for MS-PIM.
//| where InitiatingAppName != "MS-PIM" and InitiatingAppName != "MS-PIM-Fairfax"
| project TimeGenerated, AADOperationType, Category, OperationName, AADTenantId, InitiatingUserPrincipalName, InitiatingAadUserId, InitiatingAppName, InitiatingAppId, InitiatingAppServicePrincipalId, InitiatingIpAddress, InitiatingUserRoles, DisplayName, GroupName, TargetUserPrincipalName
| extend InitiatedByName = tostring(split(InitiatingUserPrincipalName,'@',0)[0]), InitiatedByUPNSuffix = tostring(split(InitiatingUserPrincipalName,'@',1)[0])
| extend TargetName = tostring(split(TargetUserPrincipalName,'@',0)[0]), TargetUPNSuffix = tostring(split(TargetUserPrincipalName,'@',1)[0])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: InitiatingUserPrincipalName
- identifier: Name
columnName: InitiatedByName
- identifier: UPNSuffix
columnName: InitiatedByUPNSuffix
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: TargetUserPrincipalName
- identifier: Name
columnName: TargetName
- identifier: UPNSuffix
columnName: TargetUPNSuffix
- entityType: Account
fieldMappings:
- identifier: AadUserId
columnName: InitiatingAadUserId
- entityType: Account
fieldMappings:
- identifier: AadUserId
columnName: InitiatingAppServicePrincipalId
- entityType: IP
fieldMappings:
- identifier: Address
columnName: InitiatingIpAddress
version: 1.0.7
kind: NRT
Stages and Predicates
Parameters
let OperationList = dynamic(["Add member to role","Add eligible member to role"]);
let PrivilegedGroups = dynamic(["UserAccountAdmins","PrivilegedRoleAdmins","TenantAdmins","PrivilegedAuthenticationAdmins"]);
Stage 1: source
AuditLogs
Stage 2: where
| where Category =~ "RoleManagement"
Stage 3: where
| where OperationName in~ (OperationList)
Stage 4: kusto:mv-apply
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type =~ "User"
| extend TargetUserPrincipalName = tostring(TargetResource.userPrincipalName),
modProps = TargetResource.modifiedProperties
)
Stage 5: kusto:mv-apply
| mv-apply Property = modProps on
(
where Property.displayName =~ "Role.WellKnownObjectName"
| extend DisplayName = trim('"',tostring(Property.displayName)),
GroupName = trim('"',tostring(Property.newValue))
)
Stage 6: extend (8 consecutive steps)
| extend InitiatingAppId = tostring(InitiatedBy.app.appId)
| extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
| extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
| extend InitiatingAppServicePrincipalName = tostring(InitiatedBy.app.servicePrincipalName)
| extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend InitiatingIpAddress = tostring(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))
| extend InitiatingUserRoles = InitiatedBy.user.roles
Stage 7: where
| where GroupName in~ (PrivilegedGroups)
Stage 8: project
| project TimeGenerated, AADOperationType, Category, OperationName, AADTenantId, InitiatingUserPrincipalName, InitiatingAadUserId, InitiatingAppName, InitiatingAppId, InitiatingAppServicePrincipalId, InitiatingIpAddress, InitiatingUserRoles, DisplayName, GroupName, TargetUserPrincipalName
Stage 9: extend
| extend InitiatedByName = tostring(split(InitiatingUserPrincipalName,'@',0)[0]), InitiatedByUPNSuffix = tostring(split(InitiatingUserPrincipalName,'@',1)[0])
Stage 10: extend
| extend TargetName = tostring(split(TargetUserPrincipalName,'@',0)[0]), TargetUPNSuffix = tostring(split(TargetUserPrincipalName,'@',1)[0])
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Category | eq |
| field:"Category" kind:eq value:"RoleManagement" |
GroupName | in |
| field:"GroupName" kind:in |
OperationName | in |
| field:"OperationName" kind:in |
Property.displayName | eq |
| field:"Property.displayName" kind:eq value:"Role.WellKnownObjectName" |
TargetResource.type | eq |
| field:"TargetResource.type" kind:eq value:"User" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AADOperationType | project |
AADTenantId | project |
Category | project |
DisplayName | project |
GroupName | project |
InitiatingAadUserId | project |
InitiatingAppId | project |
InitiatingAppName | project |
InitiatingAppServicePrincipalId | project |
InitiatingIpAddress | project |
InitiatingUserPrincipalName | project |
InitiatingUserRoles | project |
OperationName | project |
TargetUserPrincipalName | project |
TimeGenerated | project |
InitiatedByName | extend |
InitiatedByUPNSuffix | extend |
TargetName | extend |
TargetUPNSuffix | extend |
Pathlock TDnR - Authorization Profile Changes
#Detects changes to SAP authorization profiles, forwarded by Pathlock Threat Detection and Response. Unauthorized profile modifications may grant excessive privileges, create privilege escalation paths, or be used to establish persistent privileged access in SAP systems.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| Privilege Escalation |
Rule Body
id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c41
name: Pathlock TDnR - Authorization Profile Changes
kind: Scheduled
description: >-
Detects changes to SAP authorization profiles, forwarded by Pathlock Threat Detection and Response.
Unauthorized profile modifications may grant excessive privileges, create privilege escalation paths,
or be used to establish persistent privileged access in SAP systems.
severity: High
status: Available
requiredDataConnectors:
- connectorId: Pathlock_TDnR
dataTypes:
- Pathlock_TDnR_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- PrivilegeEscalation
- Persistence
relevantTechniques:
- T1548
- T1098
query: |
Pathlock_TDnR_CL
| where DataSource == "PROFILE_CHANGES"
| project TimeGenerated, Sysid, DataSource, Eventid, Instance, Hostname, Bname,
Tcode, Report, Area, Subid, SrcIp, DestIp, AffectedUser, LogLine,
MsgType, MsgId, MsgNo, MessageV1, MessageV2, MessageV3, MessageV4, CentralTs
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: Bname
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: Hostname
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIp
eventGroupingSettings:
aggregationKind: SingleAlert
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 5h
matchingMethod: AnyAlert
groupByEntities: []
groupByAlertDetails: []
groupByCustomDetails: []
suppressionDuration: 5h
suppressionEnabled: false
version: 1.0.0
Stages and Predicates
Stage 1: source
Pathlock_TDnR_CL
Stage 2: where
| where DataSource == "PROFILE_CHANGES"
Stage 3: project
| project TimeGenerated, Sysid, DataSource, Eventid, Instance, Hostname, Bname,
Tcode, Report, Area, Subid, SrcIp, DestIp, AffectedUser, LogLine,
MsgType, MsgId, MsgNo, MessageV1, MessageV2, MessageV3, MessageV4, CentralTs
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DataSource | eq |
| field:"DataSource" kind:eq value:"PROFILE_CHANGES" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AffectedUser | project |
Area | project |
Bname | project |
CentralTs | project |
DataSource | project |
DestIp | project |
Eventid | project |
Hostname | project |
Instance | project |
LogLine | project |
MessageV1 | project |
MessageV2 | project |
MessageV3 | project |
MessageV4 | project |
MsgId | project |
MsgNo | project |
MsgType | project |
Report | project |
SrcIp | project |
Subid | project |
Sysid | project |
Tcode | project |
TimeGenerated | project |
Pathlock TDnR - User-Role Assignment Changes
#Detects changes to user-to-role assignments in SAP (SU01 roles tab), forwarded by Pathlock Threat Detection and Response. Unauthorized role assignments are the primary mechanism for granting and revoking SAP access, and unexpected changes may indicate privilege escalation, account backdooring, or unauthorized access provisioning.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| Privilege Escalation |
Rule Body
id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c76
name: Pathlock TDnR - User-Role Assignment Changes
kind: Scheduled
description: >-
Detects changes to user-to-role assignments in SAP (SU01 roles tab), forwarded by Pathlock
Threat Detection and Response. Unauthorized role assignments are the primary mechanism for
granting and revoking SAP access, and unexpected changes may indicate privilege escalation,
account backdooring, or unauthorized access provisioning.
severity: High
status: Available
requiredDataConnectors:
- connectorId: Pathlock_TDnR
dataTypes:
- Pathlock_TDnR_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- PrivilegeEscalation
- Persistence
relevantTechniques:
- T1548
- T1098
query: |
Pathlock_TDnR_CL
| where DataSource == "USER_ROLES"
| project TimeGenerated, Sysid, DataSource, Eventid, Instance, Hostname, Bname,
Tcode, Report, Area, Subid, SrcIp, DestIp, AffectedUser, LogLine,
MsgType, MsgId, MsgNo, MessageV1, MessageV2, MessageV3, MessageV4, CentralTs
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: Bname
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: Hostname
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIp
eventGroupingSettings:
aggregationKind: SingleAlert
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 5h
matchingMethod: AnyAlert
groupByEntities: []
groupByAlertDetails: []
groupByCustomDetails: []
suppressionDuration: 5h
suppressionEnabled: false
version: 1.0.0
Stages and Predicates
Stage 1: source
Pathlock_TDnR_CL
Stage 2: where
| where DataSource == "USER_ROLES"
Stage 3: project
| project TimeGenerated, Sysid, DataSource, Eventid, Instance, Hostname, Bname,
Tcode, Report, Area, Subid, SrcIp, DestIp, AffectedUser, LogLine,
MsgType, MsgId, MsgNo, MessageV1, MessageV2, MessageV3, MessageV4, CentralTs
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DataSource | eq |
| field:"DataSource" kind:eq value:"USER_ROLES" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AffectedUser | project |
Area | project |
Bname | project |
CentralTs | project |
DataSource | project |
DestIp | project |
Eventid | project |
Hostname | project |
Instance | project |
LogLine | project |
MessageV1 | project |
MessageV2 | project |
MessageV3 | project |
MessageV4 | project |
MsgId | project |
MsgNo | project |
MsgType | project |
Report | project |
SrcIp | project |
Subid | project |
Sysid | project |
Tcode | project |
TimeGenerated | project |
Potentially Relayed NTLM Authentication - Microsoft Sentinel
#The below query detects Kerberos logons of computer accounts where there isn't any ticket request in the last 12h (10h is the default ticket expiration) coming from the same IpAddress with the same TargetUserName. The query can be enriched further if needed.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access | No specific technique |
Telemetry Coverage
Rule Body
// Author : Cyb3rMonk(https://twitter.com/Cyb3rMonk, https://mergene.medium.com)
//
// Link to original post:
// https://posts.bluraven.io/detecting-kerberos-relaying-e6be66fa647c
//
// Description: This query detects Kerberos logons of computer accounts where there isn't any ticket request in the last 12h (10h is the default ticket expiration) coming from the same IpAddress with the same TargetUserName. The query can be enriched further if needed.
//
// Query parameters:
//
let Ticket_Requests = materialize (
SecurityEvent
| where TimeGenerated > ago(12h)
| where EventID == 4769
| where EventData has '<Data Name="Status">0x0</Data>'
| where EventData !has'<Data Name="IpAddress">::1</Data>'
| parse EventData with * 'TargetUserName">' TargetUserName '</Data' * 'TargetDomainName">' TargetDomainName '</Data' * 'ServiceName">' ServiceName '<' * 'IpAddress">::ffff:' IpAddress '<' * 'Status">' Status '<' *
| where TargetUserName !has ServiceName
| where TargetUserName contains "$"
| where ServiceName has "$"
| project TimeGenerated, TargetUserName=tolower(TargetUserName), TargetDomainName, ServiceName=tolower(replace_string(ServiceName, '$', '')), IpAddress, Status
)
;
let Suspicious_Logons =
Ticket_Requests
| join kind=rightanti (
SecurityEvent
| where TimeGenerated > ago(1h)
| where EventID == 4624
| where AuthenticationPackageName == "Kerberos"
| where IpAddress !in ('-', '::1', '127.0.0.1')
| where IpAddress !startswith "169.254."
| where Account endswith_cs "$"
| project TimeGenerated, Computer = tolower(replace_regex(Computer, @'(\w+)\..*', @'\1')), Account, TargetUserName=tolower(TargetUserName), IpAddress
| where TargetUserName !has Computer
) on IpAddress, $left.ServiceName==$right.Computer
;
Suspicious_Logons
| join kind=leftouter (
Ticket_Requests
| extend TargetUserName = replace_regex(TargetUserName, @'(\w+\$)@.*', @'\1')
) on IpAddress, TargetUserName
| summarize FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated), count(), dcount(ServiceName) by TargetUserName, IpAddress
// Filter results
// we don't expect a successful ticket request coming from the rogue(attacker) device befor the relaying attack.
// If there is at least one ticket request coming from the suspicious IP with the same TargetUserName, assume it's a legitimate activity.
| where isempty(dcount_ServiceName)
Stages and Predicates
let Ticket_Requests and let Suspicious_Logons are inlined into the numbered stages below.
Stage 0: let
let Ticket_Requests = materialize(<inlined as stages below>);
let Suspicious_Logons = Ticket_Requests <inlined as stages below>;
Stages 1 to 13 define let Suspicious_Logons as the rule's main pipeline source. Stages 14 to 16 use it.
Stage 1: source
let Ticket_Requests
Stage 2: source
let Suspicious_Logons
Stage 3: source
SecurityEvent
Stage 4: where
where TimeGenerated > ago(43200s)
Stage 5: where
where EventID == 4769
Stage 6: where
where EventData has "<Data Name=\"Status\">0x0</Data>"
Stage 7: where
where not (EventData has "<Data Name=\"IpAddress\">::1</Data>")
Stage 8: parse
parse
Stage 9: where
where TargetUserName =~ ServiceName
Stage 10: where
where TargetUserName contains "$"
Stage 11: where
where ServiceName has "$"
Stage 12: project
project IpAddress, ServiceName, Status, TargetDomainName, TargetUserName, TimeGenerated
Stage 13: join
| join kind=rightanti (
SecurityEvent
| where TimeGenerated > ago(1h)
| where EventID == 4624
| where AuthenticationPackageName == "Kerberos"
| where IpAddress !in ('-', '::1', '127.0.0.1')
| where IpAddress !startswith "169.254."
| where Account endswith_cs "$"
| project TimeGenerated, Computer = tolower(replace_regex(Computer, @'(\w+)\..*', @'\1')), Account, TargetUserName=tolower(TargetUserName), IpAddress
| where TargetUserName !has Computer
) on IpAddress, $left.ServiceName==$right.Computer
Stage 14: join
Suspicious_Logons
| join kind=leftouter (
Ticket_Requests
| extend TargetUserName = replace_regex(TargetUserName, @'(\w+\$)@.*', @'\1')
) on IpAddress, TargetUserName
Stage 15: summarize
summarize FirstSeen, LastSeen by TargetUserName, IpAddress
Stage 16: where
where isempty(dcount_ServiceName)
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Account | ends_with |
| field:"Account" kind:ends_with value:"$" |
AuthenticationPackageName | eq |
| field:"AuthenticationPackageName" kind:eq value:"Kerberos" |
EventData | match |
| field:"EventData" kind:match |
EventID | eq |
| field:"EventID" kind:eq |
ServiceName | match |
| field:"ServiceName" kind:match value:"$" |
TargetUserName | contains |
| field:"TargetUserName" kind:contains value:"$" |
dcount_ServiceName | is_null | field:"dcount_ServiceName" kind:is_null |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
EventData | match | <Data Name="IpAddress">::1</Data> | excludes:EventData |
IpAddress | in | -, 127.0.0.1, ::1 | excludes:IpAddress field:"IpAddress" value:"-" field:"IpAddress" value:"127.0.0.1" field:"IpAddress" value:"::1" |
IpAddress | starts_with | 169.254. | excludes:IpAddress field:"IpAddress" value:"169.254." |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
FirstSeen | summarize |
IpAddress | summarize |
LastSeen | summarize |
TargetUserName | summarize |
Power Apps - Multiple users access a malicious link after launching new app
#Identifies a chain of events, where a new Power App is created, followed by mulitple users launching the app within the detection window and clicking on the same malicious URL.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Telemetry Coverage
Rule Body
id: 4bd7e93a-0646-4e02-8dcb-aa16d16618f4
kind: Scheduled
name: Power Apps - Multiple users access a malicious link after launching new app
description: Identifies a chain of events, where a new Power App is created, followed
by mulitple users launching the app within the detection window and clicking on
the same malicious URL.
severity: High
status: Available
requiredDataConnectors:
- connectorId: PowerPlatformAdmin
dataTypes:
- PowerPlatformAdminActivity
- connectorId: MicrosoftThreatProtection
dataTypes:
- UrlClickEvents
- connectorId: ThreatIntelligence
dataTypes:
- ThreatIntelligenceIndicator
- connectorId: ThreatIntelligenceTaxii
dataTypes:
- ThreatIntelligenceIndicator
- connectorId: MicrosoftDefenderThreatIntelligence
dataTypes:
- ThreatIntelligenceIndicator
- connectorId: ThreatIntelligence
dataTypes:
- ThreatIntelligenceIndicator
- connectorId: ThreatIntelligenceTaxii
dataTypes:
- ThreatIntelligenceIndicator
- connectorId: MicrosoftDefenderThreatIntelligence
dataTypes:
- ThreatIntelligenceIndicator
- connectorId: MicrosoftThreatProtection
dataTypes:
- UrlClickEvents
- connectorId: AzureActiveDirectoryIdentityProtection
dataTypes:
- SecurityAlert
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1189
- T1566
query: |
// Define a threshold (distinct_user_launch_threshold) for
// the minimum number of users who launched an app
// to be in scope of this detection
let distinct_user_launch_threshold = 2;
// Define a threshold for the minumum number of users
// who clicked the same malicious link after launching the app
// to be in scope of this detection
let distinct_user_url_click_threshold = 2;
let query_frequency = 1h;
let query_lookback = 14d;
let new_app_creation_activity = materialize(
PowerPlatformAdminActivity
| where TimeGenerated >= ago (query_lookback)
| where EventOriginalType == "CreatePowerApp"
| extend Properties = tostring(PropertyCollection)
| extend SrcIpAddr = extract(@'"enduser.ip_address","Value":"([^"]+)"', 1, Properties)
| extend SrcIpAddr = iif(SrcIpAddr startswith '::ffff:', replace_string(SrcIpAddr, '::ffff:', ''), SrcIpAddr)
| extend AppId = extract(@'"powerplatform.analytics.resource.power_app.id","Value":"([^"]+)"', 1, Properties)
| extend AppId = tolower(replace_string(AppId, '/providers/Microsoft.PowerApps/apps/', ''))
| extend
AppName = extract(@'"powerplatform.analytics.resource.power_app.display_name","Value":"([^"]+)"', 1, Properties),
EnvironmentId = extract(@'"powerplatform.analytics.resource.environment.id","Value":"([^"]+)"', 1, Properties)
| project-rename
AppCreatedTime = TimeGenerated,
AppCreator = ActorName,
AppCreatorIpAddr = SrcIpAddr
);
let distinct_apps = new_app_creation_activity
| distinct AppName;
let new_app_launch_activity = materialize(
new_app_creation_activity
| join kind=inner (
PowerPlatformAdminActivity
| where TimeGenerated >= ago (query_lookback)
| where EventOriginalType == "LaunchPowerApp"
| where PropertyCollection has_any (distinct_apps)
| extend Properties = tostring(PropertyCollection)
| extend AppName = extract(@'"powerplatform.analytics.resource.power_app.display_name","Value":"([^"]+)"', 1, Properties)
| summarize FirstAppLaunchTime = min(TimeGenerated) by ActorName, AppName)
on AppName
| where FirstAppLaunchTime > AppCreatedTime
);
let new_app_launch_users = new_app_launch_activity
| summarize LaunchCount = dcount(ActorName) by AppName
| where LaunchCount > distinct_user_launch_threshold
| join kind=inner new_app_launch_activity on AppName
| summarize
by
ActorName,
FirstAppLaunchTime,
AppName,
AppId,
EnvironmentId,
AppCreator,
AppCreatorIpAddr;
let detected_urls = union isfuzzy=true
(
SecurityAlert
| where TimeGenerated >= ago (query_lookback)
| where Entities has_cs '"Type":"url"'
| mv-expand todynamic(Entities)
| where tostring(Entities.Type) == "url"
| project Url = tostring(Entities.Url), Source = "SecurityAlert"
),
(
ThreatIntelligenceIndicator
| where TimeGenerated >= ago(query_lookback)
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
| where Active == true and ExpirationDateTime > now()
| where isnotempty(isnotempty(Url))
| project Url, Source = "ThreatIntelligence"
)
| summarize by Url, Source;
let url_click_events = materialize(
union isfuzzy=true
(
UrlClickEvents
| where TimeGenerated >= ago(query_frequency)
| where isnotempty(ThreatTypes)
| join kind=inner (new_app_launch_users) on $left.AccountUpn == $right.ActorName
| where TimeGenerated between (FirstAppLaunchTime .. (FirstAppLaunchTime + 1h))
| summarize by ActorName, Url, Source = "MicrosoftDefender"
),
(
_Im_WebSession
| where TimeGenerated >= ago(query_frequency)
| join kind=inner (new_app_launch_users) on $left.SrcUsername == $right.ActorName
| join kind=inner (detected_urls) on Url
| where TimeGenerated between (FirstAppLaunchTime .. (FirstAppLaunchTime + 1h))
| summarize by ActorName, Url, Source
)
);
let distinct_url_click_events_count = toscalar(
url_click_events
| summarize DistinctUserCount = dcount(ActorName) by Url
| where DistinctUserCount > distinct_user_url_click_threshold
| summarize sum(DistinctUserCount)
);
url_click_events
| summarize DistinctUserCount = dcount(ActorName) by Url
| where DistinctUserCount >= distinct_user_url_click_threshold
| join kind=inner url_click_events on Url
| join kind=inner (new_app_launch_users) on ActorName
| extend
PowerAppsEntityId = 27593,
DataverseId = 32780,
AccountName = tostring(split(ActorName, '@')[0]),
UPNSuffix = tostring(split(ActorName, '@')[1])
| project
FirstAppLaunchTime,
AppCreator,
AppName,
AppId,
ImpactedUser = ActorName,
AccountName,
UPNSuffix,
EnvironmentId,
Url,
Source,
PowerAppsEntityId
eventGroupingSettings:
aggregationKind: AlertPerResult
entityMappings:
- entityType: CloudApplication
fieldMappings:
- identifier: AppId
columnName: PowerAppsEntityId
- identifier: InstanceName
columnName: AppName
- entityType: URL
fieldMappings:
- identifier: Url
columnName: Url
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: AppCreator
- entityType: Account
fieldMappings:
- identifier: UPNSuffix
columnName: UPNSuffix
- identifier: Name
columnName: AccountName
alertDetailsOverride:
alertDisplayNameFormat: 'Possible malicious app detected - {{AppName}} '
alertDescriptionFormat: 'Multiple users opened a malicious link after launching
app {{AppName}}. Click here to navigate to the Power Apps Portal to examine the
app: https://make.powerapps.com/environments/{{EnvironmentId}}/apps'
customDetails:
Environment: EnvironmentId
PowerAppsAppName: AppName
PowerAppsApp: AppId
AppCreator: AppCreator
version: 3.2.0
Stages and Predicates
Parameters
let distinct_user_launch_threshold = 2;
let distinct_user_url_click_threshold = 2;
let query_frequency = 1h;
let query_lookback = 14d;
Let Binding: new_app_creation_activity
let new_app_creation_activity = materialize(
PowerPlatformAdminActivity
| where TimeGenerated >= ago (query_lookback)
| where EventOriginalType == "CreatePowerApp"
| extend Properties = tostring(PropertyCollection)
| extend SrcIpAddr = extract(@'"enduser.ip_address","Value":"([^"]+)"', 1, Properties)
| extend SrcIpAddr = iif(SrcIpAddr startswith '::ffff:', replace_string(SrcIpAddr, '::ffff:', ''), SrcIpAddr)
| extend AppId = extract(@'"powerplatform.analytics.resource.power_app.id","Value":"([^"]+)"', 1, Properties)
| extend AppId = tolower(replace_string(AppId, '/providers/Microsoft.PowerApps/apps/', ''))
| extend
AppName = extract(@'"powerplatform.analytics.resource.power_app.display_name","Value":"([^"]+)"', 1, Properties),
EnvironmentId = extract(@'"powerplatform.analytics.resource.environment.id","Value":"([^"]+)"', 1, Properties)
| project-rename
AppCreatedTime = TimeGenerated,
AppCreator = ActorName,
AppCreatorIpAddr = SrcIpAddr
);
Let Binding: distinct_apps
let distinct_apps = new_app_creation_activity
| distinct AppName;
Let Binding: new_app_launch_activity
let new_app_launch_activity = materialize(
new_app_creation_activity
| join kind=inner (
PowerPlatformAdminActivity
| where TimeGenerated >= ago (query_lookback)
| where EventOriginalType == "LaunchPowerApp"
| where PropertyCollection has_any (distinct_apps)
| extend Properties = tostring(PropertyCollection)
| extend AppName = extract(@'"powerplatform.analytics.resource.power_app.display_name","Value":"([^"]+)"', 1, Properties)
| summarize FirstAppLaunchTime = min(TimeGenerated) by ActorName, AppName)
on AppName
| where FirstAppLaunchTime > AppCreatedTime
);
Let Binding: new_app_launch_users
let new_app_launch_users = new_app_launch_activity
| summarize LaunchCount = dcount(ActorName) by AppName
| where LaunchCount > distinct_user_launch_threshold
| join kind=inner new_app_launch_activity on AppName
| summarize
by
ActorName,
FirstAppLaunchTime,
AppName,
AppId,
EnvironmentId,
AppCreator,
AppCreatorIpAddr;
Let Binding: detected_urls
let detected_urls = union isfuzzy=true
(
SecurityAlert
| where TimeGenerated >= ago (query_lookback)
| where Entities has_cs '"Type":"url"'
| mv-expand todynamic(Entities)
| where tostring(Entities.Type) == "url"
| project Url = tostring(Entities.Url), Source = "SecurityAlert"
),
(
ThreatIntelligenceIndicator
| where TimeGenerated >= ago(query_lookback)
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
| where Active == true and ExpirationDateTime > now()
| where isnotempty(isnotempty(Url))
| project Url, Source = "ThreatIntelligence"
)
| summarize by Url, Source;
Let Binding: distinct_url_click_events_count
let distinct_url_click_events_count = toscalar(
url_click_events
| summarize DistinctUserCount = dcount(ActorName) by Url
| where DistinctUserCount > distinct_user_url_click_threshold
| summarize sum(DistinctUserCount)
);
Union isfuzzy=true (2 Sources)
Each leg below queries one source; the rule matches if any leg does. Sources: UrlClickEvents, _Im_WebSession
Leg 1: UrlClickEvents
UrlClickEvents
| where TimeGenerated >= ago(query_frequency)
| where isnotempty(ThreatTypes)
| join kind=inner (new_app_launch_users) on $left.AccountUpn == $right.ActorName
| where TimeGenerated between (FirstAppLaunchTime .. (FirstAppLaunchTime + 1h))
| summarize by ActorName, Url, Source = "MicrosoftDefender"
Leg 2: _Im_WebSession
_Im_WebSession
| where TimeGenerated >= ago(query_frequency)
| join kind=inner (new_app_launch_users) on $left.SrcUsername == $right.ActorName
| join kind=inner (detected_urls) on Url
| where TimeGenerated between (FirstAppLaunchTime .. (FirstAppLaunchTime + 1h))
| summarize by ActorName, Url, Source
Applied to the Combined Result
| summarize DistinctUserCount = dcount(ActorName) by Url | where DistinctUserCount >= distinct_user_url_click_threshold | join kind=inner url_click_events on Url | join kind=inner (new_app_launch_users) on ActorName | extend
PowerAppsEntityId = 27593,
DataverseId = 32780,
AccountName = tostring(split(ActorName, '@')[0]),
UPNSuffix = tostring(split(ActorName, '@')[1]) | project
FirstAppLaunchTime,
AppCreator,
AppName,
AppId,
ImpactedUser = ActorName,
AccountName,
UPNSuffix,
EnvironmentId,
Url,
Source,
PowerAppsEntityId
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Active | eq |
| field:"Active" kind:eq value:"true" |
DistinctUserCount | ge |
| field:"DistinctUserCount" kind:ge value:"2" |
Entities | match |
| field:"Entities" kind:match |
Entities.Type | eq |
| field:"Entities.Type" kind:eq value:"url" |
EventOriginalType | eq |
| field:"EventOriginalType" kind:eq |
LaunchCount | gt |
| field:"LaunchCount" kind:gt value:"2" |
PropertyCollection | match |
| field:"PropertyCollection" kind:match value:"distinct_apps" |
ThreatTypes | is_not_null | field:"ThreatTypes" kind:is_not_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountName | project |
AppCreator | project |
AppId | project |
AppName | project |
EnvironmentId | project |
FirstAppLaunchTime | project |
ImpactedUser | project |
PowerAppsEntityId | project |
Source | project |
UPNSuffix | project |
Url | project |
Red Sift - New email with URL from previously unseen sender
#'Detects email forensics events that contain one or more URLs where the sender in the from field has not been seen in the previous 14 days, which may indicate phishing activity or a newly observed sender.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: 6e0b70d4-0ab8-480e-9707-8ad45fc21a65
name: Red Sift - New email with URL from previously unseen sender
description: |
'Detects email forensics events that contain one or more URLs where the sender in the from field has not been seen in the previous 14 days, which may indicate phishing activity or a newly observed sender.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: RedSiftPush
dataTypes:
- RedSiftEmailForensics_CL
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1566
query: |
let lookback = 14d;
let recentWindow = 1h;
let historicalSenders = RedSiftEmailForensics_CL
| extend
EmailFrom = tostring(column_ifexists("EmailFrom", ""))
| where TimeGenerated between (ago(lookback) .. ago(recentWindow))
| where isnotempty(EmailFrom)
| summarize by EmailFrom;
RedSiftEmailForensics_CL
| extend
EmailFrom = tostring(column_ifexists("EmailFrom", "")),
EmailSubject = tostring(column_ifexists("EmailSubject", "")),
EmailReturnPath = tostring(column_ifexists("EmailReturnPath", "")),
EmailMessageUid = tostring(column_ifexists("EmailMessageUid", "")),
SrcIp = tostring(column_ifexists("SrcIp", "")),
DstHostname = tostring(column_ifexists("DstHostname", "")),
Severity = tostring(column_ifexists("Severity", "")),
Message = tostring(column_ifexists("Message", "")),
CorrelationUid = tostring(column_ifexists("CorrelationUid", "")),
EmailUrls = todynamic(column_ifexists("EmailUrls", "[]"))
| where TimeGenerated >= ago(recentWindow)
| where isnotempty(EmailFrom)
| extend UrlCount = array_length(EmailUrls)
| where UrlCount > 0
| mv-apply Url = EmailUrls on (
summarize UrlSet = make_set(tostring(Url.url_string), 50)
)
| extend UrlList = strcat_array(UrlSet, ", ")
| join kind=leftanti (historicalSenders) on EmailFrom
| project
TimeGenerated,
EmailFrom,
EmailSubject,
EmailReturnPath,
EmailMessageUid,
SrcIp,
DstHostname,
UrlCount,
UrlList,
Severity,
Message,
CorrelationUid
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: EmailFrom
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIp
- entityType: DNS
fieldMappings:
- identifier: DomainName
columnName: DstHostname
customDetails:
EmailSubject: EmailSubject
ReturnPath: EmailReturnPath
UrlCount: UrlCount
UrlList: UrlList
CorrelationUid: CorrelationUid
alertDetailsOverride:
alertDisplayNameFormat: "RedSift - New URL-bearing sender {{EmailFrom}}"
alertDescriptionFormat: "Email from previously unseen sender {{EmailFrom}} contains {{UrlCount}} URL(s)."
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: P1D
matchingMethod: Selected
groupByEntities:
- Account
groupByCustomDetails:
- EmailSubject
eventGroupingSettings:
aggregationKind: AlertPerResult
suppressionEnabled: false
suppressionDuration: PT1H
version: 1.0.0
kind: Scheduled
Stages and Predicates
Parameters
let lookback = 14d;
let recentWindow = 1h;
Let Binding: historicalSenders
let historicalSenders = RedSiftEmailForensics_CL
| extend
EmailFrom = tostring(column_ifexists("EmailFrom", ""))
| where TimeGenerated between (ago(lookback) .. ago(recentWindow))
| where isnotempty(EmailFrom)
| summarize by EmailFrom;
Stage 1: source
RedSiftEmailForensics_CL
Stage 2: extend
| extend
EmailFrom = tostring(column_ifexists("EmailFrom", "")),
EmailSubject = tostring(column_ifexists("EmailSubject", "")),
EmailReturnPath = tostring(column_ifexists("EmailReturnPath", "")),
EmailMessageUid = tostring(column_ifexists("EmailMessageUid", "")),
SrcIp = tostring(column_ifexists("SrcIp", "")),
DstHostname = tostring(column_ifexists("DstHostname", "")),
Severity = tostring(column_ifexists("Severity", "")),
Message = tostring(column_ifexists("Message", "")),
CorrelationUid = tostring(column_ifexists("CorrelationUid", "")),
EmailUrls = todynamic(column_ifexists("EmailUrls", "[]"))
Stage 3: where
| where TimeGenerated >= ago(recentWindow)
Stage 4: where
| where isnotempty(EmailFrom)
Stage 5: extend
| extend UrlCount = array_length(EmailUrls)
Stage 6: where
| where UrlCount > 0
Stage 7: kusto:mv-apply
| mv-apply Url = EmailUrls on (
summarize UrlSet = make_set(tostring(Url.url_string), 50)
)
Stage 8: extend
| extend UrlList = strcat_array(UrlSet, ", ")
Stage 9: join (negated)
join kind=leftanti (historicalSenders) on EmailFrom
Stage 10: project
project CorrelationUid, DstHostname, EmailFrom, EmailMessageUid, EmailReturnPath, EmailSubject, Message, Severity, SrcIp, TimeGenerated, UrlCount, UrlList
Stage 11: summarize aggregation inside the mv-apply branch
summarize
Stage 12: summarize aggregation inside the join branch
summarize by EmailFrom
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EmailFrom | is_not_null | field:"EmailFrom" kind:is_not_null | |
UrlCount | gt |
| field:"UrlCount" kind:gt value:"0" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
EmailFrom | is_not_null | excludes:EmailFrom |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
EmailFrom | summarize |
Removable storage ONLINE event from secRMM
#Detect when a removable storage device is plugged in by the end-user.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Collection |
Rule Body
id: A22B2ECF-1478-4400-877E-07A32E53A897
name: Removable storage ONLINE event from secRMM
description: Detect when a removable storage device is plugged in by the end-user.
severity: High
kind: NRT
apiVersion: 2025-09-01
displayName: Removable Storage ONLINE
enabled: true
query: |+
secRMM_CL | where Event_s == "ONLINE" |
extend AccountCustomEntity = User_s |
extend HostCustomEntity = Computer
suppressionDuration: PT5H
suppressionEnabled: false
tactics:
- Collection
relevantTechniques:
- T1025
subTechniques: []
alertRuleTemplateName:
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
groupByEntities: []
groupByAlertDetails: []
groupByCustomDetails: []
eventGroupingSettings:
aggregationKind: AlertPerResult
alertDetailsOverride:
customDetails:
entityMappings:
- entityType: Account
fieldMappings:
- identifier: AadUserId
columnName: User_s
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: Computer
Stages and Predicates
Stage 1: source
secRMM_CL
Stage 2: where
| where Event_s == "ONLINE"
Stage 3: extend
| extend AccountCustomEntity = User_s
Stage 4: extend
| extend HostCustomEntity = Computer
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Event_s | eq |
| field:"Event_s" kind:eq value:"ONLINE" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountCustomEntity | extend |
HostCustomEntity | extend |
Sentinel One - Same custom rule triggered on different hosts
#'Detects when same custom rule was triggered on different hosts.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Lateral Movement |
Rule Body
id: 5586d378-1bce-4d9b-9ac8-e7271c9d5a9a
name: Sentinel One - Same custom rule triggered on different hosts
description: |
'Detects when same custom rule was triggered on different hosts.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: SentinelOne
dataTypes:
- SentinelOne
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
- LateralMovement
relevantTechniques:
- T1190
- T1210
query: |
SentinelOne
| where ActivityType == 3608
| extend RuleName = extract(@'Custom Rule:\s(.*?)\sin Group', 1, EventOriginalMessage)
| extend DstHostname = extract(@'detected on\s(\S+)\.', 1, EventOriginalMessage)
| summarize hosts = makeset(DstHostname) by RuleName, bin(TimeGenerated, 15m)
| where array_length(hosts) > 1
| extend HostCustomEntity = hosts
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: HostCustomEntity
version: 1.0.2
kind: Scheduled
Stages and Predicates
Stage 1: source
SentinelOne
Stage 2: where
| where ActivityType == 3608
Stage 3: extend
| extend RuleName = extract(@'Custom Rule:\s(.*?)\sin Group', 1, EventOriginalMessage)
Stage 4: extend
| extend DstHostname = extract(@'detected on\s(\S+)\.', 1, EventOriginalMessage)
Stage 5: summarize
| summarize hosts = makeset(DstHostname) by RuleName, bin(TimeGenerated, 15m)
Stage 6: where
| where array_length(hosts) > 1
Stage 7: extend
| extend HostCustomEntity = hosts
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ActivityType | eq |
| field:"ActivityType" kind:eq value:"3608" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
RuleName | summarize |
hosts | summarize |
HostCustomEntity | extend |
Trend Micro CAS - Threat detected and not blocked
#'Detects when threat was not blocked by CAS solution.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Defense Impairment |
Rule Body
id: c8e2ad52-bd5f-4f74-a2f7-6c3ab8ba687a
name: Trend Micro CAS - Threat detected and not blocked
description: |
'Detects when threat was not blocked by CAS solution.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: TrendMicroCAS
dataTypes:
- TrendMicroCAS
queryFrequency: 10m
queryPeriod: 10m
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
relevantTechniques:
- T1562
query: |
TrendMicroCAS
| where isnotempty(SecurityRiskName)
| where EventOriginalResultDetails !has 'Blocked' or EventOriginalResultDetails !has 'Block' or EventOriginalResultDetails !has 'Quarantine' or TriggeredPolicyName has 'Monitor Only'
| extend AccountCustomEntity = DstUserName
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountCustomEntity
version: 1.0.1
kind: Scheduled
Stages and Predicates
Stage 1: source
TrendMicroCAS
Stage 2: where
| where isnotempty(SecurityRiskName)
Stage 3: where
| where EventOriginalResultDetails !has 'Blocked' or EventOriginalResultDetails !has 'Block' or EventOriginalResultDetails !has 'Quarantine' or TriggeredPolicyName has 'Monitor Only'
Stage 4: extend
| extend AccountCustomEntity = DstUserName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
SecurityRiskName | is_not_null | field:"SecurityRiskName" kind:is_not_null | |
TriggeredPolicyName | match |
| field:"TriggeredPolicyName" kind:match value:"Monitor Only" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountCustomEntity | extend |
UniFi Site Manager: ISP Downtime
#Identifies when the ISP connection experiences downtime, impacting business operations and requiring documentation for SLA purposes.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Impact |
Rule Body
id: b54123ef-cfa7-769e-a959-f437404a1192
name: "UniFi Site Manager: ISP Downtime"
description: |
Identifies when the ISP connection experiences downtime, impacting business operations and requiring documentation for SLA purposes.
severity: High
status: Available
requiredDataConnectors:
- connectorId: UniFiSiteManagerConnectorDefinition
dataTypes:
- Unifi_SiteManager_ISPMetrics_CL
queryFrequency: 15m
queryPeriod: 30m
triggerOperator: gt
triggerThreshold: 0
tactics:
- Impact
relevantTechniques:
- T1489
- T1499
subTechniques:
- T1499.002
query: |
// UniFi ISP Downtime Detection
let MinDowntimeSeconds = 60;
Unifi_SiteManager_ISPMetrics_CL
| where TimeGenerated > ago(30m)
| mv-expand period = Periods
| extend
metricTime = todatetime(period.metricTime),
downtime = toint(period.data.wan.downtime),
uptime = todouble(period.data.wan.uptime),
ispName = tostring(period.data.wan.ispName),
ispAsn = tostring(period.data.wan.ispAsn)
// De-duplicate Periods: each poll returns the same hour buckets, so collapse to latest value per metricTime
| summarize arg_max(TimeGenerated, downtime, uptime, ispAsn) by tostring(SiteId), ispName, metricTime
| where metricTime > ago(30m)
| where downtime > 0
| summarize
TotalDowntimeSeconds = sum(downtime),
EventCount = count(),
AvgUptime = round(avg(uptime), 2),
FirstSeen = min(metricTime),
LastSeen = max(metricTime)
by SiteId, ispName, ispAsn
| where TotalDowntimeSeconds >= MinDowntimeSeconds
| extend
TimeGenerated = now(),
DowntimeMinutes = round(TotalDowntimeSeconds / 60.0, 2)
| project
TimeGenerated,
SiteId = SiteId,
ISPName = ispName,
ISPAsn = ispAsn,
TotalDowntimeSeconds,
DowntimeMinutes,
AvgUptimePct = AvgUptime,
EventCount,
FirstSeen,
LastSeen
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: SiteId
- entityType: CloudApplication
fieldMappings:
- identifier: Name
columnName: ISPName
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: true
lookbackDuration: PT4H
matchingMethod: AllEntities
kind: Scheduled
version: 1.0.1
Stages and Predicates
Parameters
let MinDowntimeSeconds = 60;
Stage 1: source
Unifi_SiteManager_ISPMetrics_CL
Stage 2: where
| where TimeGenerated > ago(30m)
Stage 3: mv-expand
| mv-expand period = Periods
Stage 4: extend
| extend
metricTime = todatetime(period.metricTime),
downtime = toint(period.data.wan.downtime),
uptime = todouble(period.data.wan.uptime),
ispName = tostring(period.data.wan.ispName),
ispAsn = tostring(period.data.wan.ispAsn)
Stage 5: summarize
| summarize arg_max(TimeGenerated, downtime, uptime, ispAsn) by tostring(SiteId), ispName, metricTime
Stage 6: where
| where metricTime > ago(30m)
Stage 7: where
| where downtime > 0
Stage 8: summarize
| summarize
TotalDowntimeSeconds = sum(downtime),
EventCount = count(),
AvgUptime = round(avg(uptime), 2),
FirstSeen = min(metricTime),
LastSeen = max(metricTime)
by SiteId, ispName, ispAsn
Stage 9: where
| where TotalDowntimeSeconds >= MinDowntimeSeconds
Stage 10: extend
| extend
TimeGenerated = now(),
DowntimeMinutes = round(TotalDowntimeSeconds / 60.0, 2)
Stage 11: project
| project
TimeGenerated,
SiteId = SiteId,
ISPName = ispName,
ISPAsn = ispAsn,
TotalDowntimeSeconds,
DowntimeMinutes,
AvgUptimePct = AvgUptime,
EventCount,
FirstSeen,
LastSeen
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
TotalDowntimeSeconds | ge |
| field:"TotalDowntimeSeconds" kind:ge value:"60" |
downtime | gt |
| field:"downtime" kind:gt value:"0" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AvgUptimePct | project |
DowntimeMinutes | project |
EventCount | project |
FirstSeen | project |
ISPAsn | project |
ISPName | project |
LastSeen | project |
SiteId | project |
TimeGenerated | project |
TotalDowntimeSeconds | project |
User Accounts - Sign in Failure due to CA Spikes
#Identifies spike in failed sign-ins from user accounts due to conditional access policied. Spike is determined based on Time series anomaly which will look at historical baseline values. Ref : https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-user-accounts#monitoring-for-failed-unusual-sign-ins This query has also been updated to include UEBA logs IdentityInfo and BehaviorAnalytics for contextual information around the results.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Telemetry Coverage
Rule Body
id: 3a9d5ede-2b9d-43a2-acc4-d272321ff77c
name: User Accounts - Sign in Failure due to CA Spikes
description: |
' Identifies spike in failed sign-ins from user accounts due to conditional access policied.
Spike is determined based on Time series anomaly which will look at historical baseline values.
Ref : https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-user-accounts#monitoring-for-failed-unusual-sign-ins
This query has also been updated to include UEBA logs IdentityInfo and BehaviorAnalytics for contextual information around the results.'
severity: Medium
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- SigninLogs
- connectorId: AzureActiveDirectory
dataTypes:
- AADNonInteractiveUserSignInLogs
- connectorId: BehaviorAnalytics
dataTypes:
- BehaviorAnalytics
- connectorId: BehaviorAnalytics
dataTypes:
- IdentityInfo
queryFrequency: 1d
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- InitialAccess
relevantTechniques:
- T1078.004
tags:
- AADSecOpsGuide
query: |
let riskScoreCutoff = 20; //Adjust this based on volume of results
let starttime = 14d;
let timeframe = 1d;
let scorethreshold = 3;
let baselinethreshold = 50;
let aadFunc = (tableName:string){
// Failed Signins attempts with reasoning related to conditional access policies.
table(tableName)
| where TimeGenerated between (startofday(ago(starttime))..startofday(now()))
| where ResultDescription has_any ("conditional access", "CA") or ResultType in (50005, 50131, 53000, 53001, 53002, 52003, 70044)
| extend UserPrincipalName = tolower(UserPrincipalName)
| extend timestamp = TimeGenerated, AccountCustomEntity = UserPrincipalName
};
let aadSignin = aadFunc("SigninLogs");
let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");
let allSignins = union isfuzzy=true aadSignin, aadNonInt;
let TimeSeriesAlerts =
allSignins
| make-series DailyCount=count() on TimeGenerated from startofday(ago(starttime)) to startofday(now()) step 1d by UserPrincipalName
| extend (anomalies, score, baseline) = series_decompose_anomalies(DailyCount, scorethreshold, -1, 'linefit')
| mv-expand DailyCount to typeof(double), TimeGenerated to typeof(datetime), anomalies to typeof(double), score to typeof(double), baseline to typeof(long)
// Filtering low count events per baselinethreshold
| where anomalies > 0 and baseline > baselinethreshold
| extend AnomalyHour = TimeGenerated
| project UserPrincipalName, AnomalyHour, TimeGenerated, DailyCount, baseline, anomalies, score;
// Filter the alerts for specified timeframe
TimeSeriesAlerts
| where TimeGenerated > startofday(ago(timeframe))
| join kind=inner (
allSignins
| where TimeGenerated > startofday(ago(timeframe))
// create a new column and round to hour
| extend DateHour = bin(TimeGenerated, 1h)
| summarize PartialFailedSignins = count(), LatestAnomalyTime = arg_max(TimeGenerated, *) by bin(TimeGenerated, 1h), OperationName, Category, ResultType, ResultDescription, UserPrincipalName, UserDisplayName, AppDisplayName, ClientAppUsed, IPAddress, ResourceDisplayName
) on UserPrincipalName, $left.AnomalyHour == $right.DateHour
| project LatestAnomalyTime, OperationName, Category, UserPrincipalName, UserDisplayName, ResultType, ResultDescription, AppDisplayName, ClientAppUsed, UserAgent, IPAddress, Location, AuthenticationRequirement, ConditionalAccessStatus, ResourceDisplayName, PartialFailedSignins, TotalFailedSignins = DailyCount, baseline, anomalies, score
| extend timestamp = LatestAnomalyTime, Name = tostring(split(UserPrincipalName,'@',0)[0]), UPNSuffix = tostring(split(UserPrincipalName,'@',1)[0])
| extend UserPrincipalName = tolower(UserPrincipalName)
| join kind=leftouter (
IdentityInfo
| summarize LatestReportTime = arg_max(TimeGenerated, *) by AccountUPN
| project AccountUPN, Tags, JobTitle, GroupMembership, AssignedRoles, UserType, IsAccountEnabled
| summarize
Tags = make_set(Tags, 1000),
GroupMembership = make_set(GroupMembership, 1000),
AssignedRoles = make_set(AssignedRoles, 1000),
UserType = make_set(UserType, 1000),
UserAccountControl = make_set(UserType, 1000)
by AccountUPN
| extend UserPrincipalName=tolower(AccountUPN)
) on UserPrincipalName
| join kind=leftouter (
BehaviorAnalytics
| where ActivityType in ("FailedLogOn", "LogOn")
| where isnotempty(SourceIPAddress)
| project UsersInsights, DevicesInsights, ActivityInsights, InvestigationPriority, SourceIPAddress
| project-rename IPAddress = SourceIPAddress
| summarize
UsersInsights = make_set(UsersInsights, 1000),
DevicesInsights = make_set(DevicesInsights, 1000),
IPInvestigationPriority = sum(InvestigationPriority)
by IPAddress)
on IPAddress
| extend UEBARiskScore = IPInvestigationPriority
| where UEBARiskScore > riskScoreCutoff
| sort by UEBARiskScore desc
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: UserPrincipalName
- identifier: Name
columnName: Name
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPAddress
version: 2.0.5
kind: Scheduled
Stages and Predicates
Parameters
let riskScoreCutoff = 20;
let starttime = 14d;
let timeframe = 1d;
let scorethreshold = 3;
let baselinethreshold = 50;
let aadSignin = aadFunc("SigninLogs");
let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");
let allSignins and let TimeSeriesAlerts are inlined into the numbered stages below.
Let Binding: aadFunc
let aadFunc = (tableName:string){
table(tableName)
| where TimeGenerated between (startofday(ago(starttime))..startofday(now()))
| where ResultDescription has_any ("conditional access", "CA") or ResultType in (50005, 50131, 53000, 53001, 53002, 52003, 70044)
| extend UserPrincipalName = tolower(UserPrincipalName)
| extend timestamp = TimeGenerated, AccountCustomEntity = UserPrincipalName
};
Stages 1 to 9 define let TimeSeriesAlerts as the rule's main pipeline source. Stages 10 to 19 use it.
Stage 1: union
union of 2 branches
Stage 2: source
aadSignin
Stage 3: source
aadNonInt
Stage 4: summarize
summarize DailyCount by UserPrincipalName
Stage 5: extend
extend anomalies, baseline, score
Stage 6: mv-expand
mv-expand DailyCount
Stage 7: where
where anomalies > 0 and baseline > 50
Stage 8: extend
extend AnomalyHour
Stage 9: project
project AnomalyHour, DailyCount, TimeGenerated, UserPrincipalName, anomalies, baseline, score
Stage 10: where
where TimeGenerated > ago(86400s)
Stage 11: join
join kind=inner (allSignins) on UserPrincipalName, $left.AnomalyHour, $right.DateHour
Stage 12: project
project AppDisplayName, AuthenticationRequirement, Category, ClientAppUsed, ConditionalAccessStatus, IPAddress, LatestAnomalyTime, Location, OperationName, PartialFailedSignins, ResourceDisplayName, ResultDescription, ResultType, TotalFailedSignins, UserAgent, UserDisplayName, UserPrincipalName, anomalies, baseline, score
Stage 13: extend
extend Name, UPNSuffix, timestamp
Stage 14: extend
extend UserPrincipalName
Stage 15: join
| join kind=leftouter (
IdentityInfo
| summarize LatestReportTime = arg_max(TimeGenerated, *) by AccountUPN
| project AccountUPN, Tags, JobTitle, GroupMembership, AssignedRoles, UserType, IsAccountEnabled
| summarize
Tags = make_set(Tags, 1000),
GroupMembership = make_set(GroupMembership, 1000),
AssignedRoles = make_set(AssignedRoles, 1000),
UserType = make_set(UserType, 1000),
UserAccountControl = make_set(UserType, 1000)
by AccountUPN
| extend UserPrincipalName=tolower(AccountUPN)
) on UserPrincipalName
Stage 16: join
| join kind=leftouter (
BehaviorAnalytics
| where ActivityType in ("FailedLogOn", "LogOn")
| where isnotempty(SourceIPAddress)
| project UsersInsights, DevicesInsights, ActivityInsights, InvestigationPriority, SourceIPAddress
| project-rename IPAddress = SourceIPAddress
| summarize
UsersInsights = make_set(UsersInsights, 1000),
DevicesInsights = make_set(DevicesInsights, 1000),
IPInvestigationPriority = sum(InvestigationPriority)
by IPAddress)
on IPAddress
Stage 17: extend
extend UEBARiskScore
Stage 18: where
where UEBARiskScore > 20
Stage 19: sort
sort by UEBARiskScore
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ActivityType | in |
| field:"ActivityType" kind:in |
SourceIPAddress | is_not_null | field:"SourceIPAddress" kind:is_not_null | |
UEBARiskScore | gt |
| field:"UEBARiskScore" kind:gt value:"20" |
anomalies | gt |
| field:"anomalies" kind:gt value:"0" |
baseline | gt |
| field:"baseline" kind:gt value:"50" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AppDisplayName | project |
AuthenticationRequirement | project |
Category | project |
ClientAppUsed | project |
ConditionalAccessStatus | project |
IPAddress | project |
LatestAnomalyTime | project |
Location | project |
OperationName | project |
PartialFailedSignins | project |
ResourceDisplayName | project |
ResultDescription | project |
ResultType | project |
TotalFailedSignins | project |
UserAgent | project |
UserDisplayName | project |
UserPrincipalName | extend |
anomalies | project |
baseline | project |
score | project |
Name | extend |
UPNSuffix | extend |
timestamp | extend |
UEBARiskScore | extend |
VMware ESXi - Root impersonation
#'Detects when root impersonation occurs.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Privilege Escalation |
Rule Body
id: 23a3cf72-9497-408e-8144-87958a60d31a
name: VMware ESXi - Root impersonation
description: |
'Detects when root impersonation occurs.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: SyslogAma
datatypes:
- Syslog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- PrivilegeEscalation
relevantTechniques:
- T1078
query: |
VMwareESXi
| where SyslogMessage has_all ('ImpersonateUser', 'VcIntegrity', 'root')
| extend user = 'root'
| extend AccountCustomEntity = user
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountCustomEntity
version: 1.0.2
kind: Scheduled
Stages and Predicates
Stage 1: source
VMwareESXi
Stage 2: where
| where SyslogMessage has_all ('ImpersonateUser', 'VcIntegrity', 'root')
Stage 3: extend
| extend user = 'root'
Stage 4: extend
| extend AccountCustomEntity = user
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
SyslogMessage | match |
| field:"SyslogMessage" kind:match |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
user | extend |
AccountCustomEntity | extend |
Windows Binaries Lolbins Renamed
#This query detects the execution of renamed Windows binaries (Lolbins). This is a common technique used by adversaries to evade detection. Ref: https://docs.paloaltonetworks.com/cortex/cortex-xdr/cortex-xdr-analytics-alert-reference/cortex-xdr-analytics-alert-reference/execution-of-renamed-lolbin.html
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Execution |
Telemetry Coverage
| Provider | Record / event type |
|---|---|
| Sysmon | Event ID 1: Process creation |
Rule Body
id: cbf6ad48-fa5c-4bf7-b205-28dbadb91255
name: Windows Binaries Lolbins Renamed
description: |
'This query detects the execution of renamed Windows binaries (Lolbins). This is a common technique used by adversaries to evade detection.
Ref: https://docs.paloaltonetworks.com/cortex/cortex-xdr/cortex-xdr-analytics-alert-reference/cortex-xdr-analytics-alert-reference/execution-of-renamed-lolbin.html'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: SecurityEvents
dataTypes:
- SecurityEvent
- connectorId: WindowsSecurityEvents
dataTypes:
- SecurityEvent
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Execution
relevantTechniques:
- T1059
query: |
let procList = externaldata(Process:string) [@"https://raw.githubusercontent.com/Azure/Azure-Sentinel/master/Sample%20Data/Microsoft_Lolbas_Execution_Binaries.csv"] with (format="csv", ignoreFirstRecord=True);
Event
| where EventLog =~ "Microsoft-Windows-Sysmon/Operational" and EventID==1
| parse EventData with * 'Image">' Image "<" * 'OriginalFileName">' OriginalFileName "<" *
| where OriginalFileName has_any (procList) and not (Image has_any (procList))
| parse EventData with * 'ProcessGuid">' ProcessGuid "<" * 'Description">' Description "<" * 'CommandLine">' CommandLine "<" * 'CurrentDirectory">' CurrentDirectory "<" * 'User">' User "<" * 'LogonGuid">' LogonGuid "<" * 'Hashes">' Hashes "<" * 'ParentProcessGuid">' ParentProcessGuid "<" * 'ParentImage">' ParentImage "<" * 'ParentCommandLine">' ParentCommandLine "<" * 'ParentUser">' ParentUser "<" *
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by EventID, Computer, User, ParentImage, ParentProcessGuid, ParentCommandLine, ParentUser, Image, ProcessGuid, CommandLine, Description, OriginalFileName, CurrentDirectory, Hashes
| extend HostName = iif(Computer has '.',substring(Computer,0,indexof(Computer,'.')),Computer) , DnsDomain = iif(Computer has '.',substring(Computer,indexof(Computer,'.')+1),'')
entityMappings:
- entityType: Process
fieldMappings:
- identifier: CommandLine
columnName: CommandLine
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: Computer
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: DnsDomain
- entityType: Account
fieldMappings:
- identifier: Name
columnName: User
version: 1.0.3
kind: Scheduled
Stages and Predicates
Let Binding: procList
let procList = externaldata(Process:string) [@"https://raw.githubusercontent.com/Azure/Azure-Sentinel/master/Sample%20Data/Microsoft_Lolbas_Execution_Binaries.csv"] with (format="csv", ignoreFirstRecord=True);
Stage 1: source
Event
Stage 2: where
| where EventLog =~ "Microsoft-Windows-Sysmon/Operational" and EventID==1
Stage 3: parse
| parse EventData with * 'Image">' Image "<" * 'OriginalFileName">' OriginalFileName "<" *
Stage 4: where
| where OriginalFileName has_any (procList) and not (Image has_any (procList))
Stage 5: parse
| parse EventData with * 'ProcessGuid">' ProcessGuid "<" * 'Description">' Description "<" * 'CommandLine">' CommandLine "<" * 'CurrentDirectory">' CurrentDirectory "<" * 'User">' User "<" * 'LogonGuid">' LogonGuid "<" * 'Hashes">' Hashes "<" * 'ParentProcessGuid">' ParentProcessGuid "<" * 'ParentImage">' ParentImage "<" * 'ParentCommandLine">' ParentCommandLine "<" * 'ParentUser">' ParentUser "<" *
Stage 6: summarize
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by EventID, Computer, User, ParentImage, ParentProcessGuid, ParentCommandLine, ParentUser, Image, ProcessGuid, CommandLine, Description, OriginalFileName, CurrentDirectory, Hashes
Stage 7: extend
| extend HostName = iif(Computer has '.',substring(Computer,0,indexof(Computer,'.')),Computer) , DnsDomain = iif(Computer has '.',substring(Computer,indexof(Computer,'.')+1),'')
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventID | eq |
| field:"EventID" kind:eq value:"1" |
EventLog | eq |
| field:"EventLog" kind:eq value:"Microsoft-Windows-Sysmon/Operational" |
OriginalFileName | match |
| field:"OriginalFileName" kind:match value:"procList" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
Image | match | procList | excludes:Image field:"Image" value:"procList" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
CommandLine | summarize |
Computer | summarize |
CurrentDirectory | summarize |
Description | summarize |
EndTime | summarize |
EventID | summarize |
Hashes | summarize |
Image | summarize |
OriginalFileName | summarize |
ParentCommandLine | summarize |
ParentImage | summarize |
ParentProcessGuid | summarize |
ParentUser | summarize |
ProcessGuid | summarize |
StartTime | summarize |
User | summarize |
DnsDomain | extend |
HostName | extend |