Detection rules › Kusto

Kusto rules: 29

Anomalous Single Factor Signin

#
Severity
low
Time window
7d
Author
Pete Bryan
Source
github.com/Azure/Azure-Sentinel

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

TacticTechniques
Initial Access

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

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 used in Stage 7

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 used in Stage 7

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.

Exclusions

The rule actively suppresses these predicates.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AppDisplayNameproject
AppIdproject
AuthenticationRequirementproject
Browserproject
Cityproject
ClientAppUsedproject
ConditionalAccessStatusproject
DeviceIdproject
DeviceNameproject
HomeTenantIdproject
IPAddressproject
Identityproject
Locationproject
MfaDetailproject
OSproject
ResourceDisplayNameproject
ResourceTenantIdproject
ResultDescriptionproject
ResultTypeproject
Stateproject
Statusproject
TimeGeneratedproject
Typeproject
UserAgentproject
UserDisplayNameproject
UserIdproject
UserPrincipalNameproject
Nameextend
UPNSuffixextend

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.

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

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.

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

Cisco SEG - Possible outbreak

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

'Detects possible outbreak activity.'

MITRE ATT&CK Coverage

TacticTechniques
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.

Output Fields

These fields are emitted when the rule matches.

FieldSource
of_verdictextend
AccountCustomEntityextend

Cisco WSA - Access to unwanted site

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

'Detects when users attempting to access sites from high risk category.'

MITRE ATT&CK Coverage

TacticTechniques
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.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountCustomEntityextend

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.

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

'Triggers an incident for every Morphisec alert whose attacks severity is critical.'

MITRE ATT&CK Coverage

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.

Output Fields

These fields are emitted when the rule matches.

FieldSource
idsummarize

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.

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

"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

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.

Exclusions

The rule actively suppresses these predicates.

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

Output Fields

These fields are emitted when the rule matches.

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

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.

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

"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

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.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AlertTitleproject
AlertUIDproject
AssetTypeproject
AssetValueproject
Descriptionproject
FirstSeenproject
Impactproject
LastSeenproject
ProductNameproject
ProviderNameproject
Recommendationproject
RiskScoreproject
Sourceproject
TimeGeneratedproject
UIDproject

GitLab - SSO - Sign-Ins Burst

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

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

TacticTechniques
Credential Access

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

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.

FieldSource
CountOfLocationssummarize
Locationssummarize
Usersummarize

GTI - CISA Known Exploited Vulnerability Detected

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

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

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.

FieldKindValuesSearch
CisaKnownExploitedis_not_null
  • (no value, null check)
field:"CisaKnownExploited" kind:is_not_null
CisaKnownExploitedAddedDateis_not_null
  • (no value, null check)
field:"CisaKnownExploitedAddedDate" kind:is_not_null
CisaKnownExploitedDueDateis_not_null
  • (no value, null check)
field:"CisaKnownExploitedDueDate" kind:is_not_null
CisaKnownExploitedRansomwareUseis_not_null
  • (no value, null check)
field:"CisaKnownExploitedRansomwareUse" kind:is_not_null
Tagsis_not_null
  • (no value, null check)
field:"Tags" kind:is_not_null
Tagsmatch
  • cisa exploited transforms: tolower, tostring, term
  • cisa-kev transforms: tolower, tostring, term
  • cisa_exploited transforms: tolower, tostring, term
field:"Tags" kind:match
_c.start_cpeis_not_null
  • (no value, null check)
field:"_c.start_cpe" kind:is_not_null

Jira - Project roles changed

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

'Detects when project roles were changed.'

MITRE ATT&CK Coverage

TacticTechniques
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.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AssociatedItemsproject
EventCreationTimeproject
ObjectItemNameproject
SrcIpAddrproject
UserNameproject
AccountCustomEntityextend
IPCustomEntityextend

NRT Sensitive Azure Key Vault operations

#
Status
available
Severity
low
Group by
Resource, ResourceType, ResultType, clientInfo_s, identity_claim_http_schemas_microsoft_com_identity_claims_objectidentifier_g, identity_claim_upn_s
Source
github.com/Azure/Azure-Sentinel

'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

TacticTechniques
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 used in Stage 4

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.

FieldKindValuesSearch
OperationNamein
  • KeyBackup
  • KeyDelete
  • KeyPurge
  • SecretBackup
  • SecretDelete
  • SecretPurge
  • VaultDelete
field:"OperationName" kind:in
ResourceTypeeq
  • VAULTS
field:"ResourceType" kind:eq value:"VAULTS"
ResultTypeeq
  • Success
