Detection rules › Kusto

Kusto rules: 30

RuleSeverity
Abnormal Deny Rate for Source IPmedium
Abnormal Security - Vendor compromise case detectedmedium
AIShield - Image classification AI Model Evasion low suspicious vulnerability detectionhigh
Atlassian Beacon Alerthigh
AV detections related to Europium actorshigh
Azure WAF matching for Log4j vuln(CVE-2021-44228)high
Bitglass - Login from new devicemedium
BloodHound Attack Path Finding - AddOwner Role on Tier Zero Resourcemedium
C2-NamedPipehigh
Cisco Duo - Multiple admin 2FA failureshigh
Cisco WSA - Multiple errors to URLmedium
ContraForce - Machine credential activitymedium
CYFIRMA - Medium severity File Hash Indicators with Block Action Rulemedium
CYFIRMA - Medium severity Malicious Network Indicators Associated with Malware - Monitor Recommended Rulemedium
Digital Guardian - Multiple incidents from userhigh
Execution of software vulnerable to webp buffer overflow of CVE-2023-4863informational
GitHub - A payment method was removedmedium
Google DNS - CVE-2021-34527 (PrintNightmare) external exploithigh
GSA - Detect Source IP Scanning Multiple Open Portsmedium
GTI - Zero-Day Vulnerability Detectedhigh
KMS Server Settings Updatedhigh
M365D Alerts Correlation to non-Microsoft Network device network activity involved in successful sign-in Activitymedium
Microsoft Defender for Endpoint (MDE) signatures for Azure Synapse pipelines and Azure Data Factoryhigh
Netskope - Suspicious Application Activity (Low Confidence / Risky App)medium
Netskope - Suspicious Network Context (Unusual IPs/Geo/Ports)medium
Pathlock TDnR - Dynamic Access Control Eventshigh
Ping Federate - Forbidden countryhigh
PROD (TM017.1) - GROUP - Tier 0 Added to Allow RODC Password Replication Grouphigh
Red Sift - MFA disabled on accounthigh
Silverfort - Certifried Incidenthigh
Suspicious number of resource creation or deployment activitiesmedium
Syntax errors stateful anomaly on databasemedium
Threat Connect TI map Domain entity to DnsEventsmedium
Valence Security Alertshigh
Zscaler - Forbidden countrieshigh

Abnormal Deny Rate for Source IP

#
Status
available
Severity
medium
Time window
25h
Group by
Fqdn, SourceIp
Source
github.com/Azure/Azure-Sentinel

'Identifies abnormal deny rate for specific source IP to destination IP based on the normal average and standard deviation learned during a configured period. This can indicate potential exfiltration, initial access or C2, where attacker tries to exploit the same vulnerability on machines in the organization, but is being blocked by firewall rules. Configurable Parameters: - Minimum of stds threshold - the number of stds to use in the threshold calculation. Default is set to 3. - Learning period time - learning period for threshold calculation in days. Default is set to 5. - Bin time - learning buckets time in hours. Default is set to 1 hour. - Minimum threshold - minimum threshold for alert. Default is set to 5. - Minimum bucket threshold - minimum learning buckets threshold for alert. Default is set to 5.'

MITRE ATT&CK Coverage

Rule Body

id: d36bb1e3-5abc-4037-ad9a-24ba3469819e
name: Abnormal Deny Rate for Source IP
description: |
  'Identifies abnormal deny rate for specific source IP to destination IP based on the normal average and standard deviation learned during a configured period. This can indicate potential exfiltration, initial access or C2, where attacker tries to exploit the same vulnerability on machines in the organization, but is being blocked by firewall rules.
  
  Configurable Parameters:
  
  - Minimum of stds threshold - the number of stds to use in the threshold calculation. Default is set to 3.
  - Learning period time - learning period for threshold calculation in days. Default is set to 5.
  - Bin time - learning buckets time in hours. Default is set to 1 hour.
  - Minimum threshold - minimum threshold for alert. Default is set to 5.
  - Minimum bucket threshold - minimum learning buckets threshold for alert. Default is set to 5.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: AzureFirewall
    dataTypes: 
      - AzureDiagnostics
      - AZFWApplicationRule
      - AZFWNetworkRule
      - AZFWFlowTrace
      - AZFWIdpsSignature
queryFrequency: 1h
queryPeriod: 25h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
  - Exfiltration
  - CommandAndControl
  - Discovery
relevantTechniques:
  - T1190
  - T1041
  - T1568.001
  - T1568.002
  - T1046
query: |
  let LearningPeriod = 1d;
  let RunTime = 1h;
  let StartLearningPeriod = LearningPeriod + RunTime;
  let EndRunTime = RunTime - 1h;
  let BinTime = 1h;
  let NumOfStdsThreshold = 3;
  let MinThreshold = 5.0;
  let MinLearningBuckets = 5;
  // Restrict unioned tables to the full analysis window to avoid full-table scans
  let FullWindowStart = ago(StartLearningPeriod);
  let FullWindowEnd = now();
  let TrafficLogs = (union isfuzzy=true
  (AzureDiagnostics
  | where TimeGenerated between (FullWindowStart .. FullWindowEnd)
  | where OperationName == "AzureFirewallApplicationRuleLog" or OperationName == "AzureFirewallNetworkRuleLog" 
  | parse msg_s with * "from " SourceIp ":" SourcePort:int " to " Fqdn ":" DestinationPort:int ". " * "Action: " Action "." *
  | where Action == "Deny"
  | where isnotempty(Fqdn) and isnotempty(SourceIp)),
  (AZFWNetworkRule
  | where TimeGenerated between (FullWindowStart .. FullWindowEnd)
  | extend Fqdn = DestinationIp
  | where Action == "Deny"
  | where isnotempty(Fqdn) and isnotempty(SourceIp)),
  (AZFWFlowTrace
  | where TimeGenerated between (FullWindowStart .. FullWindowEnd)
  | extend Fqdn = DestinationIp
  | where Action == "Deny"
  | where isnotempty(Fqdn) and isnotempty(SourceIp)),
  (AZFWIdpsSignature
  | where TimeGenerated between (FullWindowStart .. FullWindowEnd)
  | extend Fqdn = DestinationIp
  | where Action == "Deny"
  | where isnotempty(Fqdn) and isnotempty(SourceIp)),
  (AZFWApplicationRule
  | where TimeGenerated between (FullWindowStart .. FullWindowEnd)
  | where Action == "Deny"
  | where isnotempty(Fqdn) and isnotempty(SourceIp)));
  let LearningSrcIpDenyRate = (TrafficLogs
  | where TimeGenerated between (ago(StartLearningPeriod) .. ago(RunTime))
  | summarize count() by SourceIp, bin(TimeGenerated, BinTime), Fqdn
  | summarize LearningTimeSrcIpDenyRateAvg = avg(count_), LearningTimeSrcIpDenyRateStd = stdev(count_), LearningTimeBuckets = count() by SourceIp, Fqdn
  | where LearningTimeBuckets > MinLearningBuckets);
  let AlertTimeSrcIpDenyRate = (TrafficLogs
  | where TimeGenerated between (ago(RunTime) .. ago(EndRunTime))
  | summarize AlertTimeSrcIpDenyRateCount = count() by SourceIp, Fqdn);
  AlertTimeSrcIpDenyRate
  | join kind=leftouter (LearningSrcIpDenyRate) on SourceIp, Fqdn
  | extend LearningThreshold = max_of(LearningTimeSrcIpDenyRateAvg + NumOfStdsThreshold * LearningTimeSrcIpDenyRateStd, MinThreshold)
  | where AlertTimeSrcIpDenyRateCount > LearningThreshold
  | project SourceIp, Fqdn, AlertTimeSrcIpDenyRateCount, LearningTimeBuckets, LearningThreshold
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIp
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: Fqdn
customDetails:
  AlertCount: AlertTimeSrcIpDenyRateCount
  Threshold: LearningThreshold
alertDetailsOverride:
  alertDisplayNameFormat: 'Abnormal Deny Rate for Source IP {{SourceIp}}'
  alertDescriptionFormat: |
    The source IP {{SourceIp}} has an abnormal deny rate to destination {{Fqdn}} with {{AlertTimeSrcIpDenyRateCount}} denies, which is above the calculated threshold. This could indicate potential exfiltration, initial access or C2 activities.
version: 1.1.3
kind: Scheduled

Stages and Predicates

Parameters

let LearningPeriod = 1d;
let RunTime = 1h;
let StartLearningPeriod = LearningPeriod + RunTime;
let EndRunTime = RunTime - 1h;
let BinTime = 1h;
let NumOfStdsThreshold = 3;
let MinThreshold = 5.0;
let MinLearningBuckets = 5;
let FullWindowStart = ago(StartLearningPeriod);
let FullWindowEnd = now();

let TrafficLogs and let AlertTimeSrcIpDenyRate are inlined into the numbered stages below.

Let Binding: LearningSrcIpDenyRate used in Stage 29

let LearningSrcIpDenyRate = (TrafficLogs
| where TimeGenerated between (ago(StartLearningPeriod) .. ago(RunTime))
| summarize count() by SourceIp, bin(TimeGenerated, BinTime), Fqdn
| summarize LearningTimeSrcIpDenyRateAvg = avg(count_), LearningTimeSrcIpDenyRateStd = stdev(count_), LearningTimeBuckets = count() by SourceIp, Fqdn
| where LearningTimeBuckets > MinLearningBuckets);

Stages 1 to 28 define let AlertTimeSrcIpDenyRate as the rule's main pipeline source. Stages 29 to 32 use it.

Stage 1: union

union of 5 branches

Stage 2: source

AzureDiagnostics

Stage 3: where

| where TimeGenerated between (FullWindowStart .. FullWindowEnd)

Stage 4: where

| where OperationName == "AzureFirewallApplicationRuleLog" or OperationName == "AzureFirewallNetworkRuleLog"

Stage 5: parse

| parse msg_s with * "from " SourceIp ":" SourcePort:int " to " Fqdn ":" DestinationPort:int ". " * "Action: " Action "." *

Stage 6: where

| where Action == "Deny"

Stage 7: where

| where isnotempty(Fqdn) and isnotempty(SourceIp)

Stage 8: source

AZFWNetworkRule

Stage 9: where

| where TimeGenerated between (FullWindowStart .. FullWindowEnd)

Stage 10: extend

| extend Fqdn = DestinationIp

Stage 11: where

| where Action == "Deny"

Stage 12: where

| where isnotempty(Fqdn) and isnotempty(SourceIp)

Stage 13: source

AZFWFlowTrace

Stage 14: where

| where TimeGenerated between (FullWindowStart .. FullWindowEnd)

Stage 15: extend

| extend Fqdn = DestinationIp

Stage 16: where

| where Action == "Deny"

Stage 17: where

| where isnotempty(Fqdn) and isnotempty(SourceIp)

Stage 18: source

AZFWIdpsSignature

Stage 19: where

| where TimeGenerated between (FullWindowStart .. FullWindowEnd)

Stage 20: extend

| extend Fqdn = DestinationIp

Stage 21: where

| where Action == "Deny"

Stage 22: where

| where isnotempty(Fqdn) and isnotempty(SourceIp)

Stage 23: source

AZFWApplicationRule

Stage 24: where

| where TimeGenerated between (FullWindowStart .. FullWindowEnd)

Stage 25: where

| where Action == "Deny"

Stage 26: where

| where isnotempty(Fqdn) and isnotempty(SourceIp)

Stage 27: where

where TimeGenerated between (ago(3600s) .. now())

Stage 28: summarize

summarize AlertTimeSrcIpDenyRateCount by SourceIp, Fqdn

Stage 29: join

join kind=leftouter (LearningSrcIpDenyRate) on SourceIp, Fqdn

Stage 30: extend

extend LearningThreshold

Stage 31: where

where AlertTimeSrcIpDenyRateCount > LearningThreshold

Stage 32: project

project AlertTimeSrcIpDenyRateCount, Fqdn, LearningThreshold, LearningTimeBuckets, SourceIp

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AlertTimeSrcIpDenyRateCountproject
Fqdnproject
LearningThresholdproject
LearningTimeBucketsproject
SourceIpproject

Abnormal Security - Vendor compromise case detected

#
Status
available
Severity
medium
Time window
1h
Group by
CaseId
Source
github.com/Azure/Azure-Sentinel

'Creates an incident when Abnormal Security opens a vendor case indicating a compromised or impersonated vendor domain (vendor email compromise). Surfaces the vendor domain and Abnormal insights so analysts can review related correspondence.'

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Rule Body

id: b15ea4c9-58da-44d8-90e4-6591d947e7e3
name: Abnormal Security - Vendor compromise case detected
description: |
  'Creates an incident when Abnormal Security opens a vendor case indicating a
  compromised or impersonated vendor domain (vendor email compromise). Surfaces the
  vendor domain and Abnormal insights so analysts can review related correspondence.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: AbnormalSecurityPush
    dataTypes:
      - ABNORMAL_SECURITY_VENDOR_CASE_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1566
query: |
  ABNORMAL_SECURITY_VENDOR_CASE_CL
  | where isnotempty(abx_body_abx_body_vendorCaseId_g)
  // Collapse to one row per vendor case (earliest event in the window) so repeated
  // events for the same case do not raise duplicate alerts.
  | summarize arg_min(TimeGenerated, *) by CaseId = abx_body_abx_body_vendorCaseId_g
  | extend
      VendorDomain = abx_body_abx_body_vendorDomain_s,
      Insights = abx_body_abx_body_insights_s,
      FirstObserved = abx_body_abx_body_firstObservedTime_t
  | project TimeGenerated, VendorDomain, CaseId, Insights, FirstObserved
entityMappings:
  - entityType: DNS
    fieldMappings:
      - identifier: DomainName
        columnName: VendorDomain
customDetails:
  VendorCaseId: CaseId
  VendorDomain: VendorDomain
alertDetailsOverride:
  alertDisplayNameFormat: "Abnormal Security: vendor compromise case for {{VendorDomain}}"
  alertDescriptionFormat: "Abnormal Security opened vendor case {{CaseId}} for the domain {{VendorDomain}}. Review related correspondence for vendor email compromise."
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: PT5H
    matchingMethod: AllEntities
eventGroupingSettings:
  aggregationKind: AlertPerResult
suppressionDuration: PT5H
suppressionEnabled: false
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

ABNORMAL_SECURITY_VENDOR_CASE_CL

Stage 2: where

| where isnotempty(abx_body_abx_body_vendorCaseId_g)

Stage 3: summarize

| summarize arg_min(TimeGenerated, *) by CaseId = abx_body_abx_body_vendorCaseId_g

Stage 4: extend

| extend
    VendorDomain = abx_body_abx_body_vendorDomain_s,
    Insights = abx_body_abx_body_insights_s,
    FirstObserved = abx_body_abx_body_firstObservedTime_t

Stage 5: project

| project TimeGenerated, VendorDomain, CaseId, Insights, FirstObserved

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
CaseIdproject
FirstObservedproject
Insightsproject
TimeGeneratedproject
VendorDomainproject

AIShield - Image classification AI Model Evasion low suspicious vulnerability detection

#
Status
available
Severity
high
Time window
1h
Source
github.com/Azure/Azure-Sentinel

'This alert creates an incident when Image classification AI Model Evasion low suspicious, high severity vulnerability detected from the AIShield.'

Rule Body

id: c4bedb3c-4fb8-4b1c-af5b-8229bd25f521
name: AIShield - Image classification AI Model Evasion low suspicious vulnerability detection
description: |
  'This alert creates an incident when Image classification AI Model Evasion low suspicious, high severity vulnerability detected from the AIShield.'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: BoschAIShield
    dataTypes:
      - AIShield
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: |
  AIShield
  | where Message has 'Image Classification AI Model Evasion Attack Identified'
  | where Severity =~ 'Medium'
  | where SuspiciousLevel =~ 'low suspicious attack'
  | extend NTDomain = tostring(split(Computer, '\\')[0]), HostName = tostring(split(Computer, '.')[0])
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: HostName
      - identifier: NTDomain
        columnName: NTDomain
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceName
eventGroupingSettings:
  aggregationKind: SingleAlert
alertDetailsOverride:
  alertDisplayNameFormat: AIShield - Image Classification AI Model Evasion low suspicious vulnerability detected.
  alertDescriptionFormat: |
    This query detects Image Classification AI Model Evasion low suspicious, medium severity alert from AIShield generated at {{TimeGenerated}}.\n\nPlease check the source for more information and investigate further.
  alertTacticsColumnName: null
  alertSeverityColumnName: Severity
version: 1.0.4
kind: Scheduled

Stages and Predicates

Stage 1: source

AIShield

Stage 2: where

| where Message has 'Image Classification AI Model Evasion Attack Identified'

Stage 3: where

| where Severity =~ 'Medium'

Stage 4: where

| where SuspiciousLevel =~ 'low suspicious attack'

Stage 5: extend

| extend NTDomain = tostring(split(Computer, '\\')[0]), HostName = tostring(split(Computer, '.')[0])

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
HostNameextend
NTDomainextend

Atlassian Beacon Alert

#

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.

