Detection rules › Kusto

Kusto rules: 1l

AIShield - Tabular classification AI Model Evasion high suspicious vulnerability detection

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

'This alert creates an incident when Tabular classification AI Model Evasion high suspicious, high severity vulnerability detected from the AIShield.'

Rule Body

id: 669680fb-91e5-4cbd-9eb6-e5352e0f8af0
name: AIShield - Tabular classification AI Model Evasion high suspicious vulnerability detection
description: |
  'This alert creates an incident when Tabular classification AI Model Evasion high suspicious, high severity vulnerability detected from the AIShield.'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: BoschAIShield
    dataTypes:
      - AIShield
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: |
  AIShield
  | where Message has 'Tabular Classification AI Model Evasion Attack Identified'
  | where Severity =~ 'High'
  | where SuspiciousLevel =~ 'high suspicious attack'
  | extend NTDomain = tostring(split(Computer, '\\')[0]), HostName = tostring(split(Computer, '.')[0])
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: HostName
      - identifier: NTDomain
        columnName: NTDomain
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceName
eventGroupingSettings:
  aggregationKind: SingleAlert
alertDetailsOverride:
  alertDisplayNameFormat: AIShield - Tabular Classification AI Model Evasion high suspicious vulnerability detected.
  alertDescriptionFormat: |
    This query detects Tabular Classification AI Model Evasion high suspicious, high severity alert from AIShield generated at {{TimeGenerated}}.\n\nPlease check the source for more information and investigate further.
  alertTacticsColumnName: null
  alertSeverityColumnName: Severity
version: 1.0.4
kind: Scheduled

Stages and Predicates

Stage 1: source

AIShield

Stage 2: where

| where Message has 'Tabular Classification AI Model Evasion Attack Identified'

Stage 3: where

| where Severity =~ 'High'

Stage 4: where

| where SuspiciousLevel =~ 'high suspicious attack'

Stage 5: extend

| extend NTDomain = tostring(split(Computer, '\\')[0]), HostName = tostring(split(Computer, '.')[0])

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
HostNameextend
NTDomainextend

API - Kiterunner detection

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

'42Crunch API protection against Kiterunner enumeration'

MITRE ATT&CK Coverage

Rule Body

id: 421b38ec-4295-4aed-8299-c92e268ad663
name: API - Kiterunner detection
description: |
  '42Crunch API protection against Kiterunner enumeration'
severity: Medium
requiredDataConnectors:
  - connectorId: 42CrunchAPIProtection
    dataTypes:
      - FortyTwoCrunchAPIProtection
  - connectorId: FortyTwoCrunchAPIProtection
    dataTypes:
      - FortyTwoCrunchAPIProtection
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
  aggregationKind: SingleAlert
status: Available  
tactics:
  - Reconnaissance
  - Discovery
relevantTechniques:
  - T1595
  - T1580
  - T1083
query: |
  let discoveryRecords = FortyTwoCrunchAPIProtection
  | where TimeGenerated >= ago(5m) 
  | project-away NonBlockingMode, SourcePort, DestinationPort, Query, ApiId, RequestHeader, ResponseHeader, Errors, EventType, Uuid 
  | where Status == 404 and ErrorMessage startswith "path not defined in OAS";
  let discoveryCnt = (toscalar(discoveryRecords | count));
  let recCount = iff((discoveryCnt > 500), 1, 0);
  discoveryRecords | top recCount by Timestamp desc
customDetails:
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIp
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: Hostname
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: InstanceName
version: 3.0.1
kind: Scheduled

Stages and Predicates

Parameters

let recCount = iff((discoveryCnt > 500), 1, 0);

let discoveryRecords is inlined into the numbered stages below.

Let Binding: discoveryCnt

let discoveryCnt = (toscalar(discoveryRecords | count));

Stages 1 to 4 define let discoveryRecords as the rule's main pipeline source. Stage 5 uses it.

Stage 1: source

FortyTwoCrunchAPIProtection

Stage 2: where

| where TimeGenerated >= ago(5m)

Stage 3: project-away

| project-away NonBlockingMode, SourcePort, DestinationPort, Query, ApiId, RequestHeader, ResponseHeader, Errors, EventType, Uuid

Stage 4: where

| where Status == 404 and ErrorMessage startswith "path not defined in OAS"

Stage 5: top

discoveryRecords
| top recCount by Timestamp desc

Indicators

These rows show field, operator, and value matches.

Bitglass - The SmartEdge endpoint agent was uninstalled

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

'Detects when SmartEdge endpoint agent was uninstalled.'

MITRE ATT&CK Coverage

TacticTechniques
Stealth

Rule Body

id: 40f69a27-8c68-4c8c-bb7c-7eb0f0a8a1fa
name: Bitglass - The SmartEdge endpoint agent was uninstalled
description: |
  'Detects when SmartEdge endpoint agent was uninstalled.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: Bitglass
    dataTypes:
      - Bitglass
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - DefenseEvasion
relevantTechniques:
  - T1070
query: |
  Bitglass
  | where EventType =~ 'access'
  | where EventResultDetails startswith 'The SmartEdge endpoint agent was uninstalled'
  | extend IPCustomEntity = SrcIpAddr
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPCustomEntity
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

Bitglass

Stage 2: where

| where EventType =~ 'access'

Stage 3: where

| where EventResultDetails startswith 'The SmartEdge endpoint agent was uninstalled'

Stage 4: extend

| extend IPCustomEntity = SrcIpAddr

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
IPCustomEntityextend

BloodHound Attack Path Finding - Non-Tier Zero Principal Can Grant Tier Zero App Roles

#

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 - Non-Tier Zero Principal Can Grant Tier Zero App Roles

Rule Body

id: 8e1df707-a242-42ba-a0aa-68b61f1e620d
name: BloodHound Attack Path Finding - Non-Tier Zero Principal Can Grant Tier Zero
  App Roles
description: BloodHound Attack Path Finding - Non-Tier Zero Principal Can Grant Tier
  Zero App Roles
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: BloodHoundEnterprise
    dataTypes:
      - BHEAttackPathsData_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: "BHEAttackPathsData_CL \n| where Finding == \"AzureT0MGGrantAppRoles\""
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 == "AzureT0MGGrantAppRoles"

Indicators

These rows show field, operator, and value matches.

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

Cisco Duo - Admin user created

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

'Detects when new admin user is created.'

MITRE ATT&CK Coverage

TacticTechniques
Persistence
Privilege Escalation

Rule Body

id: 0724cb01-4866-483d-a149-eb400fe1daa8
name: Cisco Duo - Admin user created
description: |
  'Detects when new admin user is created.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: CiscoDuoActivityConnectorDefinition
    dataTypes:
      - DuoActivity_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Persistence
  - PrivilegeEscalation
relevantTechniques:
  - T1078
query: |
  DuoActivity_CL
  | where ActionName =~ "admin_create"
  | extend AccountCustomEntity = TargetName
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountCustomEntity
version: 1.0.1
kind: Scheduled

Stages and Predicates

Stage 1: source

DuoActivity_CL

Stage 2: where

| where ActionName =~ "admin_create"

Stage 3: extend

| extend AccountCustomEntity = TargetName

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountCustomEntityextend

CiscoISE - Log files deleted

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

'Detects log file deleting activity.'

MITRE ATT&CK Coverage

Rule Body

id: 21d3be4c-6088-4e76-b6eb-d25479019cb9
name: CiscoISE - Log files deleted
description: |
  'Detects log file deleting activity.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: SyslogAma
    datatypes:
      - Syslog
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - DefenseEvasion
relevantTechniques:
  - T1070.004
query: |
  let lbtime = 5m;
  CiscoISEEvent
  | where TimeGenerated > ago(lbtime)
  | where EventId in ('58015', '59101', '59102')
  | project TimeGenerated, DvcHostname, DvcIpAddr, DstUserName
  | extend HostCustomEntity = DvcHostname
  | extend IPCustomEntity = DvcIpAddr
  | extend AccountCustomEntity = DstUserName
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: AccountCustomEntity
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPCustomEntity
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: HostCustomEntity
version: 1.0.3
kind: Scheduled

Stages and Predicates

Parameters

let lbtime = 5m;

Stage 1: source

CiscoISEEvent

Stage 2: where

| where TimeGenerated > ago(lbtime)

Stage 3: where

| where EventId in ('58015', '59101', '59102')

Stage 4: project

| project TimeGenerated, DvcHostname, DvcIpAddr, DstUserName

Stage 5: extend (3 consecutive steps)

| extend HostCustomEntity = DvcHostname
| extend IPCustomEntity = DvcIpAddr
| extend AccountCustomEntity = DstUserName

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
EventIdin
  • 58015
  • 59101
  • 59102
field:"EventId" kind:in

Output Fields

These fields are emitted when the rule matches.

FieldSource
DstUserNameproject
DvcHostnameproject
DvcIpAddrproject
TimeGeneratedproject
HostCustomEntityextend
IPCustomEntityextend
AccountCustomEntityextend

Cloudflare - Empty user agent

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

'Detects requests where user agent is empty.'

MITRE ATT&CK Coverage

Rule Body

id: 729c6d21-fad9-4a6a-9c7f-482393c95957
name: Cloudflare - Empty user agent
description: |
  'Detects requests where user agent is empty.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: CloudflareDataConnector
    dataTypes:
      - Cloudflare
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1190
  - T1133
query: |
  Cloudflare
  | where isempty(HttpUserAgentOriginal)
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SrcIpAddr
version: 1.0.1
kind: Scheduled

Stages and Predicates

Stage 1: source

Cloudflare

Stage 2: where

| where isempty(HttpUserAgentOriginal)

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
HttpUserAgentOriginalis_null
  • (no value, null check)
field:"HttpUserAgentOriginal" kind:is_null

