Detection rules › Kusto
Kusto rules: 30
Abnormal Deny Rate for Source IP
#'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
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Discovery | |
| Command & Control | |
| Exfiltration |
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
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.
| Field | Kind | Values | Search |
|---|---|---|---|
Action | eq |
| field:"Action" kind:eq value:"Deny" |
Fqdn | is_not_null | field:"Fqdn" kind:is_not_null | |
LearningTimeBuckets | gt |
| field:"LearningTimeBuckets" kind:gt value:"5" |
OperationName | eq |
| field:"OperationName" kind:eq |
SourceIp | is_not_null | field:"SourceIp" kind:is_not_null | |
TimeGenerated | ge |
| field:"TimeGenerated" kind:ge value:"FullWindowStart" |
TimeGenerated | le |
| field:"TimeGenerated" kind:le value:"FullWindowEnd" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AlertTimeSrcIpDenyRateCount | project |
Fqdn | project |
LearningThreshold | project |
LearningTimeBuckets | project |
SourceIp | project |
Abnormal Security - Vendor compromise case detected
#'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
| Tactic | Techniques |
|---|---|
| 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.
| Field | Kind | Values | Search |
|---|---|---|---|
abx_body_abx_body_vendorCaseId_g | is_not_null | field:"abx_body_abx_body_vendorCaseId_g" kind:is_not_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
CaseId | project |
FirstObserved | project |
Insights | project |
TimeGenerated | project |
VendorDomain | project |
AIShield - Image classification AI Model Evasion low suspicious vulnerability detection
#'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.
| Field | Kind | Values | Search |
|---|---|---|---|
Message | match |
| field:"Message" kind:match value:"Image Classification AI Model Evasion Attack Identified" |
Severity | eq |
| field:"Severity" kind:eq value:"Medium" |
SuspiciousLevel | eq |
| field:"SuspiciousLevel" kind:eq value:"low suspicious attack" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
HostName | extend |
NTDomain | extend |
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.
'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.
| Field | Source |
|---|---|
TimeGenerated | project |
Type | project |
activity_action_s | project |
activity_subject_ari_s | project |
actor_name_s | project |
actor_sessions_s | project |
actor_url_s | project |
alertDetailURL_s | project |
alertTitle_s | project |
alert_created_t | project |
alert_product_s | project |
alert_title_s | project |
alert_url_s | project |
atlassianAlertType_s | project |
atlassianWorkspace_id_g | project |
atlassianWorkspace_orgId_s | project |
detectiontime_d | project |
AV detections related to Europium actors
#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
| Tactic | Techniques |
|---|---|
| Impact |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Microsoft Defender for Endpoint | DeviceInfo action any: Device information |
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 =CompromisedEntity != -1substring(CompromisedEntity, (DomainIndex + 1))CompromisedEntityIndicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ProviderName | eq |
| field:"ProviderName" kind:eq value:"MDATP" |
ThreatFamilyName | in |
| field:"ThreatFamilyName" kind:in |
ThreatName | in |
| field:"ThreatName" kind:in |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AlertSeverity | summarize |
CompromisedEntity | summarize |
Description | summarize |
DeviceId | summarize |
DisplayName | summarize |
Entities | summarize |
ProductName | summarize |
PublicIP | summarize |
TenantId | summarize |
ThreatFamilyName | summarize |
ThreatName | summarize |
DomainIndex | extend |
HostName | extend |
HostNameDomain | extend |
Azure WAF matching for Log4j vuln(CVE-2021-44228)
#'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
| Tactic | Techniques |
|---|---|
| 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 =isnotempty(details_data_s)details_data_siff(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 =MaliciousCommand has "Base64/"split(split(MaliciousCommand, "Base64/", 1)[0], "}", 0)[0]""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.
| Field | Kind | Values | Search |
|---|---|---|---|
Category | in |
| field:"Category" kind:in |
ResourceProvider | eq |
| field:"ResourceProvider" kind:eq value:"MICROSOFT.NETWORK" |
details_data_s | match |
| field:"details_data_s" kind:match |
details_message_s | match |
| field:"details_message_s" kind:match |
details_msg_s | match |
| field:"details_msg_s" kind:match |
requestUri_s | match |
| field:"requestUri_s" kind:match |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DecodedCmdLine | project |
MaliciousCommand | project |
MaliciousHost | project |
Message | project |
OperationName | project |
SubscriptionId | project |
Target | project |
TimeGenerated | project |
details_data_s | project |
details_file_s | project |
details_message_s | project |
details_msg_s | project |
ruleSetType_s | project |
timestamp | extend |
Bitglass - Login from new device
#'Detects when a user logins from new device.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| 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.
| Field | Kind | Values | Search |
|---|---|---|---|
EventMessage | eq |
| field:"EventMessage" kind:eq value:"Login" |
EventType | eq |
| field:"EventType" kind:eq value:"access" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
User | summarize |
dev | summarize |
AccountCustomEntity | extend |
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.
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.
| Field | Kind | Values | Search |
|---|---|---|---|
Finding | eq |
| field:"Finding" kind:eq value:"AzureT0AddOwner" |
C2-NamedPipe
#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
| Tactic | Techniques |
|---|---|
| Command & Control |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Microsoft Defender for Endpoint | DeviceEvents action NamedPipeEvent: Named pipe event |
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
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 =DeviceName has "."substring(DeviceName, 0, indexof(DeviceName, '.'))DeviceNameStage 8: extend
| extend DnsDomain = iff(DeviceName has '.', substring(DeviceName, indexof(DeviceName, '.') + 1), "")
DnsDomain =DeviceName has "."substring(DeviceName, (indexof(DeviceName, '.') + 1))""Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ActionType | eq |
| field:"ActionType" kind:eq value:"NamedPipeEvent" |
ParsedFields.FileOperation | eq |
| field:"ParsedFields.FileOperation" kind:eq value:"File created" |
ParsedFields.PipeName | match |
| field:"ParsedFields.PipeName" kind:match value:"badPipeNames" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActionType | project |
DeviceName | project |
InitiatingProcessAccountDomain | project |
InitiatingProcessAccountName | project |
InitiatingProcessCommandLine | project |
InitiatingProcessFolderPath | project |
ParsedFields.FileOperation | project |
ParsedFields.PipeName | project |
TimeGenerated | project |
HostName | extend |
DnsDomain | extend |
Cisco Duo - Multiple admin 2FA failures
#'Detects when multiple admin 2FA failures occurs.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| 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.
| Field | Kind | Values | Search |
|---|---|---|---|
ActionName | eq |
| field:"ActionName" kind:eq value:"admin_2fa_error" |
count_ | gt |
| field:"count_" kind:gt value:"10" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActorName | summarize |
AccountCustomEntity | extend |
Cisco WSA - Multiple errors to URL
#'Detects multiple connection errors to URL.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| 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.
| Field | Kind | Values | Search |
|---|---|---|---|
DvcAction | eq |
| field:"DvcAction" kind:eq value:"NONE" |
count_ | ge |
| field:"count_" kind:ge value:"5" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
SrcUserName | summarize |
UrlOriginal | summarize |
AccountCustomEntity | extend |
URLCustomEntity | extend |
ContraForce - Machine credential activity
#'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
| Tactic | Techniques |
|---|---|
| Persistence |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
ActivityType | in |
| field:"ActivityType" kind:in |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActivityAction | project |
ActivityType | project |
ActorEmail | project |
ActorName | project |
CfWorkspaceId | project |
EventId | project |
Metadata | project |
TargetResourceId | project |
TargetResourceName | project |
TargetResourceType | project |
TimeGenerated | project |
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.
"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
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Execution | |
| Stealth | |
| Impact |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
ConfidenceScore | ge |
| field:"ConfidenceScore" kind:ge value:"50" |
ConfidenceScore | lt |
| field:"ConfidenceScore" kind:lt value:"80" |
RecommendedActions | match |
| field:"RecommendedActions" kind:match value:"Block" |
Roles | is_null | field:"Roles" kind:is_null | |
pattern | contains |
| field:"pattern" kind:contains value:"file:hashes" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Algo_MD5 | project |
Algo_SHA1 | project |
Algo_SHA256 | project |
ConfidenceScore | project |
Country | project |
Description | project |
IndicatorID | project |
MD5 | project |
ProductName | project |
ProviderName | project |
RecommendedActions | project |
Roles | project |
SHA1 | project |
SHA256 | project |
SecurityVendors | project |
Sources | project |
Tags | project |
ThreatActors | project |
ThreatType | project |
TimeGenerated | project |
created | project |
modified | project |
name | project |
valid_from | project |
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.
"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
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Execution | |
| Command & Control |
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.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
pattern | contains | file:hashes | excludes:pattern field:"pattern" value:"file:hashes" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ConfidenceScore | project |
Country | project |
Description | project |
Domain | project |
IPAbuse | project |
IPv4 | project |
IPv6 | project |
IndicatorID | project |
ProductName | project |
ProviderName | project |
RecommendedActions | project |
Roles | project |
SecurityVendors | project |
Sources | project |
Tags | project |
ThreatActors | project |
ThreatType | project |
TimeGenerated | project |
URL | project |
created | project |
modified | project |
name | project |
valid_from | project |
Digital Guardian - Multiple incidents from user
#'Detects multiple incidents from user.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Exfiltration |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
MatchedPolicies | is_not_null | field:"MatchedPolicies" kind:is_not_null | |
count_ | ge |
| field:"count_" kind:ge value:"2" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
SrcUserName | summarize |
AccountCustomEntity | extend |
Execution of software vulnerable to webp buffer overflow of CVE-2023-4863
#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
| Tactic | Techniques |
|---|---|
| Execution |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
DeviceId | in |
| field:"DeviceId" kind:in value:"VulnDevices" |
InitiatingProcessCommandLine | match |
| field:"InitiatingProcessCommandLine" kind:match value:".webp" |
ProcessCommandLine | match |
| field:"ProcessCommandLine" kind:match value:".webp" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Name | extend |
UPNSuffix | extend |
DomainIndex | extend |
HostName | extend |
HostNameDomain | extend |
GitHub - A payment method was removed
#Detect activities when a payment method was removed. This query runs every day and its severity is Medium.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| GitHub | Audit log action payment_method.remove: Payment method removed |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
Action | eq |
| field:"Action" kind:eq value:"payment_method.remove" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountName | extend |
AccountUPNSuffix | extend |
Google DNS - CVE-2021-34527 (PrintNightmare) external exploit
#'Detects CVE-2021-34527 (PrintNightmare) external exploit'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| 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.
| Field | Kind | Values | Search |
|---|---|---|---|
Query | match |
| field:"Query" kind:match value:"gentilkiwi" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DNSCustomEntity | extend |
IPCustomEntity | extend |
GSA - Detect Source IP Scanning Multiple Open Ports
#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
| Tactic | Techniques |
|---|---|
| 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.
| Field | Kind | Values | Search |
|---|---|---|---|
Action | eq |
| field:"Action" kind:eq value:"Allowed" |
PortsScanned | gt |
| field:"PortsScanned" kind:gt value:"100" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DestinationFqdn | project |
PortsScanned | project |
SourceIp | project |
TimeGenerated | project |
GTI - Zero-Day Vulnerability Detected
#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
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Execution | |
| Privilege Escalation | |
| Impact |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
Tags | match |
| field:"Tags" kind:match |
_c.start_cpe | is_not_null | field:"_c.start_cpe" kind:is_not_null |
KMS Server Settings Updated
#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.
| Field | Kind | Values | Search |
|---|---|---|---|
instanceId | eq |
| field:"instanceId" kind:eq value:"42302" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DataSource | project |
Date | project |
EventId | project |
MessageDetails | project |
Obeject Name | project |
Severity | project |
M365D Alerts Correlation to non-Microsoft Network device network activity involved in successful sign-in Activity
#This content is employed to correlate with Microsoft Defender XDR phishing-related alerts. It focuses on instances where a user successfully connects to a phishing URL from a non-Microsoft network device and subsequently makes successful sign-in attempts from the phishing IP address.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Privilege Escalation |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Entra ID | SigninLogs sign-in event: Successful sign-in |
Rules Detecting the Same Action
These rules filter on the same operation.
- [Entra ID] Suspicious Continuous OAuth Token Usage (Kusto)
- Anomalous sign-in location by user account and authenticating application (Kusto)
- Anomalous Single Factor Signin (Kusto)
- Authentications of Privileged Accounts Outside of Expected Controls (Kusto)
- Azure Portal sign in from another Azure Tenant (Kusto)
- Azure Service Principal Sign-In Followed by Arc Cluster Credential Access (Elastic)
- Azure SignIn via Legacy Authentication Protocol (Panther)
- Cisco - firewall block but success logon to Microsoft Entra ID (Kusto)
Rule Body
id: 779731f7-8ba0-4198-8524-5701b7defddc
name: M365D Alerts Correlation to non-Microsoft Network device network activity involved in successful sign-in Activity
description: |
'This content is employed to correlate with Microsoft Defender XDR phishing-related alerts. It focuses on instances where a user successfully connects to a phishing URL from a non-Microsoft network device and subsequently makes successful sign-in attempts from the phishing IP address.'
severity: Medium
requiredDataConnectors:
- connectorId: OfficeATP
dataTypes:
- SecurityAlert
- connectorId: PaloAltoNetworks
dataTypes:
- CommonSecurityLog (PaloAlto)
- connectorId: Fortinet
dataTypes:
- CommonSecurityLog (Fortinet)
- connectorId: CheckPoint
dataTypes:
- CommonSecurityLog (CheckPoint)
- connectorId: Zscaler
dataTypes:
- CommonSecurityLog (Zscaler)
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- PrivilegeEscalation
relevantTechniques:
- T1078
query: |
let Alert_List= dynamic([
"Phishing link click observed in Network Traffic",
"Phish delivered due to an IP allow policy",
"A potentially malicious URL click was detected",
"High Risk Sign-in Observed in Network Traffic",
"A user clicked through to a potentially malicious URL",
"Suspicious network connection to AitM phishing site",
"Messages containing malicious entity not removed after delivery",
"Email messages containing malicious URL removed after delivery",
"Email reported by user as malware or phish",
"Phish delivered due to an ETR override",
"Phish not zapped because ZAP is disabled"]);
SecurityAlert
| where AlertName in~ (Alert_List)
//Findling Alerts which has the URL
| where Entities has "url"
//extracting Entities
| extend Entities = parse_json(Entities)
| mv-apply Entity = Entities on
(
where Entity.Type == 'url'
| extend EntityUrl = tostring(Entity.Url)
)
| summarize
Url=tostring(tolower(take_any(EntityUrl))),
AlertTime= min(TimeGenerated),
make_set(SystemAlertId, 100)
by ProductName, AlertName
// matching with 3rd party network logs and 3p Alerts
| join kind= inner (CommonSecurityLog
| where DeviceVendor has_any ("Palo Alto Networks", "Fortinet", "Check Point", "Zscaler")
| where DeviceProduct startswith "FortiGate" or DeviceProduct startswith "PAN" or DeviceProduct startswith "VPN" or DeviceProduct startswith "FireWall" or DeviceProduct startswith "NSSWeblog" or DeviceProduct startswith "URL"
| where DeviceAction != "Block"
| where isnotempty(RequestURL)
| project
3plogTime=TimeGenerated,
DeviceVendor,
DeviceProduct,
Activity,
DestinationHostName,
DestinationIP,
RequestURL=tostring(tolower(RequestURL)),
MaliciousIP,
SourceUserName=tostring(tolower(SourceUserName)),
IndicatorThreatType,
ThreatSeverity,
ThreatConfidence,
SourceUserID,
SourceHostName)
on $left.Url == $right.RequestURL
// matching successful Login from suspicious IP
| join kind=inner (SigninLogs
//filtering the Successful Login
| where ResultType == 0
| project
IPAddress,
SourceSystem,
SigniningTime= TimeGenerated,
OperationName,
ResultType,
ResultDescription,
AlternateSignInName,
AppDisplayName,
AuthenticationRequirement,
ClientAppUsed,
RiskState,
RiskLevelDuringSignIn,
UserPrincipalName=tostring(tolower(UserPrincipalName)),
Name = tostring(split(UserPrincipalName, "@")[0]),
UPNSuffix =tostring(split(UserPrincipalName, "@")[1]))
on $left.DestinationIP == $right.IPAddress and $left.SourceUserName == $right.UserPrincipalName
| where SigniningTime between ((AlertTime - 6h) .. (AlertTime + 6h)) and 3plogTime between ((AlertTime - 6h) .. (AlertTime + 6h))
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: UserPrincipalName
- identifier: Name
columnName: Name
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: DestinationIP
- entityType: DNS
fieldMappings:
- identifier: DomainName
columnName: DestinationHostName
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: SourceSystem
- entityType: URL
fieldMappings:
- identifier: Url
columnName: RequestURL
kind: Scheduled
version: 1.0.5
metadata:
source:
kind: Community
author:
name: Arjun Trivedi
support:
tier: Community
categories:
domains: [ "Security - Threat Protection" ]
Stages and Predicates
Let Binding: Alert_List
let Alert_List = dynamic([
"Phishing link click observed in Network Traffic",
"Phish delivered due to an IP allow policy",
"A potentially malicious URL click was detected",
"High Risk Sign-in Observed in Network Traffic",
"A user clicked through to a potentially malicious URL",
"Suspicious network connection to AitM phishing site",
"Messages containing malicious entity not removed after delivery",
"Email messages containing malicious URL removed after delivery",
"Email reported by user as malware or phish",
"Phish delivered due to an ETR override",
"Phish not zapped because ZAP is disabled"]);
Stage 1: source
SecurityAlert
Stage 2: where
| where AlertName in~ (Alert_List)
Stage 3: where
| where Entities has "url"
Stage 4: extend
| extend Entities = parse_json(Entities)
Stage 5: kusto:mv-apply
| mv-apply Entity = Entities on
(
where Entity.Type == 'url'
| extend EntityUrl = tostring(Entity.Url)
)
Stage 6: summarize
| summarize
Url=tostring(tolower(take_any(EntityUrl))),
AlertTime= min(TimeGenerated),
make_set(SystemAlertId, 100)
by ProductName, AlertName
Stage 7: join
| join kind= inner (CommonSecurityLog
| where DeviceVendor has_any ("Palo Alto Networks", "Fortinet", "Check Point", "Zscaler")
| where DeviceProduct startswith "FortiGate" or DeviceProduct startswith "PAN" or DeviceProduct startswith "VPN" or DeviceProduct startswith "FireWall" or DeviceProduct startswith "NSSWeblog" or DeviceProduct startswith "URL"
| where DeviceAction != "Block"
| where isnotempty(RequestURL)
| project
3plogTime=TimeGenerated,
DeviceVendor,
DeviceProduct,
Activity,
DestinationHostName,
DestinationIP,
RequestURL=tostring(tolower(RequestURL)),
MaliciousIP,
SourceUserName=tostring(tolower(SourceUserName)),
IndicatorThreatType,
ThreatSeverity,
ThreatConfidence,
SourceUserID,
SourceHostName)
on $left.Url == $right.RequestURL
Stage 8: join
| join kind=inner (SigninLogs
| where ResultType == 0
| project
IPAddress,
SourceSystem,
SigniningTime= TimeGenerated,
OperationName,
ResultType,
ResultDescription,
AlternateSignInName,
AppDisplayName,
AuthenticationRequirement,
ClientAppUsed,
RiskState,
RiskLevelDuringSignIn,
UserPrincipalName=tostring(tolower(UserPrincipalName)),
Name = tostring(split(UserPrincipalName, "@")[0]),
UPNSuffix =tostring(split(UserPrincipalName, "@")[1]))
on $left.DestinationIP == $right.IPAddress and $left.SourceUserName == $right.UserPrincipalName
Stage 9: where
where /* macro: (3plogTime between ((AlertTime - 6h) .. (AlertTime + 6h))) */
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AlertName | in |
| field:"AlertName" kind:in |
DeviceAction | ne |
| field:"DeviceAction" kind:ne value:"Block" |
DeviceProduct | starts_with |
| field:"DeviceProduct" kind:starts_with |
DeviceVendor | match |
| field:"DeviceVendor" kind:match |
Entities | match |
| field:"Entities" kind:match value:"url" |
Entity.Type | eq |
| field:"Entity.Type" kind:eq value:"url" |
RequestURL | is_not_null | field:"RequestURL" kind:is_not_null | |
ResultType | eq |
| field:"ResultType" kind:eq value:"0" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AlertName | summarize |
AlertTime | summarize |
ProductName | summarize |
Url | summarize |
Microsoft Defender for Endpoint (MDE) signatures for Azure Synapse pipelines and Azure Data Factory
#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
| Tactic | Techniques |
|---|---|
| Initial Access |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Microsoft Defender for Endpoint | DeviceInfo action any: Device information |
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
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 =DomainIndex != -1substring(CompromisedEntity, (DomainIndex + 1))CompromisedEntityStage 7: project-away
project-away DomainIndex
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ProviderName | eq |
| field:"ProviderName" kind:eq value:"MDATP" |
ThreatFamilyName | in |
| field:"ThreatFamilyName" kind:in |
ThreatName | in |
| field:"ThreatName" kind:in |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AlertSeverity | summarize |
CompromisedEntity | summarize |
Description | summarize |
DeviceId | summarize |
DisplayName | summarize |
Entities | summarize |
ProductName | summarize |
PublicIP | summarize |
TenantId | summarize |
ThreatFamilyName | summarize |
ThreatName | summarize |
HostName | extend |
HostNameDomain | extend |
Netskope - Suspicious Application Activity (Low Confidence / Risky App)
#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
| Tactic | Techniques |
|---|---|
| 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.
| Field | Kind | Values | Search |
|---|---|---|---|
Action | eq |
| field:"Action" kind:eq value:"block" |
Activity | in |
| field:"Activity" kind:in |
App | is_not_null | field:"App" kind:is_not_null | |
Ccl | in |
| field:"Ccl" kind:in |
Ccl | ne |
| field:"Ccl" kind:ne value:"excellent" |
DistinctApps | gt |
| field:"DistinctApps" kind:gt value:"3" |
EventCount | gt |
| field:"EventCount" kind:gt value:"5" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Actions | project |
Activities | project |
Apps | project |
CCLs | project |
Categories | project |
DeviceClassification | project |
DistinctApps | project |
EventCount | project |
Hostname | project |
LastSeen | project |
RiskIndicators | project |
User | project |
Userip | project |
Netskope - Suspicious Network Context (Unusual IPs/Geo/Ports)
#Detects suspicious network activity based on unusual source/destination IPs, geographic anomalies, uncommon ports, and high traffic volumes.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Discovery | |
| Command & Control | |
| Exfiltration |
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.
| Field | Source |
|---|---|
DestinationCountries | project |
DestinationIPs | project |
DestinationPorts | project |
EventCount | project |
HighRiskCountryAccessCount | project |
RiskFactors | project |
SourceCountry | project |
SourceIP | project |
SourceLocation | project |
SuspiciousPortAccessCount | project |
TargetHosts | project |
TimeGenerated | project |
TotalDataMB | project |
UniqueDstIPCount | project |
User | project |
Pathlock TDnR - Dynamic Access Control Events
#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
| Tactic | Techniques |
|---|---|
| Privilege Escalation |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
DataSource | eq |
| field:"DataSource" kind:eq value:"PATHLOCK_DAC" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AffectedUser | project |
Area | project |
Bname | project |
CentralTs | project |
DataSource | project |
DestIp | project |
Eventid | project |
Hostname | project |
Instance | project |
LogLine | project |
MessageV1 | project |
MessageV2 | project |
MessageV3 | project |
MessageV4 | project |
MsgId | project |
MsgNo | project |
MsgType | project |
Report | project |
SrcIp | project |
Subid | project |
Sysid | project |
Tcode | project |
TimeGenerated | project |
Ping Federate - Forbidden country
#'Detects requests from forbidden countries.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| 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.
| Field | Kind | Values | Search |
|---|---|---|---|
DstGeoCountry | in |
| field:"DstGeoCountry" kind:in value:"forbidden_geo" |
DstGeoCountry | is_not_null | field:"DstGeoCountry" kind:is_not_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountCustomEntity | extend |
IpCustomEntity | extend |
PROD (TM017.1) - GROUP - Tier 0 Added to Allow RODC Password Replication Group
#Adding a Tier 0 object to the Allow RODC Password Replication Group.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Privilege Escalation |
Telemetry Coverage
| Provider | Record / event type |
|---|---|
| Security-Auditing | Event ID 5136: A directory service object was modified. |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
Activity | match |
| field:"Activity" kind:match value:"modified" |
EventData | match |
| field:"EventData" kind:match |
EventData | regex_match |
| field:"EventData" kind:regex_match value:"(?i)CN=Allowed RODC Password Replication Group,CN=Users" |
EventID | eq |
| field:"EventID" kind:eq value:"5136" |
ObjectTier | eq |
| field:"ObjectTier" kind:eq value:"Tier 0" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Account | project |
ActivityType | project |
Channel | project |
Computer | project |
Domain | project |
EventData | project |
EventID | project |
GroupName | project |
ObjectName | project |
ObjectTier | project |
ObjectType | project |
OperationType | project |
TimeGenerated | project |
Red Sift - MFA disabled on account
#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
| Tactic | Techniques |
|---|---|
| Persistence | |
| Defense Impairment | |
| Credential Access |
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 =isnotempty(UserEmail)UserEmailActorUserEmailStage 6: project
| project
TimeGenerated,
TargetAccount,
UserEmail,
ActorUserEmail,
SrcIp,
HttpUserAgent,
ServiceName,
ActivityName,
Severity,
UserUid,
ActorUserUid
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ActivityName | eq |
| field:"ActivityName" kind:eq value:"MFA Disabled" |
ActorUserEmail | is_not_null | field:"ActorUserEmail" kind:is_not_null | |
UserEmail | is_not_null | field:"UserEmail" kind:is_not_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActivityName | project |
ActorUserEmail | project |
ActorUserUid | project |
HttpUserAgent | project |
ServiceName | project |
Severity | project |
SrcIp | project |
TargetAccount | project |
TimeGenerated | project |
UserEmail | project |
UserUid | project |
Silverfort - Certifried Incident
#'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
| Tactic | Techniques |
|---|---|
| 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.
| Field | Kind | Values | Search |
|---|---|---|---|
DeviceEventClassID | eq |
| field:"DeviceEventClassID" kind:eq value:"NewIncident" |
DeviceProduct | match |
| field:"DeviceProduct" kind:match value:"Admin Console" |
DeviceVendor | match |
| field:"DeviceVendor" kind:match value:"Silverfort" |
Message | match |
| field:"Message" kind:match value:"Certifried" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
UserName | extend |
Suspicious number of resource creation or deployment activities
#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
| Tactic | Techniques |
|---|---|
| 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.
| Field | Kind | Values | Search |
|---|---|---|---|
OperationNameValue | in |
| field:"azure_ad::operation_name_value" kind:in |
anomalies | gt |
| field:"anomalies" kind:gt value:"0" |
baseline | gt |
| field:"baseline" kind:gt value:"0" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Caller | project |
TimeGenerated | project |
Total | project |
anomalies | project |
baseline | project |
score | project |
Name | extend |
UPNSuffix | extend |
AadUserId | extend |
Syntax errors stateful anomaly on database
#'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
| Tactic | Techniques |
|---|---|
| Initial Access |
Rules Detecting the Same Action
These rules filter on the same operation.
- Affected rows stateful anomaly on database (Kusto)
- Credential errors stateful anomaly on database (Kusto)
- Drop attempts stateful anomaly on database (Kusto)
- Execution attempts stateful anomaly on database (Kusto)
- Firewall errors stateful anomaly on database (Kusto)
- Firewall rule manipulation attempts stateful anomaly on database (Kusto)
- OLE object manipulation attempts stateful anomaly on database (Kusto)
- Outgoing connection attempts stateful anomaly on database (Kusto)
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
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 =/* macro: (timeSlice >= ago(detectionWindow)) */'detection'/* macro: (ago(trainingWindow) <= timeSlice) */'training''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 =countStatementsWithError >= monitoredStatementsThreshold and countSlicesWithErrors <= trainingSlicesThresholdtruefalseStage 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.
| Field | Kind | Values | Search |
|---|---|---|---|
Category | eq |
| field:"Category" kind:eq value:"SQLSecurityAuditEvents" |
IsErrorAnomalyOnStatement | eq |
| field:"IsErrorAnomalyOnStatement" kind:eq value:"true" |
WindowType | eq |
| field:"WindowType" kind:eq |
WindowType | in |
| field:"WindowType" kind:in |
action_id_s | match |
| field:"action_id_s" kind:match |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ApplicationName | project |
ClientIp | project |
Database | project |
HostName | project |
PrincipalName | project |
ResourceId | project |
TimeGenerated | project |
anomalyScore | project |
anyInfo | project |
anyMonitoredStatement | project |
countEvents | project |
countStatements | project |
countStatementsWithError | project |
Name | extend |
UPNSuffix | extend |
Threat Connect TI map Domain entity to DnsEvents
#Identifies a match in DnsEvents from any ThreatConnect Domain IOC from TI
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| 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
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.
| Field | Kind | Values | Search |
|---|---|---|---|
Active | eq |
| field:"Active" kind:eq value:"true" |
DomainName | is_not_null | field:"DomainName" kind:is_not_null | |
Name | is_not_null | field:"Name" kind:is_not_null | |
SourceSystem | starts_with |
| field:"SourceSystem" kind:starts_with value:"ThreatConnect-" |
tld | in |
| field:"tld" kind:in value:"list_tlds" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActivityGroupNames | project |
ClientIP | project |
Computer | project |
ConfidenceScore | project |
DNS_TimeGenerated | project |
Description | project |
ExpirationDateTime | project |
IndicatorId | project |
Name | project |
QueryType | project |
TI_DomainEntity | project |
ThreatType | project |
Type | project |
Url | project |
DnsDomain | extend |
HostName | extend |
timestamp | extend |
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.
'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
#'Detects suspicious ZPA connections from forbidden countries.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
DvcAction | eq |
| field:"DvcAction" kind:eq value:"open" |
SrcGeoCountry | in |
| field:"SrcGeoCountry" kind:in |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountCustomEntity | extend |
IPCustomEntity | extend |