Status
available
Severity
high
Time window
5m
Source
github.com/Azure/Azure-Sentinel

'The analytic rule creates an incident when an alert is created in Atlassian Beacon. The incident's events contains values such as alert name, alert url, actor name, actor details, worskpace id of the atlassian beacon, etc. Navigate to the alertDetailURL to view more information on recommendations and remediations.'

Rule Body

id: 83fbf6a2-f227-48f4-8e7b-0b0ecac2381b
name: Atlassian Beacon Alert
description: |
  'The analytic rule creates an incident when an alert is created in Atlassian Beacon. The incident's events contains values such as alert name, alert url, actor name, actor details, worskpace id of the atlassian beacon, etc. Navigate to the alertDetailURL to view more information on recommendations and remediations.'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: AtlassianBeaconAlerts
    dataTypes:
      - atlassian_beacon_alerts_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
suppressionDuration: 5h
suppressionEnabled: false
tactics: []
relevantTechniques: []
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: 5h
    matchingMethod: AllEntities
eventGroupingSettings:
  aggregationKind: SingleAlert
alertDetailsOverride:
  alertDisplayNameFormat: 'Atlassian Beacon - {alertTitle_s}'
customDetails: null
entityMappings: null
sentinelEntitiesMappings: null
query: |
  atlassian_beacon_alerts_CL
  | project TimeGenerated, detectiontime_d, alert_created_t, alert_title_s, alertTitle_s, alert_url_s, alertDetailURL_s, activity_action_s, alert_product_s, activity_subject_ari_s, actor_name_s, actor_url_s, actor_sessions_s, atlassianAlertType_s, atlassianWorkspace_id_g, atlassianWorkspace_orgId_s, Type
version: 1.0.1
kind: Scheduled

Stages and Predicates

Stage 1: source

atlassian_beacon_alerts_CL

Stage 2: project

| project TimeGenerated, detectiontime_d, alert_created_t, alert_title_s, alertTitle_s, alert_url_s, alertDetailURL_s, activity_action_s, alert_product_s, activity_subject_ari_s, actor_name_s, actor_url_s, actor_sessions_s, atlassianAlertType_s, atlassianWorkspace_id_g, atlassianWorkspace_orgId_s, Type

Output Fields

These fields are emitted when the rule matches.

FieldSource
TimeGeneratedproject
Typeproject
activity_action_sproject
activity_subject_ari_sproject
actor_name_sproject
actor_sessions_sproject
actor_url_sproject
alertDetailURL_sproject
alertTitle_sproject
alert_created_tproject
alert_product_sproject
alert_title_sproject
alert_url_sproject
atlassianAlertType_sproject
atlassianWorkspace_id_gproject
atlassianWorkspace_orgId_sproject
detectiontime_dproject

AV detections related to Europium actors

#
Severity
high
Time window
1d
Group by
$left.DeviceName, $right.CompromisedEntity, AlertSeverity, CompromisedEntity, Description, DeviceId, DisplayName, Entities, ProductName, PublicIP, TenantId, ThreatFamilyName, ThreatName
Author
Microsoft Security Research
Source
github.com/Azure/Azure-Sentinel

This query looks for Microsoft Defender AV detections related to Europium actor. In Microsoft Sentinel the SecurityAlerts table includes only the Device Name of the affected device, this query joins the DeviceInfo table to clearly connect other information such as Device group, ip, etc. This would allow the Microsoft Sentinel analyst to have more context related to the alert, if available. Reference: https://www.microsoft.com/security/blog/2022/09/08/microsoft-investigates-iranian-attacks-against-the-albanian-government

MITRE ATT&CK Coverage

TacticTechniques
Impact

Telemetry Coverage

Rule Body

id: 186970ee-5001-41c1-8c73-3178f75ce96a
name: AV detections related to Europium actors
description: |
  'This query looks for Microsoft Defender AV detections related to  Europium actor. 
  In Microsoft Sentinel the SecurityAlerts table includes only the Device Name of the affected device, this query joins the DeviceInfo table to clearly connect other information such as Device group, ip, etc. This would allow the Microsoft Sentinel analyst to have more context related to the alert, if available.
   Reference: https://www.microsoft.com/security/blog/2022/09/08/microsoft-investigates-iranian-attacks-against-the-albanian-government '
severity: High
requiredDataConnectors:
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - SecurityAlert
      - DeviceInfo
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Impact
relevantTechniques:
  - T1486
tags:
  -  Europium
query: |
  let Europium_threats = dynamic(["TrojanDropper:ASP/WebShell!MSR", "Trojan:Win32/BatRunGoXml", "DoS:Win64/WprJooblash", "Ransom:Win32/Eagle!MSR", "Trojan:Win32/Debitom.A"]);
  DeviceInfo
  | extend DeviceName = tolower(DeviceName)
  | join kind=inner ( SecurityAlert
  | where ProviderName == "MDATP"
  | extend ThreatName = tostring(parse_json(ExtendedProperties).ThreatName)
  | extend ThreatFamilyName = tostring(parse_json(ExtendedProperties).ThreatFamilyName)
  | where ThreatName in~ (Europium_threats) or ThreatFamilyName in~ (Europium_threats)
  | extend CompromisedEntity = tolower(CompromisedEntity)
  ) on $left.DeviceName == $right.CompromisedEntity
  | summarize by DisplayName, ThreatName, ThreatFamilyName, PublicIP, AlertSeverity, Description, tostring(LoggedOnUsers), DeviceId, TenantId, bin(TimeGenerated, 1d), CompromisedEntity, tostring(LoggedOnUsers), ProductName, Entities
  | extend HostName = tostring(split(CompromisedEntity, ".")[0]), DomainIndex = toint(indexof(CompromisedEntity, '.'))
  | extend HostNameDomain = iff(CompromisedEntity != -1, substring(CompromisedEntity, DomainIndex + 1), CompromisedEntity)
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: CompromisedEntity
      - identifier: HostName
        columnName: HostName
      - identifier: NTDomain
        columnName: HostNameDomain
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: PublicIP
version: 1.1.2
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: Microsoft Security Research
    support:
        tier: Community
    categories:
        domains: [ "Security - Threat Intelligence" ]

Stages and Predicates

Parameters

let Europium_threats = dynamic(["TrojanDropper:ASP/WebShell!MSR", "Trojan:Win32/BatRunGoXml", "DoS:Win64/WprJooblash", "Ransom:Win32/Eagle!MSR", "Trojan:Win32/Debitom.A"]);

Stage 1: source

DeviceInfo

Stage 2: extend

| extend DeviceName = tolower(DeviceName)

Stage 3: join

| join kind=inner ( SecurityAlert
| where ProviderName == "MDATP"
| extend ThreatName = tostring(parse_json(ExtendedProperties).ThreatName)
| extend ThreatFamilyName = tostring(parse_json(ExtendedProperties).ThreatFamilyName)
| where ThreatName in~ (Europium_threats) or ThreatFamilyName in~ (Europium_threats)
| extend CompromisedEntity = tolower(CompromisedEntity)
) on $left.DeviceName == $right.CompromisedEntity

Stage 4: summarize

summarize by DisplayName, ThreatName, ThreatFamilyName, PublicIP, AlertSeverity, Description, DeviceId, TenantId, CompromisedEntity, ProductName, Entities

Stage 5: extend

extend DomainIndex, HostName

Stage 6: extend

extend HostNameDomain
HostNameDomain =
ifCompromisedEntity != -1substring(CompromisedEntity, (DomainIndex + 1))
elseCompromisedEntity

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
ProviderNameeq
  • MDATP
field:"ProviderName" kind:eq value:"MDATP"
ThreatFamilyNamein
  • DoS:Win64/WprJooblash
  • Ransom:Win32/Eagle!MSR
  • Trojan:Win32/BatRunGoXml
  • Trojan:Win32/Debitom.A
  • TrojanDropper:ASP/WebShell!MSR
field:"ThreatFamilyName" kind:in
ThreatNamein
  • DoS:Win64/WprJooblash
  • Ransom:Win32/Eagle!MSR
  • Trojan:Win32/BatRunGoXml
  • Trojan:Win32/Debitom.A
  • TrojanDropper:ASP/WebShell!MSR
field:"ThreatName" kind:in

Output Fields

These fields are emitted when the rule matches.

FieldSource
AlertSeveritysummarize
CompromisedEntitysummarize
Descriptionsummarize
DeviceIdsummarize
DisplayNamesummarize
Entitiessummarize
ProductNamesummarize
PublicIPsummarize
TenantIdsummarize
ThreatFamilyNamesummarize
ThreatNamesummarize
DomainIndexextend
HostNameextend
HostNameDomainextend

Azure WAF matching for Log4j vuln(CVE-2021-44228)

#
Status
available
Severity
high
Time window
6h
Source
github.com/Azure/Azure-Sentinel

'This query will alert on a positive pattern match by Azure WAF for CVE-2021-44228 log4j vulnerability exploitation attempt. If possible, it then decodes the malicious command for further analysis. Reference: https://www.microsoft.com/security/blog/2021/12/11/guidance-for-preventing-detecting-and-hunting-for-cve-2021-44228-log4j-2-exploitation/'

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: 2de8abd6-a613-450e-95ed-08e503369fb3
name: Azure WAF matching for Log4j vuln(CVE-2021-44228)
description: |
  'This query will alert on a positive pattern match by Azure WAF for CVE-2021-44228 log4j vulnerability exploitation attempt. If possible, it then decodes the malicious command for further analysis.
   Reference: https://www.microsoft.com/security/blog/2021/12/11/guidance-for-preventing-detecting-and-hunting-for-cve-2021-44228-log4j-2-exploitation/'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: WAF
    dataTypes:
      - AzureDiagnostics
queryFrequency: 6h
queryPeriod: 6h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1190
tags:
  - CVE-2021-44228
  - log4j 
  - log4shell
query: |
  let log4jioc = dynamic(["jndi","ldap","${::"]);
  AzureDiagnostics
  | where ResourceProvider == "MICROSOFT.NETWORK" and Category in ("ApplicationGatewayFirewallLog", "FrontdoorWebApplicationFirewallLog")
  | extend details_data_s = column_ifexists("details_data_s", tostring(AdditionalFields.details_data))
  | extend details_msg_s = column_ifexists("details_msg_s", tostring(AdditionalFields.details_msg))
  | extend details_message_s = column_ifexists("details_message_s", tostring(AdditionalFields.details_message))
  | where requestUri_s has_any (log4jioc) or details_message_s has_any (log4jioc) or details_msg_s has_any (log4jioc) or details_data_s has_any (log4jioc)
  | extend Malicious = iff(isnotempty( details_data_s),details_data_s,iff(isnotempty( requestUri_s),requestUri_s,""))
  |parse Malicious with * '${' MaliciousCommand '}' * 
  | extend EncodeCmd = iff(MaliciousCommand has 'Base64/', split(split(MaliciousCommand, "Base64/",1)[0], "}", 0)[0], "")
  | extend EncodeCmd1 = iff(MaliciousCommand has 'base64/', split(split(MaliciousCommand, "base64/",1)[0], "}", 0)[0], "")
  | extend CmdLine = iff( isnotempty(EncodeCmd), EncodeCmd, EncodeCmd1)
  | extend DecodedCmdLine = base64_decode_tostring(tostring(CmdLine))
  | extend DecodedCmdLine = iff( isnotempty(DecodedCmdLine), DecodedCmdLine, "Unable to decode/Doesn't need decoding")
  | project TimeGenerated, Target=column_ifexists("hostname_s", tostring(AdditionalFields.hostname)), MaliciousHost = column_ifexists("clientIp_s", tostring(AdditionalFields.clientIp)) , MaliciousCommand, details_data_s = column_ifexists("details_data_s", tostring(AdditionalFields.details_data)), DecodedCmdLine, Message,
  ruleSetType_s = column_ifexists("ruleSetType_s", tostring(AdditionalFields.ruleSetType)), OperationName, SubscriptionId, details_message_s = column_ifexists("details_message_s", tostring(AdditionalFields.details_message)), details_msg_s = column_ifexists("details_msg_s", tostring(AdditionalFields.details_msg)),  details_file_s = column_ifexists("details_message_s", tostring(AdditionalFields.details_file))
  | extend timestamp = TimeGenerated
entityMappings: 
- entityType: IP
  fieldMappings:
    - identifier: Address
      columnName: MaliciousHost
version: 1.0.6
kind: Scheduled

Stages and Predicates

Parameters

let log4jioc = dynamic(["jndi","ldap","${::"]);

Stage 1: source

AzureDiagnostics

Stage 2: where

| where ResourceProvider == "MICROSOFT.NETWORK" and Category in ("ApplicationGatewayFirewallLog", "FrontdoorWebApplicationFirewallLog")

Stage 3: extend (3 consecutive steps)

| extend details_data_s = column_ifexists("details_data_s", tostring(AdditionalFields.details_data))
| extend details_msg_s = column_ifexists("details_msg_s", tostring(AdditionalFields.details_msg))
| extend details_message_s = column_ifexists("details_message_s", tostring(AdditionalFields.details_message))

Stage 4: where

| where requestUri_s has_any (log4jioc) or details_message_s has_any (log4jioc) or details_msg_s has_any (log4jioc) or details_data_s has_any (log4jioc)

Stage 5: extend

| extend Malicious = iff(isnotempty( details_data_s),details_data_s,iff(isnotempty( requestUri_s),requestUri_s,""))
Malicious =
ifisnotempty(details_data_s)details_data_s
elseiff(isnotempty(requestUri_s), requestUri_s, "")

Stage 6: parse

| parse Malicious with * '${' MaliciousCommand '}' *

Stage 7: extend (5 consecutive steps)

| extend EncodeCmd = iff(MaliciousCommand has 'Base64/', split(split(MaliciousCommand, "Base64/",1)[0], "}", 0)[0], "")
| extend EncodeCmd1 = iff(MaliciousCommand has 'base64/', split(split(MaliciousCommand, "base64/",1)[0], "}", 0)[0], "")
| extend CmdLine = iff( isnotempty(EncodeCmd), EncodeCmd, EncodeCmd1)
| extend DecodedCmdLine = base64_decode_tostring(tostring(CmdLine))
| extend DecodedCmdLine = iff( isnotempty(DecodedCmdLine), DecodedCmdLine, "Unable to decode/Doesn't need decoding")
EncodeCmd =
ifMaliciousCommand has "Base64/"split(split(MaliciousCommand, "Base64/", 1)[0], "}", 0)[0]
else""

Stage 8: project

| project TimeGenerated, Target=column_ifexists("hostname_s", tostring(AdditionalFields.hostname)), MaliciousHost = column_ifexists("clientIp_s", tostring(AdditionalFields.clientIp)) , MaliciousCommand, details_data_s = column_ifexists("details_data_s", tostring(AdditionalFields.details_data)), DecodedCmdLine, Message,
ruleSetType_s = column_ifexists("ruleSetType_s", tostring(AdditionalFields.ruleSetType)), OperationName, SubscriptionId, details_message_s = column_ifexists("details_message_s", tostring(AdditionalFields.details_message)), details_msg_s = column_ifexists("details_msg_s", tostring(AdditionalFields.details_msg)),  details_file_s = column_ifexists("details_message_s", tostring(AdditionalFields.details_file))

Stage 9: extend

| extend timestamp = TimeGenerated

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Categoryin
  • ApplicationGatewayFirewallLog
  • FrontdoorWebApplicationFirewallLog
field:"Category" kind:in
ResourceProvidereq
  • MICROSOFT.NETWORK