CYFIRMA - High severity Malicious Phishing Network Indicators - Block 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 analytics rule identifies network-based indicators such as URLs, IP addresses, and domains related to phishing campaigns, as reported by CYFIRMA threat intelligence. These indicators are flagged with a recommended action to block and are categorized under the 'Phishing' role. Such infrastructure is often used to deliver phishing emails, host fake login portals, or redirect victims to credential-harvesting pages. Blocking these indicators proactively helps prevent user compromise and data theft."

MITRE ATT&CK Coverage

Rule Body

id: 6f053867-dbd8-4755-924d-577e3db7f5a6
name: CYFIRMA - High severity Malicious Phishing Network Indicators - Block Recommended Rule
description: |
  "This analytics rule identifies network-based indicators such as URLs, IP addresses, and domains related to phishing campaigns, as reported by CYFIRMA threat intelligence. 
  These indicators are flagged with a recommended action to block and are categorized under the 'Phishing' role.
  Such infrastructure is often used to deliver phishing emails, host fake login portals, or redirect victims to credential-harvesting pages. 
  Blocking these indicators proactively helps prevent user compromise and data theft."
version: 1.0.1
kind: Scheduled
severity: High
enabled: false
requiredDataConnectors:
  - connectorId: CyfirmaCyberIntelligenceDC
    dataTypes:
      - CyfirmaIndicators_CL
query: |
  //Malicious Phishing Network Indicators - Block Recommended
  let timeFrame= 5m;
  CyfirmaIndicators_CL 
  | where ConfidenceScore >= 80
      and TimeGenerated between (ago(timeFrame) .. now())
      and pattern !contains 'file:hashes' and RecommendedActions has 'Block' and Roles has 'Phishing'
  | extend IPv4 = extract(@"ipv4-addr:value\s*=\s*'([^']+)'", 1, pattern)
  | extend IPv6 = extract(@"ipv6-addr:value\s*=\s*'([^']+)'", 1, pattern)
  | extend URL = extract(@"url:value\s*=\s*'([^']+)'", 1, pattern)
  | extend Domain = extract(@"domain-name:value\s*=\s*'([^']+)'", 1, pattern)
  | extend parsed = parse_json(extensions)
  | extend extensionKeys = bag_keys(parsed)
  | mv-expand extensionKeys
  | extend extensionKeyStr = tostring(extensionKeys)
  | extend ext = parsed[extensionKeyStr]
  | extend props = ext.properties
  | extend 
      extension_id = extensionKeyStr,
      ASN_Owner = props.asn_owner,
      ASN = props.asn,
      ProviderName = 'CYFIRMA',
      ProductName = 'DeCYFIR/DeTCT'
  | project
      IPv4,
      IPv6,
      URL,
      Domain,
      ThreatActors,
      RecommendedActions,
      Sources,
      Roles,
      Country,
      IPAbuse,
      name,
      Description,
      ConfidenceScore,
      IndicatorID,
      created,
      modified,
      valid_from,
      Tags,
      ThreatType,
      TimeGenerated,
      SecurityVendors,
      ProductName,
      ProviderName
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: GreaterThan
triggerThreshold: 0
suppressionDuration: 5m
suppressionEnabled: true
tactics:
  - InitialAccess
  - Execution
  - CredentialAccess
  - Exfiltration
relevantTechniques:
  - T1566
  - T1204
  - T1556
  - T1110
  - T1041
  - T1566.001
  - T1566.002
  - T1204.001
  - T1556.002
  - T1110.003
alertDetailsOverride:
  alertDisplayNameFormat: "High-Confidence Malicious Phishing Network Indicators - Block 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 'Block' and Roles has 'Phishing'

Stage 3: extend (6 consecutive steps)

| extend IPv4 = extract(@"ipv4-addr:value\s*=\s*'([^']+)'", 1, pattern)
| extend IPv6 = extract(@"ipv6-addr:value\s*=\s*'([^']+)'", 1, pattern)
| extend URL = extract(@"url:value\s*=\s*'([^']+)'", 1, pattern)
| extend Domain = extract(@"domain-name:value\s*=\s*'([^']+)'", 1, pattern)
| extend parsed = parse_json(extensions)
| extend extensionKeys = bag_keys(parsed)

Stage 4: mv-expand

| mv-expand extensionKeys

Stage 5: extend (4 consecutive steps)

| extend extensionKeyStr = tostring(extensionKeys)
| extend ext = parsed[extensionKeyStr]
| extend props = ext.properties
| extend 
    extension_id = extensionKeyStr,
    ASN_Owner = props.asn_owner,
    ASN = props.asn,
    ProviderName = 'CYFIRMA',
    ProductName = 'DeCYFIR/DeTCT'

Stage 6: project

| project
    IPv4,
    IPv6,
    URL,
    Domain,
    ThreatActors,
    RecommendedActions,
    Sources,
    Roles,
    Country,
    IPAbuse,
    name,
    Description,
    ConfidenceScore,
    IndicatorID,
    created,
    modified,
    valid_from,
    Tags,
    ThreatType,
    TimeGenerated,
    SecurityVendors,
    ProductName,
    ProviderName

Indicators

These rows show field, operator, and value matches.

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

Excessive NXDOMAIN DNS Queries (ASIM DNS Schema)

#
Severity
medium
Time window
15m
Group by
SrcIpAddr
Author
Yaron
Source
github.com/Azure/Azure-Sentinel

This creates an incident in the event a client generates excessive amounts of DNS queries for non-existent domains. This analytic rule uses ASIM and supports any built-in or custom source that supports the ASIM DNS schema

MITRE ATT&CK Coverage

TacticTechniques
Command & Control

Telemetry Coverage

ProviderRecord / event type
SysmonEvent ID 22: DNSEvent (DNS query)

Rule Body

