Detection rules › Kusto
Kusto rules: 0i
Cloudflare - Unexpected URI
#'Detects client requests to unusual URI.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: dcb797cd-a4cd-4306-897b-7991f71d7e28
name: Cloudflare - Unexpected URI
description: |
'Detects client requests to unusual URI.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: CloudflareDataConnector
dataTypes:
- Cloudflare
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1190
- T1133
query: |
Cloudflare
| where HttpRequestMethod =~ 'GET'
| where DstBytes != 0 or SrcBytes != 0
| where ClientRequestURI matches regex @'(127\.\d{1,3}\.\d{1,3}\.\d{1,3})|(10\.\d{1,3}\.\d{1,3}\.\d{1,3})|(172\.1[6-9]\.\d{1,3}\.\d{1,3})|(172\.2[0-9]\.\d{1,3}\.\d{1,3})|(172\.3[0-1]\.\d{1,3}\.\d{1,3})|(192\.168\.\d{1,3}\.\d{1,3})'
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIpAddr
version: 1.0.1
kind: Scheduled
Stages and Predicates
Stage 1: source
Cloudflare
Stage 2: where
| where HttpRequestMethod =~ 'GET'
Stage 3: where
| where DstBytes != 0 or SrcBytes != 0
Stage 4: where
| where ClientRequestURI matches regex @'(127\.\d{1,3}\.\d{1,3}\.\d{1,3})|(10\.\d{1,3}\.\d{1,3}\.\d{1,3})|(172\.1[6-9]\.\d{1,3}\.\d{1,3})|(172\.2[0-9]\.\d{1,3}\.\d{1,3})|(172\.3[0-1]\.\d{1,3}\.\d{1,3})|(192\.168\.\d{1,3}\.\d{1,3})'
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ClientRequestURI | regex_match |
| field:"ClientRequestURI" kind:regex_match |
DstBytes | ne |
| field:"DstBytes" kind:ne value:"0" |
HttpRequestMethod | eq |
| field:"HttpRequestMethod" kind:eq value:"GET" |
SrcBytes | ne |
| field:"SrcBytes" kind:ne value:"0" |
CTE - Rule Capacity High (90%)
#Tenant-wide blocking-rule/IOC capacity has reached 90%. Emitted by the daily tenant health-check job (api/src/functions/tenantHealthChecks.js) when usage newly crosses this threshold, and re-emitted at most once per ~30 days while it remains above threshold, so this stays a periodic reminder rather than a daily repeat.
Rule Body
id: b3eb26f4-affb-4a61-8b54-8780d234008e
name: CTE - Rule Capacity High (90%)
description: |
Tenant-wide blocking-rule/IOC capacity has reached 90%. Emitted by the daily tenant health-check job (api/src/functions/tenantHealthChecks.js) when usage newly crosses this threshold, and re-emitted at most once per ~30 days while it remains above threshold, so this stays a periodic reminder rather than a daily repeat.
severity: Medium
requiredDataConnectors:
- connectorId: cybereinforce_cte
dataTypes:
- CybereinforceCTE_CL
queryFrequency: 24h
queryPeriod: 2d
triggerOperator: gt
triggerThreshold: 0
tactics:
[]
relevantTechniques: []
query: |
CybereinforceCTE_CL
| where EventType == "rule.capacity_high"
| extend d = parse_json(Details)
| extend AppName = "Cybereinforce", RuleCount = toint(d.ruleCount), RuleLimit = toint(d.ruleLimit), UsedPct = todouble(d.usedPct)
| project TimeGenerated, TenantId, AppName, RuleCount, RuleLimit, UsedPct, Severity, Category, Source
entityMappings:
- entityType: CloudApplication
fieldMappings:
- identifier: Name
columnName: AppName
- identifier: InstanceName
columnName: TenantId
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
CybereinforceCTE_CL
Stage 2: where
| where EventType == "rule.capacity_high"
Stage 3: extend
| extend d = parse_json(Details)
Stage 4: extend
| extend AppName = "Cybereinforce", RuleCount = toint(d.ruleCount), RuleLimit = toint(d.ruleLimit), UsedPct = todouble(d.usedPct)
Stage 5: project
| project TimeGenerated, TenantId, AppName, RuleCount, RuleLimit, UsedPct, Severity, Category, Source
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventType | eq |
| field:"EventType" kind:eq value:"rule.capacity_high" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AppName | project |
Category | project |
RuleCount | project |
RuleLimit | project |
Severity | project |
Source | project |
TenantId | project |
TimeGenerated | project |
UsedPct | project |
CyberArkEPM - Unexpected executable extension
#'Detects Windows executable with unexpected extension.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Execution | |
| Stealth |
Rule Body
id: 911d5b75-a1ce-4f13-a839-9c2474768696
name: CyberArkEPM - Unexpected executable extension
description: |
'Detects Windows executable with unexpected extension.'
severity: Medium
requiredDataConnectors:
- connectorId: CyberArkEPM
dataTypes:
- CyberArkEPM
queryFrequency: 30m
queryPeriod: 30m
triggerOperator: gt
triggerThreshold: 0
tactics:
- Execution
- DefenseEvasion
relevantTechniques:
- T1204
- T1036
query: |
CyberArkEPM
| where EventSubType != 'AttackAttempt'
| where ActingProcessName has @'\'
| where ActingProcessName !endswith '.exe'
| project EventEndTime, EventMessage, ActorUsername, ActingProcessFileInternalName
| extend AccountCustomEntity = ActorUsername
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountCustomEntity
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
CyberArkEPM
Stage 2: where
| where EventSubType != 'AttackAttempt'
Stage 3: where
| where ActingProcessName has @'\'
Stage 4: where
| where ActingProcessName !endswith '.exe'
Stage 5: project
| project EventEndTime, EventMessage, ActorUsername, ActingProcessFileInternalName
Stage 6: extend
| extend AccountCustomEntity = ActorUsername
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ActingProcessName | match |
| field:"ParentImage" kind:match value:"\" |
EventSubType | ne |
| field:"EventSubType" kind:ne value:"AttackAttempt" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
ActingProcessName | ends_with | .exe | excludes:ActingProcessName field:"ActingProcessName" value:".exe" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActingProcessFileInternalName | project |
ActorUsername | project |
EventEndTime | project |
EventMessage | project |
AccountCustomEntity | extend |
Cyble Vision Alerts Darkweb Ransomware Leak
#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.
'A ransomware threat actor has posted victim data on the dark web. This alert includes leaked documents, threat actor name, victim organization, timestamps, and extracted text content for SOC triage.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Impact |
Rule Body
id: 6deaf986-a25b-47b4-afbe-667901aa313b
name: Cyble Vision Alerts Darkweb Ransomware Leak
description: |
'A ransomware threat actor has posted victim data on the dark web. This alert includes leaked documents, threat actor name, victim organization, timestamps, and extracted text content for SOC triage.'
severity: Low
status: Available
requiredDataConnectors:
- connectorId: CybleVisionAlerts
dataTypes:
- CybleVisionAlerts_CL
enabled: true
queryfrequency: 30m
queryPeriod: 30m
triggerOperator: GreaterThan
triggerThreshold: 0
eventGroupingSettings:
aggregationKind: AlertPerResult
tactics:
- Impact
- Exfiltration
- Reconnaissance
relevantTechniques:
- T1486
- T1657
query: |
Alerts_darkweb_ransomware
| where Service == "darkweb_ransomware"
| extend MappedSeverity = Severity
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
alertDetailsOverride:
alertDisplayNameFormat: Darkweb Ransomware Leak {{Victim}} by {{ThreatActor}}
alertDescriptionFormat: |
A ransomware group has posted data belonging on the dark web. A document leak has been detected, including text extracted from the file.
**Victim:** {{Victim}}
**Threat Actor:** {{ThreatActor}}
**Leaked File:** {{FileName}}
customDetails:
MappedSeverity: Severity
Status: Status
AlertID: AlertID
Service: Service
Victim: DW_Victim_Nested
ThreatActor: DW_ThreatActor_Nested
CompanyLeak: DW_CompanyLeak
DocumentYear: DW_DocumentYear
FileName: DW_FileName
OriginalFileName: DW_OriginalFileName
ContentText: DW_Text
UpdatedDate: DW_UpdatedDate_Nested
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: DW_Victim_Nested
- identifier: FullName
columnName: DW_ThreatActor_Nested
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
Alerts_darkweb_ransomware
Stage 2: where
| where Service == "darkweb_ransomware"
Stage 3: extend
| extend MappedSeverity = Severity
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Service | eq |
| field:"Service" kind:eq value:"darkweb_ransomware" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
MappedSeverity | extend |
Cyble Vision Alerts Website Defacement URL
#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.
'Detects suspicious or unexpected changes to monitored URLs which may indicate website tampering or defacement.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Impact |
Rule Body
id: 1dabe566-a0f1-4c27-8307-aea5a79eb5e9
name: Cyble Vision Alerts Website Defacement URL
description: |
'Detects suspicious or unexpected changes to monitored URLs which may indicate website tampering or defacement.'
severity: Low
status: Available
requiredDataConnectors:
- connectorId: CybleVisionAlerts
dataTypes:
- CybleVisionAlerts_CL
enabled: true
queryfrequency: 30m
queryPeriod: 30m
triggerOperator: GreaterThan
triggerThreshold: 0
eventGroupingSettings:
aggregationKind: AlertPerResult
tactics:
- Impact
relevantTechniques:
- T1491
query: |
Alerts_defacement_url
| where Service == "defacement_url"
| extend MappedSeverity = Severity
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
alertDetailsOverride:
alertDisplayNameFormat: URL Content Change Detected {{DU_DomainName}}
alertDescriptionFormat: |
A monitored URL for domain {{DU_DomainName}} has changed {{DU_Description}}. This may indicate unauthorized modification consistent with website defacement activity.
customDetails:
MappedSeverity: Severity
Status: Status
AlertID: AlertID
Service: Service
DU_description: DU_Description
DU_DomainName: DU_DomainName
DU_Keywords: DU_Keywords
DU_Type: DU_Type
DU_RecordID: DU_RecordID
entityMappings:
- entityType: Url
fieldMappings:
- identifier: Url
columnName: DU_Description
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: DU_DomainName
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
Alerts_defacement_url
Stage 2: where
| where Service == "defacement_url"
Stage 3: extend
| extend MappedSeverity = Severity
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Service | eq |
| field:"Service" kind:eq value:"defacement_url" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
MappedSeverity | extend |
CYFIRMA - High severity Malicious Network Indicators with Monitor 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 rule detects high-confidence network-based indicators such as IP addresses, domains, and URLs reported by CYFIRMA threat intelligence with a recommended action of 'Monitor' and no specified role. These indicators may be associated with infrastructure used in phishing campaigns, malware, or initial access via watering hole or drive-by compromise techniques."
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Reconnaissance | |
| Initial Access | |
| Execution | |
| Impact |
Rule Body
id: 8317de44-09e4-4a04-8fae-c38c1b72064b
name: CYFIRMA - High severity Malicious Network Indicators with Monitor Action Rule
description: |
"This rule detects high-confidence network-based indicators such as IP addresses, domains, and URLs reported by CYFIRMA threat intelligence with a recommended action of 'Monitor' and no specified role.
These indicators may be associated with infrastructure used in phishing campaigns, malware, or initial access via watering hole or drive-by compromise techniques."
version: 1.0.1
kind: Scheduled
severity: High
enabled: false
requiredDataConnectors:
- connectorId: CyfirmaCyberIntelligenceDC
dataTypes:
- CyfirmaIndicators_CL
query: |
//Network Indicators with Monitor Action
let timeFrame= 5m;
CyfirmaIndicators_CL
| where ConfidenceScore >= 80
and TimeGenerated between (ago(timeFrame) .. now())
and pattern !contains 'file:hashes' and RecommendedActions has 'Monitor' and (isempty(Roles) or not(Roles has_any ('Command & Control', 'Malware', 'TOR', 'Trojan', 'Phishing')))
| 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
- Reconnaissance
- Impact
relevantTechniques:
- T1566
- T1189
- T1204
- T1595
- T1498
- T1566.002
- T1204.001
- T1595.002
alertDetailsOverride:
alertDisplayNameFormat: "High-Confidence Malicious Network Indicators with Monitor Action Rule - {{name}} "
alertDescriptionFormat: "{{Description}} - {{name}} "
alertDynamicProperties:
- alertProperty: ProductName
value: ProductName
- alertProperty: ProviderName
value: ProviderName
customDetails:
ThreatActors: ThreatActors
Sources: Sources
RecommendedActions: RecommendedActions
Roles: Roles
Country: Country
Description: Description
ConfidenceScore: ConfidenceScore
SecurityVendors: SecurityVendors
IndicatorID: IndicatorID
Created: created
Modified: modified
ValidFrom: valid_from
Tags: Tags
ThreatType: ThreatType
TimeGenerated: TimeGenerated
IPAbuse: IPAbuse
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPv4
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPv6
- entityType: DNS
fieldMappings:
- identifier: DomainName
columnName: Domain
- entityType: URL
fieldMappings:
- identifier: Url
columnName: URL
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
eventGroupingSettings:
aggregationKind: AlertPerResult
Stages and Predicates
Parameters
let timeFrame = 5m;
Stage 1: source
CyfirmaIndicators_CL
Stage 2: where
| where ConfidenceScore >= 80
and TimeGenerated between (ago(timeFrame) .. now())
and pattern !contains 'file:hashes' and RecommendedActions has 'Monitor' and (isempty(Roles) or not(Roles has_any ('Command & Control', 'Malware', 'TOR', 'Trojan', 'Phishing')))
Stage 3: extend (6 consecutive steps)
| extend IPv4 = extract(@"ipv4-addr:value\s*=\s*'([^']+)'", 1, pattern)
| extend IPv6 = extract(@"ipv6-addr:value\s*=\s*'([^']+)'", 1, pattern)
| extend URL = extract(@"url:value\s*=\s*'([^']+)'", 1, pattern)
| extend Domain = extract(@"domain-name:value\s*=\s*'([^']+)'", 1, pattern)
| extend parsed = parse_json(extensions)
| extend extensionKeys = bag_keys(parsed)
Stage 4: mv-expand
| mv-expand extensionKeys
Stage 5: extend (4 consecutive steps)
| extend extensionKeyStr = tostring(extensionKeys)
| extend ext = parsed[extensionKeyStr]
| extend props = ext.properties
| extend
extension_id = extensionKeyStr,
ASN_Owner = props.asn_owner,
ASN = props.asn,
ProviderName = 'CYFIRMA',
ProductName = 'DeCYFIR/DeTCT'
Stage 6: project
| project
IPv4,
IPv6,
URL,
Domain,
ThreatActors,
RecommendedActions,
Sources,
Roles,
Country,
IPAbuse,
name,
Description,
ConfidenceScore,
IndicatorID,
created,
modified,
valid_from,
Tags,
ThreatType,
TimeGenerated,
SecurityVendors,
ProductName,
ProviderName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ConfidenceScore | ge |
| field:"ConfidenceScore" kind:ge value:"80" |
RecommendedActions | match |
| field:"RecommendedActions" kind:match value:"Monitor" |
Roles | is_null | field:"Roles" kind:is_null |
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 |
D3 Smart SOAR - High or critical severity incident detected
#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.
Identifies when a D3 Smart SOAR incident with High or Critical severity is ingested. This helps security teams prioritize response to the most impactful incidents reported by D3 Smart SOAR.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Impact |
Rule Body
id: 48ef0be4-8240-4a03-bbb9-320b562d6ce4
name: D3 Smart SOAR - High or critical severity incident detected
description: |
Identifies when a D3 Smart SOAR incident with High or Critical severity is ingested. This helps security teams prioritize response to the most impactful incidents reported by D3 Smart SOAR.
severity: High
status: Available
requiredDataConnectors:
- connectorId: D3SOARConnectorDefinition
dataTypes:
- D3SOARIncidents_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Impact
relevantTechniques:
- T1499
query: |
D3SOARIncidents_CL
| where TimeGenerated > ago(1h)
| where IncidentSeverity in ("High", "Critical")
| project
TimeGenerated,
IncidentNumber,
IncidentTitle,
IncidentSeverity,
IncidentStatus,
IncidentType,
IncidentPriority,
IncidentOwner,
IncidentCreator,
IncidentDescription,
IncidentStage
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: IncidentOwner
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
D3SOARIncidents_CL
Stage 2: where
| where TimeGenerated > ago(1h)
Stage 3: where
| where IncidentSeverity in ("High", "Critical")
Stage 4: project
| project
TimeGenerated,
IncidentNumber,
IncidentTitle,
IncidentSeverity,
IncidentStatus,
IncidentType,
IncidentPriority,
IncidentOwner,
IncidentCreator,
IncidentDescription,
IncidentStage
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
IncidentSeverity | in |
| field:"IncidentSeverity" kind:in |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
IncidentCreator | project |
IncidentDescription | project |
IncidentNumber | project |
IncidentOwner | project |
IncidentPriority | project |
IncidentSeverity | project |
IncidentStage | project |
IncidentStatus | project |
IncidentTitle | project |
IncidentType | project |
TimeGenerated | project |
Dataverse - Suspicious use of TDS endpoint
#Identifies Dataverse TDS (Tabular Data Stream) protocol based queries where the source user or IP address has recent security alerts and the TDS protocol has not been used previously in the target environment.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Exfiltration |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Microsoft 365 | any: Dataverse and model-driven apps audit (catch-all) |
Rule Body
id: d875af10-6bb9-4d6a-a6e4-78439a98bf4b
kind: Scheduled
name: Dataverse - Suspicious use of TDS endpoint
description: Identifies Dataverse TDS (Tabular Data Stream) protocol based queries
where the source user or IP address has recent security alerts and the TDS protocol
has not been used previously in the target environment.
severity: Low
status: Available
requiredDataConnectors:
- connectorId: Dataverse
dataTypes:
- DataverseActivity
- connectorId: AzureActiveDirectoryIdentityProtection
dataTypes:
- SecurityAlert
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Exfiltration
- InitialAccess
relevantTechniques:
- T1048
- T1190
query: |
let query_frequency = 1h;
let query_lookback = 14d;
DataverseActivity
| where TimeGenerated >= ago(query_frequency)
| where Message == 'ExecutePowerBISql'
| summarize FirstEvent = min(TimeGenerated) by UserId, ClientIp, InstanceUrl
| join kind=inner(
DataverseActivity
| where TimeGenerated >= ago(query_lookback)
| where Message == 'ExecutePowerBISql'
| summarize UniqueUsers = dcount(UserId, 4) by InstanceUrl)
on InstanceUrl
| where UniqueUsers == 1
| join kind=inner (
SecurityAlert
| where Entities has ('"Type":"ip"')
| project AlertName, SystemAlertId, Entities
| mv-expand todynamic(Entities)
| where Entities.Type == "ip"
| extend IPAddress = tostring(Entities.Address)
| summarize SystemAlerts = make_set(SystemAlertId, 100), Alerts = make_set(AlertName, 100) by IPAddress)
on $left.ClientIp == $right.IPAddress
| extend
CloudAppId = int(32780),
AccountName = tostring(split(UserId, '@')[0]),
UPNSuffix = tostring(split(UserId, '@')[1])
| join kind = inner (
SecurityAlert
| where Entities has ('Type":"account"')
| project AlertName, SystemAlertId, Entities
| mv-expand todynamic(Entities)
| where Entities.Type == "account"
| extend
UPNSuffix = tostring(Entities.UPNSuffix),
AccountName = tostring(Entities.Name)
| summarize SystemAlerts = make_set(SystemAlertId, 100), Alerts = make_set(AlertName, 100) by AccountName, UPNSuffix
| where isnotempty(AccountName) and isnotempty(UPNSuffix))
on AccountName, UPNSuffix
| summarize SystemAlerts = make_set(SystemAlerts, 100), Alerts = make_set(Alerts, 100) by FirstEvent, UserId, ClientIp, InstanceUrl, AccountName, UPNSuffix
| extend CloudAppId = int(32780)
| project
FirstEvent,
UserId,
ClientIp,
InstanceUrl,
Alerts,
SystemAlerts,
CloudAppId,
AccountName,
UPNSuffix
eventGroupingSettings:
aggregationKind: AlertPerResult
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: CloudApplication
fieldMappings:
- identifier: AppId
columnName: CloudAppId
- identifier: InstanceName
columnName: InstanceUrl
- entityType: IP
fieldMappings:
- identifier: Address
columnName: ClientIp
alertDetailsOverride:
alertDisplayNameFormat: 'Dataverse - Suspicious use of TDS endpoint in {{InstanceUrl}} '
alertDescriptionFormat: 'The TDS endpoint was used to query Dataverse instance {{InstanceUrl}}
. The use of this protocol was not seen previously and the following alerts were
associated with the caller: {{Alerts}}'
version: 3.2.0
Stages and Predicates
Parameters
let query_frequency = 1h;
let query_lookback = 14d;
Stage 1: source
DataverseActivity
Stage 2: where
| where TimeGenerated >= ago(query_frequency)
Stage 3: where
| where Message == 'ExecutePowerBISql'
Stage 4: summarize
| summarize FirstEvent = min(TimeGenerated) by UserId, ClientIp, InstanceUrl
Stage 5: join
| join kind=inner(
DataverseActivity
| where TimeGenerated >= ago(query_lookback)
| where Message == 'ExecutePowerBISql'
| summarize UniqueUsers = dcount(UserId, 4) by InstanceUrl)
on InstanceUrl
Stage 6: where
where UniqueUsers == 1
Stage 7: join
| join kind=inner (
SecurityAlert
| where Entities has ('"Type":"ip"')
| project AlertName, SystemAlertId, Entities
| mv-expand todynamic(Entities)
| where Entities.Type == "ip"
| extend IPAddress = tostring(Entities.Address)
| summarize SystemAlerts = make_set(SystemAlertId, 100), Alerts = make_set(AlertName, 100) by IPAddress)
on $left.ClientIp == $right.IPAddress
Stage 8: extend
extend AccountName, CloudAppId, UPNSuffix
Stage 9: join
| join kind = inner (
SecurityAlert
| where Entities has ('Type":"account"')
| project AlertName, SystemAlertId, Entities
| mv-expand todynamic(Entities)
| where Entities.Type == "account"
| extend
UPNSuffix = tostring(Entities.UPNSuffix),
AccountName = tostring(Entities.Name)
| summarize SystemAlerts = make_set(SystemAlertId, 100), Alerts = make_set(AlertName, 100) by AccountName, UPNSuffix
| where isnotempty(AccountName) and isnotempty(UPNSuffix))
on AccountName, UPNSuffix
Stage 10: summarize
summarize Alerts, SystemAlerts by FirstEvent, UserId, ClientIp, InstanceUrl, AccountName, UPNSuffix
Stage 11: extend
extend CloudAppId
Stage 12: project
project AccountName, Alerts, ClientIp, CloudAppId, FirstEvent, InstanceUrl, SystemAlerts, UPNSuffix, UserId
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AccountName | is_not_null | field:"AccountName" kind:is_not_null | |
Entities | match |
| field:"Entities" kind:match |
Entities.Type | eq |
| field:"Entities.Type" kind:eq |
Message | eq |
| field:"Message" kind:eq value:"ExecutePowerBISql" |
UPNSuffix | is_not_null | field:"UPNSuffix" kind:is_not_null | |
UniqueUsers | eq |
| field:"UniqueUsers" kind:eq value:"1" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountName | project |
Alerts | project |
ClientIp | project |
CloudAppId | project |
FirstEvent | project |
InstanceUrl | project |
SystemAlerts | project |
UPNSuffix | project |
UserId | project |
Deletion of data on multiple drives using cipher exe
#This query checks for attempts to delete data on multiple drives using cipher.exe. This activity is typically done by ransomware to prevent recovery of data after encryption.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Impact |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Microsoft Defender for Endpoint | DeviceProcessEvents action any: Process activity |
Rule Body
id: 03caa992-477f-4b19-8e2a-8cd58f8f9652
name: Deletion of data on multiple drives using cipher exe
description: |
This query checks for attempts to delete data on multiple drives using cipher.exe. This activity is typically done by ransomware to prevent recovery of data after encryption.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: MicrosoftThreatProtection
dataTypes:
- DeviceProcessEvents
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Impact
relevantTechniques:
- T1485
query: |
// Look for cipher.exe deleting data from multiple drives
DeviceProcessEvents
| where FileName =~ "cipher.exe"
// cipher.exe /w flag used for deleting data
| where ProcessCommandLine has "/w"
| summarize CipherCount = dcount(ProcessCommandLine), CipherList = make_set(ProcessCommandLine, 1000) by DeviceId, DeviceName, bin(TimeGenerated, 1m)
// cipher.exe accessing multiple drives in a short timeframe
| where CipherCount > 1
| 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
Stage 1: source
DeviceProcessEvents
Stage 2: where
| where FileName =~ "cipher.exe"
Stage 3: where
| where ProcessCommandLine has "/w"
Stage 4: summarize
| summarize CipherCount = dcount(ProcessCommandLine), CipherList = make_set(ProcessCommandLine, 1000) by DeviceId, DeviceName, bin(TimeGenerated, 1m)
Stage 5: where
| where CipherCount > 1
Stage 6: extend
| extend HostName = iff(DeviceName has '.', substring(DeviceName, 0, indexof(DeviceName, '.')), DeviceName)
HostName =DeviceName has "."substring(DeviceName, 0, indexof(DeviceName, '.'))DeviceNameStage 7: 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 |
|---|---|---|---|
CipherCount | gt |
| field:"CipherCount" kind:gt value:"1" |
FileName | eq |
| field:"process_basename" kind:eq value:"cipher.exe" |
ProcessCommandLine | match |
| field:"CommandLine" kind:match value:"/w" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
CipherCount | summarize |
CipherList | summarize |
DeviceId | summarize |
DeviceName | summarize |
HostName | extend |
DnsDomain | extend |
GTI Relevance System Alert - Incident by Alert ID
#Creates a Microsoft Sentinel incident for each unique Google Threat Intelligence (GTI) Alert ID. Multiple ingestion records sharing the same Alert ID (e.g. updated snapshots of the same alert) are grouped into a single incident. The rule surfaces the most recent snapshot of each alert and maps severity from the GTI severity analysis field.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Reconnaissance | |
| Initial Access | |
| Credential Access | |
| Impact |
Rule Body
id: a1b2c3d4-e5f6-7890-abcd-ef1234567891
name: GTI Relevance System Alert - Incident by Alert ID
description: |
Creates a Microsoft Sentinel incident for each unique Google Threat Intelligence (GTI) Alert ID.
Multiple ingestion records sharing the same Alert ID (e.g. updated snapshots of the same alert) are
grouped into a single incident. The rule surfaces the most recent snapshot of each alert and maps
severity from the GTI severity analysis field.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: GoogleThreatIntelligenceRelevanceSystemAlertsAPI
dataTypes:
- RelevanceSystemAlerts_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
- Reconnaissance
- Impact
- CredentialAccess
relevantTechniques:
- T1566
- T1078
- T1552
- T1486
- T1595
query: |
GTIRelevanceSystemAlerts
| extend
SentinelSeverity = case(
SeverityLevel == "CRITICAL", "High",
SeverityLevel == "HIGH", "High",
SeverityLevel == "MEDIUM", "Medium",
SeverityLevel == "LOW", "Low",
"Informational"
),
DetailSeverity = coalesce(DataLeakSeverity, InsiderThreatSeverity, InitialAccessBrokerSeverity, "")
| project
TimeGenerated,
AlertId,
AlertName = DisplayName,
AlertState = State,
DetailType,
DetailSeverity,
SeverityLevel,
SentinelSeverity,
SeverityConfidence,
SeverityReasoning,
PriorityLevel,
PriorityReasoning,
RelevanceLevel,
RelevanceConfidence,
RelevanceReasoning,
FindingCount,
AiSummary,
AuditCreateTime,
AuditUpdateTime
alertDetailsOverride:
alertDisplayNameFormat: 'GTI Alert: {{AlertName}} [{{AlertState}}]'
alertDescriptionFormat: |
Alert ID: {{AlertId}}
Type: {{DetailType}}
Summary: {{AiSummary}}
alertSeverityColumnName: SentinelSeverity
customDetails:
AlertId: AlertId
AlertState: AlertState
DetailType: DetailType
DetailSeverity: DetailSeverity
PriorityLevel: PriorityLevel
RelevanceLevel: RelevanceLevel
FindingCount: FindingCount
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: P5D
matchingMethod: Selected
groupByCustomDetails:
- AlertId
eventGroupingSettings:
aggregationKind: AlertPerResult
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
GTIRelevanceSystemAlerts
Stage 2: extend
| extend
SentinelSeverity = case(
SeverityLevel == "CRITICAL", "High",
SeverityLevel == "HIGH", "High",
SeverityLevel == "MEDIUM", "Medium",
SeverityLevel == "LOW", "Low",
"Informational"
),
DetailSeverity = coalesce(DataLeakSeverity, InsiderThreatSeverity, InitialAccessBrokerSeverity, "")
SentinelSeverity =SeverityLevel == "CRITICAL""High"SeverityLevel == "HIGH""High"SeverityLevel == "MEDIUM""Medium"SeverityLevel == "LOW""Low""Informational"Stage 3: project
| project
TimeGenerated,
AlertId,
AlertName = DisplayName,
AlertState = State,
DetailType,
DetailSeverity,
SeverityLevel,
SentinelSeverity,
SeverityConfidence,
SeverityReasoning,
PriorityLevel,
PriorityReasoning,
RelevanceLevel,
RelevanceConfidence,
RelevanceReasoning,
FindingCount,
AiSummary,
AuditCreateTime,
AuditUpdateTime
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AiSummary | project |
AlertId | project |
AlertName | project |
AlertState | project |
AuditCreateTime | project |
AuditUpdateTime | project |
DetailSeverity | project |
DetailType | project |
FindingCount | project |
PriorityLevel | project |
PriorityReasoning | project |
RelevanceConfidence | project |
RelevanceLevel | project |
RelevanceReasoning | project |
SentinelSeverity | project |
SeverityConfidence | project |
SeverityLevel | project |
SeverityReasoning | project |
TimeGenerated | project |
Guardian- Regex Policy Violation Detection
#'This alert creates an incident when Regex Policy Violation detected from the Guardian.'
Rule Body
id: d9ad323f-6115-4f19-9e81-feabceeb6730
name: Guardian- Regex Policy Violation Detection
description: |
'This alert creates an incident when Regex Policy Violation detected from the Guardian.'
severity: Low
status: Available
requiredDataConnectors:
- connectorId: BoschAIShield
dataTypes:
- Guardian
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: |
Guardian
| where PolicyViolatedControlFeature =~ 'Regex'
| where Severity =~ 'Low'
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: HostName
- identifier: NTDomain
columnName: NTDomain
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIP
eventGroupingSettings:
aggregationKind: SingleAlert
alertDetailsOverride:
alertDisplayNameFormat: Guardian- Regex Policy Violation detection
alertDescriptionFormat: |
This query detects Regex Policy Violation detected from the Guardian generated at {{TimeGenerated}}.\n\nPlease check the source for more information and investigate further.
alertTacticsColumnName: null
alertSeverityColumnName: Severity
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
Guardian
Stage 2: where
| where PolicyViolatedControlFeature =~ 'Regex'
Stage 3: where
| where Severity =~ 'Low'
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
PolicyViolatedControlFeature | eq |
| field:"PolicyViolatedControlFeature" kind:eq value:"Regex" |
Severity | eq |
| field:"Severity" kind:eq value:"Low" |
Lateral Movement Risk - Role Chain Length
#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 policy detects chains of more than 3 roles in the account, this is a misconfiguration that can enable lateral movement.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| Privilege Escalation |
Rule Body
id: 25bef734-4399-4c55-9579-4ebabd9cccf6
name: Lateral Movement Risk - Role Chain Length
kind: Scheduled
description: The policy detects chains of more than 3 roles in the account, this is a misconfiguration that can enable lateral movement.
severity: Informational
status: Available
queryFrequency: 30m
queryPeriod: 30m
triggerOperator: gt
triggerThreshold: 0
tactics:
- PrivilegeEscalation
- Persistence
relevantTechniques:
- T1098
query: |-
Authomize_v2_CL
| where ingestion_time() >= ago(30m)
| extend EventID = id_s, Policy = policy_name_s, Severity = severity_s,Description = description_s,Recommendation = recommendation_s,URL = url_s,Tactics = tactics_s
| where Policy has "Chain of 3 or more roles"
| project EventID, Policy, Severity, Description, Recommendation, URL, Category, Tactics
entityMappings:
- entityType: URL
fieldMappings:
- identifier: Url
columnName: URL
requiredDataConnectors:
- connectorId: Authomize
dataTypes: [ "Authomize_v2_CL" ]
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 5h
matchingMethod: AnyAlert
groupByEntities: []
groupByAlertDetails: []
groupByCustomDetails: []
eventGroupingSettings:
aggregationKind: SingleAlert
alertDetailsOverride:
alertnameFormat: Alert from Authomize - Account can elevate privileges by assuming a role
alertDescriptionFormat: Account can elevate privileges by assuming a role. The policy detects chains of more than 3 roles in the account, this is a misconfiguration that can enable lateral movement.
alertSeverity: Severity
alertTactics: Tactics
alertDynamicProperties:
- alertProperty: AlertLink
value: URL
customDetails:
AuthomizeEventID: EventID
EventName: Policy
EventDescription: Description
EventRecommendation: Recommendation
ReferencedURL: URL
suppressionDuration: 5h
suppressionEnabled: false
version: 1.0.3
Stages and Predicates
Stage 1: source
Authomize_v2_CL
Stage 2: where
| where ingestion_time() >= ago(30m)
Stage 3: extend
| extend EventID = id_s, Policy = policy_name_s, Severity = severity_s,Description = description_s,Recommendation = recommendation_s,URL = url_s,Tactics = tactics_s
Stage 4: where
| where Policy has "Chain of 3 or more roles"
Stage 5: project
| project EventID, Policy, Severity, Description, Recommendation, URL, Category, Tactics
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Policy | match |
| field:"policy_name_s" kind:match value:"Chain of 3 or more roles" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Category | project |
Description | project |
EventID | project |
Policy | project |
Recommendation | project |
Severity | project |
Tactics | project |
URL | project |
Missing Domain Controller Heartbeat
#'This detection will go over the heartbeats received from the agents of Domain Controllers over the last hour, and will create alerts if the last heartbeats were received an hour ago.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Stealth | |
| Impact |
Rule Body
id: b8b8ba09-1e89-45a1-8bd7-691cd23bfa32
name: Missing Domain Controller Heartbeat
description: |
'This detection will go over the heartbeats received from the agents of Domain Controllers over the last hour, and will create alerts if the last heartbeats were received an hour ago.'
severity: High
requiredDataConnectors: []
queryFrequency: 15m
queryPeriod: 2h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Impact
- DefenseEvasion
relevantTechniques:
- T1499
- T1564
query: |
let query_frequency = 15m;
let missing_period = 1h;
//Enter a reference list of hostnames for your DC servers
let DCServersList = dynamic (["DC01.simulandlabs.com","DC02.simulandlabs.com"]);
//Alternatively, a Watchlist can be used
//let DCServersList = _GetWatchlist('HostName-DomainControllers') | project HostName;
Heartbeat
| summarize arg_max(TimeGenerated, *) by Computer
| where Computer in (DCServersList)
//You may specify the OS type of your Domain Controllers
//| where OSType == 'Windows'
| where TimeGenerated between (ago(query_frequency + missing_period) .. ago(missing_period))
| project TimeGenerated, Computer, OSType, Version, ComputerEnvironment, Type, Solutions
| sort by TimeGenerated asc
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: Computer
version: 1.0.5
kind: Scheduled
metadata:
source:
kind: Community
author:
name: Jose Sebastian Canos
support:
tier: Community
providers: Microsoft
categories:
domains: [ "Security - Others" ]
Stages and Predicates
Parameters
let query_frequency = 15m;
let missing_period = 1h;
let DCServersList = dynamic (["DC01.simulandlabs.com","DC02.simulandlabs.com"]);
Stage 1: source
Heartbeat
Stage 2: summarize
| summarize arg_max(TimeGenerated, *) by Computer
Stage 3: where
| where Computer in (DCServersList)
Stage 4: where
| where TimeGenerated between (ago(query_frequency + missing_period) .. ago(missing_period))
Stage 5: project
| project TimeGenerated, Computer, OSType, Version, ComputerEnvironment, Type, Solutions
Stage 6: sort
| sort by TimeGenerated asc
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Computer | in |
| field:"Computer" kind:in |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Computer | project |
ComputerEnvironment | project |
OSType | project |
Solutions | project |
TimeGenerated | project |
Type | project |
Version | project |
Objects Added to Malware Detection Exclusions
#Detects when an object is added to malware detection exclusions.
Rule Body
id: a8ebf22b-a050-434c-8095-2267f206257a
name: Objects Added to Malware Detection Exclusions
description: Detects when an object is added to malware detection exclusions.
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 == 42260\n| extend AddedObjectNames\
\ = extract(\"AddedObjectNames=\\\"([^\\\"]*)\\\"\", 1, SyslogMessage)\n| project\n\
\ Date = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),\n DataSource\
\ = original_host,\n EventId = instanceId,\n [\"Object Names\"] = AddedObjectNames,\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 == 42260
Stage 3: extend
| extend AddedObjectNames = extract("AddedObjectNames=\"([^\"]*)\"", 1, SyslogMessage)
Stage 4: project
| project
Date = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),
DataSource = original_host,
EventId = instanceId,
["Object Names"] = AddedObjectNames,
MessageDetails = Description,
Severity = SeverityDescription
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
instanceId | eq |
| field:"instanceId" kind:eq value:"42260" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DataSource | project |
Date | project |
EventId | project |
MessageDetails | project |
Object Names | project |
Severity | project |
Palo Alto Threat signatures from Unusual IP addresses
#'Identifies Palo Alto Threat signatures from unusual IP addresses which are not historically seen. This detection is also leveraged and required for MDE and PAN Fusion scenario https://docs.microsoft.com/Azure/sentinel/fusion-scenario-reference#network-request-to-tor-anonymization-service-followed-by-anomalous-traffic-flagged-by-palo-alto-networks-firewall'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Discovery | |
| Command & Control | |
| Exfiltration |
Rule Body
id: 89a86f70-615f-4a79-9621-6f68c50f365f
name: Palo Alto Threat signatures from Unusual IP addresses
description: |
'Identifies Palo Alto Threat signatures from unusual IP addresses which are not historically seen.
This detection is also leveraged and required for MDE and PAN Fusion scenario
https://docs.microsoft.com/Azure/sentinel/fusion-scenario-reference#network-request-to-tor-anonymization-service-followed-by-anomalous-traffic-flagged-by-palo-alto-networks-firewall'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: CefAma
dataTypes:
- CommonSecurityLog
queryFrequency: 1h
queryPeriod: 7d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Discovery
- Exfiltration
- CommandAndControl
relevantTechniques:
- T1046
- T1030
- T1071.001
tags:
- Fusion
query: |
let starttime = 7d;
let endtime = 1d;
let timeframe = 1h;
let HistThreshold = 25;
let CurrThreshold = 10;
let HistoricalThreats = CommonSecurityLog
| where isnotempty(SourceIP)
| where TimeGenerated between (startofday(ago(starttime))..startofday(ago(endtime)))
| where DeviceVendor =~ "Palo Alto Networks"
| where Activity =~ "THREAT" and SimplifiedDeviceAction =~ "alert"
| where DeviceEventClassID in ('spyware', 'scan', 'file', 'vulnerability', 'flood', 'packet', 'virus','wildfire', 'wildfire-virus')
| summarize TotalEvents = count(), ThreatTypes = make_set(DeviceEventClassID), DestinationIpList = make_set(DestinationIP), FirstSeen = min(TimeGenerated) , LastSeen = max(TimeGenerated) by SourceIP, DeviceAction, DeviceVendor;
let CurrentHourThreats = CommonSecurityLog
| where isnotempty(SourceIP)
| where TimeGenerated > ago(timeframe)
| where DeviceVendor =~ "Palo Alto Networks"
| where Activity =~ "THREAT" and SimplifiedDeviceAction =~ "alert"
| where DeviceEventClassID in ('spyware', 'scan', 'file', 'vulnerability', 'flood', 'packet', 'virus','wildfire', 'wildfire-virus')
| summarize TotalEvents = count(), ThreatTypes = make_set(DeviceEventClassID), DestinationIpList = make_set(DestinationIP), FirstSeen = min(TimeGenerated) , LastSeen = max(TimeGenerated) by SourceIP, DeviceAction, DeviceProduct, DeviceVendor;
CurrentHourThreats
| where TotalEvents < CurrThreshold
| join kind = leftanti (HistoricalThreats
| where TotalEvents > HistThreshold) on SourceIP
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIP
version: 1.0.3
kind: Scheduled
Stages and Predicates
Parameters
let starttime = 7d;
let endtime = 1d;
let timeframe = 1h;
let HistThreshold = 25;
let CurrThreshold = 10;
let CurrentHourThreats is inlined into the numbered stages below.
Let Binding: HistoricalThreats
let HistoricalThreats = CommonSecurityLog
| where isnotempty(SourceIP)
| where TimeGenerated between (startofday(ago(starttime))..startofday(ago(endtime)))
| where DeviceVendor =~ "Palo Alto Networks"
| where Activity =~ "THREAT" and SimplifiedDeviceAction =~ "alert"
| where DeviceEventClassID in ('spyware', 'scan', 'file', 'vulnerability', 'flood', 'packet', 'virus','wildfire', 'wildfire-virus')
| summarize TotalEvents = count(), ThreatTypes = make_set(DeviceEventClassID), DestinationIpList = make_set(DestinationIP), FirstSeen = min(TimeGenerated) , LastSeen = max(TimeGenerated) by SourceIP, DeviceAction, DeviceVendor;
Stages 1 to 7 define let CurrentHourThreats as the rule's main pipeline source. Stages 8 to 9 use it.
Stage 1: source
CommonSecurityLog
Stage 2: where
| where isnotempty(SourceIP)
Stage 3: where
| where TimeGenerated > ago(timeframe)
Stage 4: where
| where DeviceVendor =~ "Palo Alto Networks"
Stage 5: where
| where Activity =~ "THREAT" and SimplifiedDeviceAction =~ "alert"
Stage 6: where
| where DeviceEventClassID in ('spyware', 'scan', 'file', 'vulnerability', 'flood', 'packet', 'virus','wildfire', 'wildfire-virus')
Stage 7: summarize
| summarize TotalEvents = count(), ThreatTypes = make_set(DeviceEventClassID), DestinationIpList = make_set(DestinationIP), FirstSeen = min(TimeGenerated) , LastSeen = max(TimeGenerated) by SourceIP, DeviceAction, DeviceProduct, DeviceVendor
Stage 8: where
CurrentHourThreats
| where TotalEvents < CurrThreshold
Stage 9: join (negated)
join kind=leftanti (HistoricalThreats) on SourceIP
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Activity | eq |
| field:"Activity" kind:eq value:"THREAT" |
DeviceEventClassID | in |
| field:"DeviceEventClassID" kind:in |
DeviceVendor | eq |
| field:"DeviceVendor" kind:eq value:"Palo Alto Networks" |
SimplifiedDeviceAction | eq |
| field:"SimplifiedDeviceAction" kind:eq value:"alert" |
SourceIP | is_not_null | field:"SourceIP" kind:is_not_null | |
TotalEvents | lt |
| field:"TotalEvents" kind:lt value:"10" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
Activity | eq | THREAT | excludes:Activity field:"Activity" value:"THREAT" |
DeviceEventClassID | in | file, flood, packet, scan, spyware, virus, vulnerability, wildfire, wildfire-virus | excludes:DeviceEventClassID |
DeviceVendor | eq | Palo Alto Networks | excludes:DeviceVendor field:"DeviceVendor" value:"Palo Alto Networks" |
SimplifiedDeviceAction | eq | alert | excludes:SimplifiedDeviceAction field:"SimplifiedDeviceAction" value:"alert" |
SourceIP | is_not_null | excludes:SourceIP | |
TotalEvents | gt | 25 | excludes:TotalEvents field:"TotalEvents" value:"25" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DestinationIpList | summarize |
DeviceAction | summarize |
DeviceProduct | summarize |
DeviceVendor | summarize |
FirstSeen | summarize |
LastSeen | summarize |
SourceIP | summarize |
ThreatTypes | summarize |
TotalEvents | summarize |
Potential Build Process Compromise
#The query looks for source code files being modified immediately after a build process is started. The purpose of this is to look for malicious code injection during the build process. More details: https://techcommunity.microsoft.com/t5/azure-sentinel/monitoring-the-software-supply-chain-with-azure-sentinel/ba-p/2176463
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence |
Telemetry Coverage
Rule Body
id: 5ef06767-b37c-4818-b035-47de950d0046
name: Potential Build Process Compromise
description: |
'The query looks for source code files being modified immediately after a build process is started. The purpose of this is to look for malicious code injection during the build process.
More details: https://techcommunity.microsoft.com/t5/azure-sentinel/monitoring-the-software-supply-chain-with-azure-sentinel/ba-p/2176463'
severity: Medium
requiredDataConnectors:
- connectorId: SecurityEvents
dataTypes:
- SecurityEvent
- connectorId: WindowsSecurityEvents
dataTypes:
- SecurityEvent
- connectorId: WindowsSecurityEvents
dataTypes:
- SecurityEvents
- connectorId: WindowsForwardedEvents
dataTypes:
- WindowsEvent
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
relevantTechniques:
- T1554
tags:
- Solorigate
- NOBELIUM
query: |
// How far back to look for events from
let timeframe = 1d;
// How close together build events and file modifications should occur to alert (make this smaller to reduce FPs)
let time_window = 5m;
// Edit this to include build processes used
let build_processes = dynamic(["MSBuild.exe", "dotnet.exe", "VBCSCompiler.exe"]);
// Include any processes that you want to allow to edit files during/around the build process
let allow_list = dynamic([""]);
(union isfuzzy=true
(SecurityEvent
| where TimeGenerated > ago(timeframe)
// Look for build process starts
| where EventID == 4688
| where Process has_any (build_processes)
| summarize by BuildParentProcess=ParentProcessName, BuildProcess=Process, BuildAccount = Account, Computer, BuildCommand=CommandLine, timekey= bin(TimeGenerated, time_window), BuildProcessTime=TimeGenerated
| join kind=inner(
SecurityEvent
| where TimeGenerated > ago(timeframe)
// Look for file modifications to code file
| where EventID == 4663
| where Process !in (allow_list)
// Look for code files, edit this to include file extensions used in build.
| where ObjectName endswith ".cs" or ObjectName endswith ".cpp"
// 0x6 and 0x4 for file append, 0x100 for file replacements
| where AccessMask == "0x6" or AccessMask == "0x4" or AccessMask == "0X100"
| summarize by FileEditParentProcess=ParentProcessName, FileEditAccount = Account, Computer, FileEdited=ObjectName, FileEditProcess=ProcessName, timekey= bin(TimeGenerated, time_window), FileEditTime=TimeGenerated)
// join where build processes and file modifications seen at same time on same host
on timekey, Computer
// Limit to only where the file edit happens after the build process starts
| where BuildProcessTime <= FileEditTime
| summarize make_set(FileEdited), make_set(FileEditProcess), make_set(FileEditAccount) by timekey, Computer, BuildParentProcess, BuildProcess
),
(WindowsEvent
| where TimeGenerated > ago(timeframe)
// Look for build process starts
| where EventID == 4688 and EventData has_any (build_processes)
| extend NewProcessName = tostring(EventData.NewProcessName)
| extend Process=tostring(split(NewProcessName, '\\')[-1])
| where Process has_any (build_processes)
| extend ParentProcessName = tostring(EventData.ParentProcessName)
| extend Account = strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
| extend CommandLine = tostring(EventData.CommandLine)
| summarize by BuildParentProcess=ParentProcessName, BuildProcess=Process, BuildAccount = Account, Computer, BuildCommand=CommandLine, timekey= bin(TimeGenerated, time_window), BuildProcessTime=TimeGenerated
| join kind=inner(
WindowsEvent
| where TimeGenerated > ago(timeframe)
// Look for file modifications to code file
| where EventID == 4663 and EventData has_any ("0x6", "0x4", "0X100") and EventData has_any (".cs", ".cpp")
| extend NewProcessName = tostring(EventData.NewProcessName)
| extend Process=tostring(split(NewProcessName, '\\')[-1])
| where Process !in (allow_list)
// Look for code files, edit this to include file extensions used in build.
| extend ObjectName = tostring(EventData.ObjectName)
| where ObjectName endswith ".cs" or ObjectName endswith ".cpp"
// 0x6 and 0x4 for file append, 0x100 for file replacements
| extend AccessMask = tostring(EventData.AccessMask)
| where AccessMask == "0x6" or AccessMask == "0x4" or AccessMask == "0X100"
| extend ParentProcessName = tostring(EventData.ParentProcessName)
| extend Account = strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
| extend ProcessName = tostring(EventData.ProcessName)
| summarize by FileEditParentProcess=ParentProcessName, FileEditAccount = Account, Computer, FileEdited=ObjectName, FileEditProcess=ProcessName, timekey= bin(TimeGenerated, time_window), FileEditTime=TimeGenerated)
// join where build processes and file modifications seen at same time on same host
on timekey, Computer
// Limit to only where the file edit happens after the build process starts
| where BuildProcessTime <= FileEditTime
| summarize make_set(FileEdited), make_set(FileEditProcess), make_set(FileEditAccount) by timekey, Computer, BuildParentProcess, BuildProcess
))
| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
| project-away DomainIndex
entityMappings:
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: Computer
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: HostNameDomain
version: 1.1.4
kind: Scheduled
metadata:
source:
kind: Community
author:
name: Microsoft Security Research
support:
tier: Community
categories:
domains: [ "Security - Others" ]
Stages and Predicates
Parameters
let timeframe = 1d;
let time_window = 5m;
let build_processes = dynamic(["MSBuild.exe", "dotnet.exe", "VBCSCompiler.exe"]);
let allow_list = dynamic([""]);
Union isfuzzy=true (2 Sources)
Each leg below queries one source; the rule matches if any leg does. Sources: SecurityEvent, WindowsEvent
Leg 1: SecurityEvent
SecurityEvent
| where TimeGenerated > ago(timeframe)
| where EventID == 4688
| where Process has_any (build_processes)
| summarize by BuildParentProcess=ParentProcessName, BuildProcess=Process, BuildAccount = Account, Computer, BuildCommand=CommandLine, timekey= bin(TimeGenerated, time_window), BuildProcessTime=TimeGenerated
| join kind=inner(
SecurityEvent
| where TimeGenerated > ago(timeframe)
| where EventID == 4663
| where Process !in (allow_list)
| where ObjectName endswith ".cs" or ObjectName endswith ".cpp"
| where AccessMask == "0x6" or AccessMask == "0x4" or AccessMask == "0X100"
| summarize by FileEditParentProcess=ParentProcessName, FileEditAccount = Account, Computer, FileEdited=ObjectName, FileEditProcess=ProcessName, timekey= bin(TimeGenerated, time_window), FileEditTime=TimeGenerated)
on timekey, Computer
| where BuildProcessTime <= FileEditTime
| summarize make_set(FileEdited), make_set(FileEditProcess), make_set(FileEditAccount) by timekey, Computer, BuildParentProcess, BuildProcess
Leg 2: WindowsEvent
WindowsEvent
| where TimeGenerated > ago(timeframe)
| where EventID == 4688 and EventData has_any (build_processes)
| extend NewProcessName = tostring(EventData.NewProcessName)
| extend Process=tostring(split(NewProcessName, '\\')[-1])
| where Process has_any (build_processes)
| extend ParentProcessName = tostring(EventData.ParentProcessName)
| extend Account = strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
| extend CommandLine = tostring(EventData.CommandLine)
| summarize by BuildParentProcess=ParentProcessName, BuildProcess=Process, BuildAccount = Account, Computer, BuildCommand=CommandLine, timekey= bin(TimeGenerated, time_window), BuildProcessTime=TimeGenerated
| join kind=inner(
WindowsEvent
| where TimeGenerated > ago(timeframe)
| where EventID == 4663 and EventData has_any ("0x6", "0x4", "0X100") and EventData has_any (".cs", ".cpp")
| extend NewProcessName = tostring(EventData.NewProcessName)
| extend Process=tostring(split(NewProcessName, '\\')[-1])
| where Process !in (allow_list)
| extend ObjectName = tostring(EventData.ObjectName)
| where ObjectName endswith ".cs" or ObjectName endswith ".cpp"
| extend AccessMask = tostring(EventData.AccessMask)
| where AccessMask == "0x6" or AccessMask == "0x4" or AccessMask == "0X100"
| extend ParentProcessName = tostring(EventData.ParentProcessName)
| extend Account = strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
| extend ProcessName = tostring(EventData.ProcessName)
| summarize by FileEditParentProcess=ParentProcessName, FileEditAccount = Account, Computer, FileEdited=ObjectName, FileEditProcess=ProcessName, timekey= bin(TimeGenerated, time_window), FileEditTime=TimeGenerated)
on timekey, Computer
| where BuildProcessTime <= FileEditTime
| summarize make_set(FileEdited), make_set(FileEditProcess), make_set(FileEditAccount) by timekey, Computer, BuildParentProcess, BuildProcess
Applied to the Combined Result
| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
| project-away DomainIndex
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AccessMask | eq |
| field:"AccessMask" kind:eq |
EventData | match |
| field:"EventData" kind:match |
EventID | eq |
| field:"EventID" kind:eq |
ObjectName | ends_with |
| field:"ObjectName" kind:ends_with |
Process | match |
| field:"Process" kind:match |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
Process | eq | allow_list | excludes:Process field:"Process" value:"allow_list" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Computer | summarize |
FileEditAccount | summarize |
FileEditParentProcess | summarize |
FileEditProcess | summarize |
FileEditTime | summarize |
FileEdited | summarize |
timekey | summarize |
Potentially Relayed NTLM Authentication - Microsoft Sentinel
#The below query detects NTLM logons where Network Address in the logon event doesn't match the Workstation Name's IP. This indicates potentially relayed NTLM authentication. It analyzes only the logons with domain accounts having admin privileges.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access | No specific technique |
Telemetry Coverage
Rule Body
// Author : Cyb3rMonk(https://twitter.com/Cyb3rMonk, https://mergene.medium.com)
//
// Link to original post:
// https://posts.bluraven.io/detecting-ntlm-relay-attacks-d92e99e68fb9
//
// Description: This query detects NTLM logons where Network Address in the NTLM logon event doesn't match the Workstation Name's IP.
// This indicates potentially relayed NTLM authentication. The query analyzes only the logons with domain accounts having admin privileges.
//
// Query parameters:
//
let ingestion_delay = 2h;
let rule_frequency = 2h;
let lookback = 1d;
// Specify domains in NETBIOS name and full domain format
let domains = dynamic(["PUT YOUR AD DOMAINS HERE!", "contoso","contoso.local"]);
// Exclude authentications coming from device performing SNAT.
// Exclude devices that always perform NTLM authentication
let SNAT_Subnets = datatable (subnet: string) [
"1.0.0.0/26", "1.1.1.1/32"
];
// Get NTLM relay candidates
let NTLMRelayCandidates = materialize (
SecurityEvent
| where TimeGenerated > ago(rule_frequency + ingestion_delay)
| where EventID == 4624
| where AccountType == "User"
| where AuthenticationPackageName == "NTLM"
| where LogonType == 3
| where TargetDomainName in~ (domains)
| where isnotempty(IpAddress) and IpAddress !in ('-', '::1', '127.0.0.1')
| where isnotempty(WorkstationName) and WorkstationName <> '-'
| where IpPort <> 0 and Computer !has WorkstationName
| where ElevatedToken <> '%%1843'// exclude non-admin logon sessions
| extend delay = ingestion_time() - TimeGenerated
| summarize hint.strategy=shuffle arg_max(TimeGenerated, *) by Computer, Account, IpAddress, WorkstationName
// Machine logon events have the IP address of the machine, exclude results where the IPAddress in the NTLM logon matches the IPAddress in Machine logon event
| join hint.strategy=shuffle kind=leftanti
(
SecurityEvent
| where TimeGenerated > ago(lookback)
| where EventID == 4624
| where AccountType == "Machine"
| where isnotempty(IpAddress) and IpAddress !in ('-', '::1', '127.0.0.1')
| distinct TargetUserName, IpAddress
| extend TargetUserName = toupper(replace(@'([A-z0-9-]+)\.?.*', @'\1', TargetUserName))
)
on $left.WorkstationName == $right.TargetUserName, IpAddress // filter condition
// Filter out excluded IP subnets.
| evaluate ipv4_lookup(SNAT_Subnets, IpAddress, subnet, return_unmatched = true)
| where isempty(subnet) // remove results that matched a SNAT subnet.
)
;
// Windows 2012 doesn't have elevated token info in NTLM logon events.
// Filter relayed authentications where the session has admin privileges
let Computers=
NTLMRelayCandidates| summarize make_set(Computer);
//
let Accounts =
NTLMRelayCandidates| summarize make_set(TargetUserName);
// There must be a 4672 event for an admin logon with the same logon id
NTLMRelayCandidates
| join hint.strategy=shuffle kind=inner
(
SecurityEvent
| where TimeGenerated > ago(rule_frequency + ingestion_delay)
| where Computer in (Computers)
| where SubjectUserName in (Accounts)
| where EventID == 4672
| where AccountType == "User"
| project Computer, Account, SubjectLogonId, PrivilegeList
)
on $left.TargetLogonId==$right.SubjectLogonId, Account, Computer
| extend Origin = WorkstationName, RelayingDeviceIP = IpAddress, Target = Computer
| project-reorder TimeGenerated, Computer, Origin, RelayingDeviceIP, Target, Account, PrivilegeList1
// more filtering can be done based on the privilege list, specific computers or accounts.
Stages and Predicates
Parameters
let ingestion_delay = 2h;
let rule_frequency = 2h;
let lookback = 1d;
let domains = dynamic(["PUT YOUR AD DOMAINS HERE!", "contoso","contoso.local"]);
let NTLMRelayCandidates is inlined into the numbered stages below.
Let Binding: SNAT_Subnets
let SNAT_Subnets = datatable (subnet: string) [
"1.0.0.0/26", "1.1.1.1/32"
];
Let Binding: Computers
let Computers = NTLMRelayCandidates| summarize make_set(Computer);
Let Binding: Accounts
let Accounts = NTLMRelayCandidates| summarize make_set(TargetUserName);
Stages 1 to 19 define let NTLMRelayCandidates as the rule's main pipeline source. Stages 20 to 22 use it.
Stage 1: source
let NTLMRelayCandidates
Stage 2: source
let Computers
Stage 3: source
let Accounts
Stage 4: source
SecurityEvent
Stage 5: where
where TimeGenerated > ago(14400s)
Stage 6: where
where EventID == 4624
Stage 7: where
where AccountType == "User"
Stage 8: where
where AuthenticationPackageName == "NTLM"
Stage 9: where
where LogonType == 3
Stage 10: where
where TargetDomainName in~ ("PUT YOUR AD DOMAINS HERE!", "contoso", "contoso.local")
Stage 11: where
where not (IpAddress in ("-", "127.0.0.1", "::1")) and isnotempty(IpAddress)
Stage 12: where
where isnotempty(WorkstationName) and WorkstationName != "-"
Stage 13: where
where Computer =~ WorkstationName and IpPort != 0
Stage 14: where
where ElevatedToken != "%%1843"
Stage 15: extend
extend delay
Stage 16: summarize
summarize by Computer, Account, IpAddress, WorkstationName
Stage 17: join (negated)
join kind=leftanti (SecurityEvent) on $left.WorkstationName, $right.TargetUserName, IpAddress
Stage 18: evaluate
evaluate
Stage 19: where
where isempty(subnet)
Stage 20: join
NTLMRelayCandidates
| join hint.strategy=shuffle kind=inner
(
SecurityEvent
| where TimeGenerated > ago(rule_frequency + ingestion_delay)
| where Computer in (Computers)
| where SubjectUserName in (Accounts)
| where EventID == 4672
| where AccountType == "User"
| project Computer, Account, SubjectLogonId, PrivilegeList
)
on $left.TargetLogonId==$right.SubjectLogonId, Account, Computer
Stage 21: extend
extend Origin, RelayingDeviceIP, Target
Stage 22: project-reorder
project-reorder
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AccountType | eq |
| field:"AccountType" kind:eq value:"User" |
AuthenticationPackageName | eq |
| field:"AuthenticationPackageName" kind:eq value:"NTLM" |
Computer | in |
| field:"Computer" kind:in value:"Computers" |
ElevatedToken | ne |
| field:"ElevatedToken" kind:ne value:"%%1843" |
EventID | eq |
| field:"EventID" kind:eq |
IpAddress | in |
| field:"IpAddress" kind:in |
IpAddress | is_not_null | field:"IpAddress" kind:is_not_null | |
IpPort | ne |
| field:"IpPort" kind:ne value:"0" |
LogonType | eq |
| field:"LogonType" kind:eq value:"3" |
SubjectUserName | in |
| field:"SubjectUserName" kind:in value:"Accounts" |
TargetDomainName | in |
| field:"TargetDomainName" kind:in |
WorkstationName | is_not_null | field:"WorkstationName" kind:is_not_null | |
WorkstationName | ne |
| field:"WorkstationName" kind:ne value:"-" |
subnet | is_null | field:"subnet" kind:is_null |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
IpAddress | in | -, 127.0.0.1, ::1 | excludes:IpAddress field:"IpAddress" value:"-" field:"IpAddress" value:"127.0.0.1" field:"IpAddress" value:"::1" |
AccountType | eq | Machine | excludes:AccountType field:"AccountType" value:"Machine" |
EventID | eq | 4624 | excludes:EventID field:"EventID" value:"4624" |
IpAddress | is_not_null | excludes:IpAddress |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Account | summarize |
Computer | summarize |
IpAddress | summarize |
WorkstationName | summarize |
Origin | extend |
RelayingDeviceIP | extend |
Target | extend |
Progress MOVEIt File transfer folder count above threshold
#Identifies Progress MOVEIt File Transfers with distinct folder count above certain threshold in a 15min time period. Please note that entity mapping for arrays is not supported, so when there is a single value in an array, we will pull that value from the array as a single string to populate the entity to support entity mapping features within Sentinel. Additionally, if the array is multivalued, we will input a string to indicate this with a unique hash so that matching will not occur.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Exfiltration |
Telemetry Coverage
| Provider | Record / event type |
|---|---|
| MOVEit-DMZ-Audit | Event ID 0: MOVEit file-transfer audit record |
Rule Body
id: 26a993ca-0a96-45a0-8405-05a210fb98f8
name: Progress MOVEIt File transfer folder count above threshold
description: |
'Identifies Progress MOVEIt File Transfers with distinct folder count above certain threshold in a 15min time period.
Please note that entity mapping for arrays is not supported, so when there is a single value in an array, we will pull that value from the array as a single string to populate the entity to support entity mapping features within Sentinel. Additionally, if the array is multivalued, we will input a string to indicate this with a unique hash so that matching will not occur.'
severity: Medium
requiredDataConnectors:
- connectorId: WindowsForwardedEvents
dataTypes:
- WindowsEvent
queryFrequency: 15m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
tactics:
- Exfiltration
relevantTechniques:
- T1020
query: |
let threshold = 500;
Event
| where EventID == 0 and Source == "MOVEit DMZ Audit" and EventLog == "Application"
| where RenderedDescription has_any ("Downloaded", "Uploaded")
| parse RenderedDescription with * "User '" userdisplayname "' (" *
| parse RenderedDescription with * "IPAddress: " ipaddress " FileID:" * " FileName: " filename " FolderID: " folderid " FolderPath: " folderpath " Username: " username " AgentBrand: " *
| summarize count_distinct_folderpath=dcount(folderpath), dirlist=make_set(folderpath) by Computer,userdisplayname,username,ipaddress,bin(TimeGenerated, 15m)
| where count_distinct_folderpath >= threshold
| extend DirSample = iff(array_length(dirlist) == 1, tostring(dirlist[0]), strcat("SeeDirListField","_", tostring(hash(tostring(dirlist)))))
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: userdisplayname
- entityType: IP
fieldMappings:
- identifier: Address
columnName: ipaddress
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: Computer
- entityType: File
fieldMappings:
- identifier: Name
columnName: DirSample
customDetails:
TransferCount: count_distinct_folderpath
FilesList: dirlist
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 5h
matchingMethod: Selected
groupByEntities:
- Account
- Host
version: 1.0.2
kind: Scheduled
Stages and Predicates
Parameters
let threshold = 500;
Stage 1: source
Event
Stage 2: where
| where EventID == 0 and Source == "MOVEit DMZ Audit" and EventLog == "Application"
Stage 3: where
| where RenderedDescription has_any ("Downloaded", "Uploaded")
Stage 4: parse
| parse RenderedDescription with * "User '" userdisplayname "' (" *
Stage 5: parse
| parse RenderedDescription with * "IPAddress: " ipaddress " FileID:" * " FileName: " filename " FolderID: " folderid " FolderPath: " folderpath " Username: " username " AgentBrand: " *
Stage 6: summarize
| summarize count_distinct_folderpath=dcount(folderpath), dirlist=make_set(folderpath) by Computer,userdisplayname,username,ipaddress,bin(TimeGenerated, 15m)
Stage 7: where
| where count_distinct_folderpath >= threshold
Stage 8: extend
| extend DirSample = iff(array_length(dirlist) == 1, tostring(dirlist[0]), strcat("SeeDirListField","_", tostring(hash(tostring(dirlist)))))
DirSample =dirlist == 1tostring(dirlist[0])strcat("SeeDirListField", "_", tostring(hash(tostring(dirlist))))Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventID | eq |
| field:"EventID" kind:eq value:"0" |
EventLog | eq |
| field:"EventLog" kind:eq value:"Application" |
RenderedDescription | match |
| field:"RenderedDescription" kind:match |
count_distinct_folderpath | ge |
| field:"count_distinct_folderpath" kind:ge value:"500" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Computer | summarize |
count_distinct_folderpath | summarize |
dirlist | summarize |
ipaddress | summarize |
userdisplayname | summarize |
username | summarize |
DirSample | extend |
Radiflow - Policy Violation Detected
#'Generates an incident when an unauthorized session or action is detected either by Radiflow's iSID.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Execution | |
| Persistence | |
| Evasion | |
| Lateral Movement | |
| Collection | |
| Impair Process Control |
Rule Body
id: a3f4cc3e-2403-4570-8d21-1dedd5632958
name: Radiflow - Policy Violation Detected
description: |
'Generates an incident when an unauthorized session or action is detected either by Radiflow's iSID.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: RadiflowIsid
dataTypes:
- RadiflowEvent
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- LateralMovement
- ImpairProcessControl
- Execution
- Collection
- Persistence
relevantTechniques:
- T0886
- T0855
- T0858
- T0845
- T0889
- T0843
query: |
RadiflowEvent
| where DeviceProduct =~ 'iSID'
| where EventClassID in (5, 156, 161, 169, 171, 182, 183, 184, 185)
| extend EventMessage = iif(EventMessage == ' ', 'Policy Violation', EventMessage)
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 1h
matchingMethod: AllEntities
groupByEntities: []
groupByAlertDetails: []
groupByCustomDetails: []
eventGroupingSettings:
aggregationKind: AlertPerResult
alertDetailsOverride:
alertDisplayNameFormat: Policy Violation Detected
alertDescriptionFormat: Recent activity which violates the security policy has been detected in the network. Please check the details for additional information.
alertSeverityColumnName: EventSeverity
alertDynamicProperties: []
suppressionEnabled: false
suppressionDuration: 5h
customDetails:
SourceMAC: SourceMACAddress
SourceType: SourceType
SourceVendor: SourceVendor
DestinationMAC: DestinationMACAddress
DestinationType: DestinationType
DestinationVendor: DestinationVendor
Port: Port
Protocol: Protocol
SourceVLAN: SourceVLAN
SourceHostName: SourceHostName
SourceIP: SourceIP
DestinationHostName: DestinationHostName
DestinationIP: DestinationIP
entityMappings:
- entityType: Host
fieldMappings:
- identifier: NetBiosName
columnName: SourceHostName
- identifier: HostName
columnName: SourceHostName
- entityType: Host
fieldMappings:
- identifier: NetBiosName
columnName: DestinationHostName
- identifier: HostName
columnName: DestinationHostName
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIP
- entityType: IP
fieldMappings:
- identifier: Address
columnName: DestinationIP
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
RadiflowEvent
Stage 2: where
| where DeviceProduct =~ 'iSID'
Stage 3: where
| where EventClassID in (5, 156, 161, 169, 171, 182, 183, 184, 185)
Stage 4: extend
| extend EventMessage = iif(EventMessage == ' ', 'Policy Violation', EventMessage)
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DeviceProduct | eq |
| field:"DeviceProduct" kind:eq value:"iSID" |
EventClassID | in |
| field:"EventClassID" kind:in |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
EventMessage | extend |
Shadow Copy Deletions
#This rule detects when Shadow Copies are being deleted. This is a know actions that is performed by threat actors. This query detects know commands that have been used by the ransomware actors. Some information from Mitre Attack: https://attack.mitre.org/techniques/T1490/
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Impact |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Microsoft Defender for Endpoint | DeviceProcessEvents action any: Process activity |
Rule Body
id: 28c63a44-2d35-48b7-831b-3ed24af17c7e
name: Shadow Copy Deletions
description: |
This rule detects when Shadow Copies are being deleted. This is a know actions that is performed by threat actors.
This query detects know commands that have been used by the ransomware actors.
Some information from Mitre Attack:
https://attack.mitre.org/techniques/T1490/
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: MicrosoftThreatProtection
dataTypes:
- DeviceProcessEvents
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Impact
relevantTechniques:
- T1490
query: |
let CommonRansomwareExecutionCommands = dynamic([@'vssadmin.exe delete shadows /all /quiet',
@'wmic.exe shadowcopy delete', @'wbadmin delete catalog -quiet',
@'Get-WmiObject Win32_Shadowcopy | ForEach-Object {$_.Delete();}',
@'del /s /f /q c:\*.VHD c:\*.bac c:\*.bak c:\*.wbcat c:\*.bkf c:\Backup*.* c:\backup*.* c:\*.set c:\*.win c:\*.dsk',
@'wbadmin delete systemstatebackup -keepVersions:0',
@'schtasks.exe /Change /TN "\Microsoft\Windows\SystemRestore\SR" /disable',
@'schtasks.exe /Change /TN "\Microsoft\Windows\SystemRestore\SR" /enable >nul 2>&1',
@'reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\SystemRestore" /v "DisableConfig" /t "REG_DWORD" /d "1" /f',
@'reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\SystemRestore" /v "DisableSR" /t "REG_DWORD" /d "1" /f',
@'reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SystemRestore" /v "DisableConfig" /t "REG_DWORD" /d "1" /f',
@'reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SystemRestore" /v "DisableSR" /t "REG_DWORD" /d "1" /f',
@'reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\SystemRestore" /v "DisableConfig" /f >nul 2>&1',
@'reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\SystemRestore" /v "DisableSR" /f >nul 2>&1',
@'reg delete "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SystemRestore" /v "DisableConfig" /f >nul 2>&1',
@'reg delete "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SystemRestore" /v "DisableSR" /f >nul 2>&1']);
DeviceProcessEvents
| where ProcessCommandLine has_any (CommonRansomwareExecutionCommands)
| project-reorder TimeGenerated, ProcessCommandLine, DeviceName, AccountName
| 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
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: AccountUpn
- identifier: Name
columnName: AccountName
- identifier: DnsDomain
columnName: AccountDomain
- entityType: Process
fieldMappings:
- identifier: ProcessId
columnName: ProcessId
- identifier: CommandLine
columnName: ProcessCommandLine
version: 1.0.0
kind: Scheduled
Stages and Predicates
Let Binding: CommonRansomwareExecutionCommands
let CommonRansomwareExecutionCommands = dynamic([@'vssadmin.exe delete shadows /all /quiet',
@'wmic.exe shadowcopy delete', @'wbadmin delete catalog -quiet',
@'Get-WmiObject Win32_Shadowcopy | ForEach-Object {$_.Delete();}',
@'del /s /f /q c:\*.VHD c:\*.bac c:\*.bak c:\*.wbcat c:\*.bkf c:\Backup*.* c:\backup*.* c:\*.set c:\*.win c:\*.dsk',
@'wbadmin delete systemstatebackup -keepVersions:0',
@'schtasks.exe /Change /TN "\Microsoft\Windows\SystemRestore\SR" /disable',
@'schtasks.exe /Change /TN "\Microsoft\Windows\SystemRestore\SR" /enable >nul 2>&1',
@'reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\SystemRestore" /v "DisableConfig" /t "REG_DWORD" /d "1" /f',
@'reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\SystemRestore" /v "DisableSR" /t "REG_DWORD" /d "1" /f',
@'reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SystemRestore" /v "DisableConfig" /t "REG_DWORD" /d "1" /f',
@'reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SystemRestore" /v "DisableSR" /t "REG_DWORD" /d "1" /f',
@'reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\SystemRestore" /v "DisableConfig" /f >nul 2>&1',
@'reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\SystemRestore" /v "DisableSR" /f >nul 2>&1',
@'reg delete "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SystemRestore" /v "DisableConfig" /f >nul 2>&1',
@'reg delete "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SystemRestore" /v "DisableSR" /f >nul 2>&1']);
Stage 1: source
DeviceProcessEvents
Stage 2: where
| where ProcessCommandLine has_any (CommonRansomwareExecutionCommands)
Stage 3: project-reorder
| project-reorder TimeGenerated, ProcessCommandLine, DeviceName, AccountName
Stage 4: extend
| extend HostName = iff(DeviceName has '.', substring(DeviceName, 0, indexof(DeviceName, '.')), DeviceName)
HostName =DeviceName has "."substring(DeviceName, 0, indexof(DeviceName, '.'))DeviceNameStage 5: 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 |
|---|---|---|---|
ProcessCommandLine | match |
| field:"CommandLine" kind:match |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
HostName | extend |
DnsDomain | extend |
Silent Enabling of Chrome DevTools Protocol
#CDP-Enable-BOF enables Chrome DevTools Protocol by injecting code into the main browser process that hasn't started with the --remote-debugging-port. This enables access to the current user's cookies and credential data without triggering any EDR detections that often monitors the --remote-debugging-port command line parameter.
The query below detects when a Chrome/Edge process that doesn't have the --remote-debugging-port parameter in its command line creates a listening port (remote debugging port). This behavior is normal when chromedriver/msedgedriver starts Chrome/Edge (used in automation workflows).
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access | No specific technique |
References
Telemetry Coverage
Rule Body
// Description: detects when a Chrome/Edge process that doesn't have the `--remote-debugging-port` parameter in its command line creates a listening port (remote debugging port)
// Author: Cyb3rMonk(https://twitter.com/Cyb3rMonk, https://academy.bluraven.io)
//
// Query Parameters
DeviceNetworkEvents
| where ActionType == "ListeningConnectionCreated"
| where InitiatingProcessFileName in~ ("chrome.exe", "msedge.exe") and InitiatingProcessCommandLine !has "--remote-debugging-port"
| where InitiatingProcessParentFileName !in~ ("chromedriver.exe", "msedgedriver.exe")
Stages and Predicates
Stage 1: source
DeviceNetworkEvents
Stage 2: where
| where ActionType == "ListeningConnectionCreated"
Stage 3: where
| where InitiatingProcessFileName in~ ("chrome.exe", "msedge.exe") and InitiatingProcessCommandLine !has "--remote-debugging-port"
Stage 4: where
| where InitiatingProcessParentFileName !in~ ("chromedriver.exe", "msedgedriver.exe")
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ActionType | eq |
| field:"ActionType" kind:eq value:"ListeningConnectionCreated" |
InitiatingProcessFileName | in |
| field:"process_basename" kind:in |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
InitiatingProcessCommandLine | match | --remote-debugging-port | excludes:InitiatingProcessCommandLine field:"InitiatingProcessCommandLine" value:"--remote-debugging-port" |
InitiatingProcessParentFileName | in | chromedriver.exe, msedgedriver.exe | excludes:InitiatingProcessParentFileName field:"InitiatingProcessParentFileName" value:"chromedriver.exe" field:"InitiatingProcessParentFileName" value:"msedgedriver.exe" |
SlackAudit - User login after deactivated.
#'Detects when a Slack user account was deactivated and the same user identity later authenticated again within the detection window, which may indicate account reactivation, unauthorized access, or use of a deactivated account. Analyst triage should review the deactivation time, subsequent login time, EntityUserEmail, and EntityUserId to determine whether the login was expected. This rule uses the SlackAuditAPI connector and SlackAudit_CL data type.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Persistence | |
| Privilege Escalation |
Rule Body
id: e6e99dcb-4dff-48d2-8012-206ca166b36b
name: SlackAudit - User login after deactivated.
description: |
'Detects when a Slack user account was deactivated and the same user identity later authenticated again within
the detection window, which may indicate account reactivation, unauthorized access, or use of a deactivated account. Analyst
triage should review the deactivation time, subsequent login time, EntityUserEmail, and EntityUserId to determine whether
the login was expected. This rule uses the SlackAuditAPI connector and SlackAudit_CL data type.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: SlackAuditAPI
dataTypes:
- SlackAudit_CL
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
- Persistence
- PrivilegeEscalation
relevantTechniques:
- T1078
- T1078.004
query: |
let lbperiod_max_d = 14d;
let lbperiod_min_d = 1d;
let lb_time_max_h = 24h;
SlackAudit
| where TimeGenerated between (ago(lbperiod_max_d) .. (lbperiod_min_d))
| where Action =~ 'user_deactivated'
| summarize deactivation_time = max(TimeGenerated) by EntityUserEmail, EntityUserId
| project deactivation_time, EntityUserEmail, EntityUserId
| join (SlackAudit
| where TimeGenerated > ago(lb_time_max_h)
| where Action =~ 'user_login'
| summarize new_login_time = max(TimeGenerated) by SrcUserEmail, SrcUserIdentity
| project new_login_time, SrcUserEmail, EntityUserId = SrcUserIdentity) on EntityUserId
| where EntityUserEmail == SrcUserEmail
| where deactivation_time < new_login_time
| extend AccountCustomEntity = SrcUserEmail
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: AccountCustomEntity
alertDetailsOverride:
alertDisplayNameFormat: 'Slack user login after deactivation: {{AccountCustomEntity}}'
alertDescriptionFormat: User {{AccountCustomEntity}} logged in after deactivation; review deactivation_time {{deactivation_time}}
and new_login_time {{new_login_time}}.
customDetails:
DeactivationTime: deactivation_time
NewLoginTime: new_login_time
SlackUserEmail: EntityUserEmail
SlackUserId: EntityUserId
version: 1.0.1
kind: Scheduled
Stages and Predicates
Parameters
let lbperiod_max_d = 14d;
let lbperiod_min_d = 1d;
let lb_time_max_h = 24h;
Stage 1: source
SlackAudit
Stage 2: where
| where TimeGenerated between (ago(lbperiod_max_d) .. (lbperiod_min_d))
Stage 3: where
| where Action =~ 'user_deactivated'
Stage 4: summarize
| summarize deactivation_time = max(TimeGenerated) by EntityUserEmail, EntityUserId
Stage 5: project
| project deactivation_time, EntityUserEmail, EntityUserId
Stage 6: join
| join (SlackAudit
| where TimeGenerated > ago(lb_time_max_h)
| where Action =~ 'user_login'
| summarize new_login_time = max(TimeGenerated) by SrcUserEmail, SrcUserIdentity
| project new_login_time, SrcUserEmail, EntityUserId = SrcUserIdentity) on EntityUserId
Stage 7: where
where EntityUserEmail =~ SrcUserEmail
Stage 8: where
where deactivation_time < new_login_time
Stage 9: extend
extend AccountCustomEntity
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Action | eq |
| field:"Action" kind:eq |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
EntityUserEmail | project |
EntityUserId | project |
deactivation_time | project |
AccountCustomEntity | extend |
Snowflake - Multiple failed queries
#'Detects multiple failed queries in short timeframe.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Discovery |
Rule Body
id: 5f8a81d9-7d27-4ff5-a0ce-4285ee02c2c8
name: Snowflake - Multiple failed queries
description: |
'Detects multiple failed queries in short timeframe.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: Snowflake
dataTypes:
- Snowflake
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Discovery
relevantTechniques:
- T1518
- T1082
query: |
let threshold = 50;
Snowflake
| where isnotempty(QueryExecutionStatus)
| where QueryExecutionStatus !~ 'SUCCESS'
| summarize count() by TargetUsername, bin(TimeGenerated, 5m)
| where count_ > threshold
| extend AccountCustomEntity = TargetUsername
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountCustomEntity
version: 1.0.3
kind: Scheduled
Stages and Predicates
Parameters
let threshold = 50;
Stage 1: source
Snowflake
Stage 2: where
| where isnotempty(QueryExecutionStatus)
Stage 3: where
| where QueryExecutionStatus !~ 'SUCCESS'
Stage 4: summarize
| summarize count() by TargetUsername, bin(TimeGenerated, 5m)
Stage 5: where
| where count_ > threshold
Stage 6: extend
| extend AccountCustomEntity = TargetUsername
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
QueryExecutionStatus | is_not_null | field:"QueryExecutionStatus" kind:is_not_null | |
QueryExecutionStatus | ne |
| field:"QueryExecutionStatus" kind:ne value:"SUCCESS" |
count_ | gt |
| field:"count_" kind:gt value:"50" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
TargetUsername | summarize |
AccountCustomEntity | extend |
TI Map Domain entity to Syslog
#Identifies a match in Syslog table from any Domain IOC from TI
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control |
Rule Body
id: cd19434e-10f2-4e2f-b3c1-ce6f08ac5357
name: TI Map Domain entity to Syslog
description: |
Identifies a match in Syslog table from any Domain IOC from TI
severity: Medium
requiredDataConnectors:
- connectorId: Syslog
dataTypes:
- Syslog
- connectorId: ThreatIntelligence
dataTypes:
- ThreatIntelIndicators
- connectorId: ThreatIntelligenceTaxii
dataTypes:
- ThreatIntelIndicators
- connectorId: MicrosoftDefenderThreatIntelligence
dataTypes:
- ThreatIntelIndicators
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- CommandAndControl
relevantTechniques:
- T1071
query: |
let dt_lookBack = 1h; // Define the time range to look back for syslog data (1 hour)
let ioc_lookBack = 14d; // Define the time range to look back for threat intelligence indicators (14 days)
// Create a list of top-level domains (TLDs) from the threat feed for later validation
let list_tlds = ThreatIntelIndicators
| where TimeGenerated > ago(ioc_lookBack)
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id
| where IsActive and (ValidUntil > now() or isempty(ValidUntil))
| extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))
| where IndicatorType == "domain-name"
| extend DomainName = tolower(ObservableValue)
| extend parts = split(DomainName, '.')
| extend tld = parts[(array_length(parts)-1)]
| summarize count() by tostring(tld)
| summarize make_list(tld);
// Fetch the latest active domain indicators from the threat intelligence data within the specified time range
let Domain_Indicators = ThreatIntelIndicators
| extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))
| where IndicatorType == "domain-name"
| extend DomainName = tolower(ObservableValue)
| extend TrafficLightProtocolLevel = tostring(parse_json(AdditionalFields).TLPLevel)
| where TimeGenerated >= ago(ioc_lookBack)
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id, ObservableValue
| where IsActive and (ValidUntil > now() or isempty(ValidUntil))
| extend IndicatorId = tostring(split(Id, "--")[2])
| extend Url = iff(ObservableKey == "url:value", ObservableValue, "");
// Join the threat intelligence indicators with syslog data on matching domain entities
Domain_Indicators
| project-reorder *, IsActive, Tags, TrafficLightProtocolLevel, DomainName, Type
| join kind=innerunique (
Syslog
| where TimeGenerated > ago(dt_lookBack)
// Extract domain patterns from syslog messages
| extend domain = extract("(([a-z0-9]+(-[a-z0-9]+)*\\.)+[a-z]{2,})",1, tolower(SyslogMessage))
| where isnotempty(domain)
| extend parts = split(domain, '.')
// Split out the top-level domain (TLD)
| 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 Syslog_TimeGenerated = TimeGenerated
) on $left.DomainName==$right.domain
| where Syslog_TimeGenerated < ValidUntil
// Retrieve the latest syslog timestamp for each indicator and domain combination
| summarize Syslog_TimeGenerated = arg_max(Syslog_TimeGenerated, *) by IndicatorId, domain
// Select the desired columns for the final result set
| extend ActivityGroupNames = extract(@"ActivityGroup:(\S+)", 1, tostring(parse_json(Data).labels))
| extend Description = tostring(parse_json(Data).description)
| project Syslog_TimeGenerated, Description, ActivityGroupNames, Id, ValidUntil, Confidence, SyslogMessage, Computer, ProcessName, domain, HostIP, Type, DomainName, Url
// Extract the hostname from the Computer field
| extend HostName = tostring(split(Computer, '.', 0)[0])
// Extract the DNS domain from the Computer field
| extend DnsDomain = tostring(strcat_array(array_slice(split(Computer, '.'), 1, -1), '.'))
// Assign the Syslog_TimeGenerated value to the timestamp field
| extend timestamp = Syslog_TimeGenerated
entityMappings:
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: Computer
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: DnsDomain
- entityType: IP
fieldMappings:
- identifier: Address
columnName: HostIP
- entityType: URL
fieldMappings:
- identifier: Url
columnName: Url
version: 1.4.7
kind: Scheduled
Stages and Predicates
Parameters
let dt_lookBack = 1h;
let ioc_lookBack = 14d;
let Domain_Indicators is inlined into the numbered stages below.
Let Binding: list_tlds
let list_tlds = ThreatIntelIndicators
| where TimeGenerated > ago(ioc_lookBack)
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id
| where IsActive and (ValidUntil > now() or isempty(ValidUntil))
| extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))
| where IndicatorType == "domain-name"
| extend DomainName = tolower(ObservableValue)
| extend parts = split(DomainName, '.')
| extend tld = parts[(array_length(parts)-1)]
| summarize count() by tostring(tld)
| summarize make_list(tld);
Stages 1 to 10 define let Domain_Indicators as the rule's main pipeline source. Stages 11 to 18 use it.
Stage 1: source
ThreatIntelIndicators
Stage 2: extend
| extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))
Stage 3: where
| where IndicatorType == "domain-name"
Stage 4: extend
| extend DomainName = tolower(ObservableValue)
Stage 5: extend
| extend TrafficLightProtocolLevel = tostring(parse_json(AdditionalFields).TLPLevel)
Stage 6: where
| where TimeGenerated >= ago(ioc_lookBack)
Stage 7: summarize
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id, ObservableValue
Stage 8: where
| where IsActive and (ValidUntil > now() or isempty(ValidUntil))
Stage 9: extend
| extend IndicatorId = tostring(split(Id, "--")[2])
Stage 10: extend
| extend Url = iff(ObservableKey == "url:value", ObservableValue, "")
Url =ObservableKey == "url:value"ObservableValue""Stage 11: project-reorder
Domain_Indicators
| project-reorder *, IsActive, Tags, TrafficLightProtocolLevel, DomainName, Type
Stage 12: join
| join kind=innerunique (
Syslog
| where TimeGenerated > ago(dt_lookBack)
| extend domain = extract("(([a-z0-9]+(-[a-z0-9]+)*\\.)+[a-z]{2,})",1, tolower(SyslogMessage))
| where isnotempty(domain)
| extend parts = split(domain, '.')
| extend tld = parts[(array_length(parts)-1)]
| where tld in~ (list_tlds)
| extend Syslog_TimeGenerated = TimeGenerated
) on $left.DomainName==$right.domain
Stage 13: where
where Syslog_TimeGenerated < ValidUntil
Stage 14: summarize
summarize Syslog_TimeGenerated by IndicatorId, domain
Stage 15: extend
extend ActivityGroupNames
Stage 16: extend
extend Description
Stage 17: project
project ActivityGroupNames, Computer, Confidence, Description, DomainName, HostIP, Id, ProcessName, SyslogMessage, Syslog_TimeGenerated, Type, Url, ValidUntil, domain
Stage 18: extend (3 consecutive steps)
extend DnsDomain, HostName, timestamp
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
IndicatorType | eq |
| field:"IndicatorType" kind:eq value:"domain-name" |
IsActive | eq |
| field:"IsActive" kind:eq value:"true" |
ValidUntil | is_null | field:"ValidUntil" kind:is_null | |
domain | is_not_null | field:"domain" kind:is_not_null | |
tld | in |
| field:"tld" kind:in value:"list_tlds" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActivityGroupNames | project |
Computer | project |
Confidence | project |
Description | project |
DomainName | project |
HostIP | project |
Id | project |
ProcessName | project |
SyslogMessage | project |
Syslog_TimeGenerated | project |
Type | project |
Url | project |
ValidUntil | project |
domain | project |
HostName | extend |
DnsDomain | extend |
timestamp | extend |