field:"ResultType" kind:eq value:"Success"

Output Fields

These fields are emitted when the rule matches.

FieldSource
CallerIPListsummarize
CallerIPMaxsummarize
EndTimeUtcsummarize
EventCountsummarize
OperationNameListsummarize
RequestURLListsummarize
Resourcesummarize
ResourceTypesummarize
ResultTypesummarize
StartTimeUtcsummarize
TimeTriggeredsummarize
clientInfo_ssummarize
identity_claim_http_schemas_microsoft_com_identity_claims_objectidentifier_gsummarize
identity_claim_upn_ssummarize
timestampextend
AadUserIdextend
Nameextend
UPNSuffixextend

OCI - Discovery activity

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

'Detects possible discovery activity.'

MITRE ATT&CK Coverage

TacticTechniques
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.

Output Fields

These fields are emitted when the rule matches.

FieldSource
data_definedTags_Oracle_Tags_CreatedBy_ssummarize
AccountCustomEntityextend

Pathlock TDnR - HANA Standalone DB Connection Events

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

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

TacticTechniques
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.

Output Fields

These fields are emitted when the rule matches.

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

Pathlock TDnR - User Master Data Changes

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

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

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.

Output Fields

These fields are emitted when the rule matches.

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

SAP BTP - Unaudited custom app with login-only activity

#
Status
available
Severity
medium
Time window
7d
Group by
AlsServiceId, ClientId, Tenant, XsAppName
Source
github.com/Azure/Azure-Sentinel

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

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 used in Stage 14

let platform_service_patterns = dynamic([
    "app-studio", "auditlog", "cis-local", "service-manager",
    "destination-xsappname", "connectivity-proxy", "feature-flags"
]);

Let Binding: audited_apps used in Stage 16

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 used in Stage 17

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.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
XsAppNamematchapp-studio, auditlog, cis-local, service-manager, destination-xsappname, connectivity-proxy, feature-flagsexcludes:XsAppName
AuditedAppNameis_not_null(no value, null check)excludes:AuditedAppName
TimeGeneratedcross_field_compareaudit_lookbackexcludes:TimeGenerated field:"TimeGenerated" value:"audit_lookback"
UserNamestarts_withsb-excludes:UserName field:"UserName" value:"sb-"
AlsServiceIdis_not_null(no value, null check)excludes:AlsServiceId
Categoryeqaudit.security-eventsexcludes:Category field:"Category" value:"audit.security-events"
UserNamecontainscustomer-auditlogexcludes:UserName field:"UserName" value:"customer-auditlog"

Output Fields

These fields are emitted when the rule matches.

FieldSource
AppNameproject
ClientIdproject
CloudAppproject
FirstLoginproject
IPsproject
LastLoginproject
LoginCountproject
OrgIdproject
SpaceIdproject
SubaccountNameproject
Tenantproject
Usersproject
XsAppNameproject

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.

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

"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

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.

TI map Domain entity to PaloAlto

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

'Identifies a match in Palo Alto data in CommonSecurityLog table from any Domain IOC from TI'

MITRE ATT&CK Coverage

TacticTechniques
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 used in Stage 7

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.

Output Fields

These fields are emitted when the rule matches.

FieldSource
ActivityGroupNamesproject
ApplicationProtocolproject
CommonSecurityLog_TimeGeneratedproject
ConfidenceScoreproject
Descriptionproject
DestinationIPproject
DestinationPortproject
DeviceActionproject
DeviceNameproject
Domainproject
ExpirationDateTimeproject
IndicatorIdproject
PA_Urlproject
RequestMethodproject
SourceIPproject
SourcePortproject
TI_DomainEntityproject
ThreatTypeproject
Typeproject
timestampextend

TI Map Email entity to SecurityEvent

#
Severity
medium
Time window
14d
Group by
$left.EmailSenderAddress, $right.TargetUserName, Id, ObservableValue, TargetUserName
Source
github.com/Azure/Azure-Sentinel

'Identifies a match in SecurityEvent table from any Email IOC from TI'

MITRE ATT&CK Coverage

TacticTechniques
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.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountTypeproject
Activityproject
ActivityGroupNamesproject
Computerproject
Confidenceproject
Descriptionproject
EmailSenderAddressproject
EventIDproject
Idproject
IpAddressproject
LogonProcessNameproject
LogonTypeNameproject
SecurityEvent_TimeGeneratedproject
Statusproject
SubStatusproject
TargetUserNameproject
Urlproject
ValidUntilproject
DnsDomainextend
HostNameextend
timestampextend

Back to all Kusto rules