id: c3b11fb2-9201-4844-b7b9-6b7bf6d9b851
name: Excessive NXDOMAIN DNS Queries (ASIM DNS Schema)
description: |
  'This creates an incident in the event a client generates excessive amounts of DNS queries for non-existent domains. 
  This analytic rule uses [ASIM](https://aka.ms/AboutASIM) and supports any built-in or custom source that supports the ASIM DNS schema'
severity: Medium
requiredDataConnectors:
  - connectorId: DNS
    dataTypes:
      - DnsEvents
  - connectorId: AzureFirewall
    dataTypes: 
      - AzureDiagnostics
  - connectorId: Zscaler
    dataTypes:
      - CommonSecurityLog
  - connectorId: InfobloxNIOS
    dataTypes: 
      - Syslog
  - connectorId: GCPDNSDataConnector
    dataTypes: 
      - GCP_DNS_CL
  - connectorId: NXLogDnsLogs
    dataTypes: 
      - NXLog_DNS_Server_CL
  - connectorId: CiscoUmbrellaDataConnector
    dataTypes: 
      - Cisco_Umbrella_dns_CL

  - connectorId: Corelight
    dataTypes: 
      - Corelight_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
relevantTechniques:
  - T1568
  - T1008
tags:
  - ParentAlert: https://github.com/Azure/Azure-Sentinel/blob/master/Detections/InfobloxNIOS/ExcessiveNXDOMAINDNSQueries.yaml
    version: 1.0.0
  - Schema: ASIMDns
    SchemaVersion: 0.1.1
query: |
  let threshold = 200;
  _Im_Dns(responsecodename='NXDOMAIN')
  | where isnotempty(DnsResponseCodeName)
  //| where DnsResponseCodeName =~ "NXDOMAIN"
  | summarize count() by SrcIpAddr, bin(TimeGenerated,15m)
  | where count_ > threshold
  | join kind=inner (_Im_Dns(responsecodename='NXDOMAIN')
      ) on SrcIpAddr
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SrcIpAddr
version: 1.3.4
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: Yaron
    support:
        tier: Community
    categories:
        domains: [ "Security - Network" ]

Stages and Predicates

Parameters

let threshold = 200;

Stage 1: source

_Im_Dns(responsecodename='NXDOMAIN')

Stage 2: where

| where isnotempty(DnsResponseCodeName)

Stage 3: summarize

| summarize count() by SrcIpAddr, bin(TimeGenerated,15m)

Stage 4: where

| where count_ > threshold

Stage 5: join

| join kind=inner (_Im_Dns(responsecodename='NXDOMAIN')
    ) on SrcIpAddr

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
DnsResponseCodeNameis_not_null
  • (no value, null check)
field:"DnsResponseCodeName" kind:is_not_null
count_gt
  • 200 transforms: number
field:"count_" kind:gt value:"200"

Output Fields

These fields are emitted when the rule matches.

FieldSource
SrcIpAddrsummarize

Exchange workflow MailItemsAccessed operation anomaly

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

Identifies anomalous increases in Exchange mail items accessed operations. The query leverages KQL built-in anomaly detection algorithms to find large deviations from baseline patterns. Sudden increases in execution frequency of sensitive actions should be further investigated for malicious activity. Manually change scorethreshold from 1.5 to 3 or higher to reduce the noise based on outliers flagged from the query criteria. Read more about MailItemsAccessed- https://learn.microsoft.com/en-us/purview/audit-log-investigate-accounts

MITRE ATT&CK Coverage

TacticTechniques
Collection

Telemetry Coverage

PlatformRecord / event type
Microsoft 365MailItemsAccessed

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: b4ceb583-4c44-4555-8ecf-39f572e827ba
name: Exchange workflow MailItemsAccessed operation anomaly
description: |
  'Identifies anomalous increases in Exchange mail items accessed operations.
  The query leverages KQL built-in anomaly detection algorithms to find large deviations from baseline patterns.
  Sudden increases in execution frequency of sensitive actions should be further investigated for malicious activity.
  Manually change scorethreshold from 1.5 to 3 or higher to reduce the noise based on outliers flagged from the query criteria.
  Read more about MailItemsAccessed- https://learn.microsoft.com/en-us/purview/audit-log-investigate-accounts'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: Office365
    dataTypes:
      - OfficeActivity (Exchange)
queryFrequency: 1d
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Collection
relevantTechniques:
  - T1114
tags:
  - Solorigate
  - NOBELIUM
query: |
  let starttime = 14d;
  let endtime = 1d;
  let timeframe = 1h;
  let scorethreshold = 1.5;
  let percentthreshold = 50;
  // Preparing the time series data aggregated hourly count of MailItemsAccessd Operation in the form of multi-value array to use with time series anomaly function.
  let TimeSeriesData =
  OfficeActivity
  | where TimeGenerated  between (startofday(ago(starttime))..startofday(ago(endtime)))
  | where OfficeWorkload=~ "Exchange" and Operation =~ "MailItemsAccessed" and ResultStatus =~ "Succeeded"
  | project TimeGenerated, Operation, MailboxOwnerUPN
  | make-series Total=count() on TimeGenerated from startofday(ago(starttime)) to startofday(ago(endtime)) step timeframe;
  let TimeSeriesAlerts = TimeSeriesData
  | extend (anomalies, score, baseline) = series_decompose_anomalies(Total, scorethreshold, -1, 'linefit')
  | mv-expand Total to typeof(double), TimeGenerated to typeof(datetime), anomalies to typeof(double), score to typeof(double), baseline to typeof(long)
  | where anomalies > 0
  | project TimeGenerated, Total, baseline, anomalies, score;
  // Joining the flagged outlier from the previous step with the original dataset to present contextual information
  // during the anomalyhour to analysts to conduct investigation or informed decisions.
  TimeSeriesAlerts | where TimeGenerated > ago(2d)
  // Join against base logs since specified timeframe to retrive records associated with the hour of anomoly
  | join kind=innerunique (
      OfficeActivity
      | where TimeGenerated > ago(2d)
      | extend DateHour = bin(TimeGenerated, 1h)
      | where OfficeWorkload=~ "Exchange" and Operation =~ "MailItemsAccessed" and ResultStatus =~ "Succeeded"
      | summarize HourlyCount=count(), TimeGeneratedMax = arg_max(TimeGenerated, *), IPAdressList = make_set(Client_IPAddress, 1000), SourceIPMax= arg_max(Client_IPAddress, *), ClientInfoStringList= make_set(ClientInfoString, 1000) by MailboxOwnerUPN, Logon_Type, TenantId, UserType, TimeGenerated = bin(TimeGenerated, 1h)
      | where HourlyCount > 25 // Only considering operations with more than 25 hourly count to reduce False Positivies
      | order by HourlyCount desc
  ) on TimeGenerated
  | extend PercentofTotal = round(HourlyCount/Total, 2) * 100
  | where PercentofTotal > percentthreshold // Filter Users with count of less than 5 percent of TotalEvents per Hour to remove FPs/ users with very low count of MailItemsAccessed events
  | order by PercentofTotal desc
  | project-reorder TimeGeneratedMax, Type, OfficeWorkload, Operation, UserId, SourceIPMax, IPAdressList, ClientInfoStringList, HourlyCount, PercentofTotal, Total, baseline, score, anomalies
  | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: UserId
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: AccountUPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: Client_IPAddress
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIPMax
version: 2.0.6
kind: Scheduled

Stages and Predicates

Parameters

let starttime = 14d;
let endtime = 1d;
let timeframe = 1h;
let scorethreshold = 1.5;
let percentthreshold = 50;

let TimeSeriesData and let TimeSeriesAlerts are inlined into the numbered stages below.

Stages 1 to 9 define let TimeSeriesAlerts as the rule's main pipeline source. Stages 10 to 16 use it.

Stage 1: source

OfficeActivity

Stage 2: where

| where TimeGenerated  between (startofday(ago(starttime))..startofday(ago(endtime)))

Stage 3: where

| where OfficeWorkload=~ "Exchange" and Operation =~ "MailItemsAccessed" and ResultStatus =~ "Succeeded"

Stage 4: project

| project TimeGenerated, Operation, MailboxOwnerUPN

The stages below score time-series anomalies (make-series, series_decompose_anomalies).

Stage 5: make-series

| make-series Total=count() on TimeGenerated from startofday(ago(starttime)) to startofday(ago(endtime)) step timeframe

Stage 6: extend

| extend (anomalies, score, baseline) = series_decompose_anomalies(Total, scorethreshold, -1, 'linefit')

Stage 7: mv-expand

| mv-expand Total to typeof(double), TimeGenerated to typeof(datetime), anomalies to typeof(double), score to typeof(double), baseline to typeof(long)

Stage 8: where

| where anomalies > 0

Stage 9: project

| project TimeGenerated, Total, baseline, anomalies, score

Stage 10: where

TimeSeriesAlerts
| where TimeGenerated > ago(2d)

Stage 11: join

| join kind=innerunique (
    OfficeActivity
    | where TimeGenerated > ago(2d)
    | extend DateHour = bin(TimeGenerated, 1h)
    | where OfficeWorkload=~ "Exchange" and Operation =~ "MailItemsAccessed" and ResultStatus =~ "Succeeded"
    | summarize HourlyCount=count(), TimeGeneratedMax = arg_max(TimeGenerated, *), IPAdressList = make_set(Client_IPAddress, 1000), SourceIPMax= arg_max(Client_IPAddress, *), ClientInfoStringList= make_set(ClientInfoString, 1000) by MailboxOwnerUPN, Logon_Type, TenantId, UserType, TimeGenerated = bin(TimeGenerated, 1h)
    | where HourlyCount > 25
    | order by HourlyCount desc
) on TimeGenerated

Stage 12: extend

extend PercentofTotal

Stage 13: where

where PercentofTotal > 50

Stage 14: sort

sort by PercentofTotal

Stage 15: project-reorder

project-reorder

Stage 16: extend

extend AccountName, AccountUPNSuffix

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
TimeGeneratedproject
Totalproject
anomaliesproject
baselineproject
scoreproject
PercentofTotalextend
AccountNameextend
AccountUPNSuffixextend

Failover Plan Deleted

#
Status
available
Severity
low
Time window
3h
Source
github.com/Azure/Azure-Sentinel

Detects when a failover plan is deleted from Veeam Backup & Replication. This might indicate unauthorized removal of critical components.

Rule Body

id: 5f6f5a46-42d3-4961-94db-68b6229bc84e
name: Failover Plan Deleted
description: Detects when a failover plan is deleted from Veeam Backup & Replication.
  This might indicate unauthorized removal of critical components.
severity: Low
status: Available
requiredDataConnectors:
- connectorId: Syslog
  dataTypes:
  - Syslog
- connectorId: SyslogAma
  dataTypes:
  - Syslog
queryFrequency: 3h
queryPeriod: 3h
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
  aggregationKind: AlertPerResult
tactics: []
relevantTechniques: []
query: "Veeam_GetSecurityEvents\n| where instanceId == 26100\n| extend FailoverPlanName\
  \ =  extract(\"FailoverPlanName=\\\"(?<FailoverPlanName>[^\\\"]*)\\\"\", 1, SyslogMessage)\n\
  | project\n    Date = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),\n    DataSource\
  \ = original_host,\n    EventId = instanceId,\n    UserName = user,\n    [\"Failover\
  \ Plan Name\"] = FailoverPlanName,\n    MessageDetails = Description,\n    Severity\
  \ = SeverityDescription"
version: 1.0.1
kind: Scheduled
customDetails:
  Date: Date
  VbrHostName: DataSource
  EventId: EventId
  MessageDetails: MessageDetails
  Severity: Severity

Stages and Predicates

Stage 1: source

Veeam_GetSecurityEvents

Stage 2: where

| where instanceId == 26100

Stage 3: extend

| extend FailoverPlanName =  extract("FailoverPlanName=\"(?<FailoverPlanName>[^\"]*)\"", 1, SyslogMessage)

Stage 4: project

| project
    Date = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),
    DataSource = original_host,
    EventId = instanceId,
    UserName = user,
    ["Failover Plan Name"] = FailoverPlanName,
    MessageDetails = Description,
    Severity = SeverityDescription

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
DataSourceproject
Dateproject
EventIdproject
Failover Plan Nameproject
MessageDetailsproject
Severityproject
UserNameproject

GCP Audit Logs - DNSSEC Disabled on Managed DNS Zone

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

Detects when DNSSEC (DNS Security Extensions) is disabled on a Google Cloud DNS managed zone. DNSSEC provides cryptographic authentication of DNS data, preventing DNS spoofing and cache poisoning attacks. Adversaries may disable DNSSEC to enable DNS-based command and control, phishing campaigns, or to redirect traffic to malicious infrastructure without cryptographic validation. This rule monitors DNS zone patch operations where DNSSEC state changes from ON to OFF.

MITRE ATT&CK Coverage

Telemetry Coverage

Rule Body

id: 9129a43e-e204-4a9a-969e-d8861ce3437c
name: GCP Audit Logs - DNSSEC Disabled on Managed DNS Zone
description: |
  'Detects when DNSSEC (DNS Security Extensions) is disabled on a Google Cloud DNS managed zone.
  DNSSEC provides cryptographic authentication of DNS data, preventing DNS spoofing and cache poisoning attacks.
  Adversaries may disable DNSSEC to enable DNS-based command and control, phishing campaigns, or
  to redirect traffic to malicious infrastructure without cryptographic validation.
  This rule monitors DNS zone patch operations where DNSSEC state changes from ON to OFF.'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: GCPAuditLogsDefinition
    dataTypes:
      - GCPAuditLogs
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - DefenseEvasion
  - CommandAndControl
  - ResourceDevelopment
relevantTechniques:
  - T1562.001
  - T1071.004
  - T1584.002
