Detection rules › Kusto
Kusto rules: 39
Cisco SDWAN - Intrusion Events
#'This Analytic rule will monitor Intrusion events in Cisco syslog data based on the provided Signature ID. This will create an incident if that Signature ID is found in the specified time range.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: 232a1c75-63fc-4c81-8b18-b4a739fccba8
name: Cisco SDWAN - Intrusion Events
description: |
'This Analytic rule will monitor Intrusion events in Cisco syslog data based on the provided Signature ID. This will create an incident if that Signature ID is found in the specified time range.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: CiscoSDWAN
dataTypes:
- CiscoSyslogUTD
queryFrequency: 3h
queryPeriod: 3h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1190
- T1189
query: |
CiscoSyslogUTD
| where SignatureId == "Enter SignatureId"
| distinct SignatureId,SourceIP
incidentConfiguration:
createIncident: true
eventGroupingSettings:
aggregationKind: AlertPerResult
customDetails:
signatureid: SignatureId
entityMappings:
- entityType: RegistryKey
fieldMappings:
- identifier: Key
columnName: SignatureId
version: 1.0.1
kind: Scheduled
Stages and Predicates
Stage 1: source
CiscoSyslogUTD
Stage 2: where
| where SignatureId == "Enter SignatureId"
Stage 3: distinct
| distinct SignatureId,SourceIP
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
SignatureId | eq |
| field:"SignatureId" kind:eq value:"Enter SignatureId" |
Cloudflare - Multiple user agents for single source
#'Detects requests with different user agents from one source in short timeframe.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: fc50076a-0275-43d5-b9dd-38346c061f68
name: Cloudflare - Multiple user agents for single source
description: |
'Detects requests with different user agents from one source in short timeframe.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: CloudflareDataConnector
dataTypes:
- Cloudflare
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1190
- T1133
query: |
let threshold = 10;
Cloudflare
| where isnotempty(HttpUserAgentOriginal)
| summarize d_ua = dcount(HttpUserAgentOriginal) by SrcIpAddr, bin(TimeGenerated, 3m)
| where d_ua > threshold
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIpAddr
version: 1.0.1
kind: Scheduled
Stages and Predicates
Parameters
let threshold = 10;
Stage 1: source
Cloudflare
Stage 2: where
| where isnotempty(HttpUserAgentOriginal)
Stage 3: summarize
| summarize d_ua = dcount(HttpUserAgentOriginal) by SrcIpAddr, bin(TimeGenerated, 3m)
Stage 4: where
| where d_ua > threshold
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
HttpUserAgentOriginal | is_not_null | field:"HttpUserAgentOriginal" kind:is_not_null | |
d_ua | gt |
| field:"d_ua" kind:gt value:"10" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
SrcIpAddr | summarize |
d_ua | summarize |
Cognni Incidents for Highly Sensitive HR Information
#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.
'Display incidents in which highly sensitive HR information was placed at risk by user sharing.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Collection |
Rule Body
id: f68846cf-ec99-497d-9ce1-80a9441564fb
name: Cognni Incidents for Highly Sensitive HR Information
description: |
'Display incidents in which highly sensitive HR information was placed at risk by user sharing.'
severity: High
requiredDataConnectors:
- connectorId: CognniSentinelDataConnector
dataTypes:
- CognniIncidents_CL
queryFrequency: 5h
queryPeriod: 5h
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- Collection
relevantTechniques:
- T1530
query: |
let highRisk = 3;
let hr = 'HR Information';
CognniIncidents_CL
| where Severity == highRisk
| where informationType_s == hr
| where TimeGenerated >= ago(5h)
| extend AccountCustomEntity = userId_s
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: AccountCustomEntity
version: 1.0.0
kind: Scheduled
Stages and Predicates
Parameters
let highRisk = 3;
let hr = 'HR Information';
Stage 1: source
CognniIncidents_CL
Stage 2: where
| where Severity == highRisk
Stage 3: where
| where informationType_s == hr
Stage 4: where
| where TimeGenerated >= ago(5h)
Stage 5: extend
| extend AccountCustomEntity = userId_s
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Severity | eq |
| field:"Severity" kind:eq value:"3" |
informationType_s | eq |
| field:"informationType_s" kind:eq value:"HR Information" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountCustomEntity | extend |
CTE - Device Enrolled but On Hold
#A device enrollment is on hold, typically due to a license/device-capacity limit being reached.
Rule Body
id: 21818920-85eb-4955-a7cc-f62c731f32ac
name: CTE - Device Enrolled but On Hold
description: |
A device enrollment is on hold, typically due to a license/device-capacity limit being reached.
severity: Low
requiredDataConnectors:
- connectorId: cybereinforce_cte
dataTypes:
- CybereinforceCTE_CL
queryFrequency: 30m
queryPeriod: 30m
triggerOperator: gt
triggerThreshold: 0
tactics:
[]
relevantTechniques: []
query: |
CybereinforceCTE_CL
| where EventType == "device.onhold"
| extend d = parse_json(Details)
| extend
HostName = iff(isnotempty(tostring(d.hostname)), tostring(d.hostname), strcat("cte-device-", substring(EntityId, 0, 8))),
DeviceId = tostring(d.deviceId),
DeviceUser = tostring(d.deviceUser),
DeviceOs = tostring(d.deviceOs),
AppName = "Cybereinforce"
| project TimeGenerated, TenantId, DeviceId, HostName, DeviceUser, DeviceOs, AppName, Actor, Severity, Category, Source
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: HostName
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
CybereinforceCTE_CL
Stage 2: where
| where EventType == "device.onhold"
Stage 3: extend
| extend d = parse_json(Details)
Stage 4: extend
| extend
HostName = iff(isnotempty(tostring(d.hostname)), tostring(d.hostname), strcat("cte-device-", substring(EntityId, 0, 8))),
DeviceId = tostring(d.deviceId),
DeviceUser = tostring(d.deviceUser),
DeviceOs = tostring(d.deviceOs),
AppName = "Cybereinforce"
HostName =isnotempty(tostring(d.hostname))tostring(d.hostname)strcat("cte-device-", substring(EntityId, 0, 8))Stage 5: project
| project TimeGenerated, TenantId, DeviceId, HostName, DeviceUser, DeviceOs, AppName, Actor, Severity, Category, Source
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventType | eq |
| field:"EventType" kind:eq value:"device.onhold" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Actor | project |
AppName | project |
Category | project |
DeviceId | project |
DeviceOs | project |
DeviceUser | project |
HostName | project |
Severity | project |
Source | project |
TenantId | project |
TimeGenerated | project |
CyberArkEPM - Uncommon process Internet access
#'Detects access to the Internet by uncommon processes.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Execution | |
| Stealth | |
| Command & Control |
Rule Body
id: 9d0d44ab-54dc-472a-9931-53521e888932
name: CyberArkEPM - Uncommon process Internet access
description: |
'Detects access to the Internet by uncommon processes.'
severity: High
requiredDataConnectors:
- connectorId: CyberArkEPM
dataTypes:
- CyberArkEPM
queryFrequency: 30m
queryPeriod: 30m
triggerOperator: gt
triggerThreshold: 0
tactics:
- Execution
- DefenseEvasion
- CommandAndControl
relevantTechniques:
- T1204
- T1036
- T1095
query: |
let lb_period = 14d;
let q_time = 1h;
let inet_access_proc = CyberArkEPM
| where TimeGenerated between (ago(lb_period) .. ago(q_time))
| where EventSubType =~ 'DetectAccessInternet'
| where isnotempty(ActingProcessFileInternalName)
| summarize makeset(ActingProcessFileInternalName);
CyberArkEPM
| where TimeGenerated > ago(q_time)
| where EventSubType =~ 'DetectAccessInternet'
| where ActingProcessFileInternalName !in (inet_access_proc)
| extend AccountCustomEntity = ActorUsername
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountCustomEntity
version: 1.0.0
kind: Scheduled
Stages and Predicates
Parameters
let lb_period = 14d;
let q_time = 1h;
Let Binding: inet_access_proc
let inet_access_proc = CyberArkEPM
| where TimeGenerated between (ago(lb_period) .. ago(q_time))
| where EventSubType =~ 'DetectAccessInternet'
| where isnotempty(ActingProcessFileInternalName)
| summarize makeset(ActingProcessFileInternalName);
Stage 1: source
CyberArkEPM
Stage 2: where
| where TimeGenerated > ago(q_time)
Stage 3: where
| where EventSubType =~ 'DetectAccessInternet'
Stage 4: where
| where ActingProcessFileInternalName !in (inet_access_proc)
Stage 5: extend
| extend AccountCustomEntity = ActorUsername
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventSubType | eq |
| field:"EventSubType" kind:eq value:"DetectAccessInternet" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
ActingProcessFileInternalName | eq | inet_access_proc | excludes:ActingProcessFileInternalName field:"ActingProcessFileInternalName" value:"inet_access_proc" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountCustomEntity | extend |
CYFIRMA - High severity Malicious Network Indicators with Block Action Rule
#This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.
"This rule detects network-based indicators such as IP addresses, domains, and URLs reported by CYFIRMA threat intelligence with a recommended action of 'Block' 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: 58ae2c87-fc07-434b-aacf-f66d25b25e7e
name: CYFIRMA - High severity Malicious Network Indicators with Block Action Rule
description: |
"This rule detects network-based indicators such as IP addresses, domains, and URLs reported by CYFIRMA threat intelligence with a recommended action of 'Block' 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 Block Action Rule
let timeFrame= 5m;
CyfirmaIndicators_CL
| where ConfidenceScore >= 80
and TimeGenerated between (ago(timeFrame) .. now())
and pattern !contains 'file:hashes' and RecommendedActions has 'Block' 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 Block 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 'Block' 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:"Block" |
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 |
Dataverse - Terminated employee exfiltration over email
#This query identifies Dataverse exfiltration via email by terminated employees.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Exfiltration | |
| Exfiltration |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Microsoft Defender for Endpoint | EmailEvents action any: Email processed |
Rule Body
id: de039242-47e0-43fa-84d7-b6be24305349
kind: Scheduled
name: Dataverse - Terminated employee exfiltration over email
description: This query identifies Dataverse exfiltration via email by terminated
employees.
severity: High
status: Available
requiredDataConnectors:
- connectorId: MicrosoftThreatProtection
dataTypes:
- EmailEvents
- connectorId: AzureActiveDirectoryIdentityProtection
dataTypes:
- SecurityAlert
- connectorId: IdentityInfo
dataTypes:
- IdentityInfo
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Exfiltration
relevantTechniques:
- T1639
- T1567
query: |
// Note this detection relies upon the user's UPN matching their email address.
// UEBA can provide more accurate data if enabled.
let query_frequency = 1h;
let allowed_destination_smtp_domains = dynamic([
// Specify a list of recipient domains to exclude from alerting.
// Example:
// "microsoft.com", "contoso.com"
]);
let exfiltration_alert_users = SecurityAlert
| where Tactics has 'Exfiltration' and Entities has_all ('account', '32780')
| mv-expand DataverseEntities = todynamic(Entities)
| where DataverseEntities.AppId == 32780
| extend InstanceUrl = tostring(DataverseEntities.InstanceName)
| mv-expand AccountEntities = todynamic(Entities)
| where AccountEntities.Type == 'account'
| extend
AccountName = tostring(AccountEntities.Name),
UPNSuffix = tostring(AccountEntities.UPNSuffix)
| summarize InstanceUrls = make_set(InstanceUrl, 100) by AccountName, UPNSuffix
| extend UserId = tolower(strcat(AccountName, "@", UPNSuffix));
exfiltration_alert_users
| join kind=inner (
MSBizAppsTerminatedEmployees
| project UserId = tolower(UserPrincipalName), NotificationDate
| where startofday(NotificationDate) <= startofday(now()))
// Uncomment the below KQL if UEBA is available to gain more accurate
// email address data:
// | join kind=leftouter (_ASIM_IdentityInfo) on $left.UserId == $right.Username
// | extend UserId = iif(UserId == UserMailAddress or isempty(UserMailAddress), UserId, UserMailAddress))
on UserId
| join kind=inner (
EmailEvents
| where TimeGenerated >= ago (query_frequency)
| where EmailDirection == "Outbound" and AttachmentCount > 0
| extend RecipientDomain = tolower(split(RecipientEmailAddress, '@')[1])
| where RecipientDomain !in (allowed_destination_smtp_domains)
| summarize
RecipientAddresses = make_set(RecipientEmailAddress, 1000),
Subject = make_set(Subject, 1000)
by SenderAddress = tolower(SenderMailFromAddress), SenderIPv4)
on $left.UserId == $right.SenderAddress
| mv-expand InstanceUrl = InstanceUrls to typeof(string)
| extend
CloudAppId = int(32780),
AccountName = tostring(split(UserId, "@")[0]),
UPNSuffix = tostring(split(UserId, "@")[1])
| project
UserId,
InstanceUrl,
SenderIPv4,
RecipientAddresses,
Subject,
AccountName,
UPNSuffix,
CloudAppId
eventGroupingSettings:
aggregationKind: AlertPerResult
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SenderIPv4
- entityType: CloudApplication
fieldMappings:
- identifier: AppId
columnName: CloudAppId
- identifier: InstanceName
columnName: InstanceUrl
alertDetailsOverride:
alertDisplayNameFormat: Email attachment sent externally by terminated user following
Dataverse exfiltration alerts
alertDescriptionFormat: 'Departing or terminated user {{UserId}} was found to send
email to external domains not on the allowed list: {{RecipientAddresses}}'
version: 3.2.0
Stages and Predicates
Parameters
let query_frequency = 1h;
let exfiltration_alert_users is inlined into the numbered stages below.
Let Binding: allowed_destination_smtp_domains
let allowed_destination_smtp_domains = dynamic([
]);
Stages 1 to 10 define let exfiltration_alert_users as the rule's main pipeline source. Stages 11 to 15 use it.
Stage 1: source
SecurityAlert
Stage 2: where
| where Tactics has 'Exfiltration' and Entities has_all ('account', '32780')
Stage 3: mv-expand
| mv-expand DataverseEntities = todynamic(Entities)
Stage 4: where
| where DataverseEntities.AppId == 32780
Stage 5: extend
| extend InstanceUrl = tostring(DataverseEntities.InstanceName)
Stage 6: mv-expand
| mv-expand AccountEntities = todynamic(Entities)
Stage 7: where
| where AccountEntities.Type == 'account'
Stage 8: extend
| extend
AccountName = tostring(AccountEntities.Name),
UPNSuffix = tostring(AccountEntities.UPNSuffix)
Stage 9: summarize
| summarize InstanceUrls = make_set(InstanceUrl, 100) by AccountName, UPNSuffix
Stage 10: extend
| extend UserId = tolower(strcat(AccountName, "@", UPNSuffix))
Stage 11: join
exfiltration_alert_users
| join kind=inner (
MSBizAppsTerminatedEmployees
| project UserId = tolower(UserPrincipalName), NotificationDate
| where startofday(NotificationDate) <= startofday(now()))
on UserId
Stage 12: join
| join kind=inner (
EmailEvents
| where TimeGenerated >= ago (query_frequency)
| where EmailDirection == "Outbound" and AttachmentCount > 0
| extend RecipientDomain = tolower(split(RecipientEmailAddress, '@')[1])
| where RecipientDomain !in (allowed_destination_smtp_domains)
| summarize
RecipientAddresses = make_set(RecipientEmailAddress, 1000),
Subject = make_set(Subject, 1000)
by SenderAddress = tolower(SenderMailFromAddress), SenderIPv4)
on $left.UserId == $right.SenderAddress
Stage 13: mv-expand
mv-expand InstanceUrl
Stage 14: extend
extend AccountName, CloudAppId, UPNSuffix
Stage 15: project
project AccountName, CloudAppId, InstanceUrl, RecipientAddresses, SenderIPv4, Subject, UPNSuffix, UserId
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AccountEntities.Type | eq |
| field:"AccountEntities.Type" kind:eq value:"account" |
AttachmentCount | gt |
| field:"AttachmentCount" kind:gt value:"0" |
DataverseEntities.AppId | eq |
| field:"DataverseEntities.AppId" kind:eq value:"32780" |
EmailDirection | eq |
| field:"EmailDirection" kind:eq value:"Outbound" |
Entities | match |
| field:"Entities" kind:match |
Tactics | match |
| field:"Tactics" kind:match value:"Exfiltration" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
RecipientDomain | eq | [] | excludes:RecipientDomain field:"RecipientDomain" value:"[]" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountName | project |
CloudAppId | project |
InstanceUrl | project |
RecipientAddresses | project |
SenderIPv4 | project |
Subject | project |
UPNSuffix | project |
UserId | project |
Discord CDN Risky File Download (ASIM Web Session Schema)
#'Identifies callouts to Discord CDN addresses for risky file extensions. This detection will trigger when a callout for a risky file is made to a discord server that has only been seen once in your environment. Unique discord servers are identified using the server ID that is included in the request URL (DiscordServerId in query). Discord CDN has been used in multiple campaigns to download additional payloads. This analytic rule uses ASIM and supports any built-in or custom source that supports the ASIM WebSession schema (ASIM WebSession Schema)'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control |
Rule Body
id: 01e8ffff-dc0c-43fe-aa22-d459c4204553
name: Discord CDN Risky File Download (ASIM Web Session Schema)
description: |
'Identifies callouts to Discord CDN addresses for risky file extensions. This detection will trigger when a callout for a risky file is made to a discord server that has only been seen once in your environment.
Unique discord servers are identified using the server ID that is included in the request URL (DiscordServerId in query). Discord CDN has been used in multiple campaigns to download additional payloads.
This analytic rule uses [ASIM](https://aka.ms/AboutASIM) and supports any built-in or custom source that supports the ASIM WebSession schema (ASIM WebSession Schema)'
severity: Medium
requiredDataConnectors:
- connectorId: SquidProxy
dataTypes:
- SquidProxy_CL
- connectorId: Zscaler
dataTypes:
- CommonSecurityLog
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- CommandAndControl
relevantTechniques:
- T1071.001
tags:
- Discord
query: |
let discord=dynamic(["cdn.discordapp.com", "media.discordapp.com"]);
_Im_WebSession(url_has_any=discord, eventresult='Success')
| where Url has "attachments"
| extend DiscordServerId = extract(@"\/attachments\/([0-9]+)\/", 1, Url)
| summarize dcount(Url), make_set(SrcUsername), make_set(SrcIpAddr), make_set(Url), min(TimeGenerated), max(TimeGenerated), make_set(EventResult) by DiscordServerId
| mv-expand set_SrcUsername to typeof(string), set_Url to typeof(string), set_EventResult to typeof(string), set_SrcIpAddr to typeof(string)
| summarize by DiscordServerId, dcount_Url, set_SrcUsername, min_TimeGenerated, max_TimeGenerated, set_EventResult, set_SrcIpAddr, set_Url
| project StartTime=min_TimeGenerated, EndTime=max_TimeGenerated, Result=set_EventResult, SourceUser=set_SrcUsername, SourceIP=set_SrcIpAddr, RequestURL=set_Url
| where RequestURL has_any (".bin",".exe",".dll",".bin",".msi")
| extend AccountName = tostring(split(SourceUser, "@")[0]), AccountUPNSuffix = tostring(split(SourceUser, "@")[1])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: SourceUser
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: AccountUPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIP
- entityType: URL
fieldMappings:
- identifier: Url
columnName: RequestURL
version: 1.1.4
kind: Scheduled
metadata:
source:
kind: Community
author:
name: Microsoft Security Research
support:
tier: Community
categories:
domains: [ "Security - Threat Protection" ]
Stages and Predicates
Parameters
let discord = dynamic(["cdn.discordapp.com", "media.discordapp.com"]);
Stage 1: source
_Im_WebSession(url_has_any=discord, eventresult='Success')
Stage 2: where
| where Url has "attachments"
Stage 3: extend
| extend DiscordServerId = extract(@"\/attachments\/([0-9]+)\/", 1, Url)
Stage 4: summarize
| summarize dcount(Url), make_set(SrcUsername), make_set(SrcIpAddr), make_set(Url), min(TimeGenerated), max(TimeGenerated), make_set(EventResult) by DiscordServerId
Stage 5: mv-expand
| mv-expand set_SrcUsername to typeof(string), set_Url to typeof(string), set_EventResult to typeof(string), set_SrcIpAddr to typeof(string)
Stage 6: summarize
| summarize by DiscordServerId, dcount_Url, set_SrcUsername, min_TimeGenerated, max_TimeGenerated, set_EventResult, set_SrcIpAddr, set_Url
Stage 7: project
| project StartTime=min_TimeGenerated, EndTime=max_TimeGenerated, Result=set_EventResult, SourceUser=set_SrcUsername, SourceIP=set_SrcIpAddr, RequestURL=set_Url
Stage 8: where
| where RequestURL has_any (".bin",".exe",".dll",".bin",".msi")
Stage 9: extend
| extend AccountName = tostring(split(SourceUser, "@")[0]), AccountUPNSuffix = tostring(split(SourceUser, "@")[1])
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
RequestURL | match |
| field:"RequestURL" kind:match |
Url | match |
| field:"Url" kind:match value:"attachments" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
EndTime | project |
RequestURL | project |
Result | project |
SourceIP | project |
SourceUser | project |
StartTime | project |
AccountName | extend |
AccountUPNSuffix | extend |
External guest invitation followed by Microsoft Entra ID PowerShell signin
#By default guests have capability to invite more external guest users, guests also can do suspicious Microsoft Entra ID enumeration. This detection look at guest users, who have been invited or have invited recently, who also are logging via various PowerShell CLI. Ref : 'https://danielchronlund.com/2021/11/18/scary-azure-ad-tenant-enumeration-using-regular-b2b-guest-accounts/
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Persistence | |
| Discovery |
Telemetry Coverage
Rules Detecting the Same Action
These rules filter on the same operation.
- [Entra ID] Authentication Method Changed for Privileged Account (Kusto)
- Account Created and Deleted in Short Timeframe (Kusto)
- Account Created And Deleted Within A Close Time Frame (Sigma)
- Account created from non-approved sources (Kusto)
- Account created or deleted by non-approved user (Kusto)
- Addition of a Temporary Access Pass to a Privileged Account (Kusto)
- Authentication Method Changed for Privileged Account (Kusto)
- Authentication Methods Changed for Privileged Account (Kusto)
Rule Body
id: acc4c247-aaf7-494b-b5da-17f18863878a
name: External guest invitation followed by Microsoft Entra ID PowerShell signin
description: |
'By default guests have capability to invite more external guest users, guests also can do suspicious Microsoft Entra ID enumeration. This detection look at guest users, who have been invited or have invited recently, who also are logging via various PowerShell CLI.
Ref : 'https://danielchronlund.com/2021/11/18/scary-azure-ad-tenant-enumeration-using-regular-b2b-guest-accounts/'
severity: Medium
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- AuditLogs
- connectorId: AzureActiveDirectory
dataTypes:
- SigninLogs
queryFrequency: 1h
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- InitialAccess
- Persistence
- Discovery
relevantTechniques:
- T1078.004
- T1136.003
- T1087.004
query: |
let queryfrequency = 1h;
let queryperiod = 1d;
AuditLogs
| where TimeGenerated > ago(queryperiod)
| where OperationName in ("Invite external user", "Bulk invite users - started (bulk)", "Invite external user with reset invitation status")
| extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
| extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
| extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend InitiatingIpAddress = tostring(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))
| extend InitiatedBy = iff(isnotempty(InitiatingUserPrincipalName), InitiatingUserPrincipalName, InitiatingAppName)
// Uncomment the following line to filter events where the inviting user was a guest user
//| where InitiatedBy has_any ("live.com#", "#EXT#")
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type =~ "User"
| extend InvitedUser = tostring(TargetResource.userPrincipalName)
)
| mv-expand UserToCompare = pack_array(InitiatedBy, InvitedUser) to typeof(string)
| where UserToCompare has_any ("live.com#", "#EXT#")
| extend
parsedUser = replace_string(tolower(iff(UserToCompare startswith "live.com#", tostring(split(UserToCompare, "#")[1]), tostring(split(UserToCompare, "#EXT#")[0]))), "@", "_"),
InvitationTime = TimeGenerated
| join (
(union isfuzzy=true SigninLogs, AADNonInteractiveUserSignInLogs)
| where TimeGenerated > ago(queryfrequency)
| where UserType != "Member"
| where AppId has_any // This web may contain a list of these apps: https://msshells.net/
("1b730954-1685-4b74-9bfd-dac224a7b894",// Azure Active Directory PowerShell
"04b07795-8ddb-461a-bbee-02f9e1bf7b46",// Microsoft Azure CLI
"1950a258-227b-4e31-a9cf-717495945fc2",// Microsoft Azure PowerShell
"a0c73c16-a7e3-4564-9a95-2bdf47383716",// Microsoft Exchange Online Remote PowerShell
"fb78d390-0c51-40cd-8e17-fdbfab77341b",// Microsoft Exchange REST API Based Powershell
"d1ddf0e4-d672-4dae-b554-9d5bdfd93547",// Microsoft Intune PowerShell
"9bc3ab49-b65d-410a-85ad-de819febfddc",// Microsoft SharePoint Online Management Shell
"12128f48-ec9e-42f0-b203-ea49fb6af367",// MS Teams Powershell Cmdlets
"23d8f6bd-1eb0-4cc2-a08c-7bf525c67bcd",// Power BI PowerShell
"31359c7f-bd7e-475c-86db-fdb8c937548e",// PnP Management Shell
"90f610bf-206d-4950-b61d-37fa6fd1b224",// Aadrm Admin Powershell
"14d82eec-204b-4c2f-b7e8-296a70dab67e",// Microsoft Graph PowerShell
"9cee029c-6210-4654-90bb-17e6e9d36617" // Power Platform CLI - pac"
)
| summarize arg_min(TimeGenerated, *) by UserPrincipalName
| extend
parsedUser = replace_string(UserPrincipalName, "@", "_"),
SigninTime = TimeGenerated
)
on parsedUser
| project InvitationTime, InitiatedBy, InitiatingUserPrincipalName, InitiatingAadUserId, InitiatingAppName, InitiatingAppServicePrincipalId, InitiatingIpAddress, OperationName, InvitedUser, SigninTime, SigninCategory = Category1, SigninUserPrincipalName = UserPrincipalName, AppDisplayName, ResourceDisplayName, UserAgent, InvitationAdditionalDetails = AdditionalDetails, InvitationTargetResources = TargetResources
| extend InvitedUserName = tostring(split(InvitedUser,'@',0)[0]), InvitedUserUPNSuffix = tostring(split(InvitedUser,'@',1)[0]),
InitiatedByName = tostring(split(InitiatingUserPrincipalName,'@',0)[0]), InitiatedByUPNSuffix = tostring(split(InitiatingUserPrincipalName,'@',1)[0])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: InvitedUser
- identifier: Name
columnName: InvitedUserName
- identifier: UPNSuffix
columnName: InvitedUserUPNSuffix
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: InitiatingUserPrincipalName
- identifier: Name
columnName: InitiatedByName
- identifier: UPNSuffix
columnName: InitiatedByUPNSuffix
- entityType: Account
fieldMappings:
- identifier: AadUserId
columnName: InitiatingAadUserId
- entityType: Account
fieldMappings:
- identifier: AadUserId
columnName: InitiatingAppServicePrincipalId
- entityType: IP
fieldMappings:
- identifier: Address
columnName: InitiatingIpAddress
version: 1.0.3
kind: Scheduled
Stages and Predicates
Parameters
let queryfrequency = 1h;
let queryperiod = 1d;
Stage 1: source
AuditLogs
Stage 2: where
| where TimeGenerated > ago(queryperiod)
Stage 3: where
| where OperationName in ("Invite external user", "Bulk invite users - started (bulk)", "Invite external user with reset invitation status")
Stage 4: extend (6 consecutive steps)
| extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
| extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
| extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend InitiatingIpAddress = tostring(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))
| extend InitiatedBy = iff(isnotempty(InitiatingUserPrincipalName), InitiatingUserPrincipalName, InitiatingAppName)
Stage 5: kusto:mv-apply
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type =~ "User"
| extend InvitedUser = tostring(TargetResource.userPrincipalName)
)
Stage 6: mv-expand
| mv-expand UserToCompare = pack_array(InitiatedBy, InvitedUser) to typeof(string)
Stage 7: where
| where UserToCompare has_any ("live.com#", "#EXT#")
Stage 8: extend
| extend
parsedUser = replace_string(tolower(iff(UserToCompare startswith "live.com#", tostring(split(UserToCompare, "#")[1]), tostring(split(UserToCompare, "#EXT#")[0]))), "@", "_"),
InvitationTime = TimeGenerated
Stage 9: join
join (...) on parsedUser
Stage 10: project
project AppDisplayName, InitiatedBy, InitiatingAadUserId, InitiatingAppName, InitiatingAppServicePrincipalId, InitiatingIpAddress, InitiatingUserPrincipalName, InvitationAdditionalDetails, InvitationTargetResources, InvitationTime, InvitedUser, OperationName, ResourceDisplayName, SigninCategory, SigninTime, SigninUserPrincipalName, UserAgent
Stage 11: extend
extend InitiatedByName, InitiatedByUPNSuffix, InvitedUserName, InvitedUserUPNSuffix
Stage 12: summarize aggregation inside the join branch
summarize by UserPrincipalName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AppId | match |
| field:"AppId" kind:match |
OperationName | in |
| field:"OperationName" kind:in |
TargetResource.type | eq |
| field:"TargetResource.type" kind:eq value:"User" |
UserToCompare | match |
| field:"UserToCompare" kind:match |
UserType | ne |
| field:"UserType" kind:ne value:"Member" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
UserPrincipalName | summarize |
GitLab - User Impersonation
#'This queries GitLab Audit Logs for user impersonation. A malicious operator or a compromised admin account could leverage the impersonation feature of GitLab to change code or repository settings bypassing usual processes. This hunting queries allows you to track the audit actions done under impersonation.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence |
Rule Body
id: 0f4f16a2-b464-4c10-9a42-993da3e15a40
name: GitLab - User Impersonation
description: |
'This queries GitLab Audit Logs for user impersonation. A malicious operator or a compromised admin account could leverage the impersonation feature of GitLab to change code or repository settings bypassing usual processes. This hunting queries allows you to track the audit actions done under impersonation.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: SyslogAma
dataTypes:
- Syslog
queryFrequency: 1h
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
relevantTechniques:
- T1078
query: |
let impersonationStart = (GitLabAudit
| where CustomMessage == 'Started Impersonation');
let impersonationStop = (GitLabAudit
| where CustomMessage == 'Stopped Impersonation');
impersonationStart
| join kind=inner impersonationStop on $left.TargetID == $right.TargetID and $left.AuthorID == $right.AuthorID
| where EventTime1 > EventTime
| extend TimeGenerated, AuthorID, AuthorName, TargetID, TargetDetails = TargetDetails, IPStart = IPAddress, IPStop = IPAddress1, ImpStartTime = EventTime, ImpStopTime = EventTime1, EntityName
| join kind=inner (GitLabAudit | extend ActionTime = EventTime, AuthorName) on $left.TargetDetails == $right.AuthorName
| where ImpStartTime < ActionTime and ActionTime > ImpStopTime
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPStart
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPStop
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: EntityName
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: TargetDetails
version: 1.0.2
kind: Scheduled
Stages and Predicates
let impersonationStart is inlined into the numbered stages below.
Let Binding: impersonationStop
let impersonationStop = (GitLabAudit
| where CustomMessage == 'Stopped Impersonation');
Stages 1 to 2 define let impersonationStart as the rule's main pipeline source. Stages 3 to 7 use it.
Stage 1: source
GitLabAudit
Stage 2: where
| where CustomMessage == 'Started Impersonation'
Stage 3: join
impersonationStart
| join kind=inner impersonationStop on $left.TargetID == $right.TargetID and $left.AuthorID == $right.AuthorID
Stage 4: where
where EventTime1 > EventTime
Stage 5: extend
extend AuthorID, AuthorName, EntityName, IPStart, IPStop, ImpStartTime, ImpStopTime, TargetDetails, TargetID, TimeGenerated
Stage 6: join
| join kind=inner (GitLabAudit | extend ActionTime = EventTime, AuthorName) on $left.TargetDetails == $right.AuthorName
Stage 7: where
where ActionTime > ImpStopTime and ImpStartTime < ActionTime
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
CustomMessage | eq |
| field:"CustomMessage" kind:eq |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AuthorID | extend |
AuthorName | extend |
EntityName | extend |
IPStart | extend |
IPStop | extend |
ImpStartTime | extend |
ImpStopTime | extend |
TargetDetails | extend |
TargetID | extend |
TimeGenerated | extend |
Jamf Protect - Alerts
#This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.
'Creates an incident based on Jamf Protect Alert data in Microsoft Sentinel'
Rule Body
id: 6098daa0-f05e-44d5-b5a0-913e63ba3179
name: Jamf Protect - Alerts
description: |
'Creates an incident based on Jamf Protect Alert data in Microsoft Sentinel'
severity: High
status: Available
requiredDataConnectors:
- connectorId: JamfProtect
dataTypes:
- jamfprotectalerts_CL
suppressionDuration: PT5H
suppressionEnabled: false
tactics:
relevantTechniques:
query: |
jamfprotectalerts_CL
| extend
algorithm = "SHA256",
Host_IPs = tostring(parse_json(DvcIpAddr)[0]),
Tags = parse_json(tostring(parse_json(tostring(input.match)).tags)),
Tactics = case(input.match.tags has "Execution", "Execution", input.match.tags has "Visibility", "Visibility", input.match.tags has "Persistence", "Persistence", input.match.tags has "LateralMovement", "LateralMovement", input.match.tags has "CredentialAccess", "CredentialAcccess", input.match.tags has "DefenseEvasion", "DefenseEvasion", input.match.tags has "PrivilegeEscalation", "PrivilegeEscalation", input.match.tags has "Impact", "Impact", input.match.tags has "CommandAndControl", "CommandandControl", input.match.tags has "Discovery", "Discovery", input.match.tags has "InitialAccess", "InitialAccess", ""),
Techniques = pack_array(extract(@"[A-Za-z]\d{4}", 0, tostring(input.match.tags))),
JamfPro = case(input.match.actions has "SmartGroup", "Workflow with Jamf Pro", input.match.actions has "Prevented", "No workflow, Prevented by Protect", "No workflow")
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
eventGroupingSettings:
aggregationKind: AlertPerResult
alertDetailsOverride:
alertDisplayNameFormat: "{{EventMessage}} detected on {{DvcHostname}}"
alertDescriptionFormat: "{{EventResultMessage}} - Please investigate"
alertTacticsColumnName: Tactics
alertSeverityColumnName: EventSeverity
alertDynamicProperties:
- alertProperty: ProviderName
value: EventVendor
- alertProperty: ProductName
value: EventProduct
- alertProperty: Techniques
value: Techniques
customDetails:
Protect_Event_Type: EventType
Protect_Analytic: EventMessage
Related_File_hash: TargetBinarySHA256
Related_Binaries: TargetBinaryFilePath
TargetBinarySignMsg: TargetBinarySigningInfoMessage
TargetbinarySign: TargetbinarySignerType
TargetBinarySigner: TargetBinarySigningTeamID
JamfPro_Status: JamfPro
Protect_Tags: Tags
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: DvcHostname
- identifier: OSFamily
columnName: DvcOs
- identifier: OSVersion
columnName: DvcOsVersion
- entityType: IP
fieldMappings:
- identifier: Address
columnName: Host_IPs
- entityType: Process
fieldMappings:
- identifier: CommandLine
columnName: TargetProcessCurrentDirectory
- identifier: ProcessId
columnName: TargetProcessId
- entityType: FileHash
fieldMappings:
- identifier: Algorithm
columnName: algorithm
- identifier: Value
columnName: TargetBinarySHA256
version: 1.0.6
kind: NRT
Stages and Predicates
Stage 1: source
jamfprotectalerts_CL
Stage 2: extend
| extend
algorithm = "SHA256",
Host_IPs = tostring(parse_json(DvcIpAddr)[0]),
Tags = parse_json(tostring(parse_json(tostring(input.match)).tags)),
Tactics = case(input.match.tags has "Execution", "Execution", input.match.tags has "Visibility", "Visibility", input.match.tags has "Persistence", "Persistence", input.match.tags has "LateralMovement", "LateralMovement", input.match.tags has "CredentialAccess", "CredentialAcccess", input.match.tags has "DefenseEvasion", "DefenseEvasion", input.match.tags has "PrivilegeEscalation", "PrivilegeEscalation", input.match.tags has "Impact", "Impact", input.match.tags has "CommandAndControl", "CommandandControl", input.match.tags has "Discovery", "Discovery", input.match.tags has "InitialAccess", "InitialAccess", ""),
Techniques = pack_array(extract(@"[A-Za-z]\d{4}", 0, tostring(input.match.tags))),
JamfPro = case(input.match.actions has "SmartGroup", "Workflow with Jamf Pro", input.match.actions has "Prevented", "No workflow, Prevented by Protect", "No workflow")
JamfPro =input.match.actions has "SmartGroup""Workflow with Jamf Pro"input.match.actions has "Prevented""No workflow, Prevented by Protect""No workflow"Tactics =input.match.tags has "Execution""Execution"input.match.tags has "Visibility""Visibility"input.match.tags has "Persistence""Persistence"input.match.tags has "LateralMovement""LateralMovement"input.match.tags has "CredentialAccess""CredentialAcccess"input.match.tags has "DefenseEvasion""DefenseEvasion"input.match.tags has "PrivilegeEscalation""PrivilegeEscalation"input.match.tags has "Impact""Impact"input.match.tags has "CommandAndControl""CommandandControl"input.match.tags has "Discovery""Discovery"input.match.tags has "InitialAccess""InitialAccess"""Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Host_IPs | extend |
JamfPro | extend |
Tactics | extend |
Tags | extend |
Techniques | extend |
algorithm | extend |
NGINX - Sql injection patterns
#'Detects possible sql injection patterns'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: 3bac451d-f919-4c92-9be7-694990e0ca4b
name: NGINX - Sql injection patterns
description: |
'Detects possible sql injection patterns'
severity: High
status: Available
requiredDataConnectors:
- connectorId: CustomLogsAma
dataTypes:
- NGINX_CL
queryFrequency: 10m
queryPeriod: 10m
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1190
query: |
let sql_patterns = dynamic([@"1/*'*/", @"1'||'asd'||'", @"'1'='1", @"1' or '1'='1", @"1 or 1=1", @"1=1", @"1/*!1111'*/", @"'or''='"]);
NGINXHTTPServer
| where UrlOriginal has_any (sql_patterns)
| extend IPCustomEntity = SrcIpAddr, UrlCustomEntity = UrlOriginal
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPCustomEntity
- entityType: URL
fieldMappings:
- identifier: Url
columnName: UrlCustomEntity
version: 1.0.3
kind: Scheduled
Stages and Predicates
Let Binding: sql_patterns
let sql_patterns = dynamic([@"1/*'*/", @"1'||'asd'||'", @"'1'='1", @"1' or '1'='1", @"1 or 1=1", @"1=1", @"1/*!1111'*/", @"'or''='"]);
Stage 1: source
NGINXHTTPServer
Stage 2: where
| where UrlOriginal has_any (sql_patterns)
Stage 3: extend
| extend IPCustomEntity = SrcIpAddr, UrlCustomEntity = UrlOriginal
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
UrlOriginal | match |
| field:"UrlOriginal" kind:match |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
IPCustomEntity | extend |
UrlCustomEntity | extend |
Pathlock TDnR - Critical File Integrity Changes
#Detects changes to checksums of critical SAP files, forwarded by Pathlock Threat Detection and Response. File integrity violations may indicate malware installation, unauthorized patching, or tampering with SAP executables and configuration files.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Stealth | |
| Defense Impairment |
Rule Body
id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c24
name: Pathlock TDnR - Critical File Integrity Changes
kind: Scheduled
description: >-
Detects changes to checksums of critical SAP files, forwarded by Pathlock Threat Detection
and Response. File integrity violations may indicate malware installation, unauthorized
patching, or tampering with SAP executables and configuration files.
severity: High
status: Available
requiredDataConnectors:
- connectorId: Pathlock_TDnR
dataTypes:
- Pathlock_TDnR_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
- Persistence
relevantTechniques:
- T1562
- T1036
query: |
Pathlock_TDnR_CL
| where DataSource == "FILE_CHECKSUM"
| project TimeGenerated, Sysid, DataSource, Eventid, Instance, Hostname, Bname,
Tcode, Report, Area, Subid, SrcIp, DestIp, AffectedUser, LogLine,
MsgType, MsgId, MsgNo, MessageV1, MessageV2, MessageV3, MessageV4, CentralTs
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: Bname
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: Hostname
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIp
eventGroupingSettings:
aggregationKind: SingleAlert
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 5h
matchingMethod: AnyAlert
groupByEntities: []
groupByAlertDetails: []
groupByCustomDetails: []
suppressionDuration: 5h
suppressionEnabled: false
version: 1.0.0
Stages and Predicates
Stage 1: source
Pathlock_TDnR_CL
Stage 2: where
| where DataSource == "FILE_CHECKSUM"
Stage 3: project
| project TimeGenerated, Sysid, DataSource, Eventid, Instance, Hostname, Bname,
Tcode, Report, Area, Subid, SrcIp, DestIp, AffectedUser, LogLine,
MsgType, MsgId, MsgNo, MessageV1, MessageV2, MessageV3, MessageV4, CentralTs
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DataSource | eq |
| field:"DataSource" kind:eq value:"FILE_CHECKSUM" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AffectedUser | project |
Area | project |
Bname | project |
CentralTs | project |
DataSource | project |
DestIp | project |
Eventid | project |
Hostname | project |
Instance | project |
LogLine | project |
MessageV1 | project |
MessageV2 | project |
MessageV3 | project |
MessageV4 | project |
MsgId | project |
MsgNo | project |
MsgType | project |
Report | project |
SrcIp | project |
Subid | project |
Sysid | project |
Tcode | project |
TimeGenerated | project |
Pathlock TDnR - Function Module Tested in Production
#Detects execution of SAP function modules in a test environment context within production systems, forwarded by Pathlock Threat Detection and Response. This activity may indicate unauthorized code execution, exploitation of function module interfaces, or abuse of debugging capabilities.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Execution |
Rule Body
id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c25
name: Pathlock TDnR - Function Module Tested in Production
kind: Scheduled
description: >-
Detects execution of SAP function modules in a test environment context within production systems,
forwarded by Pathlock Threat Detection and Response. This activity may indicate unauthorized
code execution, exploitation of function module interfaces, or abuse of debugging capabilities.
severity: High
status: Available
requiredDataConnectors:
- connectorId: Pathlock_TDnR
dataTypes:
- Pathlock_TDnR_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Execution
relevantTechniques:
- T1059
query: |
Pathlock_TDnR_CL
| where DataSource == "FUNCTION_MODULE_TEST"
| project TimeGenerated, Sysid, DataSource, Eventid, Instance, Hostname, Bname,
Tcode, Report, Area, Subid, SrcIp, DestIp, AffectedUser, LogLine,
MsgType, MsgId, MsgNo, MessageV1, MessageV2, MessageV3, MessageV4, CentralTs
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: Bname
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: Hostname
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIp
eventGroupingSettings:
aggregationKind: SingleAlert
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 5h
matchingMethod: AnyAlert
groupByEntities: []
groupByAlertDetails: []
groupByCustomDetails: []
suppressionDuration: 5h
suppressionEnabled: false
version: 1.0.0
Stages and Predicates
Stage 1: source
Pathlock_TDnR_CL
Stage 2: where
| where DataSource == "FUNCTION_MODULE_TEST"
Stage 3: project
| project TimeGenerated, Sysid, DataSource, Eventid, Instance, Hostname, Bname,
Tcode, Report, Area, Subid, SrcIp, DestIp, AffectedUser, LogLine,
MsgType, MsgId, MsgNo, MessageV1, MessageV2, MessageV3, MessageV4, CentralTs
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DataSource | eq |
| field:"DataSource" kind:eq value:"FUNCTION_MODULE_TEST" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AffectedUser | project |
Area | project |
Bname | project |
CentralTs | project |
DataSource | project |
DestIp | project |
Eventid | project |
Hostname | project |
Instance | project |
LogLine | project |
MessageV1 | project |
MessageV2 | project |
MessageV3 | project |
MessageV4 | project |
MsgId | project |
MsgNo | project |
MsgType | project |
Report | project |
SrcIp | project |
Subid | project |
Sysid | project |
Tcode | project |
TimeGenerated | project |
SUNBURST suspicious SolarWinds child processes
#Identifies suspicious child processes of SolarWinds.Orion.Core.BusinessLayer.dll that may be evidence of the SUNBURST backdoor References: - https://www.fireeye.com/blog/threat-research/2020/12/evasive-attacker-leverages-solarwinds-supply-chain-compromises-with-sunburst-backdoor.html - https://gist.github.com/olafhartong/71ffdd4cab4b6acd5cbcd1a0691ff82f
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Execution | No specific technique |
| Persistence | No specific technique |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Microsoft Defender for Endpoint | DeviceProcessEvents action any: Process activity |
Rule Body
id: 4a3073ac-7383-48a9-90a8-eb6716183a54
name: SUNBURST suspicious SolarWinds child processes
description: |
Identifies suspicious child processes of SolarWinds.Orion.Core.BusinessLayer.dll that may be evidence of the SUNBURST backdoor
References:
- https://www.fireeye.com/blog/threat-research/2020/12/evasive-attacker-leverages-solarwinds-supply-chain-compromises-with-sunburst-backdoor.html
- https://gist.github.com/olafhartong/71ffdd4cab4b6acd5cbcd1a0691ff82f
severity: Medium
requiredDataConnectors:
- connectorId: MicrosoftThreatProtection
dataTypes:
- DeviceProcessEvents
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Execution
- Persistence
tags:
- Solorigate
- NOBELIUM
query: |
let excludeProcs = dynamic([@"\SolarWinds\Orion\APM\APMServiceControl.exe", @"\SolarWinds\Orion\ExportToPDFCmd.Exe", @"\SolarWinds.Credentials\SolarWinds.Credentials.Orion.WebApi.exe", @"\SolarWinds\Orion\Topology\SolarWinds.Orion.Topology.Calculator.exe", @"\SolarWinds\Orion\Database-Maint.exe", @"\SolarWinds.Orion.ApiPoller.Service\SolarWinds.Orion.ApiPoller.Service.exe", @"\Windows\SysWOW64\WerFault.exe"]);
DeviceProcessEvents
| where InitiatingProcessFileName =~ "solarwinds.businesslayerhost.exe"
| where not(FolderPath has_any (excludeProcs))
| extend
timestamp = TimeGenerated,
InitiatingProcessAccountUPNSuffix = tostring(split(InitiatingProcessAccountUpn, "@")[1]),
Algorithm = "MD5"
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: InitiatingProcessAccountName
- identifier: NTDomain
columnName: InitiatingProcessAccountDomain
- identifier: Sid
columnName: InitiatingProcessAccountSid
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: DeviceName
- entityType: FileHash
fieldMappings:
- identifier: Algorithm
columnName: Algorithm
- identifier: Value
columnName: MD5
version: 1.0.4
kind: Scheduled
metadata:
source:
kind: Community
author:
name: pemontto
support:
tier: Community
categories:
domains: [ "Security - Threat Protection" ]
Stages and Predicates
Let Binding: excludeProcs
let excludeProcs = dynamic([@"\SolarWinds\Orion\APM\APMServiceControl.exe", @"\SolarWinds\Orion\ExportToPDFCmd.Exe", @"\SolarWinds.Credentials\SolarWinds.Credentials.Orion.WebApi.exe", @"\SolarWinds\Orion\Topology\SolarWinds.Orion.Topology.Calculator.exe", @"\SolarWinds\Orion\Database-Maint.exe", @"\SolarWinds.Orion.ApiPoller.Service\SolarWinds.Orion.ApiPoller.Service.exe", @"\Windows\SysWOW64\WerFault.exe"]);
Stage 1: source
DeviceProcessEvents
Stage 2: where
| where InitiatingProcessFileName =~ "solarwinds.businesslayerhost.exe"
Stage 3: where
| where not(FolderPath has_any (excludeProcs))
Stage 4: extend
| extend
timestamp = TimeGenerated,
InitiatingProcessAccountUPNSuffix = tostring(split(InitiatingProcessAccountUpn, "@")[1]),
Algorithm = "MD5"
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
InitiatingProcessFileName | eq |
| field:"parent_process_basename" kind:eq value:"solarwinds.businesslayerhost.exe" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
FolderPath | match | \SolarWinds\Orion\APM\APMServiceControl.exe, \SolarWinds\Orion\ExportToPDFCmd.Exe, \SolarWinds.Credentials\SolarWinds.Credentials.Orion.WebApi.exe, \SolarWinds\Orion\Topology\SolarWinds.Orion.Topology.Calculator.exe, \SolarWinds\Orion\Database-Maint.exe, \SolarWinds.Orion.ApiPoller.Service\SolarWinds.Orion.ApiPoller.Service.exe, \Windows\SysWOW64\WerFault.exe | excludes:FolderPath |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Algorithm | extend |
InitiatingProcessAccountUPNSuffix | extend |
timestamp | extend |
TI Map Email entity to AzureActivity
#'Identifies a match in AzureActivity table from any Email IOC from TI'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: a9a4d1ee-0f52-4a1f-8def-a2fb4462104c
name: TI Map Email entity to AzureActivity
description: |
'Identifies a match in AzureActivity table from any Email IOC from TI'
severity: Medium
requiredDataConnectors:
- connectorId: AzureActivity
dataTypes:
- AzureActivity
- connectorId: ThreatIntelligence
dataTypes:
- ThreatIntelIndicators
- connectorId: ThreatIntelligenceTaxii
dataTypes:
- ThreatIntelIndicators
- connectorId: MicrosoftDefenderThreatIntelligence
dataTypes:
- ThreatIntelIndicators
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1566
query: |
let dt_lookBack = 1h;
let ioc_lookBack = 14d;
let emailregex = @'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$';
ThreatIntelIndicators
//Filtering the table for Email related IOCs
| extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))
| where IndicatorType == "email-addr"
| extend EmailSenderAddress = ObservableValue
| extend EmailSourceDomain = substring(EmailSenderAddress, indexof(EmailSenderAddress, "@") + 1, strlen(EmailSenderAddress) - indexof(EmailSenderAddress, "@") - 1)
| extend Url = iff(ObservableKey == "url:value", 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))
| project-reorder *, IsActive, TrafficLightProtocolLevel, EmailSenderAddress, EmailSourceDomain, Type
// using innerunique to keep perf fast and result set low, we only need one match to indicate potential malicious activity that needs to be investigated
| join kind=innerunique (
AzureActivity | where TimeGenerated >= ago(dt_lookBack) and isnotempty(Caller)
| extend Caller = tolower(Caller)
| where Caller matches regex emailregex
| extend AzureActivity_TimeGenerated = TimeGenerated
)
on $left.EmailSenderAddress == $right.Caller
| where AzureActivity_TimeGenerated < ValidUntil
| summarize AzureActivity_TimeGenerated = arg_max(AzureActivity_TimeGenerated, *) by Id, Caller
| extend Description = tostring(parse_json(Data).description)
| extend ActivityGroupNames = extract(@"ActivityGroup:(\S+)", 1, tostring(parse_json(Data).labels))
| project AzureActivity_TimeGenerated, Description, ActivityGroupNames, Id, ValidUntil, Confidence, EmailSenderAddress, EmailSourceDomain, Caller, CallerIpAddress, Url
| extend Name = tostring(split(Caller, '@', 0)[0]), UPNSuffix = tostring(split(Caller, '@', 1)[0])
| extend timestamp = AzureActivity_TimeGenerated
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: Caller
- identifier: Name
columnName: Name
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: CallerIpAddress
- entityType: URL
fieldMappings:
- identifier: Url
columnName: Url
version: 1.2.12
kind: Scheduled
Stages and Predicates
Parameters
let dt_lookBack = 1h;
let ioc_lookBack = 14d;
let emailregex = @'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$';
Stage 1: source
ThreatIntelIndicators
Stage 2: extend
| extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))
Stage 3: where
| where IndicatorType == "email-addr"
Stage 4: extend (4 consecutive steps)
| extend EmailSenderAddress = ObservableValue
| extend EmailSourceDomain = substring(EmailSenderAddress, indexof(EmailSenderAddress, "@") + 1, strlen(EmailSenderAddress) - indexof(EmailSenderAddress, "@") - 1)
| extend Url = iff(ObservableKey == "url:value", ObservableValue, "")
| extend TrafficLightProtocolLevel = tostring(parse_json(AdditionalFields).TLPLevel)
Stage 5: where
| where TimeGenerated >= ago(ioc_lookBack)
Stage 6: summarize
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id, ObservableValue
Stage 7: where
| where IsActive and (ValidUntil > now() or isempty(ValidUntil))
Stage 8: project-reorder
| project-reorder *, IsActive, TrafficLightProtocolLevel, EmailSenderAddress, EmailSourceDomain, Type
Stage 9: join
| join kind=innerunique (
AzureActivity | where TimeGenerated >= ago(dt_lookBack) and isnotempty(Caller)
| extend Caller = tolower(Caller)
| where Caller matches regex emailregex
| extend AzureActivity_TimeGenerated = TimeGenerated
)
on $left.EmailSenderAddress == $right.Caller
Stage 10: where
where AzureActivity_TimeGenerated < ValidUntil
Stage 11: summarize
summarize AzureActivity_TimeGenerated by Id, Caller
Stage 12: extend
extend Description
Stage 13: extend
extend ActivityGroupNames
Stage 14: project
project ActivityGroupNames, AzureActivity_TimeGenerated, Caller, CallerIpAddress, Confidence, Description, EmailSenderAddress, EmailSourceDomain, Id, Url, ValidUntil
Stage 15: extend
extend Name, UPNSuffix
Stage 16: extend
extend timestamp
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Caller | is_not_null | field:"Caller" kind:is_not_null | |
Caller | regex_match |
| field:"Caller" kind:regex_match value:"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+$" |
IndicatorType | eq |
| field:"IndicatorType" kind:eq value:"email-addr" |
IsActive | eq |
| field:"IsActive" kind:eq value:"true" |
ValidUntil | is_null | field:"ValidUntil" kind:is_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActivityGroupNames | project |
AzureActivity_TimeGenerated | project |
Caller | project |
CallerIpAddress | project |
Confidence | project |
Description | project |
EmailSenderAddress | project |
EmailSourceDomain | project |
Id | project |
Url | project |
ValidUntil | project |
Name | extend |
UPNSuffix | extend |
timestamp | extend |