field:"ResourceProvider" kind:eq value:"MICROSOFT.NETWORK"
details_data_smatch
  • ${:: transforms: term
  • jndi transforms: term
  • ldap transforms: term
field:"details_data_s" kind:match
details_message_smatch
  • ${:: transforms: term
  • jndi transforms: term
  • ldap transforms: term
field:"details_message_s" kind:match
details_msg_smatch
  • ${:: transforms: term
  • jndi transforms: term
  • ldap transforms: term
field:"details_msg_s" kind:match
requestUri_smatch
  • ${:: transforms: term
  • jndi transforms: term
  • ldap transforms: term
field:"requestUri_s" kind:match

Output Fields

These fields are emitted when the rule matches.

FieldSource
DecodedCmdLineproject
MaliciousCommandproject
MaliciousHostproject
Messageproject
OperationNameproject
SubscriptionIdproject
Targetproject
TimeGeneratedproject
details_data_sproject
details_file_sproject
details_message_sproject
details_msg_sproject
ruleSetType_sproject
timestampextend

Bitglass - Login from new device

#
Status
available
Severity
medium
Time window
14d
Group by
User
Source
github.com/Azure/Azure-Sentinel

'Detects when a user logins from new device.'

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Rule Body

id: bfca0251-1581-4185-906b-4805099e3216
name: Bitglass - Login from new device
description: |
  'Detects when a user logins from new device.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: Bitglass
    dataTypes:
      - Bitglass
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1078
query: |
  Bitglass
  | where EventType =~ 'access'
  | where EventMessage =~ 'Login'
  | summarize dev = makeset(Dvc) by User
  | join (Bitglass
          | where EventType =~ 'access'
          | where EventMessage =~ 'Login') on User
  | where dev !contains Dvc
  | extend AccountCustomEntity = User
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountCustomEntity
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

Bitglass

Stage 2: where

| where EventType =~ 'access'

Stage 3: where

| where EventMessage =~ 'Login'

Stage 4: summarize

| summarize dev = makeset(Dvc) by User

Stage 5: join

| join (Bitglass
        | where EventType =~ 'access'
        | where EventMessage =~ 'Login') on User

Stage 6: where

where dev !contains Dvc

Stage 7: extend

extend AccountCustomEntity

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
Usersummarize
devsummarize
AccountCustomEntityextend

BloodHound Attack Path Finding - AddOwner Role on Tier Zero Resource

#

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.

Status
available
Severity
medium
Time window
1h
Source
github.com/Azure/Azure-Sentinel

BloodHound Attack Path Finding - AddOwner Role on Tier Zero Resource

Rule Body

id: 3d6fa984-a989-4742-bcc2-099d2a995ef7
name: BloodHound Attack Path Finding - AddOwner Role on Tier Zero Resource
description: BloodHound Attack Path Finding - AddOwner Role on Tier Zero Resource
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: BloodHoundEnterprise
    dataTypes:
      - BHEAttackPathsData_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: BHEAttackPathsData_CL | where Finding == "AzureT0AddOwner"
entityMappings:
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: domain_name
customDetails:
  Description: ShortDescription
  Remediation: ShortRemediation
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

BHEAttackPathsData_CL

Stage 2: where

| where Finding == "AzureT0AddOwner"

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Findingeq
  • AzureT0AddOwner
field:"Finding" kind:eq value:"AzureT0AddOwner"

C2-NamedPipe

#
Status
available
Severity
high
Time window
1d
Source
github.com/Azure/Azure-Sentinel

Detects the creation of a named pipe used by known APT malware. Reference - https://docs.microsoft.com/openspecs/windows_protocols/ms-wpo/4de75e21-36fd-440a-859b-75accc74487c

MITRE ATT&CK Coverage

TacticTechniques
Command & Control

Telemetry Coverage

Rule Body

id: 7ce00cba-f76f-4026-ab7f-7e4f1b67bd18
name: C2-NamedPipe
description: |
  Detects the creation of a named pipe used by known APT malware.
  Reference - https://docs.microsoft.com/openspecs/windows_protocols/ms-wpo/4de75e21-36fd-440a-859b-75accc74487c
severity: High
status: Available
requiredDataConnectors:
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - DeviceEvents
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
relevantTechniques:
  - T1105
tags:
  - APT Malware
query: |
  // this is what should be constantly tweaked with default C2 framework names, search uses has_any (wildcard)
  let badPipeNames = pack_array(
      '\\psexec',                                     // PSexec default pipe
      '\\paexec',                                     // PSexec default pipe
      '\\remcom',                                     // PSexec default pipe
      '\\csexec',                                     // PSexec default pipe
      '\\isapi_http',                                 // Uroburos Malware Named Pipe
      '\\isapi_dg',                                   // Uroburos Malware Named Pipe
      '\\isapi_dg2',                                  // Uroburos Malware Named Pipe
      '\\sdlrpc',                                     // Cobra Trojan Named Pipe http://goo.gl/8rOZUX
      '\\ahexec',                                     // Sofacy group malware
      '\\winsession',                                 // Wild Neutron APT malware https://goo.gl/pivRZJ
      '\\lsassw',                                     // Wild Neutron APT malware https://goo.gl/pivRZJ
      '\\46a676ab7f179e511e30dd2dc41bd388',           // Project Sauron https://goo.gl/eFoP4A
      '\\9f81f59bc58452127884ce513865ed20',           // Project Sauron https://goo.gl/eFoP4A
      '\\e710f28d59aa529d6792ca6ff0ca1b34',           // Project Sauron https://goo.gl/eFoP4A
      '\\rpchlp_3',                                   // Project Sauron https://goo.gl/eFoP4A - Technical Analysis Input
      '\\NamePipe_MoreWindows',                       // US-CERT Alert - RedLeaves https://www.us-cert.gov/ncas/alerts/TA17-117A
      '\\pcheap_reuse',                               // Pipe used by Equation Group malware 77486bb828dba77099785feda0ca1d4f33ad0d39b672190079c508b3feb21fb0
      '\\gruntsvc',                                   // Covenant default named pipe
      '\\583da945-62af-10e8-4902-a8f205c72b2e',       // SolarWinds SUNBURST malware report https://www.fireeye.com/blog/threat-research/2020/12/evasive-attacker-leverages-solarwinds-supply-chain-compromises-with-sunburst-backdoor.html
      '\\bizkaz',                                     // Snatch Ransomware https://thedfirreport.com/2020/06/21/snatch-ransomware/
      '\\atctl',                                      // https://www.virustotal.com/#/file/a4ddb2664a6c87a1d3c5da5a5a32a5df9a0b0c8f2e951811bd1ec1d44d42ccf1/detection
      '\\userpipe',                                   // ruag apt case
      '\\iehelper',                                   // ruag apt case
      '\\sdlrpc',                                     // project cobra https://www.gdatasoftware.com/blog/2015/01/23926-analysis-of-project-cobra
      '\\comnap',                                     // https://www.gdatasoftware.com/blog/2015/01/23926-analysis-of-project-cobra
      '\\lsadump',                                    // Cred Dump-Tools Named Pipes
      '\\cachedump',                                  // Cred Dump-Tools Named Pipes
      '\\wceservicepipe',                             // Cred Dump-Tools Named Pipes
      '\\jaccdpqnvbrrxlaf',                           // PoshC2 default named pipe
      '\\svcctl',                                     // CrackMapExec default named pipe
      '\\csexecsvc'                                   // CSEXEC default named pipe
      '\\status_',                                    // CS default named pipes https://github.com/Neo23x0/sigma/issues/253
      '\\MSSE-',                                      // CobaltStrike default named pipe
      '\\status_',                                    // CobaltStrike default named pipe
      '\\msagent_',                                   // (target) CobaltStrike default named pipe
      '\\postex_ssh_',                                // CobaltStrike default named pipe
      '\\postex_',                                    // CobaltStrike default named pipe
      '\\Posh'                                        // PoshC2 default named pipe
  );
  DeviceEvents
  | where ActionType == "NamedPipeEvent"
  | extend ParsedFields=parse_json(AdditionalFields)
  | where ParsedFields.FileOperation == "File created"
  | where ParsedFields.PipeName has_any (badPipeNames)
  | project TimeGenerated, ActionType, DeviceName, InitiatingProcessAccountDomain, InitiatingProcessAccountName, InitiatingProcessFolderPath, InitiatingProcessCommandLine, ParsedFields.FileOperation, ParsedFields.PipeName
  | extend HostName = iff(DeviceName has '.', substring(DeviceName, 0, indexof(DeviceName, '.')), DeviceName)
  | extend DnsDomain = iff(DeviceName has '.', substring(DeviceName, indexof(DeviceName, '.') + 1), "")
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: DeviceName
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: DnsDomain
version: 1.0.0
kind: Scheduled

Stages and Predicates

Let Binding: badPipeNames used in Stage 5

let badPipeNames = pack_array(
    '\\psexec',
    '\\paexec',
    '\\remcom',
    '\\csexec',
    '\\isapi_http',
    '\\isapi_dg',
    '\\isapi_dg2',
    '\\sdlrpc',
    '\\ahexec',
    '\\winsession',
    '\\lsassw',
    '\\46a676ab7f179e511e30dd2dc41bd388',
    '\\9f81f59bc58452127884ce513865ed20',
    '\\e710f28d59aa529d6792ca6ff0ca1b34',
    '\\rpchlp_3',
    '\\NamePipe_MoreWindows',
    '\\pcheap_reuse',
    '\\gruntsvc',
    '\\583da945-62af-10e8-4902-a8f205c72b2e',
    '\\bizkaz',
    '\\atctl',
    '\\userpipe',
    '\\iehelper',
    '\\sdlrpc',
    '\\comnap',
    '\\lsadump',
    '\\cachedump',
    '\\wceservicepipe',
    '\\jaccdpqnvbrrxlaf',
    '\\svcctl',
    '\\csexecsvc'
    '\\status_',
    '\\MSSE-',
    '\\status_',
    '\\msagent_',
    '\\postex_ssh_',
    '\\postex_',
    '\\Posh'
);

Stage 1: source

DeviceEvents

Stage 2: where

| where ActionType == "NamedPipeEvent"

Stage 3: extend

| extend ParsedFields=parse_json(AdditionalFields)

Stage 4: where

| where ParsedFields.FileOperation == "File created"

Stage 5: where

| where ParsedFields.PipeName has_any (badPipeNames)

Stage 6: project

| project TimeGenerated, ActionType, DeviceName, InitiatingProcessAccountDomain, InitiatingProcessAccountName, InitiatingProcessFolderPath, InitiatingProcessCommandLine, ParsedFields.FileOperation, ParsedFields.PipeName

Stage 7: extend

| extend HostName = iff(DeviceName has '.', substring(DeviceName, 0, indexof(DeviceName, '.')), DeviceName)
HostName =
ifDeviceName has "."substring(DeviceName, 0, indexof(DeviceName, '.'))
elseDeviceName

Stage 8: extend

| extend DnsDomain = iff(DeviceName has '.', substring(DeviceName, indexof(DeviceName, '.') + 1), "")
DnsDomain =
ifDeviceName has "."substring(DeviceName, (indexof(DeviceName, '.') + 1))
else""

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
ActionTypeeq
  • NamedPipeEvent corpus 2 (kusto 2)
field:"ActionType" kind:eq value:"NamedPipeEvent"
ParsedFields.FileOperationeq
  • File created transforms: kusto_dynamic_path
field:"ParsedFields.FileOperation" kind:eq value:"File created"
ParsedFields.PipeNamematch
  • badPipeNames transforms: term, kusto_dynamic_path
field:"ParsedFields.PipeName" kind:match value:"badPipeNames"

Output Fields

These fields are emitted when the rule matches.

FieldSource
ActionTypeproject
DeviceNameproject
InitiatingProcessAccountDomainproject
InitiatingProcessAccountNameproject
InitiatingProcessCommandLineproject
InitiatingProcessFolderPathproject
ParsedFields.FileOperationproject
ParsedFields.PipeNameproject
TimeGeneratedproject
HostNameextend
DnsDomainextend

Cisco Duo - Multiple admin 2FA failures

#
Status
available
Severity
high
Time window
10m
Group by
ActorName
Source
github.com/Azure/Azure-Sentinel

'Detects when multiple admin 2FA failures occurs.'

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Rule Body

id: e46c5588-e643-4a60-a008-5ba9a4c84328
name: Cisco Duo - Multiple admin 2FA failures
description: |
  'Detects when multiple admin 2FA failures occurs.'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: CiscoDuoActivityConnectorDefinition
    dataTypes:
      - DuoActivity_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1078
query: |
  let threshold = 10;
  DuoActivity_CL
  | where ActionName =~ "admin_2fa_error"
  | summarize count() by ActorName, bin(TimeGenerated, 10m)
  | where count_ > threshold
  | extend AccountCustomEntity = ActorName
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountCustomEntity
version: 1.0.2
kind: Scheduled

Stages and Predicates

Parameters

let threshold = 10;

Stage 1: source

DuoActivity_CL

Stage 2: where

| where ActionName =~ "admin_2fa_error"

Stage 3: summarize

| summarize count() by ActorName, bin(TimeGenerated, 10m)

Stage 4: where

| where count_ > threshold

Stage 5: extend

| extend AccountCustomEntity = ActorName

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
ActorNamesummarize
AccountCustomEntityextend

Cisco WSA - Multiple errors to URL

#
Status
available
Severity
medium
Time window
5m
Group by
SrcUserName, UrlOriginal
Source
github.com/Azure/Azure-Sentinel

'Detects multiple connection errors to URL.'

MITRE ATT&CK Coverage

TacticTechniques
Command & Control

Rule Body

id: 1db49647-435c-41ad-bf8c-7130ba75429d
name: Cisco WSA - Multiple errors to URL
description: |
  'Detects multiple connection errors to URL.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: SyslogAma
    datatypes:
      - Syslog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
relevantTechniques:
  - T1102
query: |
  let threshold = 5;
  CiscoWSAEvent
  | where DvcAction =~ 'NONE'
  | summarize count() by SrcUserName, UrlOriginal, bin(TimeGenerated, 5m)
  | where count_ >= threshold
  | extend URLCustomEntity = UrlOriginal, AccountCustomEntity = SrcUserName
entityMappings:
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: URLCustomEntity
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountCustomEntity
version: 1.0.2
kind: Scheduled

Stages and Predicates

Parameters

let threshold = 5;

Stage 1: source

CiscoWSAEvent

Stage 2: where

| where DvcAction =~ 'NONE'

Stage 3: summarize

| summarize count() by SrcUserName, UrlOriginal, bin(TimeGenerated, 5m)

Stage 4: where

| where count_ >= threshold

Stage 5: extend

| extend URLCustomEntity = UrlOriginal, AccountCustomEntity = SrcUserName

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
SrcUserNamesummarize
UrlOriginalsummarize
AccountCustomEntityextend
URLCustomEntityextend

ContraForce - Machine credential activity

#
Status
available
Severity
medium
Time window
1d
Source
github.com/Azure/Azure-Sentinel

'Identifies service-account and machine-credential lifecycle activity in the ContraForce workspace: service accounts created or deleted, credentials created, regenerated or revoked, and API client secret regenerations. Machine credentials grant standing programmatic access, so unexpected lifecycle events can indicate persistence being established or an attempt to rotate credentials out from under their legitimate owner.'

MITRE ATT&CK Coverage

Rule Body

id: 8a2c6e94-1b5d-4f7a-b3e8-2c9d4f6a1e52
name: ContraForce - Machine credential activity
description: |
  'Identifies service-account and machine-credential lifecycle activity in the ContraForce
  workspace: service accounts created or deleted, credentials created, regenerated or revoked,
  and API client secret regenerations. Machine credentials grant standing programmatic access,
  so unexpected lifecycle events can indicate persistence being established or an attempt to
  rotate credentials out from under their legitimate owner.'
severity: Medium
requiredDataConnectors:
  - connectorId: ContraForceEvents
    dataTypes:
      - ContraForceEvents_CL
queryFrequency: 5m
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - Persistence
  - CredentialAccess
relevantTechniques:
  - T1098
  - T1136
query: |
  ContraForceEvents_CL
  | where ingestion_time() > ago(5m)
  | where ActivityType in ("ServiceAccountCreated", "ServiceAccountDeleted", "ServiceAccountCredentialCreated", "ServiceAccountCredentialRegenerated", "ServiceAccountCredentialRevoked", "ApiClientSecretRegenerated")
  | extend Meta = parse_json(Metadata)
  | project TimeGenerated, EventId, CfWorkspaceId, ActivityType, ActivityAction,
            ActorName, ActorEmail, TargetResourceType, TargetResourceId, TargetResourceName, Metadata
eventGroupingSettings:
  aggregationKind: AlertPerResult
alertDetailsOverride:
  alertDisplayNameFormat: 'ContraForce machine credential activity: {{ActivityType}} on {{TargetResourceName}}'
  alertDescriptionFormat: '{{ActorName}} performed {{ActivityType}} on {{TargetResourceName}}.'
customDetails:
  EventId: EventId
  ActorEmail: ActorEmail
  TargetResourceId: TargetResourceId
  CfWorkspaceId: CfWorkspaceId
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: PT2H
    matchingMethod: Selected
    groupByCustomDetails:
      - EventId
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

ContraForceEvents_CL

Stage 2: where

| where ingestion_time() > ago(5m)

Stage 3: where

| where ActivityType in ("ServiceAccountCreated", "ServiceAccountDeleted", "ServiceAccountCredentialCreated", "ServiceAccountCredentialRegenerated", "ServiceAccountCredentialRevoked", "ApiClientSecretRegenerated")

Stage 4: extend

| extend Meta = parse_json(Metadata)

Stage 5: project

| project TimeGenerated, EventId, CfWorkspaceId, ActivityType, ActivityAction,
          ActorName, ActorEmail, TargetResourceType, TargetResourceId, TargetResourceName, Metadata

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
ActivityTypein
  • ApiClientSecretRegenerated
  • ServiceAccountCreated
  • ServiceAccountCredentialCreated
  • ServiceAccountCredentialRegenerated
  • ServiceAccountCredentialRevoked
  • ServiceAccountDeleted
field:"ActivityType" kind:in

Output Fields

These fields are emitted when the rule matches.

FieldSource
ActivityActionproject
ActivityTypeproject
ActorEmailproject
ActorNameproject
CfWorkspaceIdproject
EventIdproject
Metadataproject
TargetResourceIdproject
TargetResourceNameproject
TargetResourceTypeproject
TimeGeneratedproject

CYFIRMA - Medium severity File Hash Indicators with Block Action 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.

Severity
medium
Time window
5m
Source
github.com/Azure/Azure-Sentinel

"This query retrieves file hash indicators marked for blocking, with no assigned role, from the CyfirmaIndicators_CL table. It extracts MD5, SHA1, and SHA256 hashes and includes threat metadata for use in preventive security controls such as EDRs, threat intel platforms, or automated blocklists."

MITRE ATT&CK Coverage

Rule Body

id: cd06dae8-7b79-436b-b1a6-9d256830f9dd
name: CYFIRMA - Medium severity File Hash Indicators with Block Action Rule
description: |
  "This query retrieves file hash indicators marked for blocking, with no assigned role, from the CyfirmaIndicators_CL table. 
  It extracts MD5, SHA1, and SHA256 hashes and includes threat metadata for use in preventive security controls such as EDRs, threat intel platforms, or automated blocklists."
version: 1.0.1
kind: Scheduled
severity: Medium
enabled: false
requiredDataConnectors:
  - connectorId: CyfirmaCyberIntelligenceDC
    dataTypes:
      - CyfirmaIndicators_CL
query: |
  // File Hash Indicators with Block Action
  let timeFrame = 5m;
  CyfirmaIndicators_CL 
  | where  (ConfidenceScore < 80 and ConfidenceScore >= 50)
      and TimeGenerated between (ago(timeFrame) .. now())
      and pattern contains 'file:hashes' and RecommendedActions has 'Block' and (isempty(Roles) or not(Roles has_any ('Malware', 'Trojan')))
  | extend MD5 = extract(@"file:hashes\.md5\s*=\s*'([a-fA-F0-9]{32})'", 1, pattern)
  | extend SHA1 = extract(@"file:hashes\.'SHA-1'\s*=\s*'([a-fA-F0-9]{40})'", 1, pattern)
  | extend SHA256 = extract(@"file:hashes\.'SHA-256'\s*=\s*'([a-fA-F0-9]{64})'", 1, pattern)
  | extend
      Algo_MD5='MD5',
      Algo_SHA1= 'SHA1',
      Algo_SHA256='SHA256',
      ProviderName = 'CYFIRMA',
      ProductName = 'DeCYFIR/DeTCT'
  | project  
      MD5,
      SHA1,
      SHA256,
      Algo_MD5,
      Algo_SHA1,
      Algo_SHA256,
      ThreatActors,
      Sources,
      RecommendedActions,
      Roles,
      Country,
      name,
      Description,
      ConfidenceScore,
      SecurityVendors,
      IndicatorID,
      created,
      modified,
      valid_from,
      Tags,
      ThreatType,
      TimeGenerated,
      ProductName,
      ProviderName
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: GreaterThan
triggerThreshold: 0
suppressionDuration: 5m
suppressionEnabled: true
tactics:
  - Execution
  - InitialAccess
  - DefenseEvasion
  - Impact
relevantTechniques:
  - T1204
  - T1566
  - T1027
  - T1486
  - T1566.001
alertDetailsOverride:
  alertDisplayNameFormat: "High-Confidence File Hash Indicators with Block Action - {{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
entityMappings:
  - entityType: FileHash
    fieldMappings:
      - identifier: Algorithm
        columnName: Algo_MD5
      - identifier: Value
        columnName: MD5
  - entityType: FileHash
    fieldMappings:
      - identifier: Algorithm
        columnName: Algo_SHA1
      - identifier: Value
        columnName: SHA1
  - entityType: FileHash
    fieldMappings:
      - identifier: Algorithm
        columnName: Algo_SHA256
      - identifier: Value
        columnName: SHA256
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 ConfidenceScore >= 50)
    and TimeGenerated between (ago(timeFrame) .. now())
    and pattern contains 'file:hashes' and RecommendedActions has 'Block' and (isempty(Roles) or not(Roles has_any ('Malware', 'Trojan')))

Stage 3: extend (4 consecutive steps)

| extend MD5 = extract(@"file:hashes\.md5\s*=\s*'([a-fA-F0-9]{32})'", 1, pattern)
| extend SHA1 = extract(@"file:hashes\.'SHA-1'\s*=\s*'([a-fA-F0-9]{40})'", 1, pattern)
| extend SHA256 = extract(@"file:hashes\.'SHA-256'\s*=\s*'([a-fA-F0-9]{64})'", 1, pattern)
| extend
    Algo_MD5='MD5',
    Algo_SHA1= 'SHA1',
    Algo_SHA256='SHA256',
    ProviderName = 'CYFIRMA',
    ProductName = 'DeCYFIR/DeTCT'

Stage 4: project

| project  
    MD5,
    SHA1,
    SHA256,
    Algo_MD5,
    Algo_SHA1,
    Algo_SHA256,
    ThreatActors,
    Sources,
    RecommendedActions,
    Roles,
    Country,
    name,
    Description,
    ConfidenceScore,
    SecurityVendors,
    IndicatorID,
    created,
    modified,
    valid_from,
    Tags,
    ThreatType,
    TimeGenerated,
    ProductName,
    ProviderName

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
Algo_MD5project
Algo_SHA1project
Algo_SHA256project
ConfidenceScoreproject
Countryproject
Descriptionproject
IndicatorIDproject
MD5project
ProductNameproject
ProviderNameproject
RecommendedActionsproject
Rolesproject
SHA1project
SHA256project
SecurityVendorsproject
Sourcesproject
Tagsproject
ThreatActorsproject
ThreatTypeproject
TimeGeneratedproject
createdproject
modifiedproject
nameproject
valid_fromproject

CYFIRMA - Medium severity Malicious Network Indicators Associated with Malware - Monitor 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.

Severity
medium
Time window
5m
Source
github.com/Azure/Azure-Sentinel

"This analytics rule detects network-based indicators identified by CYFIRMA threat intelligence as associated with Command & Control (C2) infrastructure. These may represent attacker-controlled endpoints used to maintain persistence, exfiltrate data, or receive commands from malware-infected hosts."

MITRE ATT&CK Coverage

Rule Body

id: ee2325cc-f5ba-49ac-b4d9-58ef0766e168
name: CYFIRMA - Medium severity Malicious Network Indicators Associated with Malware - Monitor Recommended Rule
description: |
  "This analytics rule detects network-based indicators identified by CYFIRMA threat intelligence as associated with Command & Control (C2) infrastructure.
  These may represent attacker-controlled endpoints used to maintain persistence, exfiltrate data, or receive commands from malware-infected hosts."
version: 1.0.1
kind: Scheduled
severity: Medium
enabled: false
requiredDataConnectors:
  - connectorId: CyfirmaCyberIntelligenceDC
    dataTypes:
      - CyfirmaIndicators_CL
query: |
  //Network Indicators Associated with Malware - Monitor Recommended Rule
  let timeFrame= 5m;
  CyfirmaIndicators_CL 
  | where  (ConfidenceScore < 80 and ConfidenceScore >= 50)
      and TimeGenerated between (ago(timeFrame) .. now())
      and pattern !contains 'file:hashes' and RecommendedActions has 'Monitor' and Roles has 'Malware'
  | 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:
  - InitialAccess
  - Execution
  - CommandAndControl
relevantTechniques:
  - T1566
  - T1189
  - T1204
  - T1071
  - T1090
  - T1059
  - T1568
  - T1566.002
  - T1204.001
  - T1071.001
  - T1568.002
alertDetailsOverride:
  alertDisplayNameFormat: "High-Confidence Malicious Network Indicators Associated with Malware - Monitor 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 ConfidenceScore >= 50)
    and TimeGenerated between (ago(timeFrame) .. now())
    and pattern !contains 'file:hashes' and RecommendedActions has 'Monitor' and Roles has 'Malware'

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.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
patterncontainsfile:hashesexcludes:pattern field:"pattern" value:"file:hashes"

Output Fields

These fields are emitted when the rule matches.

FieldSource
ConfidenceScoreproject
Countryproject
Descriptionproject
Domainproject
IPAbuseproject
IPv4project
IPv6project
IndicatorIDproject
ProductNameproject
ProviderNameproject
RecommendedActionsproject
Rolesproject
SecurityVendorsproject
Sourcesproject
Tagsproject
ThreatActorsproject
ThreatTypeproject
TimeGeneratedproject
URLproject
createdproject
modifiedproject
nameproject
valid_fromproject

Digital Guardian - Multiple incidents from user

#
Status
available
Severity
high
Time window
30m
Group by
SrcUserName
Source
github.com/Azure/Azure-Sentinel

'Detects multiple incidents from user.'

MITRE ATT&CK Coverage

Rule Body

id: e8901dac-2549-4948-b793-5197a5ed697a
name: Digital Guardian - Multiple incidents from user
description: |
  'Detects multiple incidents from user.'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: SyslogAma
    datatypes:
      - Syslog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Exfiltration
relevantTechniques:
  - T1048
query: |
  let threshold = 2;
  DigitalGuardianDLPEvent
  | where isnotempty(MatchedPolicies)
  | summarize count() by SrcUserName, bin(TimeGenerated, 30m)
  | where count_ >= threshold
  | extend AccountCustomEntity = SrcUserName
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountCustomEntity
version: 1.0.2
kind: Scheduled

Stages and Predicates

Parameters

let threshold = 2;

Stage 1: source

DigitalGuardianDLPEvent

Stage 2: where

| where isnotempty(MatchedPolicies)

Stage 3: summarize

| summarize count() by SrcUserName, bin(TimeGenerated, 30m)

Stage 4: where

| where count_ >= threshold

Stage 5: extend

| extend AccountCustomEntity = SrcUserName

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
MatchedPoliciesis_not_null
  • (no value, null check)
field:"MatchedPolicies" kind:is_not_null
count_ge
  • 2 transforms: number
field:"count_" kind:ge value:"2"

Output Fields

These fields are emitted when the rule matches.

FieldSource
SrcUserNamesummarize
AccountCustomEntityextend

Execution of software vulnerable to webp buffer overflow of CVE-2023-4863

#
Status
available
Severity
informational
Time window
1h
Source
github.com/Azure/Azure-Sentinel

This query looks at device, process, and network events from Defender for Endpoint that may be vulnerable to buffer overflow defined in CVE-2023-4863. Results are not an indicator of malicious activity.

MITRE ATT&CK Coverage

Telemetry Coverage

Rule Body

id: 26e81021-2de6-4442-a74a-a77885e96911
name: Execution of software vulnerable to webp buffer overflow of CVE-2023-4863
description: |
    'This query looks at device, process, and network events from Defender for Endpoint that may be vulnerable to buffer overflow defined in CVE-2023-4863. Results are not an indicator of malicious activity.'
severity: Informational
status: Available
kind: Scheduled
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Execution
relevantTechniques:
  - T1203
tags:
  - CVE-2023-4863
requiredDataConnectors:
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - DeviceProcessEvents
      - DeviceNetworkEvents
      - DeviceEvents
      - DeviceTvmSoftwareVulnerabilities
query: |-
  //CVE-2023-4863 Process activity with .webp files on devices where CVE-2023-4863 is unpatched
  //This query shows all process activity with .webp files on vulnerable machines and is not an indicator of malicious activity
  let VulnDevices = DeviceTvmSoftwareVulnerabilities
      | where CveId == "CVE-2023-4863"
      | distinct DeviceId;
  union DeviceProcessEvents, DeviceNetworkEvents, DeviceEvents
  | where DeviceId in (VulnDevices) and (InitiatingProcessCommandLine has(".webp") or ProcessCommandLine has(".webp"))
  | extend Name = tostring(split(AccountUpn, "@")[0]), UPNSuffix = tostring(split(AccountUpn, "@")[1])
  | extend HostName = tostring(split(DeviceName, ".")[0]), DomainIndex = toint(indexof(DeviceName, '.'))
  | extend HostNameDomain = iff(DomainIndex != -1, substring(DeviceName, DomainIndex + 1), DeviceName)
entityMappings:
- entityType: Host
  fieldMappings:
    - identifier: FullName
      columnName: DeviceName
    - identifier: HostName
      columnName: HostName
    - identifier: DnsDomain
      columnName: HostNameDomain
- entityType: Account
  fieldMappings:
    - identifier: FullName
      columnName: AccountUpn
    - identifier: Name
      columnName: Name
    - identifier: UPNSuffix
      columnName: UPNSuffix
- entityType: IP
  fieldMappings:
    - identifier: Address
      columnName: LocalIP
- entityType: Process
  fieldMappings:
    - identifier: ProcessId
      columnName: ProcessId
- entityType: Process
  fieldMappings:
    - identifier: ProcessId
      columnName: InitiatingProcessId
- entityType: Process
  fieldMappings:
    - identifier: CommandLine
      columnName: ProcessCommandLine
suppressionEnabled: false
incidentConfiguration:
  createIncident: false
  groupingConfiguration:
    enabled: false
    reopenClosedIncident: false
    lookbackDuration: PT5H
    matchingMethod: Selected
    groupByEntities:
    - Account
suppressionDuration: PT5H
alertDetailsOverride:
  alertDisplayNameFormat: Possible exploitation of CVE-2023-4863
  alertDynamicProperties: []
eventGroupingSettings:
  aggregationKind: SingleAlert
version: 1.1.3

Stages and Predicates

Let Binding: VulnDevices

let VulnDevices = DeviceTvmSoftwareVulnerabilities
    | where CveId == "CVE-2023-4863"
    | distinct DeviceId;

Union (3 Sources)

Each leg below queries one source; the rule matches if any leg does. Sources: DeviceProcessEvents, DeviceNetworkEvents, DeviceEvents

Leg 1: DeviceProcessEvents

Leg 2: DeviceNetworkEvents

Leg 3: DeviceEvents

Applied to the Combined Result

| where DeviceId in (VulnDevices) and (InitiatingProcessCommandLine has(".webp") or ProcessCommandLine has(".webp")) | extend Name = tostring(split(AccountUpn, "@")[0]), UPNSuffix = tostring(split(AccountUpn, "@")[1]) | extend HostName = tostring(split(DeviceName, ".")[0]), DomainIndex = toint(indexof(DeviceName, '.')) | extend HostNameDomain = iff(DomainIndex != -1, substring(DeviceName, DomainIndex + 1), DeviceName)

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
Nameextend
UPNSuffixextend
DomainIndexextend
HostNameextend
HostNameDomainextend

GitHub - A payment method was removed

#
Status
available
Severity
medium
Time window
7d
Source
github.com/Azure/Azure-Sentinel

Detect activities when a payment method was removed. This query runs every day and its severity is Medium.

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Telemetry Coverage

Rule Body

id: 6bb50582-caac-4a9b-9afb-3fee766ebbf7
name: GitHub - A payment method was removed
description: |
  'Detect activities when a payment method was removed. This query runs every day and its severity is Medium.'
severity: Medium
status: Available
requiredDataConnectors: []
queryFrequency: 1d
queryPeriod: 7d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1078
query: |
    GitHubAuditData
    | where Action == "payment_method.remove"
    | extend AccountName = tostring(split(Actor, "@")[0]), AccountUPNSuffix = tostring(split(Actor, "@")[1])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: Actor
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: AccountUPNSuffix
version: 1.0.1
kind: Scheduled

Stages and Predicates

Stage 1: source

GitHubAuditData

Stage 2: where

| where Action == "payment_method.remove"

Stage 3: extend

| extend AccountName = tostring(split(Actor, "@")[0]), AccountUPNSuffix = tostring(split(Actor, "@")[1])

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Actioneq
  • payment_method.remove
field:"Action" kind:eq value:"payment_method.remove"

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountNameextend
AccountUPNSuffixextend

Google DNS - CVE-2021-34527 (PrintNightmare) external exploit

#
Severity
high
Time window
15m
Source
github.com/Azure/Azure-Sentinel

'Detects CVE-2021-34527 (PrintNightmare) external exploit'

MITRE ATT&CK Coverage

TacticTechniques
Privilege Escalation

Rule Body

id: e632e73a-06c4-47f6-8bed-b2498aa6e30f
name: Google DNS - CVE-2021-34527 (PrintNightmare) external exploit
description: |
  'Detects CVE-2021-34527 (PrintNightmare) external exploit'
severity: High
requiredDataConnectors:
  - connectorId: GCPDNSDataConnector
    dataTypes:
      - GCPCloudDNS
queryFrequency: 15m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - PrivilegeEscalation
relevantTechniques:
  - T1068
query: |
  GCPCloudDNS
  | where Query has 'gentilkiwi'
  | extend DNSCustomEntity = Query, IPCustomEntity = SrcIpAddr
entityMappings:
  - entityType: DNS
    fieldMappings:
      - identifier: DomainName
        columnName: DNSCustomEntity
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPCustomEntity
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

GCPCloudDNS

Stage 2: where

| where Query has 'gentilkiwi'

Stage 3: extend

| extend DNSCustomEntity = Query, IPCustomEntity = SrcIpAddr

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Querymatch
  • gentilkiwi transforms: term
field:"Query" kind:match value:"gentilkiwi"

Output Fields

These fields are emitted when the rule matches.

FieldSource
DNSCustomEntityextend
IPCustomEntityextend

GSA - Detect Source IP Scanning Multiple Open Ports

#
Status
available
Severity
medium
Time window
30s
Group by
DestinationFqdn, SourceIp
Source
github.com/Azure/Azure-Sentinel

Identifies a source IP scanning multiple open ports on Global Secure Access Firewall. This can indicate malicious scanning of ports by an attacker, trying to reveal open ports in the organization that can be compromised for initial access. Configurable Parameters: - Port scan time - the time range to look for multiple ports scanned. Default is set to 30 seconds. - Minimum different ports threshold - alert only if more than this number of ports scanned. Default is set to 100.

MITRE ATT&CK Coverage

TacticTechniques
Discovery

Rule Body

id: 82cfa6b9-5f7e-4b8b-8b2f-a63f21b7a7d1
name: GSA - Detect Source IP Scanning Multiple Open Ports
description: |
  Identifies a source IP scanning multiple open ports on Global Secure Access Firewall. This can indicate malicious scanning of ports by an attacker, trying to reveal open ports in the organization that can be compromised for initial access.
    Configurable Parameters:
    - Port scan time - the time range to look for multiple ports scanned. Default is set to 30 seconds.
    - Minimum different ports threshold - alert only if more than this number of ports scanned. Default is set to 100.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - NetworkAccessTrafficLogs
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 1
tactics:
  - Discovery
relevantTechniques:
  - T1046
query: |
  let port_scan_time = 30s;
  let min_ports_threshold = 100;
  NetworkAccessTraffic
  | where TimeGenerated > ago(1d)
  | where Action == 'Allowed'
  | summarize PortsScanned = dcount(DestinationPort) by SourceIp, DestinationFqdn, bin(TimeGenerated, port_scan_time)
  | where PortsScanned > min_ports_threshold
  | project SourceIp, PortsScanned, TimeGenerated,DestinationFqdn
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIp
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: DestinationFqdn
version: 1.0.2
kind: Scheduled

Stages and Predicates

Parameters

let port_scan_time = 30s;
let min_ports_threshold = 100;

Stage 1: source

NetworkAccessTraffic

Stage 2: where

| where TimeGenerated > ago(1d)

Stage 3: where

| where Action == 'Allowed'

Stage 4: summarize

| summarize PortsScanned = dcount(DestinationPort) by SourceIp, DestinationFqdn, bin(TimeGenerated, port_scan_time)

Stage 5: where

| where PortsScanned > min_ports_threshold

Stage 6: project

| project SourceIp, PortsScanned, TimeGenerated,DestinationFqdn

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
DestinationFqdnproject
PortsScannedproject
SourceIpproject
TimeGeneratedproject

GTI - Zero-Day Vulnerability Detected

#
Status
available
Severity
high
Time window
10m
Source
github.com/Azure/Azure-Sentinel

Detects vulnerabilities ingested from Google Threat Intelligence that are tagged as Zero Day, indicating a vulnerability being exploited before a patch is publicly available. Fires when a matching record is found via the GTIVulnerabilities parser, warranting immediate analyst response.

MITRE ATT&CK Coverage

Rule Body

id: ebd03de8-0cee-4923-b510-45337e0aed84
name: GTI - Zero-Day Vulnerability Detected
description: |
  Detects vulnerabilities ingested from Google Threat Intelligence that are tagged as Zero Day,
  indicating a vulnerability being exploited before a patch is publicly available. Fires when a
  matching record is found via the GTIVulnerabilities parser, warranting immediate analyst response.
severity: High
status: Available
requiredDataConnectors:
  - connectorId: GTIVulnerabilitiesConnector
    dataTypes:
      - GTI_Vulnerabilities_CL
queryFrequency: 10m
queryPeriod: 10m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
  - Execution
  - PrivilegeEscalation
  - Impact
relevantTechniques:
  - T1190
  - T1068
  - T1203
  - T1486
query: |
  GTIVulnerabilities
  | where tolower(tostring(Tags)) has_any ("zero-day", "0day", "0-day", "zero_day", "zero Day")
  | extend Cvssv3Score = coalesce(Cvssv3xBaseScore, Cvssv3xTranslatedBaseScore)
  | extend
      Vendor       = tostring(Cpes[0].start_cpe.vendor),
      Product      = tostring(Cpes[0].start_cpe.product),
      CpeVersion   = tostring(Cpes[0].start_cpe.version),
      VendorFixUrl = coalesce(tostring(VendorFixReferences[0].url), tostring(VendorFixReferences[0]))
  | mv-apply _c = Cpes on (
      where isnotnull(_c.start_cpe)
      | summarize AffectedVendors  = make_set(tostring(_c.start_cpe.vendor)),
                  AffectedProducts = make_set(tostring(_c.start_cpe.product))
    )
  | project
      TimeGenerated,
      CveId,
      VulnName,
      RiskRating,
      Priority,
      ExploitationState,
      ExploitationConsequence,
      ExploitReleaseDate,
      Cvssv3Score,
      Cvssv4xScore,
      EpssScore,
      EpssPercentile,
      Cvssv4xThreatExploitMaturity,
      RecentActivityRelativeChange,
      Vendor,
      Product,
      CpeVersion,
      AffectedVendors,
      AffectedProducts,
      Description,
      VendorFixUrl,
      VendorFixReferences,
      Tags,
      Type
alertDetailsOverride:
  alertDisplayNameFormat: 'GTI Zero-Day: {{CveId}}'
  alertDescriptionFormat: 'CVE: {{CveId}} | Risk: {{RiskRating}} | State: {{ExploitationState}}'
entityMappings:
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: VendorFixUrl
customDetails:
  CveId: CveId
  RiskRating: RiskRating
  Priority: Priority
  ExploitState: ExploitationState
  Cvssv3Score: Cvssv3Score
  Cvssv4Score: Cvssv4xScore
  EpssScore: EpssScore
  EpssPercentile: EpssPercentile
  ExploitMaturity: Cvssv4xThreatExploitMaturity
  ActivityChange: RecentActivityRelativeChange
  AffectedVendor: Vendor
  AffectedProduct: Product
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: P1D
    matchingMethod: Selected
    groupByCustomDetails:
      - CveId
eventGroupingSettings:
  aggregationKind: AlertPerResult
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

GTIVulnerabilities

Stage 2: where

| where tolower(tostring(Tags)) has_any ("zero-day", "0day", "0-day", "zero_day", "zero Day")

Stage 3: extend

| extend Cvssv3Score = coalesce(Cvssv3xBaseScore, Cvssv3xTranslatedBaseScore)

Stage 4: extend

| extend
    Vendor       = tostring(Cpes[0].start_cpe.vendor),
    Product      = tostring(Cpes[0].start_cpe.product),
    CpeVersion   = tostring(Cpes[0].start_cpe.version),
    VendorFixUrl = coalesce(tostring(VendorFixReferences[0].url), tostring(VendorFixReferences[0]))

Stage 5: kusto:mv-apply

| mv-apply _c = Cpes on (
    where isnotnull(_c.start_cpe)
    | summarize AffectedVendors  = make_set(tostring(_c.start_cpe.vendor)),
                AffectedProducts = make_set(tostring(_c.start_cpe.product))
  )

Stage 6: project

| project
    TimeGenerated,
    CveId,
    VulnName,
    RiskRating,
    Priority,
    ExploitationState,
    ExploitationConsequence,
    ExploitReleaseDate,
    Cvssv3Score,
    Cvssv4xScore,
    EpssScore,
    EpssPercentile,
    Cvssv4xThreatExploitMaturity,
    RecentActivityRelativeChange,
    Vendor,
    Product,
    CpeVersion,
    AffectedVendors,
    AffectedProducts,
    Description,
    VendorFixUrl,
    VendorFixReferences,
    Tags,
    Type

Stage 7: summarize aggregation inside the mv-apply branch

summarize

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Tagsmatch
  • 0-day transforms: tolower, tostring, term
  • 0day transforms: tolower, tostring, term
  • zero Day transforms: tolower, tostring, term
  • zero-day transforms: tolower, tostring, term
  • zero_day transforms: tolower, tostring, term
field:"Tags" kind:match
_c.start_cpeis_not_null
  • (no value, null check)
field:"_c.start_cpe" kind:is_not_null

KMS Server Settings Updated

#
Status
available
Severity
high
Time window
5m
Source
github.com/Azure/Azure-Sentinel

Detects when KMS server settings are updated in Veeam Backup & Replication. This might indicate configuration changes that require review.

Rule Body

id: 594dafa1-e2d5-4f83-b32e-c629cb629b6f
name: KMS Server Settings Updated
description: Detects when KMS server settings are updated in Veeam Backup & Replication.
  This might indicate configuration changes that require review.
severity: High
status: Available
requiredDataConnectors:
- connectorId: Syslog
  dataTypes:
  - Syslog
- connectorId: SyslogAma
  dataTypes:
  - Syslog
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
  aggregationKind: AlertPerResult
tactics: []
relevantTechniques: []
query: "Veeam_GetSecurityEvents\n| where instanceId == 42302\n| extend Name = extract(\"\
  Name=\\\"([^\\\"]*)\\\"\", 1, SyslogMessage)\n| project\n    Date = format_datetime(TimeGenerated,\
  \ 'dd.MM.yyyy HH:mm'),\n    DataSource = original_host,\n    EventId = instanceId,\n\
  \    [\"Obeject Name\"] = Name,\n    MessageDetails = Description,\n   Severity\
  \ = SeverityDescription"
version: 1.0.1
kind: Scheduled
customDetails:
  Date: Date
  VbrHostName: DataSource
  EventId: EventId
  MessageDetails: MessageDetails
  Severity: Severity

Stages and Predicates

Stage 1: source

Veeam_GetSecurityEvents

Stage 2: where

| where instanceId == 42302

Stage 3: extend

| extend Name = extract("Name=\"([^\"]*)\"", 1, SyslogMessage)

Stage 4: project

| project
    Date = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),
    DataSource = original_host,
    EventId = instanceId,
    ["Obeject Name"] = Name,
    MessageDetails = Description,
   Severity = SeverityDescription

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
DataSourceproject
Dateproject
EventIdproject
MessageDetailsproject
Obeject Nameproject
Severityproject

M365D Alerts Correlation to non-Microsoft Network device network activity involved in successful sign-in Activity

#

Microsoft Defender for Endpoint (MDE) signatures for Azure Synapse pipelines and Azure Data Factory

#
Severity
high
Time window
1d
Group by
$left.DeviceName, $right.CompromisedEntity, AlertSeverity, CompromisedEntity, Description, DeviceId, DisplayName, Entities, ProductName, PublicIP, TenantId, ThreatFamilyName, ThreatName
Author
Microsoft Security Research
Source
github.com/Azure/Azure-Sentinel

This query looks for Microsoft Defender for Endpoint detections related to the remote command execution attempts on Azure IR with Managed VNet or SHIR. In Microsoft Sentinel, the SecurityAlerts table includes the name of the impacted device. Additionally, this query joins the DeviceInfo table to connect other information such as device group, IP address, signed in users, and others allowing analysts using Microsoft Sentinel to have more context related to the alert. Reference: https://msrc.microsoft.com/update-guide/vulnerability/CVE-2022-29972 , https://msrc-blog.microsoft.com/2022/05/09/vulnerability-mitigated-in-the-third-party-data-connector-used-in-azure-synapse-pipelines-and-azure-data-factory-cve-2022-29972

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Telemetry Coverage

Rule Body

id: a333d8bf-22a3-4c55-a1e9-5f0a135c0253
name: Microsoft Defender for Endpoint (MDE) signatures for Azure Synapse pipelines and Azure Data Factory
description: |
  'This query looks for Microsoft Defender for Endpoint detections related to the remote command execution attempts on Azure IR with Managed VNet or SHIR. 
  In Microsoft Sentinel, the SecurityAlerts table includes the name of the impacted device. Additionally, this query joins the DeviceInfo table to connect other information such as device group, IP address, signed in users, and others allowing analysts using Microsoft Sentinel to have more context related to the alert. 
  Reference: https://msrc.microsoft.com/update-guide/vulnerability/CVE-2022-29972 , 
  https://msrc-blog.microsoft.com/2022/05/09/vulnerability-mitigated-in-the-third-party-data-connector-used-in-azure-synapse-pipelines-and-azure-data-factory-cve-2022-29972'
severity: High
requiredDataConnectors:
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - SecurityAlert
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1190
tags:
  - CVE-2022-29972
query: |
  let mde_threats = dynamic(["Behavior:Win32/SuspAzureRequest.A", "Behavior:Win32/SuspAzureRequest.B", "Behavior:Win32/SuspAzureRequest.C", "Behavior:Win32/LaunchingSuspCMD.B"]);
  DeviceInfo
  | extend DeviceName = tolower(DeviceName)
  | join kind=inner ( SecurityAlert
  | where ProviderName == "MDATP"
  | extend ThreatName = tostring(parse_json(ExtendedProperties).ThreatName)
  | extend ThreatFamilyName = tostring(parse_json(ExtendedProperties).ThreatFamilyName)
  | where ThreatName in~ (mde_threats) or ThreatFamilyName in~ (mde_threats)
  | extend CompromisedEntity = tolower(CompromisedEntity)
  ) on $left.DeviceName == $right.CompromisedEntity
  | summarize by bin(TimeGenerated, 1d), DisplayName, ThreatName, ThreatFamilyName, PublicIP, AlertSeverity, Description, tostring(LoggedOnUsers), DeviceId, TenantId, CompromisedEntity, tostring(LoggedOnUsers), ProductName, Entities
  | extend HostName = tostring(split(CompromisedEntity, ".")[0]), DomainIndex = toint(indexof(CompromisedEntity, '.'))
  | extend HostNameDomain = iff(DomainIndex != -1, substring(CompromisedEntity, DomainIndex + 1), CompromisedEntity)
  | project-away DomainIndex
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: CompromisedEntity
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: HostNameDomain
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: PublicIP
version: 1.0.6
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: Microsoft Security Research
    support:
        tier: Community
    categories:
        domains: [ "Security - Threat Protection" ]

Stages and Predicates

Let Binding: mde_threats used in Stage 3

let mde_threats = dynamic(["Behavior:Win32/SuspAzureRequest.A", "Behavior:Win32/SuspAzureRequest.B", "Behavior:Win32/SuspAzureRequest.C", "Behavior:Win32/LaunchingSuspCMD.B"]);

Stage 1: source

DeviceInfo

Stage 2: extend

| extend DeviceName = tolower(DeviceName)

Stage 3: join

| join kind=inner ( SecurityAlert
| where ProviderName == "MDATP"
| extend ThreatName = tostring(parse_json(ExtendedProperties).ThreatName)
| extend ThreatFamilyName = tostring(parse_json(ExtendedProperties).ThreatFamilyName)
| where ThreatName in~ (mde_threats) or ThreatFamilyName in~ (mde_threats)
| extend CompromisedEntity = tolower(CompromisedEntity)
) on $left.DeviceName == $right.CompromisedEntity

Stage 4: summarize

summarize by DisplayName, ThreatName, ThreatFamilyName, PublicIP, AlertSeverity, Description, DeviceId, TenantId, CompromisedEntity, ProductName, Entities

Stage 5: extend

extend DomainIndex, HostName

Stage 6: extend

extend HostNameDomain
HostNameDomain =
ifDomainIndex != -1substring(CompromisedEntity, (DomainIndex + 1))
elseCompromisedEntity

Stage 7: project-away

project-away DomainIndex

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
ProviderNameeq
  • MDATP
field:"ProviderName" kind:eq value:"MDATP"
ThreatFamilyNamein
  • Behavior:Win32/LaunchingSuspCMD.B
  • Behavior:Win32/SuspAzureRequest.A
  • Behavior:Win32/SuspAzureRequest.B
  • Behavior:Win32/SuspAzureRequest.C
field:"ThreatFamilyName" kind:in
ThreatNamein
  • Behavior:Win32/LaunchingSuspCMD.B
  • Behavior:Win32/SuspAzureRequest.A
  • Behavior:Win32/SuspAzureRequest.B
  • Behavior:Win32/SuspAzureRequest.C
field:"ThreatName" kind:in

Output Fields

These fields are emitted when the rule matches.

FieldSource
AlertSeveritysummarize
CompromisedEntitysummarize
Descriptionsummarize
DeviceIdsummarize
DisplayNamesummarize
Entitiessummarize
ProductNamesummarize
PublicIPsummarize
TenantIdsummarize
ThreatFamilyNamesummarize
ThreatNamesummarize
HostNameextend
HostNameDomainextend

Netskope - Suspicious Application Activity (Low Confidence / Risky App)

#
Status
available
Severity
medium
Time window
1h
Group by
DeviceClassification, Hostname, User, Userip
Source
github.com/Azure/Azure-Sentinel

Detects activity involving risky or low Cloud Confidence Level (CCL) applications, blocked application actions, or sensitive activities (upload, share, download) on unsanctioned apps. Helps surface Shadow IT and potential data leakage via risky cloud applications.

MITRE ATT&CK Coverage

TacticTechniques
Command & Control
Exfiltration

Rule Body

id: b2e7d3c5-9c46-4f2a-8d1b-3e5f7a9c2d41
name: Netskope - Suspicious Application Activity (Low Confidence / Risky App)
description: |
  Detects activity involving risky or low Cloud Confidence Level (CCL) applications,
  blocked application actions, or sensitive activities (upload, share, download) on
  unsanctioned apps. Helps surface Shadow IT and potential data leakage via risky
  cloud applications.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: NetskopeAlertEventsConnector
    dataTypes:
      - NetskopeAlertEvents_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Exfiltration
  - CommandAndControl
relevantTechniques:
  - T1567
  - T1102
query: |
  let riskyCcl = dynamic(["low", "poor"]);
  let sensitiveActivities = dynamic(["Upload", "Share", "Download", "Post", "Send"]);
  NetskopeAlertEvents_CL
  | where TimeGenerated > ago(1h)
  | where isnotempty(App)
  | where tolower(Ccl) in (riskyCcl)
       or Action =~ "block"
       or (Activity in~ (sensitiveActivities) and tolower(Ccl) != "excellent")
  | summarize
      EventCount = count(),
      Activities = make_set(Activity, 20),
      Actions = make_set(Action, 10),
      CCLs = make_set(Ccl, 10),
      Categories = make_set(Appcategory, 20),
      DistinctApps = dcount(App),
      Apps = make_set(App, 20),
      LastSeen = max(TimeGenerated)
      by User, Userip, Hostname, DeviceClassification
  | where EventCount > 5 or DistinctApps > 3
  | extend RiskIndicators = strcat_array(array_concat(
      iff(set_has_element(CCLs, "low") or set_has_element(CCLs, "poor"), dynamic(["Low Confidence App"]), dynamic([])),
      iff(set_has_element(Actions, "block"), dynamic(["Blocked Activity"]), dynamic([])),
      iff(DistinctApps > 3, dynamic(["Multiple Risky Apps"]), dynamic([]))
  ), ", ")
  | order by EventCount desc
  | project
      LastSeen,
      User,
      Userip,
      Hostname,
      DeviceClassification,
      EventCount,
      DistinctApps,
      Apps,
      Categories,
      Activities,
      Actions,
      CCLs,
      RiskIndicators
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: User
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: Userip
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: Hostname
version: 1.0.0
kind: Scheduled

Stages and Predicates

Parameters

let riskyCcl = dynamic(["low", "poor"]);
let sensitiveActivities = dynamic(["Upload", "Share", "Download", "Post", "Send"]);

Stage 1: source

NetskopeAlertEvents_CL

Stage 2: where

| where TimeGenerated > ago(1h)

Stage 3: where

| where isnotempty(App)

Stage 4: where

| where tolower(Ccl) in (riskyCcl)
     or Action =~ "block"
     or (Activity in~ (sensitiveActivities) and tolower(Ccl) != "excellent")

Stage 5: summarize

| summarize
    EventCount = count(),
    Activities = make_set(Activity, 20),
    Actions = make_set(Action, 10),
    CCLs = make_set(Ccl, 10),
    Categories = make_set(Appcategory, 20),
    DistinctApps = dcount(App),
    Apps = make_set(App, 20),
    LastSeen = max(TimeGenerated)
    by User, Userip, Hostname, DeviceClassification

Stage 6: where

| where EventCount > 5 or DistinctApps > 3

Stage 7: extend

| extend RiskIndicators = strcat_array(array_concat(
    iff(set_has_element(CCLs, "low") or set_has_element(CCLs, "poor"), dynamic(["Low Confidence App"]), dynamic([])),
    iff(set_has_element(Actions, "block"), dynamic(["Blocked Activity"]), dynamic([])),
    iff(DistinctApps > 3, dynamic(["Multiple Risky Apps"]), dynamic([]))
), ", ")

Stage 8: sort

| order by EventCount desc

Stage 9: project

| project
    LastSeen,
    User,
    Userip,
    Hostname,
    DeviceClassification,
    EventCount,
    DistinctApps,
    Apps,
    Categories,
    Activities,
    Actions,
    CCLs,
    RiskIndicators

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Actioneq
  • block
field:"Action" kind:eq value:"block"
Activityin
  • Download
  • Post
  • Send
  • Share
  • Upload
field:"Activity" kind:in
Appis_not_null
  • (no value, null check)
field:"App" kind:is_not_null
Cclin
  • low transforms: tolower
  • poor transforms: tolower
field:"Ccl" kind:in
Cclne
  • excellent transforms: tolower
field:"Ccl" kind:ne value:"excellent"
DistinctAppsgt
  • 3 transforms: number
field:"DistinctApps" kind:gt value:"3"
EventCountgt
  • 5 transforms: number
field:"EventCount" kind:gt value:"5"

Output Fields

These fields are emitted when the rule matches.

FieldSource
Actionsproject
Activitiesproject
Appsproject
CCLsproject
Categoriesproject
DeviceClassificationproject
DistinctAppsproject
EventCountproject
Hostnameproject
LastSeenproject
RiskIndicatorsproject
Userproject
Useripproject

Netskope - Suspicious Network Context (Unusual IPs/Geo/Ports)

#
Status
available
Severity
medium
Time window
1h
Group by
CsUsername, XCCountry, XCLocation, XCsSrcIp
Source
github.com/Azure/Azure-Sentinel

Detects suspicious network activity based on unusual source/destination IPs, geographic anomalies, uncommon ports, and high traffic volumes.

MITRE ATT&CK Coverage

Rule Body

id: 6d989fb0-933e-4ae6-88fa-10e7b51c8897
name: Netskope - Suspicious Network Context (Unusual IPs/Geo/Ports)
description: |
  Detects suspicious network activity based on unusual source/destination IPs, geographic anomalies, uncommon ports, and high traffic volumes.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: NetskopeWebTxConnector
    dataTypes:
      - NetskopeWebTransactions_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
  - Exfiltration
  - Discovery
relevantTechniques:
  - T1071
  - T1048
  - T1046
query: |
  NetskopeWebTransactions_CL
  | where TimeGenerated > ago(1h)
  | where isnotempty(CsUsername)
  | extend 
      DstPort = coalesce(XCsDstPort, XSrDstPort, CsUriPort),
      SrcIP = coalesce(XCsSrcIp, CIp),
      DstIP = coalesce(XCsDstIp, XSrDstIp, SIp)
  | summarize 
      EventCount = count(),
      TotalBytes = sum(Bytes),
      UniqueDstIPs = dcount(DstIP),
      DstIPs = make_set(DstIP, 20),
      UniqueDstPorts = dcount(DstPort),
      DstPorts = make_set(DstPort, 20),
      UniqueHosts = dcount(CsHost),
      Hosts = make_set(CsHost, 20),
      Countries = make_set(XSCountry),
      SuspiciousPortHits = countif(DstPort in (20, 21, 22, 23, 25, 445, 1433, 1434, 3306, 3389, 5432, 5900, 5901)),
      HighRiskCountryHits = countif(XSCountry in ('RU', 'CN', 'KP', 'IR', 'SY'))
      by CsUsername, XCsSrcIp, XCCountry, XCLocation, bin(TimeGenerated, 1h)
  | where SuspiciousPortHits > 0 or HighRiskCountryHits > 0 or UniqueDstIPs > 50 or TotalBytes > 1073741824
  | extend 
      TotalMB = round(TotalBytes / 1048576.0, 2),
      RiskFactors = strcat_array(array_concat(
          iff(SuspiciousPortHits > 0, dynamic(['Suspicious Ports']), dynamic([])),
          iff(HighRiskCountryHits > 0, dynamic(['High Risk Country']), dynamic([])),
          iff(UniqueDstIPs > 50, dynamic(['Many Destinations']), dynamic([])),
          iff(TotalBytes > 1073741824, dynamic(['High Volume']), dynamic([]))
      ), ', ')
  | project 
      TimeGenerated,
      User = CsUsername,
      SourceIP = XCsSrcIp,
      SourceCountry = XCCountry,
      SourceLocation = XCLocation,
      DestinationIPs = DstIPs,
      UniqueDstIPCount = UniqueDstIPs,
      DestinationPorts = DstPorts,
      TargetHosts = Hosts,
      DestinationCountries = Countries,
      SuspiciousPortAccessCount = SuspiciousPortHits,
      HighRiskCountryAccessCount = HighRiskCountryHits,
      TotalDataMB = TotalMB,
      EventCount,
      RiskFactors
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: User
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIP
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

NetskopeWebTransactions_CL

Stage 2: where

| where TimeGenerated > ago(1h)

Stage 3: where

| where isnotempty(CsUsername)

Stage 4: extend

| extend 
    DstPort = coalesce(XCsDstPort, XSrDstPort, CsUriPort),
    SrcIP = coalesce(XCsSrcIp, CIp),
    DstIP = coalesce(XCsDstIp, XSrDstIp, SIp)

Stage 5: summarize

| summarize 
    EventCount = count(),
    TotalBytes = sum(Bytes),
    UniqueDstIPs = dcount(DstIP),
    DstIPs = make_set(DstIP, 20),
    UniqueDstPorts = dcount(DstPort),
    DstPorts = make_set(DstPort, 20),
    UniqueHosts = dcount(CsHost),
    Hosts = make_set(CsHost, 20),
    Countries = make_set(XSCountry),
    SuspiciousPortHits = countif(DstPort in (20, 21, 22, 23, 25, 445, 1433, 1434, 3306, 3389, 5432, 5900, 5901)),
    HighRiskCountryHits = countif(XSCountry in ('RU', 'CN', 'KP', 'IR', 'SY'))
    by CsUsername, XCsSrcIp, XCCountry, XCLocation, bin(TimeGenerated, 1h)

Stage 6: where

| where SuspiciousPortHits > 0 or HighRiskCountryHits > 0 or UniqueDstIPs > 50 or TotalBytes > 1073741824

Stage 7: extend

| extend 
    TotalMB = round(TotalBytes / 1048576.0, 2),
    RiskFactors = strcat_array(array_concat(
        iff(SuspiciousPortHits > 0, dynamic(['Suspicious Ports']), dynamic([])),
        iff(HighRiskCountryHits > 0, dynamic(['High Risk Country']), dynamic([])),
        iff(UniqueDstIPs > 50, dynamic(['Many Destinations']), dynamic([])),
        iff(TotalBytes > 1073741824, dynamic(['High Volume']), dynamic([]))
    ), ', ')

Stage 8: project

| project 
    TimeGenerated,
    User = CsUsername,
    SourceIP = XCsSrcIp,
    SourceCountry = XCCountry,
    SourceLocation = XCLocation,
    DestinationIPs = DstIPs,
    UniqueDstIPCount = UniqueDstIPs,
    DestinationPorts = DstPorts,
    TargetHosts = Hosts,
    DestinationCountries = Countries,
    SuspiciousPortAccessCount = SuspiciousPortHits,
    HighRiskCountryAccessCount = HighRiskCountryHits,
    TotalDataMB = TotalMB,
    EventCount,
    RiskFactors

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
DestinationCountriesproject
DestinationIPsproject
DestinationPortsproject
EventCountproject
HighRiskCountryAccessCountproject
RiskFactorsproject
SourceCountryproject
SourceIPproject
SourceLocationproject
SuspiciousPortAccessCountproject
TargetHostsproject
TimeGeneratedproject
TotalDataMBproject
UniqueDstIPCountproject
Userproject

Pathlock TDnR - Dynamic Access Control Events

#
Status
available
Severity
high
Time window
1h
Source
github.com/Azure/Azure-Sentinel

Detects events from Pathlock Dynamic Access Control (DAC) for SAP, forwarded to Microsoft Sentinel. DAC events capture real-time access policy decisions and violations, including blocked transactions, emergency access grants, and policy bypass attempts that require immediate investigation.

MITRE ATT&CK Coverage

Rule Body

id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c40
name: Pathlock TDnR - Dynamic Access Control Events
kind: Scheduled
description: >-
  Detects events from Pathlock Dynamic Access Control (DAC) for SAP, forwarded to Microsoft Sentinel.
  DAC events capture real-time access policy decisions and violations, including blocked transactions,
  emergency access grants, and policy bypass attempts that require immediate investigation.
severity: High
status: Available
requiredDataConnectors:
  - connectorId: Pathlock_TDnR
    dataTypes:
      - Pathlock_TDnR_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - PrivilegeEscalation
relevantTechniques:
  - T1548
  - T1134
query: |
  Pathlock_TDnR_CL
  | where DataSource == "PATHLOCK_DAC"
  | 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 == "PATHLOCK_DAC"

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.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AffectedUserproject
Areaproject
Bnameproject
CentralTsproject
DataSourceproject
DestIpproject
Eventidproject
Hostnameproject
Instanceproject
LogLineproject
MessageV1project
MessageV2project
MessageV3project
MessageV4project
MsgIdproject
MsgNoproject
MsgTypeproject
Reportproject
SrcIpproject
Subidproject
Sysidproject
Tcodeproject
TimeGeneratedproject

Ping Federate - Forbidden country

#
Status
available
Severity
high
Time window
1h
Source
github.com/Azure/Azure-Sentinel

'Detects requests from forbidden countries.'

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Rule Body

id: 14042f74-e50b-4c21-8a01-0faf4915ada4
name: Ping Federate - Forbidden country
description: |
  'Detects requests from forbidden countries.'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: CefAma
    dataTypes:
      - CommonSecurityLog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1078
query: |
  let forbidden_geo = dynamic(['CH']);
  PingFederateEvent
  | where isnotempty(DstGeoCountry)
  | where tostring(DstGeoCountry) in~ (forbidden_geo)
  | extend AccountCustomEntity = DstUserName
  | extend IpCustomEntity = SrcIpAddr
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountCustomEntity
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IpCustomEntity
version: 1.0.3
kind: Scheduled

Stages and Predicates

Parameters

let forbidden_geo = dynamic(['CH']);

Stage 1: source

PingFederateEvent

Stage 2: where

| where isnotempty(DstGeoCountry)

Stage 3: where

| where tostring(DstGeoCountry) in~ (forbidden_geo)

Stage 4: extend

| extend AccountCustomEntity = DstUserName

Stage 5: extend

| extend IpCustomEntity = SrcIpAddr

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
DstGeoCountryin
  • forbidden_geo transforms: tostring
field:"DstGeoCountry" kind:in value:"forbidden_geo"
DstGeoCountryis_not_null
  • (no value, null check)
field:"DstGeoCountry" kind:is_not_null

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountCustomEntityextend
IpCustomEntityextend

PROD (TM017.1) - GROUP - Tier 0 Added to Allow RODC Password Replication Group

#
Status
available
Severity
high
Time window
5m
Source
github.com/Azure/Azure-Sentinel

Adding a Tier 0 object to the Allow RODC Password Replication Group.

MITRE ATT&CK Coverage

TacticTechniques
Privilege Escalation

Telemetry Coverage

Rule Body

id: 634a69b4-0e46-4349-8463-af1c8d191620
name: 'PROD (TM017.1) - GROUP - Tier 0 Added to Allow RODC Password Replication Group'
description: |
  Adding a Tier 0 object to the Allow RODC Password Replication Group.
severity: High
requiredDataConnectors:
  - connectorId: SecurityEvents
    dataTypes:
      - SecurityEvent
  - connectorId: WindowsSecurityEvents
    dataTypes:
      - SecurityEvent
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - PrivilegeEscalation
relevantTechniques:
  - T1078.002
query: |
  SecurityEvent 
  | where EventID == 5136
      and Activity has "modified"
      and EventData matches regex "(?i)CN=Allowed RODC Password Replication Group,CN=Users"
      and EventData has '"AttributeLDAPDisplayName">member'
      and EventData has 'OperationType">%%14674'
  | extend GroupName_e = extract("<Data Name=\"ObjectDN\">([^<]+)</Data>", 1, EventData)
  | extend GroupName = extract("(?i)CN=([^,]+)", 1, GroupName_e)
  | extend ObjectName_e = extract("<Data Name=\"AttributeValue\">([^<]+)</Data>", 1, EventData)
  | extend ObjectName = extract("(?i)CN=([^,]+)", 1, ObjectName_e)
  | extend ObjectTier_e = extract("<Data Name=\"AttributeValue\">([^<]+)</Data>", 1, EventData)
  | extend ObjectTier = case(
      ObjectName_e has "Tier 0", "Tier 0",
      ObjectName_e has "Tier 1", "Tier 1",
      ObjectName_e has "Tier 2", "Tier 2",
      "Outside Tier Model")
  | where ObjectTier == "Tier 0"
  | extend Domain = extract("([^.]+\\.[^.]+)$", 1, Computer)
  | extend ActivityType = extract("([a-zA-Z]+)\\.$", 1, Activity)
  | extend SubjectUserName = extract("<Data Name=\"SubjectUserName\">([^<]+)</Data>", 1, EventData)
  | extend SubjectDomainName = extract("<Data Name=\"SubjectDomainName\">([^<]+)</Data>", 1, EventData)
  | extend Account = strcat(SubjectDomainName, "\\", SubjectUserName)
  | extend OperationType = iff(EventData has 'OperationType">%%14674', "Added", tostring(OperationType))
  | project TimeGenerated 
          , ObjectName
          , ObjectTier
          , ObjectType = AccountType
          , GroupName
          , ActivityType
          , OperationType
          , Account
          , Domain
          , Computer
          , Channel
          , EventID
          , EventData
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: ObjectName
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: GroupName
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: Computer
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: Account
customDetails:
  ObjectName: ObjectName
  ObjectTier: ObjectTier
  ObjectType: ObjectType
  GroupName: GroupName
  ActivityType: ActivityType
  OperationType: OperationType
  Account: Account
  Domain: Domain
  Computer: Computer
  Channel: Channel
  EventID: EventID
  EventData: EventData
eventGroupingSettings:
  aggregationKind: AlertPerResult
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: false
    reopenClosedIncident: false
    lookbackDuration: PT5H
    matchingMethod: AllEntities
alertDetailsOverride:
  alertDisplayNameFormat: '(TM017.1) A Tier 0 member was added to the Allow RODC Password Replication Group'
  alertDescriptionFormat: 'The following Tier 0 {{ObjectName}} object was added to the Allow RODC Password Replication Group.'
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

SecurityEvent

Stage 2: where

| where EventID == 5136
    and Activity has "modified"
    and EventData matches regex "(?i)CN=Allowed RODC Password Replication Group,CN=Users"
    and EventData has '"AttributeLDAPDisplayName">member'
    and EventData has 'OperationType">%%14674'

Stage 3: extend (6 consecutive steps)

| extend GroupName_e = extract("<Data Name=\"ObjectDN\">([^<]+)</Data>", 1, EventData)
| extend GroupName = extract("(?i)CN=([^,]+)", 1, GroupName_e)
| extend ObjectName_e = extract("<Data Name=\"AttributeValue\">([^<]+)</Data>", 1, EventData)
| extend ObjectName = extract("(?i)CN=([^,]+)", 1, ObjectName_e)
| extend ObjectTier_e = extract("<Data Name=\"AttributeValue\">([^<]+)</Data>", 1, EventData)
| extend ObjectTier = case(
    ObjectName_e has "Tier 0", "Tier 0",
    ObjectName_e has "Tier 1", "Tier 1",
    ObjectName_e has "Tier 2", "Tier 2",
    "Outside Tier Model")

Stage 4: where

| where ObjectTier == "Tier 0"

Stage 5: extend (6 consecutive steps)

| extend Domain = extract("([^.]+\\.[^.]+)$", 1, Computer)
| extend ActivityType = extract("([a-zA-Z]+)\\.$", 1, Activity)
| extend SubjectUserName = extract("<Data Name=\"SubjectUserName\">([^<]+)</Data>", 1, EventData)
| extend SubjectDomainName = extract("<Data Name=\"SubjectDomainName\">([^<]+)</Data>", 1, EventData)
| extend Account = strcat(SubjectDomainName, "\\", SubjectUserName)
| extend OperationType = iff(EventData has 'OperationType">%%14674', "Added", tostring(OperationType))

Stage 6: project

| project TimeGenerated 
        , ObjectName
        , ObjectTier
        , ObjectType = AccountType
        , GroupName
        , ActivityType
        , OperationType
        , Account
        , Domain
        , Computer
        , Channel
        , EventID
        , EventData

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Activitymatch
  • modified transforms: term corpus 10 (kusto 10)
field:"Activity" kind:match value:"modified"
EventDatamatch
  • "AttributeLDAPDisplayName">member transforms: term corpus 2 (kusto 2)
  • OperationType">%%14674 transforms: term corpus 3 (kusto 3)
field:"EventData" kind:match
EventDataregex_match
  • (?i)CN=Allowed RODC Password Replication Group,CN=Users
field:"EventData" kind:regex_match value:"(?i)CN=Allowed RODC Password Replication Group,CN=Users"
EventIDeq
  • 5136 corpus 20 (kusto 18, elastic 2)
field:"EventID" kind:eq value:"5136"
ObjectTiereq
  • Tier 0
field:"ObjectTier" kind:eq value:"Tier 0"

Output Fields

These fields are emitted when the rule matches.

FieldSource
Accountproject
ActivityTypeproject
Channelproject
Computerproject
Domainproject
EventDataproject
EventIDproject
GroupNameproject
ObjectNameproject
ObjectTierproject
ObjectTypeproject
OperationTypeproject
TimeGeneratedproject

Red Sift - MFA disabled on account

#
Status
available
Severity
high
Time window
1h
Source
github.com/Azure/Azure-Sentinel

This query searches for authentication events where MFA has been disabled on an account, which may indicate account takeover activity, weakened account protections, or unauthorized administrative changes.

MITRE ATT&CK Coverage

Rule Body

id: 16f26d2c-6296-490b-af4f-b30bcf1c4461
name: Red Sift - MFA disabled on account
description: |
  This query searches for authentication events where MFA has been disabled on an account, which may indicate account takeover activity, weakened account protections, or unauthorized administrative changes.
severity: High
status: Available
requiredDataConnectors:
  - connectorId: RedSiftPush
    dataTypes:
      - RedSiftAuth_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - DefenseEvasion
relevantTechniques:
  - T1556
query: |
  RedSiftAuth_CL
  | extend
      ActivityName = tostring(column_ifexists("ActivityName", "")),
      UserEmail = tostring(column_ifexists("UserEmail", "")),
      ActorUserEmail = tostring(column_ifexists("ActorUserEmail", "")),
      SrcIp = tostring(column_ifexists("SrcIp", "")),
      HttpUserAgent = tostring(column_ifexists("HttpUserAgent", "")),
      ServiceName = tostring(column_ifexists("ServiceName", "")),
      Severity = tostring(column_ifexists("Severity", "")),
      UserUid = tostring(column_ifexists("UserUid", "")),
      ActorUserUid = tostring(column_ifexists("ActorUserUid", ""))
  | where ActivityName =~ "MFA Disabled"
  | where isnotempty(UserEmail) or isnotempty(ActorUserEmail)
  | extend TargetAccount = iff(isnotempty(UserEmail), UserEmail, ActorUserEmail)
  | project
      TimeGenerated,
      TargetAccount,
      UserEmail,
      ActorUserEmail,
      SrcIp,
      HttpUserAgent,
      ServiceName,
      ActivityName,
      Severity,
      UserUid,
      ActorUserUid
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: TargetAccount
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SrcIp
customDetails:
  ActorUserEmail: ActorUserEmail
  UserAgent: HttpUserAgent
  ServiceName: ServiceName
  ActivityName: ActivityName
alertDetailsOverride:
  alertDisplayNameFormat: "RedSift - MFA Disabled for {{TargetAccount}}"
  alertDescriptionFormat: "MFA was disabled for {{TargetAccount}}. Actor: {{ActorUserEmail}}. Source IP: {{SrcIp}}."
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: P1D
    matchingMethod: Selected
    groupByEntities:
      - Account
eventGroupingSettings:
  aggregationKind: AlertPerResult
suppressionEnabled: false
suppressionDuration: PT1H
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

RedSiftAuth_CL

Stage 2: extend

| extend
    ActivityName = tostring(column_ifexists("ActivityName", "")),
    UserEmail = tostring(column_ifexists("UserEmail", "")),
    ActorUserEmail = tostring(column_ifexists("ActorUserEmail", "")),
    SrcIp = tostring(column_ifexists("SrcIp", "")),
    HttpUserAgent = tostring(column_ifexists("HttpUserAgent", "")),
    ServiceName = tostring(column_ifexists("ServiceName", "")),
    Severity = tostring(column_ifexists("Severity", "")),
    UserUid = tostring(column_ifexists("UserUid", "")),
    ActorUserUid = tostring(column_ifexists("ActorUserUid", ""))

Stage 3: where

| where ActivityName =~ "MFA Disabled"

Stage 4: where

| where isnotempty(UserEmail) or isnotempty(ActorUserEmail)

Stage 5: extend

| extend TargetAccount = iff(isnotempty(UserEmail), UserEmail, ActorUserEmail)
TargetAccount =
ifisnotempty(UserEmail)UserEmail
elseActorUserEmail

Stage 6: project

| project
    TimeGenerated,
    TargetAccount,
    UserEmail,
    ActorUserEmail,
    SrcIp,
    HttpUserAgent,
    ServiceName,
    ActivityName,
    Severity,
    UserUid,
    ActorUserUid

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
ActivityNameproject
ActorUserEmailproject
ActorUserUidproject
HttpUserAgentproject
ServiceNameproject
Severityproject
SrcIpproject
TargetAccountproject
TimeGeneratedproject
UserEmailproject
UserUidproject

Silverfort - Certifried Incident

#
Severity
high
Time window
15m
Source
github.com/Azure/Azure-Sentinel

'An Active Directory domain privilege escalation vulnerability that enables a privileged user to access the Domain Controller by abusing Active Directory Certificate Service'

MITRE ATT&CK Coverage

TacticTechniques
Privilege Escalation

Rule Body

id: 9ae540c9-c926-4100-8f07-1eac22596292
name: Silverfort - Certifried Incident
description: |
  'An Active Directory domain privilege escalation vulnerability that enables a privileged user to access the Domain Controller by abusing Active Directory Certificate Service'
severity: High
requiredDataConnectors:
  - connectorId: SilverfortAma
    dataTypes:
      - CommonSecurityLog
tactics:
  - PrivilegeEscalation
relevantTechniques:
  - T1068 #Exploitation for Privilege Escalation
query: |-
  CommonSecurityLog 
  | where DeviceVendor has 'Silverfort'
  | where DeviceProduct has 'Admin Console'
  | where DeviceEventClassID == "NewIncident"
  | where Message has "Certifried"
  | extend UserName = parse_json(replace('^""|""$', '', Message))['userName']
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: UserName
queryPeriod: 15m
queryFrequency: 15m
triggerOperator: gt
triggerThreshold: 0
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

CommonSecurityLog

Stage 2: where

| where DeviceVendor has 'Silverfort'

Stage 3: where

| where DeviceProduct has 'Admin Console'

Stage 4: where

| where DeviceEventClassID == "NewIncident"

Stage 5: where

| where Message has "Certifried"

Stage 6: extend

| extend UserName = parse_json(replace('^""|""$', '', Message))['userName']

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
UserNameextend

Suspicious number of resource creation or deployment activities

#
Status
available
Severity
medium
Time window
7d
Group by
Caller, TimeGenerated
Source
github.com/Azure/Azure-Sentinel

Indicates when an anomalous number of VM creations or deployment activities occur in Azure via the AzureActivity log. This query generates the baseline pattern of cloud resource creation by an individual and generates an anomaly when any unusual spike is detected. These anomalies from unusual or privileged users could be an indication of a cloud infrastructure takedown by an adversary.

MITRE ATT&CK Coverage

TacticTechniques
Impact

Telemetry Coverage

Rule Body

id: 361dd1e3-1c11-491e-82a3-bb2e44ac36ba
name: Suspicious number of resource creation or deployment activities
description: |
  'Indicates when an anomalous number of VM creations or deployment activities occur in Azure via the AzureActivity log. This query generates the baseline pattern of cloud resource creation by an individual and generates an anomaly when any unusual spike is detected. These anomalies from unusual or privileged users could be an indication of a cloud infrastructure takedown by an adversary.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: AzureActivity
    dataTypes:
      - AzureActivity
queryFrequency: 1d
queryPeriod: 7d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Impact
relevantTechniques:
  - T1496
query: |
  let szOperationNames = dynamic(["microsoft.compute/virtualMachines/write", "microsoft.resources/deployments/write"]);
  let starttime = 7d;
  let endtime = 1d;
  let timeframe = 1d;
  let TimeSeriesData =
  AzureActivity
  | where TimeGenerated between (startofday(ago(starttime)) .. startofday(now()))
  | where OperationNameValue in~ (szOperationNames)
  | project TimeGenerated, Caller 
  | make-series Total = count() on TimeGenerated from startofday(ago(starttime)) to startofday(now()) step timeframe by Caller; 
  TimeSeriesData
  | extend (anomalies, score, baseline) = series_decompose_anomalies(Total, 3, -1, 'linefit')
  | mv-expand Total to typeof(double), TimeGenerated to typeof(datetime), anomalies to typeof(double), score to typeof(double), baseline to typeof(long) 
  | where TimeGenerated >= startofday(ago(endtime))
  | where anomalies > 0 and baseline > 0
  | project Caller, TimeGenerated, Total, baseline, anomalies, score
  | join (AzureActivity
  | where TimeGenerated > startofday(ago(endtime)) 
  | where OperationNameValue in~ (szOperationNames)
  | summarize make_set(OperationNameValue,100), make_set(_ResourceId,100), make_set(CallerIpAddress,100) by bin(TimeGenerated, timeframe), Caller
  ) on TimeGenerated, Caller
  | mv-expand CallerIpAddress=set_CallerIpAddress
  | project-away Caller1
  | extend Name = iif(Caller has '@',tostring(split(Caller,'@',0)[0]),"")
  | extend UPNSuffix = iif(Caller has '@',tostring(split(Caller,'@',1)[0]),"")
  | extend AadUserId = iif(Caller !has '@',Caller,"")
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: Caller
      - identifier: Name
        columnName: Name
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: Account
    fieldMappings:
      - identifier: AadUserId
        columnName: AadUserId
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: CallerIpAddress
version: 2.0.4
kind: Scheduled

Stages and Predicates

Parameters

let szOperationNames = dynamic(["microsoft.compute/virtualMachines/write", "microsoft.resources/deployments/write"]);
let starttime = 7d;
let endtime = 1d;
let timeframe = 1d;

let TimeSeriesData is inlined into the numbered stages below.

Stages 1 to 5 define let TimeSeriesData as the rule's main pipeline source. Stages 6 to 14 use it.

Stage 1: source

AzureActivity

Stage 2: where

| where TimeGenerated between (startofday(ago(starttime)) .. startofday(now()))

Stage 3: where

| where OperationNameValue in~ (szOperationNames)

Stage 4: project

| project TimeGenerated, Caller

The stages below score time-series anomalies (make-series, series_decompose_anomalies).

Stage 5: make-series

| make-series Total = count() on TimeGenerated from startofday(ago(starttime)) to startofday(now()) step timeframe by Caller

Stage 6: extend

TimeSeriesData
| extend (anomalies, score, baseline) = series_decompose_anomalies(Total, 3, -1, 'linefit')

Stage 7: mv-expand

| mv-expand Total to typeof(double), TimeGenerated to typeof(datetime), anomalies to typeof(double), score to typeof(double), baseline to typeof(long)

Stage 8: where

| where TimeGenerated >= startofday(ago(endtime))

Stage 9: where

| where anomalies > 0 and baseline > 0

Stage 10: project

| project Caller, TimeGenerated, Total, baseline, anomalies, score

Stage 11: join

| join (AzureActivity
| where TimeGenerated > startofday(ago(endtime)) 
| where OperationNameValue in~ (szOperationNames)
| summarize make_set(OperationNameValue,100), make_set(_ResourceId,100), make_set(CallerIpAddress,100) by bin(TimeGenerated, timeframe), Caller
) on TimeGenerated, Caller

Stage 12: mv-expand

mv-expand CallerIpAddress

Stage 13: project-away

project-away Caller1

Stage 14: extend (3 consecutive steps)

extend AadUserId, Name, UPNSuffix

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
OperationNameValuein
  • microsoft.compute/virtualMachines/write corpus 4 (kusto 4)
  • microsoft.compute/virtualMachines/write
  • microsoft.resources/deployments/write corpus 4 (kusto 4)
  • microsoft.resources/deployments/write
field:"azure_ad::operation_name_value" kind:in
anomaliesgt
  • 0 transforms: number
field:"anomalies" kind:gt value:"0"
baselinegt
  • 0 transforms: number
field:"baseline" kind:gt value:"0"

Output Fields

These fields are emitted when the rule matches.

FieldSource
Callerproject
TimeGeneratedproject
Totalproject
anomaliesproject
baselineproject
scoreproject
Nameextend
UPNSuffixextend
AadUserIdextend

Syntax errors stateful anomaly on database

#
Status
available
Severity
medium
Time window
14d
Group by
ApplicationName, ClientIp, Database, HostName, PrincipalName, ResourceId, timeSlice
Source
github.com/Azure/Azure-Sentinel

'This query batches of distinct SQL queries that failed with error codes that might indicate malicious attempts to gain illegitimate access to the data. When blind type of attacks are performed (such as SQL injection of fuzzying), the attempted queries are often malformed and fail on wrong syntax (error 102) or wrong escaping (error 105). Thus, if a large number of different queries fail on such errors in a short amount of time, this might indicate attempted attack.'

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: c815008d-f4d1-4645-b13b-8b4bc188d5de
name: Syntax errors stateful anomaly on database
description: |
  'This query batches of distinct SQL queries that failed with error codes that might indicate malicious attempts to gain illegitimate access to the data. When blind type of attacks are performed (such as SQL injection of fuzzying), the attempted queries are often malformed and fail on wrong syntax (error 102) or wrong escaping (error 105). Thus, if a large number of different queries fail on such errors in a short amount of time, this might indicate attempted attack.'
severity: Medium
requiredDataConnectors:
  - connectorId: AzureSql
    dataTypes:
      - AzureDiagnostics
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - InitialAccess
relevantTechniques:
  - T1190 
tags:
  - SQL
query: |
    let monitoredStatementsThreshold = 1;           // Minimal number of monitored statements in the slice to trigger an anomaly.
    let trainingSlicesThreshold = 5;                // The maximal amount of slices with monitored statements in the training window before anomaly detection is throttled.
    let timeSliceSize = 1h;                         // The size of the single timeSlice for individual aggregation.
    let detectionWindow = 1h;                       // The size of the recent detection window for detecting anomalies.  
    let trainingWindow = detectionWindow + 14d;     // The size of the training window before the detection window for learning the normal state.
    let monitoredErrors = pack_array(102, 105);     // List of sql error codes relevant for this detection.
    let processedData = materialize (
        AzureDiagnostics
        | where TimeGenerated >= ago(trainingWindow)
        | where Category == 'SQLSecurityAuditEvents' and action_id_s has_any ("RCM", "BCM") // Keep only SQL affected rows
        | project TimeGenerated, PrincipalName = server_principal_name_s, ClientIp = client_ip_s, HostName = host_name_s, ResourceId,
                  ApplicationName = application_name_s, ActionName = action_name_s, Database = strcat(LogicalServerName_s, '/', database_name_s),
                  IsSuccess = succeeded_s, AffectedRows = affected_rows_d,
                  ResponseRows = response_rows_d, Statement = statement_s,
                  Error = case( additional_information_s has 'error_code', toint(extract("<error_code>([0-9.]+)", 1, additional_information_s))
                        , additional_information_s has 'failure_reason', toint(extract("<failure_reason>Err ([0-9.]+)", 1, additional_information_s))
                        , 0),
                  State = case( additional_information_s has 'error_state', toint(extract("<error_state>([0-9.]+)", 1, additional_information_s))
                        , additional_information_s has 'failure_reason', toint(extract("<failure_reason>Err ([0-9.]+), Level ([0-9.]+)", 2, additional_information_s))
                        , 0),
                  AdditionalInfo = additional_information_s, timeSlice = floor(TimeGenerated, timeSliceSize)
        | summarize countEvents = count(), countStatements = dcount(Statement), countStatementsWithError = dcountif(Statement, Error in (monitoredErrors))
            , anyMonitoredStatement = anyif(Statement, Error in (monitoredErrors)), anyInfo = anyif(AdditionalInfo, Error in (monitoredErrors))
            by Database, ClientIp, ApplicationName, PrincipalName, timeSlice,HostName,ResourceId
        | extend WindowType = case( timeSlice >= ago(detectionWindow), 'detection',
                                               (ago(trainingWindow) <= timeSlice and timeSlice < ago(detectionWindow)), 'training', 'other')
        | where WindowType in ('detection', 'training'));
    let trainingSet =
        processedData
        | where WindowType == 'training'
        | summarize countSlicesWithErrors = dcountif(timeSlice, countStatementsWithError >= monitoredStatementsThreshold)
            by Database;
    processedData
    | where WindowType == 'detection' 
    | join kind = inner (trainingSet) on Database
    | extend IsErrorAnomalyOnStatement = iff(((countStatementsWithError >= monitoredStatementsThreshold) and (countSlicesWithErrors <= trainingSlicesThreshold)), true, false)
        , anomalyScore = round(countStatementsWithError/monitoredStatementsThreshold, 0)
    | where IsErrorAnomalyOnStatement == 'true'
    | project TimeGenerated = timeSlice, Database, ClientIp, ApplicationName, PrincipalName, HostName, ResourceId, countEvents, countStatements, countStatementsWithError, anyMonitoredStatement, anyInfo, anomalyScore 
    | extend Name = tostring(split(PrincipalName,'@',0)[0]), UPNSuffix = tostring(split(PrincipalName,'@',1)[0])    
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: Name
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: ClientIp
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: HostName
  - entityType: CloudApplication
    fieldMappings:
      - identifier: Name
        columnName: ApplicationName
  - entityType: AzureResource
    fieldMappings:
      - identifier: ResourceId
        columnName: ResourceId
alertDetailsOverride:
  alertDisplayNameFormat: 'Syntax errors stateful anomaly on database {{Database}}'
  alertDescriptionFormat: 'An anomaly was detected on database {{Database}} with {{countStatementsWithError}} statements with monitored errors in the last hour. Investigate the database activity for potential malicious attempts to gain illegitimate access to the data.'
version: 1.1.2
kind: Scheduled

Stages and Predicates

Parameters

let monitoredStatementsThreshold = 1;
let trainingSlicesThreshold = 5;
let timeSliceSize = 1h;
let detectionWindow = 1h;
let trainingWindow = detectionWindow + 14d;
let monitoredErrors = pack_array(102, 105);

let processedData is inlined into the numbered stages below.

Let Binding: trainingSet used in Stage 9

let trainingSet = processedData
    | where WindowType == 'training'
    | summarize countSlicesWithErrors = dcountif(timeSlice, countStatementsWithError >= monitoredStatementsThreshold)
        by Database;

Stages 1 to 7 define let processedData as the rule's main pipeline source. Stages 8 to 13 use it.

Stage 1: source

AzureDiagnostics

Stage 2: where

| where TimeGenerated >= ago(trainingWindow)

Stage 3: where

| where Category == 'SQLSecurityAuditEvents' and action_id_s has_any ("RCM", "BCM")

Stage 4: project

| project TimeGenerated, PrincipalName = server_principal_name_s, ClientIp = client_ip_s, HostName = host_name_s, ResourceId,
              ApplicationName = application_name_s, ActionName = action_name_s, Database = strcat(LogicalServerName_s, '/', database_name_s),
              IsSuccess = succeeded_s, AffectedRows = affected_rows_d,
              ResponseRows = response_rows_d, Statement = statement_s,
              Error = case( additional_information_s has 'error_code', toint(extract("<error_code>([0-9.]+)", 1, additional_information_s))
                    , additional_information_s has 'failure_reason', toint(extract("<failure_reason>Err ([0-9.]+)", 1, additional_information_s))
                    , 0),
              State = case( additional_information_s has 'error_state', toint(extract("<error_state>([0-9.]+)", 1, additional_information_s))
                    , additional_information_s has 'failure_reason', toint(extract("<failure_reason>Err ([0-9.]+), Level ([0-9.]+)", 2, additional_information_s))
                    , 0),
              AdditionalInfo = additional_information_s, timeSlice = floor(TimeGenerated, timeSliceSize)

Stage 5: summarize

| summarize countEvents = count(), countStatements = dcount(Statement), countStatementsWithError = dcountif(Statement, Error in (monitoredErrors))
        , anyMonitoredStatement = anyif(Statement, Error in (monitoredErrors)), anyInfo = anyif(AdditionalInfo, Error in (monitoredErrors))
        by Database, ClientIp, ApplicationName, PrincipalName, timeSlice,HostName,ResourceId

Stage 6: extend

| extend WindowType = case( timeSlice >= ago(detectionWindow), 'detection',
                                           (ago(trainingWindow) <= timeSlice and timeSlice < ago(detectionWindow)), 'training', 'other')
WindowType =
if/* macro: (timeSlice >= ago(detectionWindow)) */'detection'
elif/* macro: (ago(trainingWindow) <= timeSlice) */'training'
else'other'

Stage 7: where

| where WindowType in ('detection', 'training')

Stage 8: where

processedData
| where WindowType == 'detection'

Stage 9: join

| join kind = inner (trainingSet) on Database

Stage 10: extend

extend IsErrorAnomalyOnStatement, anomalyScore
IsErrorAnomalyOnStatement =
ifcountStatementsWithError >= monitoredStatementsThreshold and countSlicesWithErrors <= trainingSlicesThresholdtrue
elsefalse

Stage 11: where

where IsErrorAnomalyOnStatement == true

Stage 12: project

project ApplicationName, ClientIp, Database, HostName, PrincipalName, ResourceId, TimeGenerated, anomalyScore, anyInfo, anyMonitoredStatement, countEvents, countStatements, countStatementsWithError

Stage 13: extend

extend Name, UPNSuffix

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
ApplicationNameproject
ClientIpproject
Databaseproject
HostNameproject
PrincipalNameproject
ResourceIdproject
TimeGeneratedproject
anomalyScoreproject
anyInfoproject
anyMonitoredStatementproject
countEventsproject
countStatementsproject
countStatementsWithErrorproject
Nameextend
UPNSuffixextend

Threat Connect TI map Domain entity to DnsEvents

#
Severity
medium
Time window
7d
Group by
$left.TI_DomainEntity, $right.Name, IndicatorId, Name
Source
github.com/Azure/Azure-Sentinel

Identifies a match in DnsEvents from any ThreatConnect Domain IOC from TI

MITRE ATT&CK Coverage

TacticTechniques
Command & Control

Rule Body

id: f8960f1c-07d2-512b-9c41-952772d40c84
name: Threat Connect TI map Domain entity to DnsEvents
version: 1.0.2
kind: Scheduled
description: |
  Identifies a match in DnsEvents from any ThreatConnect Domain IOC from TI
severity: Medium
requiredDataConnectors:
  - connectorId: DNS
    dataTypes:
      - DnsEvents
  - connectorId: ASimDnsActivityLogs
    dataTypes:
      - DnsEvents
  - connectorId: ThreatIntelligence
    dataTypes:
      - ThreatIntelligenceIndicator
  - connectorId: ThreatIntelligenceTaxii
    dataTypes:
      - ThreatIntelligenceIndicator
  - connectorId: MicrosoftDefenderThreatIntelligence
    dataTypes:
      - ThreatIntelligenceIndicator
queryFrequency: 1h
queryPeriod: 7d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
relevantTechniques:
  - T1071
query: |
  // Define the lookback periods for time-based filters
  let dt_lookBack = 1h; // Look back 1 hour for DNS events
  let ioc_lookBack = 7d; // Look back 7 days for threat intelligence indicators
  // Fetch threat intelligence indicators related to domains
  let Domain_Indicators = ThreatIntelligenceIndicator
    | where TimeGenerated >= ago(ioc_lookBack)
    | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
    | where ExpirationDateTime > now() and Active == true
    // Filter out non ThreatConnect TI Sources
    | where SourceSystem startswith "ThreatConnect-"
    // Filter out indicators without domain names
    | where isnotempty(DomainName)
    | extend TI_DomainEntity = DomainName;
  // Create a list of TLDs in our threat feed for later validation
  let maxListSize = 100000; // Define the maximum allowed size for each list
  let list_tlds = Domain_Indicators
    | extend parts = split(DomainName, '.')
    | extend tld = parts[(array_length(parts)-1)]
    | summarize count() by tostring(tld)
    | project tld
    | summarize make_list(tld, maxListSize);
  // Perform a join between domain indicators and DNS events to identify potential malicious activity
  Domain_Indicators
    // Use innerunique to keep performance fast and result set low, as we only need one match to indicate potential malicious activity that needs investigation
    | join kind=innerunique (
      DnsEvents
      | where TimeGenerated > ago(dt_lookBack)
      // Extract domain patterns from syslog message
      | where isnotempty(Name)
      | extend parts = split(Name, '.')
      | extend tld = parts[(array_length(parts)-1)]
      // Validate parsed domain by checking if the TLD is in the list of TLDs in our threat feed
      | where tld in~ (list_tlds)
      | extend DNS_TimeGenerated = TimeGenerated
    ) on $left.TI_DomainEntity==$right.Name
    // Filter out DNS events that occurred after the expiration of the corresponding indicator
    | where DNS_TimeGenerated < ExpirationDateTime
    // Group the results by IndicatorId and Name, and keep the DNS event with the latest timestamp
    | summarize DNS_TimeGenerated = arg_max(DNS_TimeGenerated, *) by IndicatorId, Name
    // Select the desired output fields
    | project DNS_TimeGenerated, Description, ActivityGroupNames, IndicatorId, ThreatType, ExpirationDateTime, ConfidenceScore, Url, Computer, ClientIP, Name, QueryType, Type, TI_DomainEntity
    // Extract hostname and DNS domain from the Computer field
    | extend HostName = tostring(split(Computer, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(Computer, '.'), 1, -1), '.'))
    // Rename the timestamp field
    | extend timestamp = DNS_TimeGenerated
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: DnsDomain 
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: ClientIP
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: Url

Stages and Predicates

Parameters

let dt_lookBack = 1h;
let ioc_lookBack = 7d;
let maxListSize = 100000;

let Domain_Indicators is inlined into the numbered stages below.

Let Binding: list_tlds used in Stage 8

let list_tlds = Domain_Indicators
  | extend parts = split(DomainName, '.')
  | extend tld = parts[(array_length(parts)-1)]
  | summarize count() by tostring(tld)
  | project tld
  | summarize make_list(tld, maxListSize);

Stages 1 to 7 define let Domain_Indicators as the rule's main pipeline source. Stages 8 to 13 use it.

Stage 1: source

ThreatIntelligenceIndicator

Stage 2: where

| where TimeGenerated >= ago(ioc_lookBack)

Stage 3: summarize

| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId

Stage 4: where

| where ExpirationDateTime > now() and Active == true

Stage 5: where

| where SourceSystem startswith "ThreatConnect-"

Stage 6: where

| where isnotempty(DomainName)

Stage 7: extend

| extend TI_DomainEntity = DomainName

Stage 8: join

Domain_Indicators
| join kind=innerunique (
    DnsEvents
    | where TimeGenerated > ago(dt_lookBack)
    | where isnotempty(Name)
    | extend parts = split(Name, '.')
    | extend tld = parts[(array_length(parts)-1)]
    | where tld in~ (list_tlds)
    | extend DNS_TimeGenerated = TimeGenerated
  ) on $left.TI_DomainEntity==$right.Name

Stage 9: where

where DNS_TimeGenerated < ExpirationDateTime

Stage 10: summarize

summarize DNS_TimeGenerated by IndicatorId, Name

Stage 11: project

project ActivityGroupNames, ClientIP, Computer, ConfidenceScore, DNS_TimeGenerated, Description, ExpirationDateTime, IndicatorId, Name, QueryType, TI_DomainEntity, ThreatType, Type, Url

Stage 12: extend

extend DnsDomain, HostName

Stage 13: extend

extend timestamp

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
ActivityGroupNamesproject
ClientIPproject
Computerproject
ConfidenceScoreproject
DNS_TimeGeneratedproject
Descriptionproject
ExpirationDateTimeproject
IndicatorIdproject
Nameproject
QueryTypeproject
TI_DomainEntityproject
ThreatTypeproject
Typeproject
Urlproject
DnsDomainextend
HostNameextend
timestampextend

Valence Security Alerts

#

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.

Status
available
Severity
high
Time window
5m
Source
github.com/Azure/Azure-Sentinel

'Valence Security Alerts'

Rule Body

id: d944d564-b6fa-470d-b5ab-a1a141878c5e
name: Valence Security Alerts
description: |
  'Valence Security Alerts'
severity: High
requiredDataConnectors:
  - connectorId: ValenceSecurity
    dataTypes:
      - ValenceAlert_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
  aggregationKind: SingleAlert
status: Available
tactics: []
relevantTechniques:
query: |
  ValenceAlert_CL
alertDetailsOverride:
  alertDisplayNameFormat: 'Valence Alert: {{alertType_name_s}}'
  alertDescriptionFormat: '{{alertType_description_s}}'
  alertSeverityColumnName: alertType_severity_s
  alertDynamicProperties: []
customDetails:
  ValenceAlertName: alertType_name_s
  ValenceAlertDesc: alertType_description_s
  ValencePlatform: alertType_platform_s
  ValenceAlertSeverity: alertType_severity_s
  ValenceCreatedTS: createdTimestamp_d
  ValenceConnectorUids: connectorUids_s
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

ValenceAlert_CL

Zscaler - Forbidden countries

#
Status
available
Severity
high
Time window
1h
Source
github.com/Azure/Azure-Sentinel

'Detects suspicious ZPA connections from forbidden countries.'

MITRE ATT&CK Coverage

Rule Body

id: b3d112b4-3e1e-11ec-9bbc-0242ac130002
name: Zscaler - Forbidden countries
description: |
  'Detects suspicious ZPA connections from forbidden countries.'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: CustomLogsAma
    datatypes:
      - ZPA_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1190
  - T1133
query: |
  let bl_countries = dynamic(['CH', 'RU']);       //List of countries from which you do not expect connections
  ZPAEvent 
  | where DvcAction == 'open'
  | where SrcGeoCountry in~ (bl_countries)
  | extend IPCustomEntity = SrcIpAddr, AccountCustomEntity = DstUserName
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountCustomEntity
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPCustomEntity
version: 1.0.2
kind: Scheduled

Stages and Predicates

Parameters

let bl_countries = dynamic(['CH', 'RU']);

Stage 1: source

ZPAEvent

Stage 2: where

| where DvcAction == 'open'

Stage 3: where

| where SrcGeoCountry in~ (bl_countries)

Stage 4: extend

| extend IPCustomEntity = SrcIpAddr, AccountCustomEntity = DstUserName

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountCustomEntityextend
IPCustomEntityextend

Back to all Kusto rules