tags:
  - GCP
  - DNS
  - DNSSEC
  - Cloud Security
query: |
  GCPAuditLogs
  | where ServiceName == "dns.googleapis.com"
  | where MethodName in ("dns.managedZones.update", "dns.managedZones.patch")
  | where GCPResourceType == "dns_managed_zone" and Severity == "NOTICE"
  | extend 
      ResponseJson = parse_json(Response),
      RequestMetadataJson = parse_json(RequestMetadata),
      AuthInfoJson = parse_json(AuthenticationInfo)
  | extend ZoneContext = ResponseJson.operation.zoneContext
  | where isnotempty(ZoneContext)
  | extend 
      OldDnsSecState = tostring(ZoneContext.oldValue.dnssecConfig.state),
      NewDnsSecState = tostring(ZoneContext.newValue.dnssecConfig.state)
  | where OldDnsSecState == "ON" and NewDnsSecState == "OFF"
  | extend 
      ManagedZoneName = extract(@"managedZones/([^/]+)", 1, GCPResourceName),
      DnsName = tostring(ResponseJson.managedZone.dnsName),
      ZoneId = tostring(ResponseJson.managedZone.id),
      ZoneDescription = tostring(ResponseJson.managedZone.description),
      Visibility = tostring(ResponseJson.managedZone.visibility),
      OperationId = tostring(ResponseJson.operation.id),
      CallerIpAddress = tostring(RequestMetadataJson.callerIp),
      AuthEmail = tostring(AuthInfoJson.principalEmail)
  | extend 
      AccountName = tostring(split(PrincipalEmail, "@")[0]), 
      AccountUPNSuffix = tostring(split(PrincipalEmail, "@")[1])
  | project TimeGenerated,
            PrincipalEmail,
            AuthEmail,
            ProjectId,
            ManagedZoneName,
            DnsName,
            ResourceName = GCPResourceName,
            Visibility,
            ZoneId,
            ZoneDescription,
            OperationId,
            CallerIpAddress,
            MethodName,
            ServiceName,
            Severity,
            LogName,
            InsertId,
            AccountName,
            AccountUPNSuffix
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: PrincipalEmail
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: AccountUPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: CallerIpAddress
  - entityType: CloudApplication
    fieldMappings:
      - identifier: Name
        columnName: ProjectId
      - identifier: InstanceName
        columnName: ResourceName
  - entityType: DNS
    fieldMappings:
      - identifier: DomainName
        columnName: DnsName
customDetails:
  ProjectId: ProjectId
  ManagedZoneName: ManagedZoneName
  DnsName: DnsName
  ResourceName: ResourceName
  Visibility: Visibility
  ZoneId: ZoneId
alertDetailsOverride:
  alertDisplayNameFormat: "DNSSEC Disabled on DNS Zone {{ManagedZoneName}} ({{DnsName}}) by {{PrincipalEmail}}"
  alertDescriptionFormat: |-
    User {{PrincipalEmail}} disabled DNSSEC on DNS managed zone {{ManagedZoneName}} ({{DnsName}}).    
    This action removes cryptographic validation of DNS responses and may indicate an attempt to facilitate DNS-based attacks.
    Investigate immediately to determine if this change was authorized and assess potential security impact.
    Review DNS query logs for suspicious activity and consider re-enabling DNSSEC if unauthorized.
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

GCPAuditLogs

Stage 2: where

| where ServiceName == "dns.googleapis.com"

Stage 3: where

| where MethodName in ("dns.managedZones.update", "dns.managedZones.patch")

Stage 4: where

| where GCPResourceType == "dns_managed_zone" and Severity == "NOTICE"

Stage 5: extend

| extend 
    ResponseJson = parse_json(Response),
    RequestMetadataJson = parse_json(RequestMetadata),
    AuthInfoJson = parse_json(AuthenticationInfo)

Stage 6: extend

| extend ZoneContext = ResponseJson.operation.zoneContext

Stage 7: where

| where isnotempty(ZoneContext)

Stage 8: extend

| extend 
    OldDnsSecState = tostring(ZoneContext.oldValue.dnssecConfig.state),
    NewDnsSecState = tostring(ZoneContext.newValue.dnssecConfig.state)

Stage 9: where

| where OldDnsSecState == "ON" and NewDnsSecState == "OFF"

Stage 10: extend

| extend 
    ManagedZoneName = extract(@"managedZones/([^/]+)", 1, GCPResourceName),
    DnsName = tostring(ResponseJson.managedZone.dnsName),
    ZoneId = tostring(ResponseJson.managedZone.id),
    ZoneDescription = tostring(ResponseJson.managedZone.description),
    Visibility = tostring(ResponseJson.managedZone.visibility),
    OperationId = tostring(ResponseJson.operation.id),
    CallerIpAddress = tostring(RequestMetadataJson.callerIp),
    AuthEmail = tostring(AuthInfoJson.principalEmail)

Stage 11: extend

| extend 
    AccountName = tostring(split(PrincipalEmail, "@")[0]), 
    AccountUPNSuffix = tostring(split(PrincipalEmail, "@")[1])

Stage 12: project

| project TimeGenerated,
          PrincipalEmail,
          AuthEmail,
          ProjectId,
          ManagedZoneName,
          DnsName,
          ResourceName = GCPResourceName,
          Visibility,
          ZoneId,
          ZoneDescription,
          OperationId,
          CallerIpAddress,
          MethodName,
          ServiceName,
          Severity,
          LogName,
          InsertId,
          AccountName,
          AccountUPNSuffix

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountNameproject
AccountUPNSuffixproject
AuthEmailproject
CallerIpAddressproject
DnsNameproject
InsertIdproject
LogNameproject
ManagedZoneNameproject
MethodNameproject
OperationIdproject
PrincipalEmailproject
ProjectIdproject
ResourceNameproject
ServiceNameproject
Severityproject
TimeGeneratedproject
Visibilityproject
ZoneDescriptionproject
ZoneIdproject

GCP IAM - Privileges Enumeration

#
Status
available
Severity
low
Time window
1h
Group by
PayloadAuthenticationinfoPrincipalemail
Source
github.com/Azure/Azure-Sentinel

Detects possible privileges enumeration.

MITRE ATT&CK Coverage

TacticTechniques
Discovery

Telemetry Coverage

PlatformRecord / event type
GCPgoogle.iam.admin.ListRoles: ListRoles

Rule Body

id: 52d88912-fa8b-4db2-b247-ee9225e41e8f
name: GCP IAM - Privileges Enumeration
description: |
  'Detects possible privileges enumeration.'
severity: Low
status: Available
requiredDataConnectors:
  - connectorId: GCPIAMDataConnector
    dataTypes:
      - GCP_IAM
queryFrequency: 12h
queryPeriod: 12h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Discovery
relevantTechniques:
  - T1069
query: |
  let threshold = 5;
  GCP_IAM
  | where PayloadMethodname =~ 'google.iam.admin.v1.ListRoles'
  | summarize count() by PayloadAuthenticationinfoPrincipalemail, bin(TimeGenerated, 1h)
  | where count_ > threshold
  | extend AccountCustomEntity = PayloadAuthenticationinfoPrincipalemail
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: AccountCustomEntity
version: 1.0.0
kind: Scheduled

Stages and Predicates

Parameters

let threshold = 5;

Stage 1: source

GCP_IAM

Stage 2: where

| where PayloadMethodname =~ 'google.iam.admin.v1.ListRoles'

Stage 3: summarize

| summarize count() by PayloadAuthenticationinfoPrincipalemail, bin(TimeGenerated, 1h)

Stage 4: where

| where count_ > threshold

Stage 5: extend

| extend AccountCustomEntity = PayloadAuthenticationinfoPrincipalemail

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
PayloadAuthenticationinfoPrincipalemailsummarize
AccountCustomEntityextend

KnowBe4 Defend - Dangerous Link Click

#
Status
available
Severity
medium
Time window
30m
Source
github.com/Azure/Azure-Sentinel

'Defend has detected a user has clicked a dangerous link in their mailbox.'

MITRE ATT&CK Coverage

TacticTechniques
Execution
Execution

Rule Body

id: af90173d-cc27-4166-a9b1-8c4347b1f4ac
name: KnowBe4 Defend - Dangerous Link Click
description: |
  'Defend has detected a user has clicked a dangerous link in their mailbox.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: KnowBe4Defend
    datatypes:
      - KnowBe4Defend_CL
queryFrequency: 30m
queryPeriod: 30m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Execution
relevantTechniques:
  - T1204
  - T0853
tags:
  - Defend
query: |
  DefendAuditData
  | where LinksClicked > 0
  | where ThreatLevel == "dangerous" or ThreatLevel == "suspicious"
  | extend Account_0_FullName = trim(@"[^@.\w]+",Recipients)
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: Account_0_FullName
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SenderIP
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: Url
  - entityType: Mailbox
    fieldMappings:
      - identifier: MailboxPrimaryAddress
        columnName: Account_0_FullName
customDetails:
  DefendSenderIP: SenderIP
  DefendSender: From
  timesClicked: LinksClicked
alertDetailsOverride:
  alertDisplayNameFormat: Alert - {{Account_0_FullName}} as clicked a suspicious link.
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

DefendAuditData

Stage 2: where

| where LinksClicked > 0

Stage 3: where

| where ThreatLevel == "dangerous" or ThreatLevel == "suspicious"

Stage 4: extend

| extend Account_0_FullName = trim(@"[^@.\w]+",Recipients)

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
LinksClickedgt
  • 0 transforms: number
field:"LinksClicked" kind:gt value:"0"
ThreatLeveleq
  • dangerous
  • suspicious
field:"ThreatLevel" kind:eq

Output Fields

These fields are emitted when the rule matches.

FieldSource
Account_0_FullNameextend

LSASS Credential Dumping with Procdump

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

