Detection rules › Kusto
Kusto rules: 29
Anomalous Single Factor Signin
#Detects successful signins using single factor authentication where the device, location, and ASN are abnormal. Single factor authentications pose an opportunity to access compromised accounts, investigate these for anomalous occurrencess. Ref: https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-devices#non-compliant-device-sign-in
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Telemetry Coverage
Rules Detecting the Same Action
These rules filter on the same operation.
- [Entra ID] Suspicious Continuous OAuth Token Usage (Kusto)
- Anomalous sign-in location by user account and authenticating application (Kusto)
- Authentications of Privileged Accounts Outside of Expected Controls (Kusto)
- Azure Portal sign in from another Azure Tenant (Kusto)
- Azure Service Principal Sign-In Followed by Arc Cluster Credential Access (Elastic)
- Azure SignIn via Legacy Authentication Protocol (Panther)
- Cisco - firewall block but success logon to Microsoft Entra ID (Kusto)
- Detect non-admin requesting token for admin applications (Kusto)
Rule Body
id: f7c3f5c8-71ea-49ff-b8b3-148f0e346291
name: Anomalous Single Factor Signin
description: |
'Detects successful signins using single factor authentication where the device, location, and ASN are abnormal.
Single factor authentications pose an opportunity to access compromised accounts, investigate these for anomalous occurrencess.
Ref: https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-devices#non-compliant-device-sign-in'
severity: Low
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- SigninLogs
queryFrequency: 1d
queryPeriod: 7d
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1078.004
tags:
- AADSecOpsGuide
query: |
let known_locations = (SigninLogs
| where TimeGenerated between(ago(7d)..ago(1d))
| where ResultType == 0
| extend LocationDetail = strcat(Location, "-", LocationDetails.state)
| summarize by LocationDetail);
let known_asn = (SigninLogs
| where TimeGenerated between(ago(7d)..ago(1d))
| where ResultType == 0
| summarize by AutonomousSystemNumber);
SigninLogs
| where TimeGenerated > ago(1d)
| where ResultType == 0
| where isempty(DeviceDetail.deviceId)
| where AuthenticationRequirement == "singleFactorAuthentication"
| extend LocationParsed = parse_json(LocationDetails), DeviceParsed = parse_json(DeviceDetail)
| extend City = tostring(LocationParsed.city), State = tostring(LocationParsed.state)
| extend LocationDetail = strcat(Location, "-", State)
| extend DeviceId = tostring(DeviceParsed.deviceId), DeviceName=tostring(DeviceParsed.displayName), OS=tostring(DeviceParsed.operatingSystem), Browser=tostring(DeviceParsed.browser)
| where AutonomousSystemNumber !in (known_asn) and LocationDetail !in (known_locations)
| project TimeGenerated, Type, UserId, UserDisplayName, UserPrincipalName, IPAddress, Location, State, City, ResultType, ResultDescription, AppId, AppDisplayName, AuthenticationRequirement, ConditionalAccessStatus, ResourceDisplayName, ClientAppUsed, Identity, HomeTenantId, ResourceTenantId, Status, UserAgent, DeviceId, DeviceName, OS, Browser, MfaDetail
| extend Name = tostring(split(UserPrincipalName,'@',0)[0]), UPNSuffix = tostring(split(UserPrincipalName,'@',1)[0])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: UserPrincipalName
- identifier: Name
columnName: Name
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPAddress
- entityType: CloudApplication
fieldMappings:
- identifier: AppId
columnName: AppId
- identifier: Name
columnName: AppDisplayName
version: 1.0.6
kind: Scheduled
metadata:
source:
kind: Community
author:
name: Pete Bryan
support:
tier: Community
categories:
domains: [ "Security - Others" ]
Stages and Predicates
Let Binding: known_locations
let known_locations = (SigninLogs
| where TimeGenerated between(ago(7d)..ago(1d))
| where ResultType == 0
| extend LocationDetail = strcat(Location, "-", LocationDetails.state)
| summarize by LocationDetail);
Let Binding: known_asn
let known_asn = (SigninLogs
| where TimeGenerated between(ago(7d)..ago(1d))
| where ResultType == 0
| summarize by AutonomousSystemNumber);
Stage 1: source
SigninLogs
Stage 2: where
| where TimeGenerated > ago(1d)
Stage 3: where
| where ResultType == 0
Stage 4: where
| where isempty(DeviceDetail.deviceId)
Stage 5: where
| where AuthenticationRequirement == "singleFactorAuthentication"
Stage 6: extend (4 consecutive steps)
| extend LocationParsed = parse_json(LocationDetails), DeviceParsed = parse_json(DeviceDetail)
| extend City = tostring(LocationParsed.city), State = tostring(LocationParsed.state)
| extend LocationDetail = strcat(Location, "-", State)
| extend DeviceId = tostring(DeviceParsed.deviceId), DeviceName=tostring(DeviceParsed.displayName), OS=tostring(DeviceParsed.operatingSystem), Browser=tostring(DeviceParsed.browser)
Stage 7: where
| where AutonomousSystemNumber !in (known_asn) and LocationDetail !in (known_locations)
Stage 8: project
| project TimeGenerated, Type, UserId, UserDisplayName, UserPrincipalName, IPAddress, Location, State, City, ResultType, ResultDescription, AppId, AppDisplayName, AuthenticationRequirement, ConditionalAccessStatus, ResourceDisplayName, ClientAppUsed, Identity, HomeTenantId, ResourceTenantId, Status, UserAgent, DeviceId, DeviceName, OS, Browser, MfaDetail
Stage 9: extend
| extend Name = tostring(split(UserPrincipalName,'@',0)[0]), UPNSuffix = tostring(split(UserPrincipalName,'@',1)[0])
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AuthenticationRequirement | eq |
| field:"AuthenticationRequirement" kind:eq value:"singleFactorAuthentication" |
DeviceDetail.deviceId | is_null | field:"DeviceDetail.deviceId" kind:is_null | |
ResultType | eq |
| field:"ResultType" kind:eq value:"0" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
AutonomousSystemNumber | eq | known_asn | excludes:AutonomousSystemNumber field:"AutonomousSystemNumber" value:"known_asn" |
LocationDetail | eq | known_locations | excludes:LocationDetail field:"LocationDetail" value:"known_locations" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AppDisplayName | project |
AppId | project |
AuthenticationRequirement | project |
Browser | project |
City | project |
ClientAppUsed | project |
ConditionalAccessStatus | project |
DeviceId | project |
DeviceName | project |
HomeTenantId | project |
IPAddress | project |
Identity | project |
Location | project |
MfaDetail | project |
OS | project |
ResourceDisplayName | project |
ResourceTenantId | project |
ResultDescription | project |
ResultType | project |
State | project |
Status | project |
TimeGenerated | project |
Type | project |
UserAgent | project |
UserDisplayName | project |
UserId | project |
UserPrincipalName | project |
Name | extend |
UPNSuffix | extend |
BloodHound Attack Path Finding - AKS Contributor Role on Tier Zero Managed Cluster
#This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.
BloodHound Attack Path Finding - AKS Contributor Role on Tier Zero Managed Cluster
Rule Body
id: 77d9d959-e899-4a43-a376-d2e4059ca8f4
name: BloodHound Attack Path Finding - AKS Contributor Role on Tier Zero Managed Cluster
description: BloodHound Attack Path Finding - AKS Contributor Role on Tier Zero Managed
Cluster
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: BloodHoundEnterprise
dataTypes:
- BHEAttackPathsData_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: BHEAttackPathsData_CL | where Finding == "AzureT0AKSContributor"
entityMappings:
- entityType: URL
fieldMappings:
- identifier: Url
columnName: domain_name
customDetails:
Description: ShortDescription
Remediation: ShortRemediation
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
BHEAttackPathsData_CL
Stage 2: where
| where Finding == "AzureT0AKSContributor"
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Finding | eq |
| field:"Finding" kind:eq value:"AzureT0AKSContributor" |
Cisco SEG - Possible outbreak
#'Detects possible outbreak activity.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: 53242559-95ea-4d4c-b003-107e8f06304b
name: Cisco SEG - Possible outbreak
description: |
'Detects possible outbreak activity.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: CefAma
dataTypes:
- CommonSecurityLog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1566
query: |
CiscoSEGEvent
| where NetworkDirection =~ 'Incoming'
| where tostring(AdditionalFields) has 'ESAOFVerdict'
| extend of_verdict = extract(@'ESAOFVerdict":"(NOT_EVALUATED|POSITIVE|NEGATIVE)"', 1, tostring(AdditionalFields))
| where of_verdict =~ 'POSITIVE'
| extend AccountCustomEntity = DstUserName
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountCustomEntity
version: 1.0.3
kind: Scheduled
Stages and Predicates
Stage 1: source
CiscoSEGEvent
Stage 2: where
| where NetworkDirection =~ 'Incoming'
Stage 3: where
| where tostring(AdditionalFields) has 'ESAOFVerdict'
Stage 4: extend
| extend of_verdict = extract(@'ESAOFVerdict":"(NOT_EVALUATED|POSITIVE|NEGATIVE)"', 1, tostring(AdditionalFields))
Stage 5: where
| where of_verdict =~ 'POSITIVE'
Stage 6: extend
| extend AccountCustomEntity = DstUserName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AdditionalFields | match |
| field:"AdditionalFields" kind:match value:"ESAOFVerdict" |
NetworkDirection | eq |
| field:"NetworkDirection" kind:eq value:"Incoming" |
of_verdict | eq |
| field:"of_verdict" kind:eq value:"POSITIVE" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
of_verdict | extend |
AccountCustomEntity | extend |
Cisco WSA - Access to unwanted site
#'Detects when users attempting to access sites from high risk category.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: 38029e86-030c-46c4-8a91-a2be7c74d74c
name: Cisco WSA - Access to unwanted site
description: |
'Detects when users attempting to access sites from high risk category.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: SyslogAma
datatypes:
- Syslog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1566
query: |
let risky_sites = dynamic(['IW_adlt', 'IW_hack', 'IW_porn']);
CiscoWSAEvent
| where UrlCategory in~ (risky_sites)
| where DvcAction =~ 'DEFAULT_CASE'
| extend AccountCustomEntity = SrcUserName
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountCustomEntity
version: 1.0.3
kind: Scheduled
Stages and Predicates
Parameters
let risky_sites = dynamic(['IW_adlt', 'IW_hack', 'IW_porn']);
Stage 1: source
CiscoWSAEvent
Stage 2: where
| where UrlCategory in~ (risky_sites)
Stage 3: where
| where DvcAction =~ 'DEFAULT_CASE'
Stage 4: extend
| extend AccountCustomEntity = SrcUserName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DvcAction | eq |
| field:"DvcAction" kind:eq value:"DEFAULT_CASE" |
UrlCategory | in |
| field:"UrlCategory" kind:in |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountCustomEntity | extend |
Critical Severity Incident
#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.
'Triggers an incident for every Morphisec alert whose attacks severity is critical.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Execution |
Rule Body
id: 4f1c9e6e-8b6b-4d2a-9f3e-123456789abc
name: Critical Severity Incident
description: |
'Triggers an incident for every Morphisec alert whose attacks severity is critical.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: MorphisecCCF
dataTypes:
- Morphisec
queryFrequency: 10m
queryPeriod: 10m
triggerOperator: gt
triggerThreshold: 0
suppressionDuration: 5h
suppressionEnabled: false
tactics:
- Execution
- DefenseEvasion
relevantTechniques:
- T1059
- T1204
query: |
MorphisecAlerts_CL
| where threatMessageArrivalTime >= ago(10m)
| where attackSeverity == "CRITICAL"
| summarize arg_max(threatMessageArrivalTime, *) by id
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: hostname
- entityType: Process
fieldMappings:
- identifier: CommandLine
columnName: processCommandLine
eventGroupingSettings:
aggregationKind: SingleAlert
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: 5h
matchingMethod: AllEntities
alertDetailsOverride:
alertDisplayNameFormat: "Critical alert detected: {{threatSubType}}"
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
MorphisecAlerts_CL
Stage 2: where
| where threatMessageArrivalTime >= ago(10m)
Stage 3: where
| where attackSeverity == "CRITICAL"
Stage 4: summarize
| summarize arg_max(threatMessageArrivalTime, *) by id
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
attackSeverity | eq |
| field:"attackSeverity" kind:eq value:"CRITICAL" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
id | summarize |
CYFIRMA - High severity TOR Node Network Indicators - Monitor Recommended Rule
#This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.
"This KQL query identifies network-based indicators from CYFIRMA intelligence that are associated with the role 'TOR'. These indicators may include IP addresses, domains, and URLs related to Tor network activity. Threat actors often use Tor for anonymous communication, command and control, data exfiltration, and evasion of network defenses."
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Reconnaissance | |
| Initial Access | |
| Persistence | |
| Command & Control | |
| Exfiltration |
Rule Body
id: 6f107cf8-02f9-4440-b5d8-1235293e5ad7
name: CYFIRMA - High severity TOR Node Network Indicators - Monitor Recommended Rule
description: |
"This KQL query identifies network-based indicators from CYFIRMA intelligence that are associated with the role 'TOR'.
These indicators may include IP addresses, domains, and URLs related to Tor network activity.
Threat actors often use Tor for anonymous communication, command and control, data exfiltration, and evasion of network defenses."
version: 1.0.1
kind: Scheduled
severity: High
enabled: false
requiredDataConnectors:
- connectorId: CyfirmaCyberIntelligenceDC
dataTypes:
- CyfirmaIndicators_CL
query: |
//TOR Node Network Indicators - Monitor Recommended
let timeFrame= 5m;
CyfirmaIndicators_CL
| where ConfidenceScore >= 80
and TimeGenerated between (ago(timeFrame) .. now())
and pattern !contains 'file:hashes' and RecommendedActions has 'Monitor' and Roles has 'TOR'
| extend IPv4 = extract(@"ipv4-addr:value\s*=\s*'([^']+)'", 1, pattern)
| extend IPv6 = extract(@"ipv6-addr:value\s*=\s*'([^']+)'", 1, pattern)
| extend URL = extract(@"url:value\s*=\s*'([^']+)'", 1, pattern)
| extend Domain = extract(@"domain-name:value\s*=\s*'([^']+)'", 1, pattern)
| extend parsed = parse_json(extensions)
| extend extensionKeys = bag_keys(parsed)
| mv-expand extensionKeys
| extend extensionKeyStr = tostring(extensionKeys)
| extend ext = parsed[extensionKeyStr]
| extend props = ext.properties
| extend
extension_id = extensionKeyStr,
ASN_Owner = props.asn_owner,
ASN = props.asn,
ProviderName = 'CYFIRMA',
ProductName = 'DeCYFIR/DeTCT'
| project
IPv4,
IPv6,
URL,
Domain,
ThreatActors,
RecommendedActions,
Sources,
Roles,
Country,
IPAbuse,
name,
Description,
ConfidenceScore,
IndicatorID,
created,
modified,
valid_from,
Tags,
ThreatType,
TimeGenerated,
SecurityVendors,
ProductName,
ProviderName
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: GreaterThan
triggerThreshold: 0
suppressionDuration: 5m
suppressionEnabled: true
tactics:
- CommandAndControl
- Exfiltration
- InitialAccess
- Persistence
- Reconnaissance
relevantTechniques:
- T1090
- T1572
- T1048
- T1071
- T1189
- T1505
- T1595
- T1090.003
- T1048.002
- T1071.001
- T1505.003
- T1595.002
alertDetailsOverride:
alertDisplayNameFormat: "High-Confidence TOR Node Network Indicators - Monitor Recommended - {{name}} "
alertDescriptionFormat: "{{Description}} - {{name}} "
alertDynamicProperties:
- alertProperty: ProductName
value: ProductName
- alertProperty: ProviderName
value: ProviderName
customDetails:
ThreatActors: ThreatActors
Sources: Sources
RecommendedActions: RecommendedActions
Roles: Roles
Country: Country
Description: Description
ConfidenceScore: ConfidenceScore
SecurityVendors: SecurityVendors
IndicatorID: IndicatorID
Created: created
Modified: modified
ValidFrom: valid_from
Tags: Tags
ThreatType: ThreatType
TimeGenerated: TimeGenerated
IPAbuse: IPAbuse
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPv4
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPv6
- entityType: DNS
fieldMappings:
- identifier: DomainName
columnName: Domain
- entityType: URL
fieldMappings:
- identifier: Url
columnName: URL
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
eventGroupingSettings:
aggregationKind: AlertPerResult
Stages and Predicates
Parameters
let timeFrame = 5m;
Stage 1: source
CyfirmaIndicators_CL
Stage 2: where
| where ConfidenceScore >= 80
and TimeGenerated between (ago(timeFrame) .. now())
and pattern !contains 'file:hashes' and RecommendedActions has 'Monitor' and Roles has 'TOR'
Stage 3: extend (6 consecutive steps)
| extend IPv4 = extract(@"ipv4-addr:value\s*=\s*'([^']+)'", 1, pattern)
| extend IPv6 = extract(@"ipv6-addr:value\s*=\s*'([^']+)'", 1, pattern)
| extend URL = extract(@"url:value\s*=\s*'([^']+)'", 1, pattern)
| extend Domain = extract(@"domain-name:value\s*=\s*'([^']+)'", 1, pattern)
| extend parsed = parse_json(extensions)
| extend extensionKeys = bag_keys(parsed)
Stage 4: mv-expand
| mv-expand extensionKeys
Stage 5: extend (4 consecutive steps)
| extend extensionKeyStr = tostring(extensionKeys)
| extend ext = parsed[extensionKeyStr]
| extend props = ext.properties
| extend
extension_id = extensionKeyStr,
ASN_Owner = props.asn_owner,
ASN = props.asn,
ProviderName = 'CYFIRMA',
ProductName = 'DeCYFIR/DeTCT'
Stage 6: project
| project
IPv4,
IPv6,
URL,
Domain,
ThreatActors,
RecommendedActions,
Sources,
Roles,
Country,
IPAbuse,
name,
Description,
ConfidenceScore,
IndicatorID,
created,
modified,
valid_from,
Tags,
ThreatType,
TimeGenerated,
SecurityVendors,
ProductName,
ProviderName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ConfidenceScore | ge |
| field:"ConfidenceScore" kind:ge value:"80" |
RecommendedActions | match |
| field:"RecommendedActions" kind:match value:"Monitor" |
Roles | match |
| field:"Roles" kind:match value:"TOR" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
pattern | contains | file:hashes | excludes:pattern field:"pattern" value:"file:hashes" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ConfidenceScore | project |
Country | project |
Description | project |
Domain | project |
IPAbuse | project |
IPv4 | project |
IPv6 | project |
IndicatorID | project |
ProductName | project |
ProviderName | project |
RecommendedActions | project |
Roles | project |
SecurityVendors | project |
Sources | project |
Tags | project |
ThreatActors | project |
ThreatType | project |
TimeGenerated | project |
URL | project |
created | project |
modified | project |
name | project |
valid_from | project |
CYFIRMA - Social and Public Exposure - Social Media Threats Activity Detected 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-severity social threat alerts from CYFIRMA related to impersonation, fake profiles, or malicious activities on social platforms that may target executives, brands, or employees. These threats can result in reputational damage, phishing, or social engineering attacks. Immediate investigation and takedown are recommended to minimize risk."
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Reconnaissance | |
| Resource Development | |
| Initial Access | |
| Impact | |
| Impact |
Rule Body
id: 4fe04459-13f1-4ff7-9b7c-f9be0c2aad6d
name: CYFIRMA - Social and Public Exposure - Social Media Threats Activity Detected Rule
description: |
"This rule detects high-severity social threat alerts from CYFIRMA related to impersonation, fake profiles, or malicious activities on social platforms that may target executives, brands, or employees.
These threats can result in reputational damage, phishing, or social engineering attacks.
Immediate investigation and takedown are recommended to minimize risk."
version: 1.0.1
kind: Scheduled
severity: High
requiredDataConnectors:
- connectorId: CyfirmaDigitalRiskAlertsConnector
dataTypes:
- CyfirmaSPESocialThreatAlerts_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- ResourceDevelopment
- Reconnaissance
- InitialAccess
- Impact
relevantTechniques:
- T1585.001
- T1593
- T1566
- T1582
- T1491
query: |
// High severity - Social and Public Exposure - Social Media Threats Activity Detected
let timeFrame = 5m;
CyfirmaSPESocialThreatAlerts_CL
| where severity == 'Critical' and TimeGenerated between (ago(timeFrame) .. now())
| extend
Description=description,
FirstSeen=first_seen,
LastSeen=last_seen,
RiskScore=risk_score,
AlertUID=alert_uid,
UID=uid,
AssetType=asset_type,
AssetValue=signature,
Source=source,
Impact=impact,
Recommendation=recommendation,
ProviderName='CYFIRMA',
ProductName='DeCYFIR/DeTCT',
AlertTitle=Alert_title
| project
TimeGenerated,
Description,
RiskScore,
FirstSeen,
LastSeen,
AlertUID,
UID,
AssetType,
AssetValue,
Source,
Impact,
Recommendation,
ProductName,
ProviderName,
AlertTitle
customDetails:
AssetType: AssetType
AssetValue: AssetValue
RiskScore: RiskScore
Source: Source
Impact: Impact
Description: Description
Recommendation: Recommendation
FirstSeen: FirstSeen
LastSeen: LastSeen
AlertUID: AlertUID
UID: UID
TimeGenerated: TimeGenerated
alertDetailsOverride:
alertDisplayNameFormat: "CYFIRMA - High Severity Alert: Social Media Threat Activity Detected - {{AlertTitle}} "
alertDescriptionFormat: "{{Description}} "
alertDynamicProperties:
- alertProperty: ProductName
value: ProductName
- alertProperty: ProviderName
value: ProviderName
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
CyfirmaSPESocialThreatAlerts_CL
Stage 2: where
| where severity == 'Critical' and TimeGenerated between (ago(timeFrame) .. now())
Stage 3: extend
| extend
Description=description,
FirstSeen=first_seen,
LastSeen=last_seen,
RiskScore=risk_score,
AlertUID=alert_uid,
UID=uid,
AssetType=asset_type,
AssetValue=signature,
Source=source,
Impact=impact,
Recommendation=recommendation,
ProviderName='CYFIRMA',
ProductName='DeCYFIR/DeTCT',
AlertTitle=Alert_title
Stage 4: project
| project
TimeGenerated,
Description,
RiskScore,
FirstSeen,
LastSeen,
AlertUID,
UID,
AssetType,
AssetValue,
Source,
Impact,
Recommendation,
ProductName,
ProviderName,
AlertTitle
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
severity | eq |
| field:"severity" kind:eq value:"Critical" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AlertTitle | project |
AlertUID | project |
AssetType | project |
AssetValue | project |
Description | project |
FirstSeen | project |
Impact | project |
LastSeen | project |
ProductName | project |
ProviderName | project |
Recommendation | project |
RiskScore | project |
Source | project |
TimeGenerated | project |
UID | project |
GitLab - SSO - Sign-Ins Burst
#This query relies on Microsoft Entra ID sign-in activity when Microsoft Entra ID is used for SSO with GitLab to highlights GitLab accounts associated with multiple authentications from different geographical locations in a short space of time.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
Telemetry Coverage
Rules Detecting the Same Action
These rules filter on the same operation.
- [Entra ID] Suspicious Continuous OAuth Token Usage (Kusto)
- Anomalous sign-in location by user account and authenticating application (Kusto)
- Anomalous Single Factor Signin (Kusto)
- Authentications of Privileged Accounts Outside of Expected Controls (Kusto)
- Azure Portal sign in from another Azure Tenant (Kusto)
- Azure Service Principal Sign-In Followed by Arc Cluster Credential Access (Elastic)
- Azure SignIn via Legacy Authentication Protocol (Panther)
- Cisco - firewall block but success logon to Microsoft Entra ID (Kusto)
Rule Body
id: 57b1634b-531d-4eab-a456-8b855887428f
name: GitLab - SSO - Sign-Ins Burst
description: |
'This query relies on Microsoft Entra ID sign-in activity when Microsoft Entra ID is used for SSO with GitLab to highlights GitLab accounts associated with multiple authentications from different geographical locations in a short space of time.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- SigninLogs
queryFrequency: 1h
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
relevantTechniques:
- T1110
query: |
let locationCountMin = 1;
let appRegistrationName = "GitLab";
SigninLogs
| where AppDisplayName == appRegistrationName
| where ResultType == 0
| where Location != ""
| summarize CountOfLocations = dcount(Location), Locations = make_set(Location) by User = Identity
| where CountOfLocations > locationCountMin
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: User
version: 1.0.1
kind: Scheduled
Stages and Predicates
Parameters
let locationCountMin = 1;
let appRegistrationName = "GitLab";
Stage 1: source
SigninLogs
Stage 2: where
| where AppDisplayName == appRegistrationName
Stage 3: where
| where ResultType == 0
Stage 4: where
| where Location != ""
Stage 5: summarize
| summarize CountOfLocations = dcount(Location), Locations = make_set(Location) by User = Identity
Stage 6: where
| where CountOfLocations > locationCountMin
Indicators
These rows show field, operator, and value matches.
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
CountOfLocations | summarize |
Locations | summarize |
User | summarize |
GTI - CISA Known Exploited Vulnerability Detected
#Detects vulnerabilities ingested from Google Threat Intelligence that are in the CISA Known Exploited Vulnerabilities (KEV) catalog, meaning CISA has confirmed active exploitation and mandated remediation for federal agencies. Uses the explicit CisaKnownExploited field from the GTIVulnerabilities parser, with Tags-based fallback for coverage.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Execution |
Rule Body
id: 650429cd-afc6-41a5-90e7-6e4a85b2335d
name: GTI - CISA Known Exploited Vulnerability Detected
description: |
Detects vulnerabilities ingested from Google Threat Intelligence that are in the CISA Known
Exploited Vulnerabilities (KEV) catalog, meaning CISA has confirmed active exploitation and
mandated remediation for federal agencies. Uses the explicit CisaKnownExploited field from the
GTIVulnerabilities parser, with Tags-based fallback for coverage.
severity: High
status: Available
requiredDataConnectors:
- connectorId: GTIVulnerabilitiesConnector
dataTypes:
- GTI_Vulnerabilities_CL
queryFrequency: 10m
queryPeriod: 10m
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
- Execution
- Impact
relevantTechniques:
- T1190
- T1203
query: |
GTIVulnerabilities
| where isnotempty(CisaKnownExploited)
or isnotempty(CisaKnownExploitedDueDate)
or isnotempty(CisaKnownExploitedAddedDate)
or isnotempty(CisaKnownExploitedRansomwareUse)
or (isnotempty(Tags) and tolower(tostring(Tags)) has_any ("cisa-kev", "cisa exploited", "cisa_exploited"))
| extend Cvssv3Score = coalesce(Cvssv3xBaseScore, Cvssv3xTranslatedBaseScore)
| extend
CisaKnownExploitedAddedDate = iif(isnotempty(CisaKnownExploitedAddedDate), datetime_add('second', toint(todouble(CisaKnownExploitedAddedDate)), datetime(1970-01-01)), datetime(null)),
CisaKnownExploitedDueDate = iif(isnotempty(CisaKnownExploitedDueDate), datetime_add('second', toint(todouble(CisaKnownExploitedDueDate)), datetime(1970-01-01)), datetime(null))
| extend
Vendor = tostring(Cpes[0].start_cpe.vendor),
Product = tostring(Cpes[0].start_cpe.product),
CpeVersion = tostring(Cpes[0].start_cpe.version),
VendorFixUrl = coalesce(tostring(VendorFixReferences[0].url), tostring(VendorFixReferences[0]))
| mv-apply _c = Cpes on (
where isnotnull(_c.start_cpe)
| summarize AffectedVendors = make_set(tostring(_c.start_cpe.vendor)),
AffectedProducts = make_set(tostring(_c.start_cpe.product))
)
| project
TimeGenerated,
CveId,
VulnName,
RiskRating,
Priority,
ExploitationState,
ExploitationConsequence,
Cvssv3Score,
Cvssv4xScore,
EpssScore,
EpssPercentile,
CisaKnownExploitedAddedDate,
CisaKnownExploitedDueDate,
CisaKnownExploitedRansomwareUse,
Vendor,
Product,
CpeVersion,
AffectedVendors,
AffectedProducts,
Description,
VendorFixUrl,
VendorFixReferences,
Tags,
Type
alertDetailsOverride:
alertDisplayNameFormat: 'GTI CISA KEV: {{CveId}}'
alertDescriptionFormat: 'CVE: {{CveId}} | Risk: {{RiskRating}} | State: {{ExploitationState}}'
entityMappings:
- entityType: URL
fieldMappings:
- identifier: Url
columnName: VendorFixUrl
customDetails:
CveId: CveId
RiskRating: RiskRating
Priority: Priority
ExploitState: ExploitationState
Cvssv3Score: Cvssv3Score
EpssScore: EpssScore
EpssPercentile: EpssPercentile
CisaAddedDate: CisaKnownExploitedAddedDate
CisaDueDate: CisaKnownExploitedDueDate
RansomwareUse: CisaKnownExploitedRansomwareUse
AffectedVendor: Vendor
AffectedProduct: Product
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: P1D
matchingMethod: Selected
groupByCustomDetails:
- CveId
eventGroupingSettings:
aggregationKind: AlertPerResult
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
GTIVulnerabilities
Stage 2: where
| where isnotempty(CisaKnownExploited)
or isnotempty(CisaKnownExploitedDueDate)
or isnotempty(CisaKnownExploitedAddedDate)
or isnotempty(CisaKnownExploitedRansomwareUse)
or (isnotempty(Tags) and tolower(tostring(Tags)) has_any ("cisa-kev", "cisa exploited", "cisa_exploited"))
Stage 3: extend (3 consecutive steps)
| extend Cvssv3Score = coalesce(Cvssv3xBaseScore, Cvssv3xTranslatedBaseScore)
| extend
CisaKnownExploitedAddedDate = iif(isnotempty(CisaKnownExploitedAddedDate), datetime_add('second', toint(todouble(CisaKnownExploitedAddedDate)), datetime(1970-01-01)), datetime(null)),
CisaKnownExploitedDueDate = iif(isnotempty(CisaKnownExploitedDueDate), datetime_add('second', toint(todouble(CisaKnownExploitedDueDate)), datetime(1970-01-01)), datetime(null))
| extend
Vendor = tostring(Cpes[0].start_cpe.vendor),
Product = tostring(Cpes[0].start_cpe.product),
CpeVersion = tostring(Cpes[0].start_cpe.version),
VendorFixUrl = coalesce(tostring(VendorFixReferences[0].url), tostring(VendorFixReferences[0]))
Stage 4: kusto:mv-apply
| mv-apply _c = Cpes on (
where isnotnull(_c.start_cpe)
| summarize AffectedVendors = make_set(tostring(_c.start_cpe.vendor)),
AffectedProducts = make_set(tostring(_c.start_cpe.product))
)
Stage 5: project
| project
TimeGenerated,
CveId,
VulnName,
RiskRating,
Priority,
ExploitationState,
ExploitationConsequence,
Cvssv3Score,
Cvssv4xScore,
EpssScore,
EpssPercentile,
CisaKnownExploitedAddedDate,
CisaKnownExploitedDueDate,
CisaKnownExploitedRansomwareUse,
Vendor,
Product,
CpeVersion,
AffectedVendors,
AffectedProducts,
Description,
VendorFixUrl,
VendorFixReferences,
Tags,
Type
Stage 6: summarize aggregation inside the mv-apply branch
summarize
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
CisaKnownExploited | is_not_null | field:"CisaKnownExploited" kind:is_not_null | |
CisaKnownExploitedAddedDate | is_not_null | field:"CisaKnownExploitedAddedDate" kind:is_not_null | |
CisaKnownExploitedDueDate | is_not_null | field:"CisaKnownExploitedDueDate" kind:is_not_null | |
CisaKnownExploitedRansomwareUse | is_not_null | field:"CisaKnownExploitedRansomwareUse" kind:is_not_null | |
Tags | is_not_null | field:"Tags" kind:is_not_null | |
Tags | match |
| field:"Tags" kind:match |
_c.start_cpe | is_not_null | field:"_c.start_cpe" kind:is_not_null |
Jira - Project roles changed
#'Detects when project roles were changed.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Impact |
Rule Body
id: fb6a8001-fe87-4177-a8f3-df2302215c4f
name: Jira - Project roles changed
description: |
'Detects when project roles were changed.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: JiraAuditAPI
dataTypes:
- JiraAudit
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Impact
relevantTechniques:
- T1531
query: |
JiraAudit
| where EventMessage =~ 'Project roles changed'
| project EventCreationTime, ObjectItemName, UserName, SrcIpAddr, AssociatedItems
| extend AccountCustomEntity = UserName, IPCustomEntity = SrcIpAddr
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountCustomEntity
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPCustomEntity
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
JiraAudit
Stage 2: where
| where EventMessage =~ 'Project roles changed'
Stage 3: project
| project EventCreationTime, ObjectItemName, UserName, SrcIpAddr, AssociatedItems
Stage 4: extend
| extend AccountCustomEntity = UserName, IPCustomEntity = SrcIpAddr
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventMessage | eq |
| field:"EventMessage" kind:eq value:"Project roles changed" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AssociatedItems | project |
EventCreationTime | project |
ObjectItemName | project |
SrcIpAddr | project |
UserName | project |
AccountCustomEntity | extend |
IPCustomEntity | extend |
NRT Sensitive Azure Key Vault operations
#'Identifies when sensitive Azure Key Vault operations are used. This includes: VaultDelete, KeyDelete, SecretDelete, SecretPurge, KeyPurge, SecretBackup, KeyBackup. Any Backup operations should match with expected scheduled backup activity.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Impact |
Rule Body
id: 884ead54-cb3f-4676-a1eb-b26532d6cbfd
name: NRT Sensitive Azure Key Vault operations
description: |
'Identifies when sensitive Azure Key Vault operations are used. This includes: VaultDelete, KeyDelete, SecretDelete, SecretPurge, KeyPurge, SecretBackup, KeyBackup.
Any Backup operations should match with expected scheduled backup activity.'
severity: Low
status: Available
requiredDataConnectors:
- connectorId: AzureKeyVault
dataTypes:
- KeyVaultData
tactics:
- Impact
relevantTechniques:
- T1485
query: |
let SensitiveOperationList = dynamic(
["VaultDelete", "KeyDelete", "SecretDelete", "SecretPurge", "KeyPurge", "SecretBackup", "KeyBackup"]);
AzureDiagnostics
| extend ResultType = column_ifexists("ResultType", "NoResultType"),
requestUri_s = column_ifexists("requestUri_s", "None"),
identity_claim_oid_g = column_ifexists("identity_claim_oid_g", "None"), CallerIPAddress = column_ifexists("CallerIPAddress", "None"),
clientInfo_s = column_ifexists("clientInfo_s", "None"),
identity_claim_upn_s = column_ifexists("identity_claim_upn_s", "None"),
identity_claim_http_schemas_microsoft_com_identity_claims_objectidentifier_g = column_ifexists("identity_claim_http_schemas_microsoft_com_identity_claims_objectidentifier_g", "None")
| where ResourceType =~ "VAULTS" and ResultType =~ "Success"
| where OperationName in~ (SensitiveOperationList)
| summarize EventCount=count(), StartTimeUtc=min(TimeGenerated), EndTimeUtc=max(TimeGenerated), TimeTriggered=make_list(TimeGenerated),OperationNameList=make_set(OperationName), RequestURLList=make_set(requestUri_s), CallerIPList = make_set(CallerIPAddress), CallerIPMax= arg_max(CallerIPAddress,*) by ResourceType, ResultType, Resource, identity_claim_upn_s, clientInfo_s, identity_claim_http_schemas_microsoft_com_identity_claims_objectidentifier_g
| extend timestamp = StartTimeUtc
| extend Name = tostring(split(identity_claim_upn_s,'@',0)[0]), UPNSuffix = tostring(split(identity_claim_upn_s,'@',1)[0]), AadUserId = identity_claim_http_schemas_microsoft_com_identity_claims_objectidentifier_g
entityMappings:
- entityType: Account
fieldMappings:
- identifier: AadUserId
columnName: AadUserId
- identifier: Name
columnName: Name
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: CallerIPMax
version: 1.0.2
kind: NRT
Stages and Predicates
Let Binding: SensitiveOperationList
let SensitiveOperationList = dynamic(
["VaultDelete", "KeyDelete", "SecretDelete", "SecretPurge", "KeyPurge", "SecretBackup", "KeyBackup"]);
Stage 1: source
AzureDiagnostics
Stage 2: extend
| extend ResultType = column_ifexists("ResultType", "NoResultType"),
requestUri_s = column_ifexists("requestUri_s", "None"),
identity_claim_oid_g = column_ifexists("identity_claim_oid_g", "None"), CallerIPAddress = column_ifexists("CallerIPAddress", "None"),
clientInfo_s = column_ifexists("clientInfo_s", "None"),
identity_claim_upn_s = column_ifexists("identity_claim_upn_s", "None"),
identity_claim_http_schemas_microsoft_com_identity_claims_objectidentifier_g = column_ifexists("identity_claim_http_schemas_microsoft_com_identity_claims_objectidentifier_g", "None")
Stage 3: where
| where ResourceType =~ "VAULTS" and ResultType =~ "Success"
Stage 4: where
| where OperationName in~ (SensitiveOperationList)
Stage 5: summarize
| summarize EventCount=count(), StartTimeUtc=min(TimeGenerated), EndTimeUtc=max(TimeGenerated), TimeTriggered=make_list(TimeGenerated),OperationNameList=make_set(OperationName), RequestURLList=make_set(requestUri_s), CallerIPList = make_set(CallerIPAddress), CallerIPMax= arg_max(CallerIPAddress,*) by ResourceType, ResultType, Resource, identity_claim_upn_s, clientInfo_s, identity_claim_http_schemas_microsoft_com_identity_claims_objectidentifier_g
Stage 6: extend
| extend timestamp = StartTimeUtc
Stage 7: extend
| extend Name = tostring(split(identity_claim_upn_s,'@',0)[0]), UPNSuffix = tostring(split(identity_claim_upn_s,'@',1)[0]), AadUserId = identity_claim_http_schemas_microsoft_com_identity_claims_objectidentifier_g
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
OperationName | in |
| field:"OperationName" kind:in |
ResourceType | eq |
| field:"ResourceType" kind:eq value:"VAULTS" |
ResultType | eq |
| field:"ResultType" kind:eq value:"Success" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
CallerIPList | summarize |
CallerIPMax | summarize |
EndTimeUtc | summarize |
EventCount | summarize |
OperationNameList | summarize |
RequestURLList | summarize |
Resource | summarize |
ResourceType | summarize |
ResultType | summarize |
StartTimeUtc | summarize |
TimeTriggered | summarize |
clientInfo_s | summarize |
identity_claim_http_schemas_microsoft_com_identity_claims_objectidentifier_g | summarize |
identity_claim_upn_s | summarize |
timestamp | extend |
AadUserId | extend |
Name | extend |
UPNSuffix | extend |
OCI - Discovery activity
#'Detects possible discovery activity.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Discovery |
Rule Body
id: 61f995d7-8038-4ff0-ad2b-eccfd18fcc8c
name: OCI - Discovery activity
description: |
'Detects possible discovery activity.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: OracleCloudInfrastructureLogsConnector
dataTypes:
- OCILogs
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Discovery
relevantTechniques:
- T1580
query: |
let threshold = 20;
OCILogs
| where data_eventName_s startswith 'List' or data_eventName_s startswith 'Get'
| summarize count() by data_definedTags_Oracle_Tags_CreatedBy_s, bin(TimeGenerated, 10m)
| where count_ > threshold
| extend AccountCustomEntity = data_definedTags_Oracle_Tags_CreatedBy_s
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountCustomEntity
version: 1.0.0
kind: Scheduled
Stages and Predicates
Parameters
let threshold = 20;
Stage 1: source
OCILogs
Stage 2: where
| where data_eventName_s startswith 'List' or data_eventName_s startswith 'Get'
Stage 3: summarize
| summarize count() by data_definedTags_Oracle_Tags_CreatedBy_s, bin(TimeGenerated, 10m)
Stage 4: where
| where count_ > threshold
Stage 5: extend
| extend AccountCustomEntity = data_definedTags_Oracle_Tags_CreatedBy_s
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
count_ | gt |
| field:"count_" kind:gt value:"20" |
data_eventName_s | starts_with |
| field:"data_eventName_s" kind:starts_with |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
data_definedTags_Oracle_Tags_CreatedBy_s | summarize |
AccountCustomEntity | extend |
Pathlock TDnR - HANA Standalone DB Connection Events
#Detects security events from HANA standalone database connections via DBCON in SAP, forwarded by Pathlock Threat Detection and Response. Unauthorized DBCON connections allow direct database access and may be used for lateral movement or to bypass SAP application-layer controls.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Lateral Movement |
Rule Body
id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c28
name: Pathlock TDnR - HANA Standalone DB Connection Events
kind: Scheduled
description: >-
Detects security events from HANA standalone database connections via DBCON in SAP, forwarded by
Pathlock Threat Detection and Response. Unauthorized DBCON connections allow direct database access
and may be used for lateral movement or to bypass SAP application-layer controls.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: Pathlock_TDnR
dataTypes:
- Pathlock_TDnR_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- LateralMovement
relevantTechniques:
- T1021
query: |
Pathlock_TDnR_CL
| where DataSource == "HANA_DBCON_CONNECT"
| 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 == "HANA_DBCON_CONNECT"
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:"HANA_DBCON_CONNECT" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AffectedUser | project |
Area | project |
Bname | project |
CentralTs | project |
DataSource | project |
DestIp | project |
Eventid | project |
Hostname | project |
Instance | project |
LogLine | project |
MessageV1 | project |
MessageV2 | project |
MessageV3 | project |
MessageV4 | project |
MsgId | project |
MsgNo | project |
MsgType | project |
Report | project |
SrcIp | project |
Subid | project |
Sysid | project |
Tcode | project |
TimeGenerated | project |
Pathlock TDnR - User Master Data Changes
#Detects changes to SAP user master data (SU01), forwarded by Pathlock Threat Detection and Response. User master data modifications include password changes, account locking/unlocking, validity date changes, and group assignments - any of which can be used to create persistent unauthorized access or facilitate account takeover.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| Privilege Escalation |
Rule Body
id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c74
name: Pathlock TDnR - User Master Data Changes
kind: Scheduled
description: >-
Detects changes to SAP user master data (SU01), forwarded by Pathlock Threat Detection and Response.
User master data modifications include password changes, account locking/unlocking, validity date
changes, and group assignments - any of which can be used to create persistent unauthorized access
or facilitate account takeover.
severity: High
status: Available
requiredDataConnectors:
- connectorId: Pathlock_TDnR
dataTypes:
- Pathlock_TDnR_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
- PrivilegeEscalation
relevantTechniques:
- T1098
- T1548
query: |
Pathlock_TDnR_CL
| where DataSource == "USER_MASTER_CHANGES"
| project TimeGenerated, Sysid, DataSource, Eventid, Instance, Hostname, Bname,
Tcode, Report, Area, Subid, SrcIp, DestIp, AffectedUser, LogLine,
MsgType, MsgId, MsgNo, MessageV1, MessageV2, MessageV3, MessageV4, CentralTs
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: Bname
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: Hostname
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIp
eventGroupingSettings:
aggregationKind: SingleAlert
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 5h
matchingMethod: AnyAlert
groupByEntities: []
groupByAlertDetails: []
groupByCustomDetails: []
suppressionDuration: 5h
suppressionEnabled: false
version: 1.0.0
Stages and Predicates
Stage 1: source
Pathlock_TDnR_CL
Stage 2: where
| where DataSource == "USER_MASTER_CHANGES"
Stage 3: project
| project TimeGenerated, Sysid, DataSource, Eventid, Instance, Hostname, Bname,
Tcode, Report, Area, Subid, SrcIp, DestIp, AffectedUser, LogLine,
MsgType, MsgId, MsgNo, MessageV1, MessageV2, MessageV3, MessageV4, CentralTs
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DataSource | eq |
| field:"DataSource" kind:eq value:"USER_MASTER_CHANGES" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AffectedUser | project |
Area | project |
Bname | project |
CentralTs | project |
DataSource | project |
DestIp | project |
Eventid | project |
Hostname | project |
Instance | project |
LogLine | project |
MessageV1 | project |
MessageV2 | project |
MessageV3 | project |
MessageV4 | project |
MsgId | project |
MsgNo | project |
MsgType | project |
Report | project |
SrcIp | project |
Subid | project |
Sysid | project |
Tcode | project |
TimeGenerated | project |
SAP BTP - Unaudited custom app with login-only activity
#Identifies SAP BTP custom applications (CloudFoundry, SAP CAP, etc.) that only produce XSUAA authentication events (TokenIssuedEvent, ClientAuthenticationSuccess) but have not generated any business audit log activity in the past 7 days. This pattern indicates that the application has not implemented audit logging (e.g., missing @AuditLog annotations in CAP or missing audit log service bindings), creating a security blind spot where user actions within the application are invisible to monitoring. The 7-day lookback avoids false positives for properly instrumented apps whose users simply have not performed auditable actions in the current session. Attackers could exploit such unaudited applications to perform malicious operations without detection.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Defense Impairment |
Rule Body
id: 5e8f2a1b-7c3d-4b9e-a6f0-1d2e3c4b5a6f
kind: Scheduled
name: SAP BTP - Unaudited custom app with login-only activity
description: |
Identifies SAP BTP custom applications (CloudFoundry, SAP CAP, etc.) that only produce
XSUAA authentication events (TokenIssuedEvent, ClientAuthenticationSuccess) but have not
generated any business audit log activity in the past 7 days. This pattern indicates that
the application has not implemented audit logging (e.g., missing @AuditLog annotations in
CAP or missing audit log service bindings), creating a security blind spot where user
actions within the application are invisible to monitoring. The 7-day lookback avoids
false positives for properly instrumented apps whose users simply have not performed
auditable actions in the current session. Attackers could exploit such unaudited
applications to perform malicious operations without detection.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: SAPBTPAuditEvents
dataTypes:
- SAPBTPAuditLog_CL
queryFrequency: 1h
queryPeriod: 7d
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
relevantTechniques:
- T1562
- T1562.008
query: |
// Lookback period for audit events - a longer window avoids false positives for
// properly instrumented apps where users logged in but haven't performed actions yet
let audit_lookback = ago(7d);
// Known BTP platform service patterns (excluded from detection)
let platform_service_patterns = dynamic([
"app-studio", "auditlog", "cis-local", "service-manager",
"destination-xsappname", "connectivity-proxy", "feature-flags"
]);
// Step 1: From XSUAA TokenIssuedEvent, identify custom apps with interactive user logins
// XSUAA is a shared subaccount service - the client_id in each token identifies the actual app
let app_logins = SAPBTPAuditLog_CL
// Scope logins to the current 1h run cycle (matches queryFrequency)
| where TimeGenerated > ago(1h)
| where Category == "audit.security-events"
| extend data_s = tostring(Message.data)
| where data_s has "TokenIssuedEvent"
| extend ParsedData = parse_json(data_s)
// origin field = client_id of the app the token was issued for
| extend ClientId = tostring(ParsedData.origin)
| where ClientId startswith "sb-"
// Parse nested event message for human user and grant type
| extend EventMessage = tostring(ParsedData.message)
| extend GrantType = extract(@'"grant_type"\s*:\s*"([^"]+)"', 1, EventMessage)
// Only interactive browser-based logins (not service-to-service tokens)
| where GrantType == "authorization_code"
// Derive app identifiers: sb-<XsAppName>!t<number>
| extend XsAppName = extract(@"^sb-(.+?)!\w+$", 1, ClientId)
| where isnotempty(XsAppName)
// Exclude known platform services
| where not(XsAppName has_any (platform_service_patterns))
// Extract human-readable app name (first segment before subaccount qualifier)
| extend AppName = extract(@"^([A-Za-z][A-Za-z0-9_]*)", 1, XsAppName)
| extend HumanUser = extract(@'"user_name"\s*:\s*"([^"]+)"', 1, EventMessage)
| extend IPAddress = tostring(Message.ip);
// Step 2a: Identify apps that have business audit events where the service binding
// identity appears in UserName (e.g. platform-generated audit events)
let audited_apps = SAPBTPAuditLog_CL
| where TimeGenerated > audit_lookback
| where Category !in ("audit.security-events")
| where UserName startswith "sb-"
| extend AuditedAppName = extract(@"^sb-(.+?)!\w+", 1, UserName)
| where isnotempty(AuditedAppName)
| distinct AuditedAppName, Tenant;
// Step 2b: Identify CF app instances that have an audit log service binding.
// When a CAP/custom app binds the audit log service, BTP provisions a
// 'customer-auditlog' service key whose XSUAA token events share the same
// AlsServiceId (CF app GUID) as the app's own login tokens. This gives a
// reliable per-app correlation that works even when the audit log service
// instance is deployed in a different CF space than the app itself.
let audited_app_instances = SAPBTPAuditLog_CL
| where TimeGenerated > audit_lookback
| where Category == "audit.security-events"
| where UserName contains "customer-auditlog"
| where isnotempty(AlsServiceId)
| distinct AlsServiceId, Tenant;
// Step 3: Find custom apps with logins but no business audit trail
app_logins
| join kind=leftanti (audited_apps | project XsAppName = AuditedAppName, Tenant) on XsAppName, Tenant
| join kind=leftanti (audited_app_instances) on AlsServiceId, Tenant
| summarize
LoginCount = count(),
FirstLogin = min(TimeGenerated),
LastLogin = max(TimeGenerated),
Users = make_set(HumanUser, 100),
IPs = make_set(IPAddress, 100),
AppName = take_any(AppName),
SubaccountName = take_any(SubaccountName),
OrgId = take_any(OrgId),
SpaceId = take_any(SpaceId)
by XsAppName, ClientId, Tenant
| extend Users = set_difference(Users, dynamic([""]))
| project
FirstLogin,
LastLogin,
AppName,
XsAppName,
ClientId,
SubaccountName,
Tenant,
OrgId,
SpaceId,
LoginCount,
Users,
IPs,
CloudApp = "SAP BTP"
eventGroupingSettings:
aggregationKind: AlertPerResult
entityMappings:
- entityType: CloudApplication
fieldMappings:
- identifier: Name
columnName: CloudApp
alertDetailsOverride:
alertDisplayNameFormat: "SAP BTP: Unaudited custom app '{{AppName}}' - login-only activity detected"
alertDescriptionFormat: |
The custom app '{{AppName}}' ({{XsAppName}}) in subaccount '{{SubaccountName}}' produced only
XSUAA authentication events but no business audit trail within the past 7 days.
This indicates the app has not implemented audit logging, creating a security blind spot
where user actions within the application are invisible to Microsoft Sentinel.
Recommended actions:
1. Review the application's audit log implementation
2. Add the audit log service binding in SAP BTP cockpit
3. For CAP apps, implement @AuditLog annotations on sensitive entities and services
4. Investigate user activity through alternative logs (application logs, HTTP access logs)
customDetails:
AppName: AppName
XsAppName: XsAppName
ClientId: ClientId
SubaccountName: SubaccountName
Tenant: Tenant
OrgId: OrgId
SpaceId: SpaceId
LoginCount: LoginCount
Users: Users
IPs: IPs
version: 1.0.4
Stages and Predicates
Parameters
let audit_lookback = ago(7d);
let app_logins is inlined into the numbered stages below.
Let Binding: platform_service_patterns
let platform_service_patterns = dynamic([
"app-studio", "auditlog", "cis-local", "service-manager",
"destination-xsappname", "connectivity-proxy", "feature-flags"
]);
Let Binding: audited_apps
let audited_apps = SAPBTPAuditLog_CL
| where TimeGenerated > audit_lookback
| where Category !in ("audit.security-events")
| where UserName startswith "sb-"
| extend AuditedAppName = extract(@"^sb-(.+?)!\w+", 1, UserName)
| where isnotempty(AuditedAppName)
| distinct AuditedAppName, Tenant;
Let Binding: audited_app_instances
let audited_app_instances = SAPBTPAuditLog_CL
| where TimeGenerated > audit_lookback
| where Category == "audit.security-events"
| where UserName contains "customer-auditlog"
| where isnotempty(AlsServiceId)
| distinct AlsServiceId, Tenant;
Stages 1 to 15 define let app_logins as the rule's main pipeline source. Stages 16 to 20 use it.
Stage 1: source
SAPBTPAuditLog_CL
Stage 2: where
| where TimeGenerated > ago(1h)
Stage 3: where
| where Category == "audit.security-events"
Stage 4: extend
| extend data_s = tostring(Message.data)
Stage 5: where
| where data_s has "TokenIssuedEvent"
Stage 6: extend
| extend ParsedData = parse_json(data_s)
Stage 7: extend
| extend ClientId = tostring(ParsedData.origin)
Stage 8: where
| where ClientId startswith "sb-"
Stage 9: extend
| extend EventMessage = tostring(ParsedData.message)
Stage 10: extend
| extend GrantType = extract(@'"grant_type"\s*:\s*"([^"]+)"', 1, EventMessage)
Stage 11: where
| where GrantType == "authorization_code"
Stage 12: extend
| extend XsAppName = extract(@"^sb-(.+?)!\w+$", 1, ClientId)
Stage 13: where
| where isnotempty(XsAppName)
Stage 14: where
| where not(XsAppName has_any (platform_service_patterns))
Stage 15: extend (3 consecutive steps)
| extend AppName = extract(@"^([A-Za-z][A-Za-z0-9_]*)", 1, XsAppName)
| extend HumanUser = extract(@'"user_name"\s*:\s*"([^"]+)"', 1, EventMessage)
| extend IPAddress = tostring(Message.ip)
Stage 16: join (negated)
join kind=leftanti (audited_apps) on XsAppName, Tenant
Stage 17: join (negated)
join kind=leftanti (audited_app_instances) on AlsServiceId, Tenant
Stage 18: summarize
summarize AppName, FirstLogin, IPs, LastLogin, LoginCount, OrgId, SpaceId, SubaccountName, Users by XsAppName, ClientId, Tenant
Stage 19: extend
extend Users
Stage 20: project
project AppName, ClientId, CloudApp, FirstLogin, IPs, LastLogin, LoginCount, OrgId, SpaceId, SubaccountName, Tenant, Users, XsAppName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Category | eq |
| field:"Category" kind:eq value:"audit.security-events" |
Category | in |
| field:"Category" kind:in value:"audit.security-events" |
ClientId | starts_with |
| field:"ClientId" kind:starts_with value:"sb-" |
GrantType | eq |
| field:"GrantType" kind:eq value:"authorization_code" |
XsAppName | is_not_null | field:"XsAppName" kind:is_not_null | |
data_s | match |
| field:"data_s" kind:match value:"TokenIssuedEvent" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
XsAppName | match | app-studio, auditlog, cis-local, service-manager, destination-xsappname, connectivity-proxy, feature-flags | excludes:XsAppName |
AuditedAppName | is_not_null | excludes:AuditedAppName | |
TimeGenerated | cross_field_compare | audit_lookback | excludes:TimeGenerated field:"TimeGenerated" value:"audit_lookback" |
UserName | starts_with | sb- | excludes:UserName field:"UserName" value:"sb-" |
AlsServiceId | is_not_null | excludes:AlsServiceId | |
Category | eq | audit.security-events | excludes:Category field:"Category" value:"audit.security-events" |
UserName | contains | customer-auditlog | excludes:UserName field:"UserName" value:"customer-auditlog" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AppName | project |
ClientId | project |
CloudApp | project |
FirstLogin | project |
IPs | project |
LastLogin | project |
LoginCount | project |
OrgId | project |
SpaceId | project |
SubaccountName | project |
Tenant | project |
Users | project |
XsAppName | project |
Theom - Overprovisioned Roles Shadow DB
#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 Sentinel incidents for critical/high Theom risks, associated with ruleId TRIS0034 (Theom has observed shadow (or clone) databases/tables. Additionally, it has observed roles that are overprovisioned for these data stores. As per this requirement, use this information to apply data access control lists or access permissions and enforce data retention policies)"
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Privilege Escalation | |
| Collection |
Rule Body
id: fb7769d0-e622-4479-95b4-f6266a5b41e2
name: Theom - Overprovisioned Roles Shadow DB
description: |
"Creates Sentinel incidents for critical/high Theom risks, associated with ruleId TRIS0034 (Theom has observed shadow (or clone) databases/tables. Additionally, it has observed roles that are overprovisioned for these data stores. As per this requirement, use this information to apply data access control lists or access permissions and enforce data retention policies)"
severity: High
status : Available
requiredDataConnectors:
- connectorId: Theom
dataTypes:
- TheomAlerts_CL
queryPeriod: 5m
queryFrequency: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
- Collection
- PrivilegeEscalation
relevantTechniques:
- T1560
- T1530
- T1078
query: |
TheomAlerts_CL
| where customProps_RuleId_s == "TRIS0034" and (priority_s == "P1" or priority_s == "P2")
eventGroupingSettings:
aggregationKind: AlertPerResult
alertDetailsOverride:
alertDisplayNameFormat: "Theom Alert ID: {{id_s}} "
alertDescriptionFormat: "\nSummary: {{summary_s}} \nAdditional info: {{details_s}}\nPlease investigate further on Theom UI at {{deepLink_s}}\n"
entityMappings:
- entityType: CloudApplication
fieldMappings:
- identifier: Name
columnName: customProps_AssetName_s
- entityType: URL
fieldMappings:
- identifier: Url
columnName: deepLink_s
version: 1.0.2
kind: Scheduled
Stages and Predicates
Stage 1: source
TheomAlerts_CL
Stage 2: where
| where customProps_RuleId_s == "TRIS0034" and (priority_s == "P1" or priority_s == "P2")
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
customProps_RuleId_s | eq |
| field:"customProps_RuleId_s" kind:eq value:"TRIS0034" |
priority_s | eq |
| field:"priority_s" kind:eq |
TI map Domain entity to PaloAlto
#'Identifies a match in Palo Alto data in CommonSecurityLog table from any Domain IOC from TI'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control |
Rule Body
id: ec21493c-2684-4acd-9bc2-696dbad72426
name: TI map Domain entity to PaloAlto
description: |
'Identifies a match in Palo Alto data in CommonSecurityLog table from any Domain IOC from TI'
severity: Medium
requiredDataConnectors:
- connectorId: PaloAltoNetworks
dataTypes:
- CommonSecurityLog
- connectorId: ThreatIntelligence
dataTypes:
- ThreatIntelligenceIndicator
- connectorId: ThreatIntelligenceTaxii
dataTypes:
- ThreatIntelligenceIndicator
- connectorId: MicrosoftDefenderThreatIntelligence
dataTypes:
- ThreatIntelligenceIndicator
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- CommandAndControl
relevantTechniques:
- T1071
query: |
let dt_lookBack = 1h; // Duration to look back for recent logs (1 hour)
let ioc_lookBack = 14d; // Duration to look back for recent threat intelligence indicators (14 days)
// Create a list of top-level domains (TLDs) in our threat feed for later validation of extracted domains
let list_tlds =
ThreatIntelligenceIndicator
| where isnotempty(DomainName)
| where TimeGenerated >= ago(ioc_lookBack)
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
| where Active == true and ExpirationDateTime > now()
| extend DomainName = tolower(DomainName)
| extend parts = split(DomainName, '.')
| extend tld = parts[(array_length(parts)-1)]
| summarize count() by tostring(tld)
| summarize make_list(tld);
let Domain_Indicators =
ThreatIntelligenceIndicator
// Filter to pick up only IOC's that contain the entities we want (in this case, DomainName)
| where isnotempty(DomainName)
| where TimeGenerated >= ago(ioc_lookBack)
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
| where Active == true and ExpirationDateTime > now()
| extend TI_DomainEntity = DomainName;
Domain_Indicators
// Join with CommonSecurityLog to find potential malicious activity
| join kind=innerunique (
CommonSecurityLog
| extend IngestionTime = ingestion_time()
| where IngestionTime > ago(dt_lookBack)
| where DeviceVendor =~ 'Palo Alto Networks'
| where DeviceEventClassID =~ 'url'
// Uncomment the line below to only alert on allowed connections
// | where DeviceAction !~ "block-url"
// Extract domain from RequestURL, if not present, extract it from AdditionalExtensions
| extend PA_Url = coalesce(RequestURL, "None")
| extend PA_Url = iif(isempty(PA_Url) and AdditionalExtensions !startswith "PanOS", extract("([^\"]+)", 1, tolower(AdditionalExtensions)), trim('"', PA_Url))
| extend PA_Url = iif(PA_Url !in~ ('None', 'http://None', 'https://None') and PA_Url !startswith "http://" and PA_Url !startswith "https://" and ApplicationProtocol !~ "ssl", strcat('http://', PA_Url), PA_Url)
| extend PA_Url = iif(PA_Url !in~ ('None', 'http://None', 'https://None') and PA_Url !startswith "https://" and ApplicationProtocol =~ "ssl", strcat('https://', PA_Url), PA_Url)
| extend Domain = trim(@"""", tostring(parse_url(PA_Url).Host))
| where isnotempty(Domain)
| extend Domain = tolower(Domain)
| extend parts = split(Domain, '.')
// Split out the top-level domain (TLD) for the purpose of checking if we have any TI indicators with this TLD to match on
| extend tld = parts[(array_length(parts)-1)]
// Validate parsed domain by checking TLD against TLDs from the threat feed and drop domains where there is no chance of a match
| where tld in~ (list_tlds)
| extend CommonSecurityLog_TimeGenerated = TimeGenerated
) on $left.TI_DomainEntity == $right.Domain
| where CommonSecurityLog_TimeGenerated < ExpirationDateTime
// Group the results by IndicatorId and Domain and keep only the latest CommonSecurityLog_TimeGenerated
| summarize CommonSecurityLog_TimeGenerated = arg_max(CommonSecurityLog_TimeGenerated, *) by IndicatorId, Domain
// Select the desired fields for the final result set
| project CommonSecurityLog_TimeGenerated, Description, ActivityGroupNames, PA_Url, Domain, IndicatorId, ThreatType, ExpirationDateTime, ConfidenceScore, DeviceAction, DestinationIP, DestinationPort, DeviceName, SourceIP, SourcePort, ApplicationProtocol, RequestMethod, Type, TI_DomainEntity
// Add a new field 'timestamp' for convenience, using the CommonSecurityLog_TimeGenerated as its value
| extend timestamp = CommonSecurityLog_TimeGenerated
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: DeviceName
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIP
- entityType: URL
fieldMappings:
- identifier: Url
columnName: PA_Url
version: 1.4.2
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 = ThreatIntelligenceIndicator
| where isnotempty(DomainName)
| where TimeGenerated >= ago(ioc_lookBack)
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
| where Active == true and ExpirationDateTime > now()
| extend DomainName = tolower(DomainName)
| extend parts = split(DomainName, '.')
| extend tld = parts[(array_length(parts)-1)]
| summarize count() by tostring(tld)
| summarize make_list(tld);
Stages 1 to 6 define let Domain_Indicators as the rule's main pipeline source. Stages 7 to 11 use it.
Stage 1: source
ThreatIntelligenceIndicator
Stage 2: where
| where isnotempty(DomainName)
Stage 3: where
| where TimeGenerated >= ago(ioc_lookBack)
Stage 4: summarize
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
Stage 5: where
| where Active == true and ExpirationDateTime > now()
Stage 6: extend
| extend TI_DomainEntity = DomainName
Stage 7: join
Domain_Indicators
| join kind=innerunique (
CommonSecurityLog
| extend IngestionTime = ingestion_time()
| where IngestionTime > ago(dt_lookBack)
| where DeviceVendor =~ 'Palo Alto Networks'
| where DeviceEventClassID =~ 'url'
| extend PA_Url = coalesce(RequestURL, "None")
| extend PA_Url = iif(isempty(PA_Url) and AdditionalExtensions !startswith "PanOS", extract("([^\"]+)", 1, tolower(AdditionalExtensions)), trim('"', PA_Url))
| extend PA_Url = iif(PA_Url !in~ ('None', 'http://None', 'https://None') and PA_Url !startswith "http://" and PA_Url !startswith "https://" and ApplicationProtocol !~ "ssl", strcat('http://', PA_Url), PA_Url)
| extend PA_Url = iif(PA_Url !in~ ('None', 'http://None', 'https://None') and PA_Url !startswith "https://" and ApplicationProtocol =~ "ssl", strcat('https://', PA_Url), PA_Url)
| extend Domain = trim(@"""", tostring(parse_url(PA_Url).Host))
| where isnotempty(Domain)
| extend Domain = tolower(Domain)
| extend parts = split(Domain, '.')
| extend tld = parts[(array_length(parts)-1)]
| where tld in~ (list_tlds)
| extend CommonSecurityLog_TimeGenerated = TimeGenerated
) on $left.TI_DomainEntity == $right.Domain
Stage 8: where
where CommonSecurityLog_TimeGenerated < ExpirationDateTime
Stage 9: summarize
summarize CommonSecurityLog_TimeGenerated by IndicatorId, Domain
Stage 10: project
project ActivityGroupNames, ApplicationProtocol, CommonSecurityLog_TimeGenerated, ConfidenceScore, Description, DestinationIP, DestinationPort, DeviceAction, DeviceName, Domain, ExpirationDateTime, IndicatorId, PA_Url, RequestMethod, SourceIP, SourcePort, TI_DomainEntity, ThreatType, Type
Stage 11: extend
extend timestamp
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Active | eq |
| field:"Active" kind:eq value:"true" |
DeviceEventClassID | eq |
| field:"DeviceEventClassID" kind:eq value:"url" |
DeviceVendor | eq |
| field:"DeviceVendor" kind:eq value:"Palo Alto Networks" |
Domain | is_not_null | field:"Domain" kind:is_not_null | |
DomainName | is_not_null | field:"DomainName" 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 |
ApplicationProtocol | project |
CommonSecurityLog_TimeGenerated | project |
ConfidenceScore | project |
Description | project |
DestinationIP | project |
DestinationPort | project |
DeviceAction | project |
DeviceName | project |
Domain | project |
ExpirationDateTime | project |
IndicatorId | project |
PA_Url | project |
RequestMethod | project |
SourceIP | project |
SourcePort | project |
TI_DomainEntity | project |
ThreatType | project |
Type | project |
timestamp | extend |
TI Map Email entity to SecurityEvent
#'Identifies a match in SecurityEvent table from any Email IOC from TI'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: 0a59051d-aed4-4fb6-bf84-bc80534482b2
name: TI Map Email entity to SecurityEvent
description: |
'Identifies a match in SecurityEvent table from any Email IOC from TI'
severity: Medium
requiredDataConnectors:
- connectorId: ThreatIntelligence
dataTypes:
- ThreatIntelIndicators
- connectorId: ThreatIntelligenceTaxii
dataTypes:
- ThreatIntelIndicators
- connectorId: SecurityEvents
dataTypes:
- SecurityEvent
- connectorId: WindowsSecurityEvents
dataTypes:
- SecurityEvents
- connectorId: WindowsForwardedEvents
dataTypes:
- WindowsEvent
- 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 TrafficLightProtocolLevel = tostring(parse_json(AdditionalFields).TLPLevel)
| extend IndicatorId = tostring(split(Id, "--")[2])
| extend Url = iff(ObservableKey == "url:value", ObservableValue, "")
| where TimeGenerated >= ago(ioc_lookBack)
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id, ObservableValue
| where IsActive and (ValidUntil > now() or isempty(ValidUntil))
| project-reorder *, 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 (
(union isfuzzy=true
(SecurityEvent
| where TimeGenerated >= ago(dt_lookBack) and isnotempty(TargetUserName)
//Normalizing the column to lower case for exact match with EmailSenderAddress column
| extend TargetUserName = tolower(TargetUserName)
// renaming timestamp column so it is clear the log this came from SecurityEvent table
| extend SecurityEvent_TimeGenerated = TimeGenerated
),
(WindowsEvent
| where TimeGenerated >= ago(dt_lookBack)
| extend TargetUserName = tostring(EventData.TargetUserName)
| where isnotempty(TargetUserName)
//Normalizing the column to lower case for exact match with EmailSenderAddress column
| extend TargetUserName = tolower(TargetUserName)
// renaming timestamp column so it is clear the log this came from SecurityEvent table
| extend SecurityEvent_TimeGenerated = TimeGenerated
))
)
on $left.EmailSenderAddress == $right.TargetUserName
| where SecurityEvent_TimeGenerated < ValidUntil
| summarize SecurityEvent_TimeGenerated = arg_max(SecurityEvent_TimeGenerated, *) by Id, TargetUserName
| extend Description = tostring(parse_json(Data).description)
| extend ActivityGroupNames = extract(@"ActivityGroup:(\S+)", 1, tostring(parse_json(Data).labels))
| project SecurityEvent_TimeGenerated, Description, ActivityGroupNames, Id, ValidUntil, Confidence,
EmailSenderAddress, Computer, EventID, TargetUserName, Activity, IpAddress, AccountType,
LogonTypeName, LogonProcessName, Status, SubStatus, Url
| extend HostName = tostring(split(Computer, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(Computer, '.'), 1, -1), '.'))
| extend timestamp = SecurityEvent_TimeGenerated
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: TargetUserName
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: DnsDomain
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IpAddress
- entityType: URL
fieldMappings:
- identifier: Url
columnName: Url
version: 1.3.11
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 (5 consecutive steps)
| extend EmailSenderAddress = ObservableValue
| extend EmailSourceDomain = substring(EmailSenderAddress, indexof(EmailSenderAddress, "@") + 1, strlen(EmailSenderAddress) - indexof(EmailSenderAddress, "@") - 1)
| extend TrafficLightProtocolLevel = tostring(parse_json(AdditionalFields).TLPLevel)
| extend IndicatorId = tostring(split(Id, "--")[2])
| extend Url = iff(ObservableKey == "url:value", ObservableValue, "")
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 *, TrafficLightProtocolLevel, EmailSenderAddress, EmailSourceDomain, Type
Stage 9: join
join kind=innerunique (...) on $left.EmailSenderAddress, $right.TargetUserName
Stage 10: where
where SecurityEvent_TimeGenerated < ValidUntil
Stage 11: summarize
summarize SecurityEvent_TimeGenerated by Id, TargetUserName
Stage 12: extend
extend Description
Stage 13: extend
extend ActivityGroupNames
Stage 14: project
project AccountType, Activity, ActivityGroupNames, Computer, Confidence, Description, EmailSenderAddress, EventID, Id, IpAddress, LogonProcessName, LogonTypeName, SecurityEvent_TimeGenerated, Status, SubStatus, TargetUserName, Url, ValidUntil
Stage 15: extend
extend DnsDomain, HostName
Stage 16: extend
extend timestamp
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
IndicatorType | eq |
| field:"IndicatorType" kind:eq value:"email-addr" |
IsActive | eq |
| field:"IsActive" kind:eq value:"true" |
TargetUserName | is_not_null | field:"TargetUserName" kind:is_not_null | |
ValidUntil | is_null | field:"ValidUntil" kind:is_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountType | project |
Activity | project |
ActivityGroupNames | project |
Computer | project |
Confidence | project |
Description | project |
EmailSenderAddress | project |
EventID | project |
Id | project |
IpAddress | project |
LogonProcessName | project |
LogonTypeName | project |
SecurityEvent_TimeGenerated | project |
Status | project |
SubStatus | project |
TargetUserName | project |
Url | project |
ValidUntil | project |
DnsDomain | extend |
HostName | extend |
timestamp | extend |