This query was originally published in the threat analytics report, "Exchange Server zero-days exploited in the wild". In early March 2021, Microsoft released patches for four different zero-day vulnerabilities affecting Microsoft Exchange Server. The vulnerabilities were being used in a coordinated attack. For more information on the vulnerabilities, visit the following links: 1. CVE-2021-26855 2. CVE-2021-26857 3. CVE-2021-26858 4. CVE-2021-27065 The following query looks for evidence of Procdump being used to dump credentials from LSASS, the Local Security Authentication Server. This might indicate an attacker has compromised user accounts. More queries related to this threat can be found under the See also section of this page. Reference - https://msrc-blog.microsoft.com/2021/03/02/multiple-security-updates-released-for-exchange-server/

MITRE ATT&CK Coverage

TacticTechniques
Credential Access

Telemetry Coverage

Rule Body

id: c332b840-61e4-462e-a201-0e2d69bad45d
name: LSASS Credential Dumping with Procdump
description: |
  This query was originally published in the threat analytics report, "Exchange Server zero-days exploited in the wild".
  In early March 2021, Microsoft released patches for four different zero-day vulnerabilities affecting Microsoft Exchange Server. The vulnerabilities were being used in a coordinated attack. For more information on the vulnerabilities, visit the following links:
  1. CVE-2021-26855
  2. CVE-2021-26857
  3. CVE-2021-26858
  4. CVE-2021-27065
  The following query looks for evidence of Procdump being used to dump credentials from LSASS, the Local Security Authentication Server. This might indicate an attacker has compromised user accounts.
  More queries related to this threat can be found under the See also section of this page.
  Reference - https://msrc-blog.microsoft.com/2021/03/02/multiple-security-updates-released-for-exchange-server/
severity: High
status: Available
requiredDataConnectors:
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - DeviceProcessEvents
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CredentialAccess
relevantTechniques:
  - T1003
tags:
  - Exchange Server
  - Zero-day
  - Procdump
  - Credential Dumping
  - LSASS
query: |
  DeviceProcessEvents 
  | where (FileName has_any ("procdump.exe", "procdump64.exe") and ProcessCommandLine has "lsass") or 
  // Looking for Accepteula flag or Write a dump file with all process memory
  (ProcessCommandLine has "lsass.exe" and (ProcessCommandLine has "-accepteula" or ProcessCommandLine contains "-ma"))
  | extend HostName = iff(DeviceName has '.', substring(DeviceName, 0, indexof(DeviceName, '.')), DeviceName)
  | extend DnsDomain = iff(DeviceName has '.', substring(DeviceName, indexof(DeviceName, '.') + 1), "")
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: DeviceName
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: DnsDomain
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

DeviceProcessEvents

Stage 2: where

| where (FileName has_any ("procdump.exe", "procdump64.exe") and ProcessCommandLine has "lsass") or 
(ProcessCommandLine has "lsass.exe" and (ProcessCommandLine has "-accepteula" or ProcessCommandLine contains "-ma"))

Stage 3: extend

| extend HostName = iff(DeviceName has '.', substring(DeviceName, 0, indexof(DeviceName, '.')), DeviceName)
HostName =
ifDeviceName has "."substring(DeviceName, 0, indexof(DeviceName, '.'))
elseDeviceName

Stage 4: extend

| extend DnsDomain = iff(DeviceName has '.', substring(DeviceName, indexof(DeviceName, '.') + 1), "")
DnsDomain =
ifDeviceName has "."substring(DeviceName, (indexof(DeviceName, '.') + 1))
else""

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
FileNamematch
  • procdump.exe transforms: term
  • procdump64.exe transforms: term
field:"process_basename" kind:match
ProcessCommandLinecontains
  • -ma corpus 3 (kusto 2, sigma 1)
field:"CommandLine" kind:contains value:"-ma"
ProcessCommandLinematch
  • -accepteula transforms: term corpus 2 (kusto 2)
  • lsass transforms: term corpus 2 (kusto 2)
  • lsass.exe transforms: term
field:"CommandLine" kind:match

Output Fields

These fields are emitted when the rule matches.

FieldSource
HostNameextend
DnsDomainextend

Malware Link Clicked

#

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
Group by
Recipient, Sender, SenderIPAddress, TimeClicked, TimeGenerated, URLClicked
Source
github.com/Azure/Azure-Sentinel

'This query identifies a user clicking on an email link whose threat category is classified as a malware'

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Rule Body

id: 8675dd7a-795e-4d56-a79c-fc848c5ee61c
name: Malware Link Clicked
description: |
  'This query identifies a user clicking on an email link whose threat category is classified as a malware'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: ProofpointTAPv2
    dataTypes:
      - ProofPointTAPClicksPermittedV2_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1566.002
query: |
  ProofPointTAPClicksPermittedV2_CL
  | where classification =~ "malware"
  | where threatStatus != "cleared"
  | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), count() by TimeGenerated, Sender = sender, SenderIPAddress = senderIP, Recipient = recipient, TimeClicked = clickTime, URLClicked = url
  | extend RecipientName = tostring(split(Recipient, "@")[0]), RecipientUPNSuffix = tostring(split(Recipient, "@")[1])
  | extend SenderName = tostring(split(Sender, "@")[0]), SenderUPNSuffix = tostring(split(Sender, "@")[1])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: Recipient
      - identifier: Name
        columnName: RecipientName
      - identifier: UPNSuffix
        columnName: RecipientUPNSuffix
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: Sender
      - identifier: Name
        columnName: SenderName
      - identifier: UPNSuffix
        columnName: SenderUPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SenderIPAddress
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: URLClicked
version: 1.0.7
kind: Scheduled

Stages and Predicates

Stage 1: source

ProofPointTAPClicksPermittedV2_CL

Stage 2: where

| where classification =~ "malware"

Stage 3: where

| where threatStatus != "cleared"

Stage 4: summarize

| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), count() by TimeGenerated, Sender = sender, SenderIPAddress = senderIP, Recipient = recipient, TimeClicked = clickTime, URLClicked = url

Stage 5: extend

| extend RecipientName = tostring(split(Recipient, "@")[0]), RecipientUPNSuffix = tostring(split(Recipient, "@")[1])

Stage 6: extend

| extend SenderName = tostring(split(Sender, "@")[0]), SenderUPNSuffix = tostring(split(Sender, "@")[1])

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
EndTimesummarize
Recipientsummarize
Sendersummarize
SenderIPAddresssummarize
StartTimesummarize
TimeClickedsummarize
TimeGeneratedsummarize
URLClickedsummarize
RecipientNameextend
RecipientUPNSuffixextend
SenderNameextend
SenderUPNSuffixextend

Mimecast Data Leak Prevention - Notifications

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

Detects threat for data leak when action is notification

MITRE ATT&CK Coverage

TacticTechniques
Exfiltration

Rule Body

id: cfd67598-ad0d-430a-a793-027eb4dbe967
name: Mimecast Data Leak Prevention - Notifications
description: Detects threat for data leak when action is notification
severity: High
requiredDataConnectors:
  - connectorId: MimecastSEGAPI
    dataTypes:
      - MimecastDLP
enabled: true
query: |
    MimecastDLP 
    | where Action == "notification"
    | extend SenderAddress = ['Sender Address'] ,RecipientAddress = ['Recipient Address']
queryFrequency: 15m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
suppressionDuration: 5h
suppressionEnabled: false
tactics:
- Exfiltration
relevantTechniques:
- T1030
alertRuleTemplateName:
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: P7D
    matchingMethod: AllEntities
eventGroupingSettings:
  aggregationKind: SingleAlert
entityMappings:
- entityType: MailMessage
  fieldMappings:
  - identifier: Sender
    columnName: SenderAddress
  - identifier: Recipient
    columnName: RecipientAddress
  - identifier: DeliveryAction
    columnName: Action
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

MimecastDLP

Stage 2: where

| where Action == "notification"

Stage 3: extend

| extend SenderAddress = ['Sender Address'] ,RecipientAddress = ['Recipient Address']

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Actioneq
  • notification
field:"Action" kind:eq value:"notification"

Output Fields

These fields are emitted when the rule matches.

FieldSource
RecipientAddressextend
SenderAddressextend

Pathlock TDnR - Business Partner Bank Data Changes

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

Detects changes to bank master data for business partners in SAP, forwarded by Pathlock Threat Detection and Response. Modifications to business partner bank accounts may indicate fraud targeting vendor or customer payments.

MITRE ATT&CK Coverage

TacticTechniques
Impact

Rule Body

id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c06
name: Pathlock TDnR - Business Partner Bank Data Changes
kind: Scheduled
description: >-
  Detects changes to bank master data for business partners in SAP, forwarded by Pathlock Threat Detection
  and Response. Modifications to business partner bank accounts may indicate fraud targeting
  vendor or customer payments.
severity: High
status: Available
requiredDataConnectors:
  - connectorId: Pathlock_TDnR
    dataTypes:
      - Pathlock_TDnR_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Impact
relevantTechniques:
  - T1565
query: |
  Pathlock_TDnR_CL
  | where DataSource == "CHANGEDOC_BUPA_BANK"
  | 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 == "CHANGEDOC_BUPA_BANK"

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

Ping Federate - Password reset request from unexpected source IP address..

#
Status
available
Severity
medium
Time window
14d
Source
github.com/Azure/Azure-Sentinel

'Detects password reset requests from unexpected source IP address.'

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Rule Body

id: 2d201d21-77b4-4d97-95f3-26b5c6bde09f
name: Ping Federate - Password reset request from unexpected source IP address..
description: |
  'Detects password reset requests from unexpected source IP address.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: CefAma
    dataTypes:
      - CommonSecurityLog
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1078
query: |
  let known_src = 
  PingFederateEvent
  | where TimeGenerated between (ago(14d) .. (1d))
  | where isnotempty(SrcIpAddr)
  | summarize makeset(SrcIpAddr);
  PingFederateEvent
  | where EventType =~ 'PWD_RESET_REQUEST'
  | where isnotempty(SrcIpAddr)
  | where SrcIpAddr !in (known_src)
  | extend AccountCustomEntity = DstUserName
  | extend IpCustomEntity = SrcIpAddr
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountCustomEntity
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IpCustomEntity
version: 1.0.3
kind: Scheduled

Stages and Predicates

Let Binding: known_src used in Stage 4

let known_src = PingFederateEvent
| where TimeGenerated between (ago(14d) .. (1d))
| where isnotempty(SrcIpAddr)
| summarize makeset(SrcIpAddr);

Stage 1: source

PingFederateEvent

Stage 2: where

| where EventType =~ 'PWD_RESET_REQUEST'

Stage 3: where

| where isnotempty(SrcIpAddr)

Stage 4: where

| where SrcIpAddr !in (known_src)

Stage 5: extend

| extend AccountCustomEntity = DstUserName

Stage 6: extend

| extend IpCustomEntity = SrcIpAddr

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
EventTypeeq
  • PWD_RESET_REQUEST
field:"EventType" kind:eq value:"PWD_RESET_REQUEST"
SrcIpAddris_not_null
  • (no value, null check)
field:"src_ip" kind:is_not_null

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
SrcIpAddreqknown_srcexcludes:SrcIpAddr field:"SrcIpAddr" value:"known_src"

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountCustomEntityextend
IpCustomEntityextend

Power Apps - Multiple apps deleted

#
Status
available
Severity
medium
Time window
7d
Group by
ActorName, AppCount, EnvCount
Source
github.com/Azure/Azure-Sentinel

Identifies mass delete activity where multiple Power Apps are deleted, matching a predefined threshold of total apps deleted or app delete events across multiple Power Platform environments.

MITRE ATT&CK Coverage

Telemetry Coverage

Rule Body

id: ed88638d-8627-4c20-ba08-67c13807a9b1
kind: Scheduled
name: Power Apps - Multiple apps deleted
description: Identifies mass delete activity where multiple Power Apps are deleted,
  matching a predefined threshold of total apps deleted or app delete events across
  multiple Power Platform environments.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: PowerPlatformAdmin
    dataTypes:
      - PowerPlatformAdminActivity
queryFrequency: 1h
queryPeriod: 7d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Impact
relevantTechniques:
  - T1485
  - T0826
query: |
  let total_app_mass_delete_threshold = 25;
  let cross_environment_delete_threshold = 10;
  let query_frequency = 1h;
  let app_delete_events = materialize(
      PowerPlatformAdminActivity
      | where TimeGenerated >= ago (query_frequency)
      | where EventOriginalType == "DeletePowerApp"
      | extend Properties = tostring(PropertyCollection)
      | extend AppId = extract(@'"powerplatform.analytics.resource.power_app.id","Value":"([^"]+)"', 1, Properties)
      | extend AppId = tolower(replace_string(AppId, '/providers/Microsoft.PowerApps/apps/', ''))
      | extend EnvironmentId = extract(@'"powerplatform.analytics.resource.environment.id","Value":"([^"]+)"', 1, Properties)
      );
  app_delete_events
  | summarize AppCount = count(), EnvCount = dcount(EnvironmentId) by ActorName
  | where AppCount >= total_app_mass_delete_threshold or EnvCount >= cross_environment_delete_threshold
  | join kind=inner app_delete_events on ActorName
  | summarize
      Apps = make_set(AppId, 1000),
      Environments = make_set(EnvironmentId, 1000),
      StartTime = min(TimeGenerated)
      by AppCount, EnvCount, ActorName
  | extend
      PowerAppsEntityId = 27593,
      DataverseId = 32780,
      AccountName = tostring(split(ActorName, '@')[0]),
      UPNSuffix = tostring(split(ActorName, '@')[1])
  | project
      StartTime,
      ActorName,
      AppCount,
      Apps,
      EnvCount,
      Environments,
      PowerAppsEntityId,
      DataverseId,
      AccountName,
      UPNSuffix
eventGroupingSettings:
  aggregationKind: SingleAlert
entityMappings:
  - entityType: CloudApplication
    fieldMappings:
      - identifier: AppId
        columnName: PowerAppsEntityId
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: UPNSuffix
alertDetailsOverride:
  alertDisplayNameFormat: Power Apps - mass deletion of apps
  alertDescriptionFormat: '{{AppCount}} apps were deleted in {{EnvCount}} environments
    by {{ActorName}} , exceeding the mass delete threshold.'
customDetails:
  EnvironmentsImpacted: Environments
  AppsDeleted: Apps
  AppDeleteCount: AppCount
  EnvironmentsCount: EnvCount
version: 3.2.0

Stages and Predicates

Parameters

let total_app_mass_delete_threshold = 25;
let cross_environment_delete_threshold = 10;
let query_frequency = 1h;

let app_delete_events is inlined into the numbered stages below.

Stages 1 to 4 define let app_delete_events as the rule's main pipeline source. Stages 5 to 10 use it.

Stage 1: source

PowerPlatformAdminActivity

Stage 2: where

| where TimeGenerated >= ago (query_frequency)

Stage 3: where

| where EventOriginalType == "DeletePowerApp"

Stage 4: extend (4 consecutive steps)

| extend Properties = tostring(PropertyCollection)
| extend AppId = extract(@'"powerplatform.analytics.resource.power_app.id","Value":"([^"]+)"', 1, Properties)
| extend AppId = tolower(replace_string(AppId, '/providers/Microsoft.PowerApps/apps/', ''))
| extend EnvironmentId = extract(@'"powerplatform.analytics.resource.environment.id","Value":"([^"]+)"', 1, Properties)

Stage 5: summarize

app_delete_events
| summarize AppCount = count(), EnvCount = dcount(EnvironmentId) by ActorName

Stage 6: where

| where AppCount >= total_app_mass_delete_threshold or EnvCount >= cross_environment_delete_threshold

Stage 7: join

| join kind=inner app_delete_events on ActorName

Stage 8: summarize

summarize Apps, Environments, StartTime by AppCount, EnvCount, ActorName

Stage 9: extend

extend AccountName, DataverseId, PowerAppsEntityId, UPNSuffix

Stage 10: project

project AccountName, ActorName, AppCount, Apps, DataverseId, EnvCount, Environments, PowerAppsEntityId, StartTime, UPNSuffix

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountNameproject
ActorNameproject
AppCountproject
Appsproject
DataverseIdproject
EnvCountproject
Environmentsproject
PowerAppsEntityIdproject
StartTimeproject
UPNSuffixproject

Semperis DSP Well-known privileged SIDs in sIDHistory

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

'This indicator looks for security principals that contain specific SIDs of accounts from built-in privileged groups within their sIDHistory attribute. This would allow those security principals to have the same privileges as those privileged accounts, but in a way that is not obvious to monitor (e.g. through group membership).'

MITRE ATT&CK Coverage

TacticTechniques
Privilege Escalation
Stealth

Rule Body

id: ddd75d93-5b8b-4349-babe-c4e15343c5a3
name: Semperis DSP Well-known privileged SIDs in sIDHistory
description: |
  'This indicator looks for security principals that contain specific SIDs of accounts from built-in privileged groups within their sIDHistory attribute. This would allow those security principals to have the same privileges as those privileged accounts, but in a way that is not obvious to monitor (e.g. through group membership).'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: SemperisDSP
    dataTypes:
      - dsp_parser
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - PrivilegeEscalation
  - DefenseEvasion
relevantTechniques:
  - T1134
query: |
  dsp_parser
  | where EventID == 9212
  | where SecurityIndicatorName == "Well-known privileged SIDs in sIDHistory"
  | extend HostName = tostring(split(Computer, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(Computer, '.'), 1, -1), '.'))
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: DnsDomain
version: 2.0.7
kind: Scheduled

Stages and Predicates

Stage 1: source

dsp_parser

Stage 2: where

| where EventID == 9212

Stage 3: where

| where SecurityIndicatorName == "Well-known privileged SIDs in sIDHistory"

Stage 4: extend

| extend HostName = tostring(split(Computer, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(Computer, '.'), 1, -1), '.'))

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
DnsDomainextend
HostNameextend

SlackAudit - Empty User Agent

#
Status
available
Severity
low
Time window
1d
Source
github.com/Azure/Azure-Sentinel

'This query shows connections to the Slack Workspace with empty User Agent.'

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Rule Body

id: 04528635-a5f1-438b-ab74-21ca7bc3aa32
name: SlackAudit - Empty User Agent
description: |
  'This query shows connections to the Slack Workspace with empty User Agent.'
severity: Low
status: Available
requiredDataConnectors:
  - connectorId: SlackAuditAPI
    dataTypes:
      - SlackAudit_CL
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1133
query: |
  SlackAudit
  | where isempty(UserAgentOriginal)
  | extend AccountCustomEntity = SrcUserName
  | project TimeGenerated, AccountCustomEntity, SrcUserName, UserAgentOriginal
entityMappings:
- entityType: Account
  fieldMappings:
  - identifier: FullName
    columnName: AccountCustomEntity
alertDetailsOverride:
  alertDisplayNameFormat: Slack audit empty user agent for {{SrcUserName}}
  alertDescriptionFormat: Slack activity from {{SrcUserName}} at {{TimeGenerated}}.
customDetails:
  SlackUser: SrcUserName
  UserAgent: UserAgentOriginal
version: 1.0.1
kind: Scheduled

Stages and Predicates

Stage 1: source

SlackAudit

Stage 2: where

| where isempty(UserAgentOriginal)

Stage 3: extend

| extend AccountCustomEntity = SrcUserName

Stage 4: project

| project TimeGenerated, AccountCustomEntity, SrcUserName, UserAgentOriginal

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
UserAgentOriginalis_null
  • (no value, null check)
field:"UserAgentOriginal" kind:is_null

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountCustomEntityproject
SrcUserNameproject
TimeGeneratedproject
UserAgentOriginalproject

SonicWall - Allowed SSH, Telnet, and RDP Connections

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

'This rule identifies allowed inbound SSH, Telnet, and RDP connections. This analytic rule leverages the SonicWall Firewall ASIM Network Session parser (ASimNetworkSessionSonicWallFirewall).'

MITRE ATT&CK Coverage

Rule Body

id: 27f1a570-5f20-496b-88f6-a9aa2c5c9534
name: SonicWall - Allowed SSH, Telnet, and RDP Connections
description: |
  'This rule identifies allowed inbound SSH, Telnet, and RDP connections. This analytic rule leverages the SonicWall Firewall ASIM Network Session parser (ASimNetworkSessionSonicWallFirewall).'
severity: Medium
status: Experimental
requiredDataConnectors:
  - connectorId: CEF
    dataTypes:
      - CommonSecurityLog
  - connectorId: SonicWallFirewall
    dataTypes:
      - ASimNetworkSessionSonicWallFirewall
  - connectorId: CefAma
    dataTypes:
      - CommonSecurityLog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
  - Execution
  - Persistence
  - CredentialAccess
  - Discovery
  - LateralMovement
  - Collection
  - Exfiltration
  - Impact
relevantTechniques:
  - T1190
  - T1133
  - T1059
  - T1133
  - T1110
  - T1003
  - T1087
  - T1018
  - T1021
  - T1005
  - T1048
  - T1041
  - T1011
  - T1567
  - T1490
query: |
  ASimNetworkSessionSonicWallFirewall(false)
  | where NetworkDirection == "Inbound" and EventResult in ("NA", "Success")
  | where EventOriginalType != 1370
  | where SrcAppName contains "Windows Remote Desktop Services"
      or SrcAppName contains "Telnet"
      or (SrcAppName contains "SSH Protocol" and ThreatId != 446) // Filters out SSH server responses.
      or SrcAppName contains "Bitvise SSH"
      or DstPortNumber in (22, 23, 3389)
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SrcIpAddr
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: DstIpAddr
version: 1.0.1
kind: Scheduled

Stages and Predicates

Stage 1: source

ASimNetworkSessionSonicWallFirewall(false)

Stage 2: where

| where NetworkDirection == "Inbound" and EventResult in ("NA", "Success")

Stage 3: where

| where EventOriginalType != 1370

Stage 4: where

| where SrcAppName contains "Windows Remote Desktop Services"
    or SrcAppName contains "Telnet"
    or (SrcAppName contains "SSH Protocol" and ThreatId != 446)
    or SrcAppName contains "Bitvise SSH"
    or DstPortNumber in (22, 23, 3389)

Indicators

These rows show field, operator, and value matches.

Subtenant Updated

#
Status
available
Severity
informational
Time window
1d
Source
github.com/Azure/Azure-Sentinel

Detects when subtenant settings are updated in Veeam Backup & Replication.

Rule Body

id: e1d0d9ce-b7c9-4e28-883d-04429f80d6d7
name: Subtenant Updated
description: Detects when subtenant settings are updated in Veeam Backup & Replication.
severity: Informational
status: Available
requiredDataConnectors:
- connectorId: Syslog
  dataTypes:
  - Syslog
- connectorId: SyslogAma
  dataTypes:
  - Syslog
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
  aggregationKind: AlertPerResult
tactics: []
relevantTechniques: []
query: "Veeam_GetSecurityEvents\n| where instanceId == 25220\n| project\n    Date\
  \ = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),\n    DataSource = original_host,\n\
  \    EventId = instanceId,\n    [\"User Name\"] = user,\n  MessageDetails = Description,\n\
  \   Severity = SeverityDescription"
version: 1.0.1
kind: Scheduled
customDetails:
  Date: Date
  VbrHostName: DataSource
  EventId: EventId
  MessageDetails: MessageDetails
  Severity: Severity

Stages and Predicates

Stage 1: source

Veeam_GetSecurityEvents

Stage 2: where

| where instanceId == 25220

Stage 3: project

| project
    Date = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),
    DataSource = original_host,
    EventId = instanceId,
    ["User Name"] = user,
  MessageDetails = Description,
   Severity = SeverityDescription

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
DataSourceproject
Dateproject
EventIdproject
MessageDetailsproject
Severityproject
User Nameproject

TI Map IP Entity to DnsEvents

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

This query maps any IP indicators of compromise (IOCs) from threat intelligence (TI), by searching for matches in DnsEvents.

MITRE ATT&CK Coverage

TacticTechniques
Command & Control

Rule Body

id: 69b7723c-2889-469f-8b55-a2d355ed9c87
name: TI Map IP Entity to DnsEvents
description: |
  This query maps any IP indicators of compromise (IOCs) from threat intelligence (TI), by searching for matches in DnsEvents.
severity: Medium
requiredDataConnectors:
  - connectorId: ThreatIntelligence
    dataTypes:
      - ThreatIntelligenceIndicator
  - connectorId: ThreatIntelligenceTaxii
    dataTypes:
      - ThreatIntelligenceIndicator
  - connectorId: DNS
    dataTypes:
      - DnsEvents
  - connectorId: MicrosoftDefenderThreatIntelligence
    dataTypes:
      - ThreatIntelligenceIndicator
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
relevantTechniques:
  - T1071
query: |
  let dt_lookBack = 1h; // Look back 1 hour for DNS events
  let ioc_lookBack = 14d; // Look back 14 days for threat intelligence indicators
  // Fetch threat intelligence indicators related to IP addresses
  let IP_Indicators = ThreatIntelligenceIndicator
    | where isnotempty(NetworkIP) or isnotempty(EmailSourceIpAddress) or isnotempty(NetworkDestinationIP) or isnotempty(NetworkSourceIP)
    | where TimeGenerated >= ago(ioc_lookBack)
    | extend TI_ipEntity = iff(isnotempty(NetworkIP), NetworkIP, NetworkDestinationIP)
    | extend TI_ipEntity = iff(isempty(TI_ipEntity) and isnotempty(NetworkSourceIP), NetworkSourceIP, TI_ipEntity)
    | extend TI_ipEntity = iff(isempty(TI_ipEntity) and isnotempty(EmailSourceIpAddress), EmailSourceIpAddress, TI_ipEntity)
    | where ipv4_is_private(TI_ipEntity) == false and  TI_ipEntity !startswith "fe80" and TI_ipEntity !startswith "::" and TI_ipEntity !startswith "127."
    | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
    | where Active == true and ExpirationDateTime > now();
  // Perform a join between IP indicators and DNS events
  IP_Indicators
    // Use innerunique to keep performance fast and result set low, as we only need one match to indicate potential malicious activity that needs investigation
    | join kind=innerunique (
        DnsEvents
        | where TimeGenerated >= ago(dt_lookBack)
        | where SubType =~ "LookupQuery" and isnotempty(IPAddresses)
        | mv-expand SingleIP = split(IPAddresses, ", ") to typeof(string)
        | extend DNS_TimeGenerated = TimeGenerated
    )
    on $left.TI_ipEntity == $right.SingleIP
    // Filter out DNS events that occurred after the expiration of the corresponding indicator
    | where DNS_TimeGenerated < ExpirationDateTime
    // Group the results by IndicatorId and SingleIP, and keep the DNS event with the latest timestamp
    | summarize DNS_TimeGenerated = arg_max(DNS_TimeGenerated, *) by IndicatorId, SingleIP
    // Select the desired output fields
    | project DNS_TimeGenerated, Description, ActivityGroupNames, IndicatorId, ThreatType, Url, DomainName, ExpirationDateTime, ConfidenceScore,
      TI_ipEntity, Computer, EventId, SubType, ClientIP, Name, IPAddresses, NetworkIP, NetworkDestinationIP, NetworkSourceIP, EmailSourceIpAddress, Type
    | extend timestamp = DNS_TimeGenerated, HostName = tostring(split(Computer, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(Computer, '.'), 1, -1), '.'))
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: Computer
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: DnsDomain
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: ClientIP
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: Url
version: 1.4.3
kind: Scheduled

Stages and Predicates

Parameters

let dt_lookBack = 1h;
let ioc_lookBack = 14d;

let IP_Indicators is inlined into the numbered stages below.

Stages 1 to 7 define let IP_Indicators as the rule's main pipeline source. Stages 8 to 12 use it.

Stage 1: source

ThreatIntelligenceIndicator

Stage 2: where

| where isnotempty(NetworkIP) or isnotempty(EmailSourceIpAddress) or isnotempty(NetworkDestinationIP) or isnotempty(NetworkSourceIP)

Stage 3: where

| where TimeGenerated >= ago(ioc_lookBack)

Stage 4: extend (3 consecutive steps)

| extend TI_ipEntity = iff(isnotempty(NetworkIP), NetworkIP, NetworkDestinationIP)
| extend TI_ipEntity = iff(isempty(TI_ipEntity) and isnotempty(NetworkSourceIP), NetworkSourceIP, TI_ipEntity)
| extend TI_ipEntity = iff(isempty(TI_ipEntity) and isnotempty(EmailSourceIpAddress), EmailSourceIpAddress, TI_ipEntity)
TI_ipEntity =
ifisnotempty(NetworkIP)NetworkIP
elseNetworkDestinationIP

Stage 5: where

| where ipv4_is_private(TI_ipEntity) == false and  TI_ipEntity !startswith "fe80" and TI_ipEntity !startswith "::" and TI_ipEntity !startswith "127."

Stage 6: summarize

| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId

Stage 7: where

| where Active == true and ExpirationDateTime > now()

Stage 8: join

IP_Indicators
| join kind=innerunique (
      DnsEvents
      | where TimeGenerated >= ago(dt_lookBack)
      | where SubType =~ "LookupQuery" and isnotempty(IPAddresses)
      | mv-expand SingleIP = split(IPAddresses, ", ") to typeof(string)
      | extend DNS_TimeGenerated = TimeGenerated
  )
  on $left.TI_ipEntity == $right.SingleIP

Stage 9: where

where DNS_TimeGenerated < ExpirationDateTime

Stage 10: summarize

summarize DNS_TimeGenerated by IndicatorId, SingleIP

Stage 11: project

project ActivityGroupNames, ClientIP, Computer, ConfidenceScore, DNS_TimeGenerated, Description, DomainName, EmailSourceIpAddress, EventId, ExpirationDateTime, IPAddresses, IndicatorId, Name, NetworkDestinationIP, NetworkIP, NetworkSourceIP, SubType, TI_ipEntity, ThreatType, Type, Url

Stage 12: extend

extend DnsDomain, HostName, timestamp

Indicators

These rows show field, operator, and value matches.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
TI_ipEntitycidr_match10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16, 127.0.0.0/8excludes:TI_ipEntity
TI_ipEntitystarts_with127.excludes:TI_ipEntity field:"TI_ipEntity" value:"127."
TI_ipEntitystarts_with::excludes:TI_ipEntity field:"TI_ipEntity" value:"::"
TI_ipEntitystarts_withfe80excludes:TI_ipEntity field:"TI_ipEntity" value:"fe80"

Output Fields

These fields are emitted when the rule matches.

FieldSource
ActivityGroupNamesproject
ClientIPproject
Computerproject
ConfidenceScoreproject
DNS_TimeGeneratedproject
Descriptionproject
DomainNameproject
EmailSourceIpAddressproject
EventIdproject
ExpirationDateTimeproject
IPAddressesproject
IndicatorIdproject
Nameproject
NetworkDestinationIPproject
NetworkIPproject
NetworkSourceIPproject
SubTypeproject
TI_ipEntityproject
ThreatTypeproject
Typeproject
Urlproject
DnsDomainextend
HostNameextend
timestampextend

TI map IP entity to OfficeActivity

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

This query maps any IP indicators of compromise (IOCs) from threat intelligence (TI), by searching for matches in OfficeActivity.

MITRE ATT&CK Coverage

TacticTechniques
Command & Control

Rule Body

id: f15370f4-c6fa-42c5-9be4-1d308f40284e
name: TI map IP entity to OfficeActivity
description: |
  This query maps any IP indicators of compromise (IOCs) from threat intelligence (TI), by searching for matches in OfficeActivity.
severity: Medium
requiredDataConnectors:
  - connectorId: ThreatIntelligence
    dataTypes:
      - ThreatIntelligenceIndicator
  - connectorId: ThreatIntelligenceTaxii
    dataTypes:
      - ThreatIntelligenceIndicator
  - connectorId: MicrosoftDefenderThreatIntelligence
    dataTypes:
      - ThreatIntelligenceIndicator
  - connectorId: Office365
    dataTypes:
      - OfficeActivity
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
relevantTechniques:
  - T1071
query: |
  let dt_lookBack = 1h; // Look back 1 hour for OfficeActivity events
  let ioc_lookBack = 14d; // Look back 14 days for threat intelligence indicators
  let OfficeActivity_ = materialize(OfficeActivity
    | where isnotempty(ClientIP)
    | where TimeGenerated >= ago(dt_lookBack)
    | extend ClientIPValues = extract_all(@'\[?(::ffff:)?(?P<IPAddress>(\d+\.\d+\.\d+\.\d+)|[^\]%]+)(%\d+)?\]?([-:](?P<Port>\d+))?', dynamic(["IPAddress", "Port"]), ClientIP)[0]
    | extend IPAddress = iff(array_length(ClientIPValues) > 0, tostring(ClientIPValues[0]), '')
    | project-rename OfficeActivity_TimeGenerated = TimeGenerated);
  let ActivityIPs = OfficeActivity_ | summarize IPs = make_list(IPAddress);
  // Fetch threat intelligence indicators related to IP addresses
  let IP_Indicators = materialize(ThreatIntelligenceIndicator
    | where isnotempty(NetworkIP) or isnotempty(EmailSourceIpAddress) or isnotempty(NetworkDestinationIP) or isnotempty(NetworkSourceIP)
    | where TimeGenerated >= ago(ioc_lookBack)
    | extend TI_ipEntity = coalesce(NetworkDestinationIP, NetworkSourceIP, EmailSourceIpAddress)
    | where TI_ipEntity in (ActivityIPs)
    | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
    | where  Active == true and ExpirationDateTime > now()
    | where Description !contains_cs "State: inactive;" and Description !contains_cs "State: falsepos;");
  IP_Indicators
  // Use innerunique to keep performance fast and result set low, as we only need one match to indicate potential malicious activity that needs investigation
  | join kind=innerunique (OfficeActivity_)
    on $left.TI_ipEntity == $right.IPAddress
  // Filter out OfficeActivity events that occurred after the expiration of the corresponding indicator
  | where OfficeActivity_TimeGenerated < ExpirationDateTime
  // Group the results by IndicatorId and keep the OfficeActivity event with the latest timestamp
  | summarize OfficeActivity_TimeGenerated = arg_max(OfficeActivity_TimeGenerated, *) by IndicatorId
  // Select the desired output fields
  | project OfficeActivity_TimeGenerated, Description, ActivityGroupNames, IndicatorId, ThreatType, Url, ExpirationDateTime, ConfidenceScore, TI_ipEntity, ClientIP, UserId, Operation, ResultStatus, RecordType, OfficeObjectId, NetworkIP, NetworkDestinationIP, NetworkSourceIP, EmailSourceIpAddress, Type
  | extend timestamp = OfficeActivity_TimeGenerated, Name = tostring(split(UserId, '@', 0)[0]), UPNSuffix = tostring(split(UserId, '@', 1)[0])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: UserId
      - identifier: Name
        columnName: Name
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: TI_ipEntity
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: Url
version: 1.4.3
kind: Scheduled

Stages and Predicates

Parameters

let dt_lookBack = 1h;
let ioc_lookBack = 14d;

let IP_Indicators is inlined into the numbered stages below.

Let Binding: OfficeActivity_ used in Stage 9

let OfficeActivity_ = materialize(OfficeActivity
  | where isnotempty(ClientIP)
  | where TimeGenerated >= ago(dt_lookBack)
  | extend ClientIPValues = extract_all(@'\[?(::ffff:)?(?P<IPAddress>(\d+\.\d+\.\d+\.\d+)|[^\]%]+)(%\d+)?\]?([-:](?P<Port>\d+))?', dynamic(["IPAddress", "Port"]), ClientIP)[0]
  | extend IPAddress = iff(array_length(ClientIPValues) > 0, tostring(ClientIPValues[0]), '')
  | project-rename OfficeActivity_TimeGenerated = TimeGenerated);

Let Binding: ActivityIPs used in Stage 5

let ActivityIPs = OfficeActivity_ | summarize IPs = make_list(IPAddress);

Stages 1 to 8 define let IP_Indicators as the rule's main pipeline source. Stages 9 to 13 use it.

Stage 1: source

ThreatIntelligenceIndicator

Stage 2: where

| where isnotempty(NetworkIP) or isnotempty(EmailSourceIpAddress) or isnotempty(NetworkDestinationIP) or isnotempty(NetworkSourceIP)

Stage 3: where

| where TimeGenerated >= ago(ioc_lookBack)

Stage 4: extend

| extend TI_ipEntity = coalesce(NetworkDestinationIP, NetworkSourceIP, EmailSourceIpAddress)

Stage 5: where

| where TI_ipEntity in (ActivityIPs)

Stage 6: summarize

| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId

Stage 7: where

| where  Active == true and ExpirationDateTime > now()

Stage 8: where

| where Description !contains_cs "State: inactive;" and Description !contains_cs "State: falsepos;"

Stage 9: join

IP_Indicators
| join kind=innerunique (OfficeActivity_)
  on $left.TI_ipEntity == $right.IPAddress

Stage 10: where

where OfficeActivity_TimeGenerated < ExpirationDateTime

Stage 11: summarize

summarize OfficeActivity_TimeGenerated by IndicatorId

Stage 12: project

project ActivityGroupNames, ClientIP, ConfidenceScore, Description, EmailSourceIpAddress, ExpirationDateTime, IndicatorId, NetworkDestinationIP, NetworkIP, NetworkSourceIP, OfficeActivity_TimeGenerated, OfficeObjectId, Operation, RecordType, ResultStatus, TI_ipEntity, ThreatType, Type, Url, UserId

Stage 13: extend

extend Name, UPNSuffix, timestamp

Indicators

These rows show field, operator, and value matches.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
DescriptioncontainsState: falsepos;excludes:Description field:"Description" value:"State: falsepos;"
DescriptioncontainsState: inactive;excludes:Description field:"Description" value:"State: inactive;"

Output Fields

These fields are emitted when the rule matches.

FieldSource
ActivityGroupNamesproject
ClientIPproject
ConfidenceScoreproject
Descriptionproject
EmailSourceIpAddressproject
ExpirationDateTimeproject
IndicatorIdproject
NetworkDestinationIPproject
NetworkIPproject
NetworkSourceIPproject
OfficeActivity_TimeGeneratedproject
OfficeObjectIdproject
Operationproject
RecordTypeproject
ResultStatusproject
TI_ipEntityproject
ThreatTypeproject
Typeproject
Urlproject
UserIdproject
Nameextend
UPNSuffixextend
timestampextend

Back to all Kusto rules