Detection rules › Kusto

Kusto rules: 3a

1Password - Changes to SSO configuration

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

This will alert when changes have been made to the SSO configuration. Once this analytics rule is triggered it will group all related future alerts for upto an hour when all related entities are the same. Ref: https://1password.com/ Ref: https://github.com/securehats/

MITRE ATT&CK Coverage

TacticTechniques
Persistence

Telemetry Coverage

PlatformRecord / event type
1Passwordany: SSO (catch-all)

Rule Body

id: 9406f5ab-1197-4db9-8042-9f3345be061c
name: 1Password - Changes to SSO configuration
version: 1.0.1
kind: Scheduled
description: |-
  This will alert when changes have been made to the SSO configuration. Once this analytics rule is triggered it will group all related future alerts for upto an hour when all related entities are the same.

  Ref: https://1password.com/
  Ref: https://github.com/securehats/
severity: Medium
requiredDataConnectors:
  - connectorId: 1Password
    dataTypes:
      - OnePasswordEventLogs_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
relevantTechniques:
- T1556
query: |-
  OnePasswordEventLogs_CL
  | where log_source == "auditevents"
  | where action has_any("enblsso", "disblsso", "chngpsso", "chngasso", "chngdsso", "addgsso", "delgsso")
  | where object_type == "sso"
  | extend
      ActorUsername = actor_details.email
      , SrcIpAddr = session.ip
suppressionEnabled: false
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: 1h
    matchingMethod: AllEntities
suppressionDuration: 5h
alertDetailsOverride:
  alertDynamicProperties: []
eventGroupingSettings:
  aggregationKind: SingleAlert
entityMappings:
- entityType: Account
  fieldMappings:
  - identifier: FullName
    columnName: ActorUsername
- entityType: IP
  fieldMappings:
  - identifier: Address
    columnName: SrcIpAddr

Stages and Predicates

Stage 1: source

OnePasswordEventLogs_CL

Stage 2: where

| where log_source == "auditevents"

Stage 3: where

| where action has_any("enblsso", "disblsso", "chngpsso", "chngasso", "chngdsso", "addgsso", "delgsso")

Stage 4: where

| where object_type == "sso"

Stage 5: extend

| extend
    ActorUsername = actor_details.email
    , SrcIpAddr = session.ip

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
actionmatch
  • addgsso transforms: term
  • chngasso transforms: term
  • chngdsso transforms: term
  • chngpsso transforms: term
  • delgsso transforms: term
  • disblsso transforms: term
  • enblsso transforms: term
field:"action" kind:match
log_sourceeq
  • auditevents corpus 12 (kusto 12)
field:"log_source" kind:eq value:"auditevents"
object_typeeq
  • sso
field:"object_type" kind:eq value:"sso"

Output Fields

These fields are emitted when the rule matches.

FieldSource
ActorUsernameextend
SrcIpAddrextend

Apache - Request to sensitive files

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

'Detects request to sensitive files.'

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Rule Body

id: d1c52578-3afc-11ec-8d3d-0242ac130003
name: Apache - Request to sensitive files
description: |
  'Detects request to sensitive files.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: CustomLogsAma
    datatypes:
      - ApacheHTTPServer_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1189
query: |
  let forbidden_files = dynamic(['shadow', 'passwd', 'id_rsa']);
  ApacheHTTPServer
  | where isnotempty(UrlOriginal)
  | extend File = extract(@'(.*\/)?(.*)', 2, tostring(UrlOriginal))
  | where File in~ (forbidden_files)
  | extend FileCustomEntity = File, UrlCustomEntity = UrlOriginal
entityMappings:
  - entityType: File
    fieldMappings:
      - identifier: Name
        columnName: FileCustomEntity
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: UrlCustomEntity
version: 1.0.2
kind: Scheduled

Stages and Predicates

Parameters

let forbidden_files = dynamic(['shadow', 'passwd', 'id_rsa']);

Stage 1: source

ApacheHTTPServer

Stage 2: where

| where isnotempty(UrlOriginal)

Stage 3: extend

| extend File = extract(@'(.*\/)?(.*)', 2, tostring(UrlOriginal))

Stage 4: where

| where File in~ (forbidden_files)

Stage 5: extend

| extend FileCustomEntity = File, UrlCustomEntity = UrlOriginal

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Filein
  • id_rsa
  • passwd
  • shadow
field:"File" kind:in
UrlOriginalis_not_null
  • (no value, null check)
field:"UrlOriginal" kind:is_not_null

Output Fields

These fields are emitted when the rule matches.

FieldSource
Fileextend
FileCustomEntityextend
UrlCustomEntityextend

ASR Bypassing Writing Executable Content

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

The query checks for any file which has been created/written by an Office application and shortly after renamed to one of the deny-listed "executable extensions" which are text files. (e.g. ps1, .js, .vbs).

MITRE ATT&CK Coverage

TacticTechniques
Stealth

Telemetry Coverage

Rule Body

id: efe4efef-5ca7-4b51-a53e-0e96492ce97a
name: ASR Bypassing Writing Executable Content
description: |
  The query checks for any file which has been created/written by an Office application and shortly after renamed to one of the deny-listed "executable extensions" which are text files. (e.g. ps1, .js, .vbs).
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - DeviceFileEvents
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - DefenseEvasion
relevantTechniques:
  - T1211
query: |
  let timeframe = 1d;
  let executableExtensions = dynamic([".js", ".hta", ".lnk", ".application", ".vb", ".vba", ".vbs", ".ps", ".ps1", ".bat", ".cmd"]);
  DeviceFileEvents
  | where Timestamp >= ago(timeframe)
  | where InitiatingProcessFileName in~ ("winword.exe", "excel.exe", "outlook.exe", "powerpnt.exe")
  | where ActionType == "FileRenamed"
  // The mv-apply is less performance compared to doing a full written out !endswith. We kept the mv-apply since We don't hit any limits and get more readable/maintainable code.
  | mv-apply ext=executableExtensions to typeof(string) on
  (
      where PreviousFileName !endswith ext and FileName endswith ext
  )
  | project-reorder PreviousFileName, FileName
  // Begin allow-list.
  // End allow-list.

entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: DeviceName
  - entityType: Account
    fieldMappings:
      - identifier: Sid
        columnName: InitiatingProcessAccountSid
      - identifier: Name
        columnName: InitiatingProcessAccountName
      - identifier: NTDomain
        columnName: InitiatingProcessAccountDomain
  - entityType: Process
    fieldMappings:
      - identifier: CommandLine
        columnName: InitiatingProcessCommandLine
version: 1.0.0
kind: Scheduled

Stages and Predicates

Parameters

let timeframe = 1d;
let executableExtensions = dynamic([".js", ".hta", ".lnk", ".application", ".vb", ".vba", ".vbs", ".ps", ".ps1", ".bat", ".cmd"]);

Stage 1: source

DeviceFileEvents

Stage 2: where

| where Timestamp >= ago(timeframe)

Stage 3: where

| where InitiatingProcessFileName in~ ("winword.exe", "excel.exe", "outlook.exe", "powerpnt.exe")

Stage 4: where

| where ActionType == "FileRenamed"

Stage 5: kusto:mv-apply

| mv-apply ext=executableExtensions to typeof(string) on
(
    where PreviousFileName !endswith ext and FileName endswith ext
)

Stage 6: project-reorder

| project-reorder PreviousFileName, FileName

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
ActionTypeeq
  • FileRenamed
field:"ActionType" kind:eq value:"FileRenamed"
FileNameends_with
  • ext
field:"FileName" kind:ends_with value:"ext"
InitiatingProcessFileNamein
  • excel.exe corpus 5 (elastic 2, splunk 2, kusto 1)
  • outlook.exe corpus 4 (splunk 2, elastic 1, kusto 1)
  • powerpnt.exe corpus 4 (splunk 2, elastic 1, kusto 1)
  • winword.exe corpus 5 (elastic 2, splunk 2, kusto 1)
field:"process_basename" kind:in

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
PreviousFileNameends_withextexcludes:PreviousFileName field:"PreviousFileName" value:"ext"

Azure Secure Score Self Service Password Reset

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

'This query searches for requires you to setup Microsoft Entra ID Connect. Microsoft Entra ID Connect is free with all Azure Subscriptions'

MITRE ATT&CK Coverage

TacticTechniques
Impact

Rule Body

id: 114120B2-AAA0-4C4E-BDF1-2EE178465047
name: Azure Secure Score Self Service Password Reset
description: |
  'This query searches for requires you to setup Microsoft Entra ID Connect. 
   Microsoft Entra ID Connect is free with all Azure Subscriptions'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: SenservaPro
    dataTypes:
      - SenservaPro_CL
queryFrequency: 6h
queryPeriod: 6h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Impact
relevantTechniques:
  - T1529
query: |
  SenservaPro_CL
  | where ControlName_s == 'AzureSecureScoreSelfServicePasswordReset'
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: ControlName_s
      - identifier: AadTenantId
        columnName: TenantId
      - identifier: DisplayName
        columnName: TenantDisplayName_s 
  - entityType: SecurityGroup
    fieldMappings:
      - identifier: DistinguishedName
        columnName: Group_s
  - entityType: AzureResource
    fieldMappings:
      - identifier: ResourceId
        columnName: SourceSystem 
version: 1.0.1
kind: Scheduled

Stages and Predicates

Stage 1: source

SenservaPro_CL

Stage 2: where

| where ControlName_s == 'AzureSecureScoreSelfServicePasswordReset'

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
ControlName_seq
  • AzureSecureScoreSelfServicePasswordReset
field:"ControlName_s" kind:eq value:"AzureSecureScoreSelfServicePasswordReset"

BloodHound Attack Path Finding - RDP Users on Tier Zero Computers

#

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 - RDP Users on Tier Zero Computers

Rule Body

id: 0eefbca4-0dde-4e72-afaa-dbacc131d07a
name: BloodHound Attack Path Finding - RDP Users on Tier Zero Computers
description: BloodHound Attack Path Finding - RDP Users on Tier Zero Computers
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 == "T0RDP"
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 == "T0RDP"

Indicators

These rows show field, operator, and value matches.

Brute force attack against user credentials (Uses Authentication Normalization)

#
Severity
medium
Time window
20m
Group by
TargetUserId, TargetUserType, TargetUsername
Author
Ofer Shezaf
Source
github.com/Azure/Azure-Sentinel

Identifies evidence of brute force activity against a user based on multiple authentication failures and at least one successful authentication within a given time window. Note that the query does not enforce any sequence, and does not require the successful authentication to occur last. The default failure threshold is 10, success threshold is 1, and the default time window is 20 minutes. To use this analytics rule, make sure you have deployed the ASIM normalization parsers

MITRE ATT&CK Coverage

TacticTechniques
Credential Access

Telemetry Coverage

Rule Body

id: a6c435a2-b1a0-466d-b730-9f8af69262e8
name: Brute force attack against user credentials (Uses Authentication Normalization)
description: |
  'Identifies evidence of brute force activity against a user based on multiple authentication failures and at least one successful authentication within a given time window.
  Note that the query does not enforce any sequence, and does not require the successful authentication to occur last.
  The default failure threshold is 10, success threshold is 1, and the default time window is 20 minutes.
  To use this analytics rule, make sure you have deployed the [ASIM normalization parsers](https://aka.ms/ASimAuthentication)'
severity: Medium
requiredDataConnectors: []
queryFrequency: 20m
queryPeriod: 20m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CredentialAccess
relevantTechniques:
  - T1110
tags:
  - Id: 28b42356-45af-40a6-a0b4-a554cdfd5d8a
    version: 1.0.0
  - Schema: ASIMAuthentication
    SchemaVersion: 0.1.0
query: |
  let failureCountThreshold = 10;
  let successCountThreshold = 1;
  // let authenticationWindow = 20m; // Implicit in the analytic rule query period 
  imAuthentication
  | where TargetUserType != "NonInteractive"
  | summarize 
        StartTime = min(TimeGenerated), 
        EndTime = max(TimeGenerated), 
        IpAddresses = make_set (SrcDvcIpAddr, 100),
        ReportedBy = make_set (strcat (EventVendor, "/", EventProduct), 100),
        FailureCount = countif(EventResult=='Failure'),
        SuccessCount = countif(EventResult=='Success')
    by 
        TargetUserId, TargetUsername, TargetUserType 
  | where FailureCount >= failureCountThreshold and SuccessCount >= successCountThreshold
  | extend
        IpAddresses = strcat_array(IpAddresses, ", "), 
        ReportedBy = strcat_array(ReportedBy, ", ")
  | extend
    Name = iif(
        TargetUsername contains "@"
            , tostring(split(TargetUsername, '@', 0)[0])
            , TargetUsername
        ),
    UPNSuffix = iif(
        TargetUsername contains "@"
        , tostring(split(TargetUsername, '@', 1)[0])
        , ""
    )

entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: TargetUserName
      - identifier: Name
        columnName: Name
      - identifier: UPNSuffix
        columnName: UPNSuffix

customDetails:
  IpAddresses: IpAddresses
  ReportedBy: ReportedBy
version: 1.2.5
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: Ofer Shezaf
    support:
        tier: Community
    categories:
        domains: [ "Security - Others", "Identity" ]

Stages and Predicates

Parameters

let failureCountThreshold = 10;
let successCountThreshold = 1;

Stage 1: source

imAuthentication

Stage 2: where

| where TargetUserType != "NonInteractive"

Stage 3: summarize

| summarize 
      StartTime = min(TimeGenerated), 
      EndTime = max(TimeGenerated), 
      IpAddresses = make_set (SrcDvcIpAddr, 100),
      ReportedBy = make_set (strcat (EventVendor, "/", EventProduct), 100),
      FailureCount = countif(EventResult=='Failure'),
      SuccessCount = countif(EventResult=='Success')
  by 
      TargetUserId, TargetUsername, TargetUserType

Stage 4: where

| where FailureCount >= failureCountThreshold and SuccessCount >= successCountThreshold

Stage 5: extend

| extend
      IpAddresses = strcat_array(IpAddresses, ", "), 
      ReportedBy = strcat_array(ReportedBy, ", ")

Stage 6: extend

| extend
  Name = iif(
      TargetUsername contains "@"
          , tostring(split(TargetUsername, '@', 0)[0])
          , TargetUsername
      ),
  UPNSuffix = iif(
      TargetUsername contains "@"
      , tostring(split(TargetUsername, '@', 1)[0])
      , ""
  )

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
EndTimesummarize
FailureCountsummarize
IpAddressesextend
ReportedByextend
StartTimesummarize
SuccessCountsummarize
TargetUserIdsummarize
TargetUserTypesummarize
TargetUsernamesummarize
Nameextend
UPNSuffixextend

Cisco Duo - New access device

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

'Detects new access device.'

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Rule Body

id: f05271b6-26a5-49cf-ad73-4a202fba6eb6
name: Cisco Duo - New access device
description: |
  'Detects new access device.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: CiscoDuoAuthConnectorDefinition
    dataTypes:
      - DuoAuthentication_CL
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1078
query: |
  DuoAuthentication_CL
  | where Result =~ 'success'
  | where isnotempty(AccessDeviceIp)
  | summarize dvc_ip = make_set(AccessDeviceIp) by UserName
  | join (DuoAuthentication_CL
          | where Result =~ 'success') on UserName
  | where dvc_ip !has AccessDeviceIp
  | extend IPCustomEntity = AccessDeviceIp, AccountCustomEntity = UserName
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPCustomEntity
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountCustomEntity
version: 1.0.1
kind: Scheduled

Stages and Predicates

Stage 1: source

DuoAuthentication_CL

Stage 2: where

| where Result =~ 'success'

Stage 3: where

| where isnotempty(AccessDeviceIp)

Stage 4: summarize

| summarize dvc_ip = make_set(AccessDeviceIp) by UserName

Stage 5: join

| join (DuoAuthentication_CL
        | where Result =~ 'success') on UserName

Stage 6: where

where dvc_ip =~ AccessDeviceIp

Stage 7: extend

extend AccountCustomEntity, IPCustomEntity

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
UserNamesummarize
dvc_ipsummarize
AccountCustomEntityextend
IPCustomEntityextend

Corelight - Multiple Compressed Files Transferred over HTTP

#
Status
available
Severity
medium
Time window
15m
Group by
id_orig_h, uri
Source
github.com/Azure/Azure-Sentinel

'Detects compressed archives transferre over HTTP.'

MITRE ATT&CK Coverage

TacticTechniques
Exfiltration

Rule Body

id: 4e55e306-3022-43a1-870a-41c4d5116079
name: Corelight - Multiple Compressed Files Transferred over HTTP
description: |
  'Detects compressed archives transferre over HTTP.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: Corelight
    dataTypes:
      - corelight_http
      - corelight_http_red
      - corelight_http2
      - corelight_http_agg
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Exfiltration
relevantTechniques:
  - T1567
query: |
  let threshold = 25;
  union isfuzzy=true corelight_http, corelight_http_agg
  | where method in~ ('POST', 'PUT')
  | where isempty(referrer)
  | where orig_mime_types has_any ('application/vnd.ms-cab-compressed', 'application/warc', 'application/x-7z-compressed', 'application/x-ace', 'application/x-arc', 'application/x-archive', 'application/x-arj', 'application/x-compress', 'application/x-cpio', 'application/x-dmg', 'application/x-eet', 'application/x-gzip', 'application/x-lha', 'application/x-lrzip', 'application/x-lz4', 'application/x-lzma', 'application/x-lzh', 'application/x-lzip', 'application/x-rar', 'application/x-rpm', 'application/x-stuffit', 'application/x-tar', 'application/x-xz', 'application/x-zoo', 'application/zip')
  | summarize count() by uri, id_orig_h, bin(TimeGenerated, 15m)
  | where count_ > threshold
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: id_orig_h
version: 2.2.0
kind: Scheduled

Stages and Predicates

Parameters

let threshold = 25;

Union isfuzzy=true (2 Sources)

Each leg below queries one source; the rule matches if any leg does. Sources: corelight_http, corelight_http_agg

Leg 1: corelight_http

Leg 2: corelight_http_agg

Applied to the Combined Result

| where method in~ ('POST', 'PUT') | where isempty(referrer) | where orig_mime_types has_any ('application/vnd.ms-cab-compressed', 'application/warc', 'application/x-7z-compressed', 'application/x-ace', 'application/x-arc', 'application/x-archive', 'application/x-arj', 'application/x-compress', 'application/x-cpio', 'application/x-dmg', 'application/x-eet', 'application/x-gzip', 'application/x-lha', 'application/x-lrzip', 'application/x-lz4', 'application/x-lzma', 'application/x-lzh', 'application/x-lzip', 'application/x-rar', 'application/x-rpm', 'application/x-stuffit', 'application/x-tar', 'application/x-xz', 'application/x-zoo', 'application/zip') | summarize count() by uri, id_orig_h, bin(TimeGenerated, 15m) | where count_ > threshold

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
count_gt
  • 25 transforms: number
field:"count_" kind:gt value:"25"
methodin
  • POST
  • PUT
field:"method" kind:in
orig_mime_typesmatch
  • application/vnd.ms-cab-compressed transforms: term
  • application/warc transforms: term
  • application/x-7z-compressed transforms: term
  • application/x-ace transforms: term
  • application/x-arc transforms: term
  • application/x-archive transforms: term
  • application/x-arj transforms: term
  • application/x-compress transforms: term
  • application/x-cpio transforms: term
  • application/x-dmg transforms: term
  • application/x-eet transforms: term
  • application/x-gzip transforms: term
  • application/x-lha transforms: term
  • application/x-lrzip transforms: term
  • application/x-lz4 transforms: term
  • application/x-lzh transforms: term
  • application/x-lzip transforms: term
  • application/x-lzma transforms: term
  • application/x-rar transforms: term
  • application/x-rpm transforms: term
  • application/x-stuffit transforms: term
  • application/x-tar transforms: term
  • application/x-xz transforms: term
  • application/x-zoo transforms: term
  • application/zip transforms: term
field:"orig_mime_types" kind:match
referreris_null
  • (no value, null check)
field:"referrer" kind:is_null

Output Fields

These fields are emitted when the rule matches.

FieldSource
id_orig_hsummarize
urisummarize

CYFIRMA - Medium severity Malicious Network Indicators with Block Action Rule

#

This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.

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

"This rule detects network-based indicators such as IP addresses, domains, and URLs reported by CYFIRMA threat intelligence with a recommended action of 'Block' and no specified role. These indicators may be associated with infrastructure used in phishing campaigns, malware, or initial access via watering hole or drive-by compromise techniques."

MITRE ATT&CK Coverage

Rule Body

id: 4e7d1851-5aab-478d-b348-4b83dc2b03d9
name: CYFIRMA - Medium severity Malicious Network Indicators with Block Action Rule
description: |
  "This rule detects network-based indicators such as IP addresses, domains, and URLs reported by CYFIRMA threat intelligence with a recommended action of 'Block' and no specified role. 
  These indicators may be associated with infrastructure used in phishing campaigns, malware, or initial access via watering hole or drive-by compromise techniques."
version: 1.0.1
kind: Scheduled
severity: Medium
enabled: false
requiredDataConnectors:
  - connectorId: CyfirmaCyberIntelligenceDC
    dataTypes:
      - CyfirmaIndicators_CL
query: |
  // Network Indicators with Block Action Rule
  let timeFrame= 5m;
  CyfirmaIndicators_CL 
  | where  (ConfidenceScore < 80 and ConfidenceScore >= 50)
      and TimeGenerated between (ago(timeFrame) .. now())
      and pattern !contains 'file:hashes' and RecommendedActions has 'Block' and (isempty(Roles) or not(Roles has_any ('Command & Control', 'Malware', 'TOR', 'Trojan', 'Phishing')))
  | extend IPv4 = extract(@"ipv4-addr:value\s*=\s*'([^']+)'", 1, pattern)
  | extend IPv6 = extract(@"ipv6-addr:value\s*=\s*'([^']+)'", 1, pattern)
  | extend URL = extract(@"url:value\s*=\s*'([^']+)'", 1, pattern)
  | extend Domain = extract(@"domain-name:value\s*=\s*'([^']+)'", 1, pattern)
  | extend parsed = parse_json(extensions)
  | extend extensionKeys = bag_keys(parsed)
  | mv-expand extensionKeys
  | extend extensionKeyStr = tostring(extensionKeys)
  | extend ext = parsed[extensionKeyStr]
  | extend props = ext.properties
  | extend 
      extension_id = extensionKeyStr,
      ASN_Owner = props.asn_owner,
      ASN = props.asn,
      ProviderName = 'CYFIRMA',
      ProductName = 'DeCYFIR/DeTCT'
  | project
      IPv4,
      IPv6,
      URL,
      Domain,
      ThreatActors,
      RecommendedActions,
      Sources,
      Roles,
      Country,
      IPAbuse,
      name,
      Description,
      ConfidenceScore,
      IndicatorID,
      created,
      modified,
      valid_from,
      Tags,
      ThreatType,
      TimeGenerated,
      SecurityVendors,
      ProductName,
      ProviderName
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: GreaterThan
triggerThreshold: 0
suppressionDuration: 5m
suppressionEnabled: true
tactics:
  - InitialAccess
  - Execution
  - Reconnaissance
  - Impact
relevantTechniques:
  - T1566
  - T1189
  - T1204
  - T1595
  - T1498
  - T1566.002
  - T1204.001
  - T1595.002
alertDetailsOverride:
  alertDisplayNameFormat: "High-Confidence Malicious Network Indicators with Block Action Rule - {{name}} "
  alertDescriptionFormat: "{{Description}} - {{name}} "
  alertDynamicProperties:
    - alertProperty: ProductName
      value: ProductName
    - alertProperty: ProviderName
      value: ProviderName
customDetails:
  ThreatActors: ThreatActors
  Sources: Sources
  RecommendedActions: RecommendedActions
  Roles: Roles
  Country: Country
  Description: Description
  ConfidenceScore: ConfidenceScore
  SecurityVendors: SecurityVendors
  IndicatorID: IndicatorID
  Created: created
  Modified: modified
  ValidFrom: valid_from
  Tags: Tags
  ThreatType: ThreatType
  TimeGenerated: TimeGenerated
  IPAbuse: IPAbuse
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPv4
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPv6
  - entityType: DNS
    fieldMappings:
      - identifier: DomainName
        columnName: Domain
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: URL
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: false
    reopenClosedIncident: false
    lookbackDuration: PT5H
    matchingMethod: AllEntities
eventGroupingSettings:
  aggregationKind: AlertPerResult

Stages and Predicates

Parameters

let timeFrame = 5m;

Stage 1: source

CyfirmaIndicators_CL

Stage 2: where

| where  (ConfidenceScore < 80 and ConfidenceScore >= 50)
    and TimeGenerated between (ago(timeFrame) .. now())
    and pattern !contains 'file:hashes' and RecommendedActions has 'Block' and (isempty(Roles) or not(Roles has_any ('Command & Control', 'Malware', 'TOR', 'Trojan', 'Phishing')))

Stage 3: extend (6 consecutive steps)

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

Stage 4: mv-expand

| mv-expand extensionKeys

Stage 5: extend (4 consecutive steps)

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

Stage 6: project

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

Indicators

These rows show field, operator, and value matches.

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

Dataverse - User bulk retrieval outside normal activity

#
Status
available
Severity
low
Time window
14d
Group by
CurrentExportRate, HistoricalBaseline, InstanceUrl, UserId
Source
github.com/Azure/Azure-Sentinel

Identifies users retrieving significantly more records from Dataverse than they have previously in the past 2 weeks.

MITRE ATT&CK Coverage

Telemetry Coverage

Rule Body

id: 08cb7ffc-59c6-4e7d-88e0-327371c9431b
kind: Scheduled
name: Dataverse - User bulk retrieval outside normal activity
description: Identifies users retrieving significantly more records from Dataverse
  than they have previously in the past 2 weeks.
severity: Low
status: Available
requiredDataConnectors:
  - connectorId: Dataverse
    dataTypes:
      - DataverseActivity
queryFrequency: 1d
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Exfiltration
relevantTechniques:
  - T1048
query: |
  let baseline_time = 14d;
  let detection_time = 1d;
  DataverseActivity
  | where TimeGenerated between(ago(baseline_time) .. ago(detection_time - 1d))
  | where Message == "RetrieveMultiple"
  | extend numQueryCount = todouble(QueryResults)
  | extend QueryCount = iif(QueryResults contains ",", todouble(countof(tostring(QueryResults), ',') + 1), numQueryCount)
  | extend QueryCount = iif(isnotempty(QueryCount), QueryCount, double(1))
  | summarize sum(QueryCount) by UserId
  | extend HistoricalBaseline = sum_QueryCount
  | join kind=inner (
      DataverseActivity
      | where TimeGenerated > ago(detection_time)
      | where Message == "RetrieveMultiple"
      | extend numQueryCount = todouble(QueryResults)
      | extend QueryCount = iif(QueryResults contains ",", todouble(countof(tostring(QueryResults), ',') + 1), numQueryCount)
      | extend QueryCount = iif(isnotempty(QueryCount), QueryCount, double(1))
      | summarize sum(QueryCount) by UserId
      | extend CurrentExportRate = sum_QueryCount)
      on UserId
  | where CurrentExportRate > HistoricalBaseline
  | project UserId, HistoricalBaseline, CurrentExportRate
  | join kind=inner(
      DataverseActivity
      | where TimeGenerated > ago(detection_time)
      | where Message == "RetrieveMultiple"
      | extend numQueryCount = todouble(QueryResults)
      | extend QueryCount = iif(QueryResults contains ",", todouble(countof(tostring(QueryResults), ',') + 1), numQueryCount)
      | extend QueryCount = iif(isnotempty(QueryCount), QueryCount, double(1)))
      on UserId
  | summarize
      QuerySizes = make_set(QueryCount),
      MostRecentQuery = max(TimeGenerated),
      IPs = make_set(ClientIp),
      UserAgents = make_set(UserAgent),
      Entities = make_set(EntityName),
      Queries = make_set(Query)
      by UserId, InstanceUrl, HistoricalBaseline, CurrentExportRate
  | extend
      AccountName = tostring(split(UserId, '@')[0]),
      UPNSuffix = tostring(split(UserId, '@')[1]),
      CloudAppId = int(32780)
  | project
      MostRecentQuery,
      UserId,
      IPs,
      UserAgents,
      InstanceUrl,
      Queries,
      QuerySizes,
      Entities,
      HistoricalBaseline,
      CurrentExportRate,
      AccountName,
      UPNSuffix,
      CloudAppId
eventGroupingSettings:
  aggregationKind: AlertPerResult
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: CloudApplication
    fieldMappings:
      - identifier: AppId
        columnName: CloudAppId
      - identifier: InstanceName
        columnName: InstanceUrl
alertDetailsOverride:
  alertDisplayNameFormat: Dataverse - Bulk record retrieval outside of normal activity
  alertDescriptionFormat: '{{UserId}} exported {{CurrentExportRate}} records, far
    beyond the historical baseline of {{{HistoricalBaseline}}.'
customDetails: {}
version: 3.2.0

Stages and Predicates

Parameters

let baseline_time = 14d;
let detection_time = 1d;

Stage 1: source

DataverseActivity

Stage 2: where

| where TimeGenerated between(ago(baseline_time) .. ago(detection_time - 1d))

Stage 3: where

| where Message == "RetrieveMultiple"

Stage 4: extend (3 consecutive steps)

| extend numQueryCount = todouble(QueryResults)
| extend QueryCount = iif(QueryResults contains ",", todouble(countof(tostring(QueryResults), ',') + 1), numQueryCount)
| extend QueryCount = iif(isnotempty(QueryCount), QueryCount, double(1))

Stage 5: summarize

| summarize sum(QueryCount) by UserId

Stage 6: extend

| extend HistoricalBaseline = sum_QueryCount

Stage 7: join

| join kind=inner (
    DataverseActivity
    | where TimeGenerated > ago(detection_time)
    | where Message == "RetrieveMultiple"
    | extend numQueryCount = todouble(QueryResults)
    | extend QueryCount = iif(QueryResults contains ",", todouble(countof(tostring(QueryResults), ',') + 1), numQueryCount)
    | extend QueryCount = iif(isnotempty(QueryCount), QueryCount, double(1))
    | summarize sum(QueryCount) by UserId
    | extend CurrentExportRate = sum_QueryCount)
    on UserId

Stage 8: where

where CurrentExportRate > HistoricalBaseline

Stage 9: project

project CurrentExportRate, HistoricalBaseline, UserId

Stage 10: join

| join kind=inner(
    DataverseActivity
    | where TimeGenerated > ago(detection_time)
    | where Message == "RetrieveMultiple"
    | extend numQueryCount = todouble(QueryResults)
    | extend QueryCount = iif(QueryResults contains ",", todouble(countof(tostring(QueryResults), ',') + 1), numQueryCount)
    | extend QueryCount = iif(isnotempty(QueryCount), QueryCount, double(1)))
    on UserId

Stage 11: summarize

summarize Entities, IPs, MostRecentQuery, Queries, QuerySizes, UserAgents by UserId, InstanceUrl, HistoricalBaseline, CurrentExportRate

Stage 12: extend

extend AccountName, CloudAppId, UPNSuffix

Stage 13: project

project AccountName, CloudAppId, CurrentExportRate, Entities, HistoricalBaseline, IPs, InstanceUrl, MostRecentQuery, Queries, QuerySizes, UPNSuffix, UserAgents, UserId

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Messageeq
  • RetrieveMultiple
field:"Message" kind:eq value:"RetrieveMultiple"

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountNameproject
CloudAppIdproject
CurrentExportRateproject
Entitiesproject
HistoricalBaselineproject
IPsproject
InstanceUrlproject
MostRecentQueryproject
Queriesproject
QuerySizesproject
UPNSuffixproject
UserAgentsproject
UserIdproject

Encryption Password Deleted

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

Detects when an encryption password is deleted in Veeam Backup & Replication. This might indicate unauthorized removal of critical components.

Rule Body

id: a2c6fe48-0d3d-416b-991c-a750ec50d7eb
name: Encryption Password Deleted
description: Detects when an encryption password is deleted in Veeam Backup & Replication.
  This might indicate unauthorized removal of critical components.
severity: High
status: Available
requiredDataConnectors:
- connectorId: Syslog
  dataTypes:
  - Syslog
- connectorId: SyslogAma
  dataTypes:
  - Syslog
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
  aggregationKind: AlertPerResult
tactics: []
relevantTechniques: []
query: "Veeam_GetSecurityEvents\n| where instanceId == 31800\n| extend Hint = extract(\"\
  Hint=\\\"([^\\\"]*)\\\"\", 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    [\"Hint\"] = Hint,\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 == 31800

Stage 3: extend

| extend Hint = extract("Hint=\"([^\"]*)\"", 1, SyslogMessage)

Stage 4: project

| project
    Date = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),
    DataSource = original_host,
    EventId = instanceId,
    UserName = user,
    ["Hint"] = Hint,
   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
Hintproject
MessageDetailsproject
Severityproject
UserNameproject

Firewall rule manipulation attempts stateful anomaly on database

#
Status
available
Severity
medium
Time window
14d
Group by
ApplicationName, ClientIp, Database, HostName, PrincipalName, ResourceId, timeSlice
Source
github.com/Azure/Azure-Sentinel

'This query detects batches of distinct SQL queries that execute (or attempt to) commands that could indicate potential security issues - such as attempts to manipulate firewall rules (e.g. for allowing malicious access to the database).'

MITRE ATT&CK Coverage

TacticTechniques
Persistence
Defense Impairment

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: 05030ca6-ef66-42ca-b672-2e84d4aaf5d7
name: Firewall rule manipulation attempts stateful anomaly on database
description: |
  'This query detects batches of distinct SQL queries that execute (or attempt to) commands that could indicate potential security issues - such as attempts to manipulate firewall rules (e.g. for allowing malicious access to the database).'
severity: Medium
requiredDataConnectors:
  - connectorId: AzureSql
    dataTypes:
      - AzureDiagnostics
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - DefenseEvasion
  - Persistence
relevantTechniques:
  - T1098
  - T1562
tags:
  - SQL
query: |
    let monitoredStatementsThreshold = 1;           // Minimal number of monitored statements in the slice to trigger an anomaly.
    let trainingSlicesThreshold = 5;                // The maximal amount of slices with monitored statements in the training window before anomaly detection is throttled.
    let timeSliceSize = 1h;                         // The size of the single timeSlice for individual aggregation.
    let detectionWindow = 1h;                       // The size of the recent detection window for detecting anomalies.  
    let trainingWindow = detectionWindow + 14d;     // The size of the training window before the detection window for learning the normal state.
    let hotwords = pack_array('sp_set_firewall_rule', 'sp_set_database_firewall_rule', 'sp_delete_database_firewall_rule', 'sp_delete_firewall_rule', 'sys.firewall_rules', 'sys.database_firewall_rules'); // List of monitored hot words.
    let processedData = materialize (
        AzureDiagnostics
        | where TimeGenerated >= ago(trainingWindow)
        | where Category == 'SQLSecurityAuditEvents' and action_id_s has_any ("RCM", "BCM") // Keep only SQL affected rows
        | project TimeGenerated, PrincipalName = server_principal_name_s, ClientIp = client_ip_s, HostName = host_name_s, ResourceId,
                  ApplicationName = application_name_s, ActionName = action_name_s, Database = strcat(LogicalServerName_s, '/', database_name_s),
                  IsSuccess = succeeded_s, AffectedRows = affected_rows_d,
                  ResponseRows = response_rows_d, Statement = statement_s,
                  Error = case( additional_information_s has 'error_code', toint(extract("<error_code>([0-9.]+)", 1, additional_information_s))
                        , additional_information_s has 'failure_reason', toint(extract("<failure_reason>Err ([0-9.]+)", 1, additional_information_s))
                        , 0),
                  State = case( additional_information_s has 'error_state', toint(extract("<error_state>([0-9.]+)", 1, additional_information_s))
                        , additional_information_s has 'failure_reason', toint(extract("<failure_reason>Err ([0-9.]+), Level ([0-9.]+)", 2, additional_information_s))
                        , 0),
                  AdditionalInfo = additional_information_s, timeSlice = floor(TimeGenerated, timeSliceSize)
        | extend hasHotword = iff(Statement has_any (hotwords), 1, 0)
        | summarize countEvents = count(), countStatements = dcount(Statement)
            , countStatementsWithHotwords = dcountif(Statement, hasHotword == 1)
            , countFailedStatementsWithHotwords = dcountif(Statement, (hasHotword == 1) and (Error > 0))
            , countSuccessfulStatementsWithHotwords = dcountif(Statement, ((hasHotword == 1)) and (Error == 0))
            , anyMonitoredStatement = anyif(Statement, (hasHotword == 1))
            , anySuccessfulMonitoredStatement = anyif(Statement, (hasHotword == 1) and (Error == 0))
            , anyInfo = anyif(AdditionalInfo, hasHotword == 1)
            , hotWord = anyif(extract(strcat_array(hotwords, '|'), 0, tolower(Statement)), hasHotword == 1)
            by Database, ClientIp, ApplicationName, PrincipalName, timeSlice,HostName,ResourceId    
        | extend WindowType = case( timeSlice >= ago(detectionWindow), 'detection',
                                               (ago(trainingWindow) <= timeSlice and timeSlice < ago(detectionWindow)), 'training', 'other')
        | where WindowType in ('detection', 'training'));
    let trainingSet =
        processedData
        | where WindowType == 'training'
        | summarize countSlicesWithHotwords = dcountif(timeSlice, countStatementsWithHotwords >= monitoredStatementsThreshold)
            by Database;
    processedData
    | where WindowType == 'detection' 
    | join kind = inner (trainingSet) on Database
    | extend IsHotwordAnomalyOnStatement = iff(((countStatementsWithHotwords >= monitoredStatementsThreshold) and (countSlicesWithHotwords <= trainingSlicesThreshold)), true, false)
        , anomalyScore = round(countStatementsWithHotwords/monitoredStatementsThreshold, 0)
    | where IsHotwordAnomalyOnStatement == 'true'
    | project TimeGenerated = timeSlice, Database, ClientIp, ApplicationName, PrincipalName, HostName, ResourceId, countEvents, countStatements, countStatementsWithHotwords, anyMonitoredStatement, anyInfo, anomalyScore, hotWord
    | extend Name = tostring(split(PrincipalName,'@',0)[0]), UPNSuffix = tostring(split(PrincipalName,'@',1)[0])    
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: Name
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: ClientIp
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: HostName
  - entityType: CloudApplication
    fieldMappings:
      - identifier: Name
        columnName: ApplicationName
  - entityType: AzureResource
    fieldMappings:
      - identifier: ResourceId
        columnName: ResourceId
customDetails:
  HotWords: hotWord
alertDetailsOverride:
  alertDisplayNameFormat: 'Firewall rule manipulation attempts stateful anomaly on database {{Database}}'
  alertDescriptionFormat: 'An anomaly was detected on database {{Database}} with {{countStatementsWithHotwords}} statements with monitored hot words in the last hour. Investigate the database activity for potential malicious attempts to manipulate firewall rules or perform other unauthorized actions.'
version: 1.1.2
kind: Scheduled

Stages and Predicates

Parameters

let monitoredStatementsThreshold = 1;
let trainingSlicesThreshold = 5;
let timeSliceSize = 1h;
let detectionWindow = 1h;
let trainingWindow = detectionWindow + 14d;

let processedData is inlined into the numbered stages below.

Let Binding: hotwords used in Stages 5, 6

let hotwords = pack_array('sp_set_firewall_rule', 'sp_set_database_firewall_rule', 'sp_delete_database_firewall_rule', 'sp_delete_firewall_rule', 'sys.firewall_rules', 'sys.database_firewall_rules');

Let Binding: trainingSet used in Stage 10

let trainingSet = processedData
    | where WindowType == 'training'
    | summarize countSlicesWithHotwords = dcountif(timeSlice, countStatementsWithHotwords >= monitoredStatementsThreshold)
        by Database;

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

Stage 1: source

AzureDiagnostics

Stage 2: where

| where TimeGenerated >= ago(trainingWindow)

Stage 3: where

| where Category == 'SQLSecurityAuditEvents' and action_id_s has_any ("RCM", "BCM")

Stage 4: project

| project TimeGenerated, PrincipalName = server_principal_name_s, ClientIp = client_ip_s, HostName = host_name_s, ResourceId,
              ApplicationName = application_name_s, ActionName = action_name_s, Database = strcat(LogicalServerName_s, '/', database_name_s),
              IsSuccess = succeeded_s, AffectedRows = affected_rows_d,
              ResponseRows = response_rows_d, Statement = statement_s,
              Error = case( additional_information_s has 'error_code', toint(extract("<error_code>([0-9.]+)", 1, additional_information_s))
                    , additional_information_s has 'failure_reason', toint(extract("<failure_reason>Err ([0-9.]+)", 1, additional_information_s))
                    , 0),
              State = case( additional_information_s has 'error_state', toint(extract("<error_state>([0-9.]+)", 1, additional_information_s))
                    , additional_information_s has 'failure_reason', toint(extract("<failure_reason>Err ([0-9.]+), Level ([0-9.]+)", 2, additional_information_s))
                    , 0),
              AdditionalInfo = additional_information_s, timeSlice = floor(TimeGenerated, timeSliceSize)

Stage 5: extend

| extend hasHotword = iff(Statement has_any (hotwords), 1, 0)
hasHotword =
ifStatement has "hotwords"1
else0

Stage 6: summarize

| summarize countEvents = count(), countStatements = dcount(Statement)
        , countStatementsWithHotwords = dcountif(Statement, hasHotword == 1)
        , countFailedStatementsWithHotwords = dcountif(Statement, (hasHotword == 1) and (Error > 0))
        , countSuccessfulStatementsWithHotwords = dcountif(Statement, ((hasHotword == 1)) and (Error == 0))
        , anyMonitoredStatement = anyif(Statement, (hasHotword == 1))
        , anySuccessfulMonitoredStatement = anyif(Statement, (hasHotword == 1) and (Error == 0))
        , anyInfo = anyif(AdditionalInfo, hasHotword == 1)
        , hotWord = anyif(extract(strcat_array(hotwords, '|'), 0, tolower(Statement)), hasHotword == 1)
        by Database, ClientIp, ApplicationName, PrincipalName, timeSlice,HostName,ResourceId

Stage 7: extend

| extend WindowType = case( timeSlice >= ago(detectionWindow), 'detection',
                                           (ago(trainingWindow) <= timeSlice and timeSlice < ago(detectionWindow)), 'training', 'other')
WindowType =
if/* macro: (timeSlice >= ago(detectionWindow)) */'detection'
elif/* macro: (ago(trainingWindow) <= timeSlice) */'training'
else'other'

Stage 8: where

| where WindowType in ('detection', 'training')

Stage 9: where

processedData
| where WindowType == 'detection'

Stage 10: join

| join kind = inner (trainingSet) on Database

Stage 11: extend

extend IsHotwordAnomalyOnStatement, anomalyScore
IsHotwordAnomalyOnStatement =
ifcountStatementsWithHotwords >= monitoredStatementsThreshold and countSlicesWithHotwords <= trainingSlicesThresholdtrue
elsefalse

Stage 12: where

where IsHotwordAnomalyOnStatement == true

Stage 13: project

project ApplicationName, ClientIp, Database, HostName, PrincipalName, ResourceId, TimeGenerated, anomalyScore, anyInfo, anyMonitoredStatement, countEvents, countStatements, countStatementsWithHotwords, hotWord

Stage 14: extend

extend Name, UPNSuffix

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
ApplicationNameproject
ClientIpproject
Databaseproject
HostNameproject
PrincipalNameproject
ResourceIdproject
TimeGeneratedproject
anomalyScoreproject
anyInfoproject
anyMonitoredStatementproject
countEventsproject
countStatementsproject
countStatementsWithHotwordsproject
hotWordproject
Nameextend
UPNSuffixextend

HoneyLabs TI Map URL Entity to CommonSecurityLog

#
Severity
high
Time window
14d
Group by
$left.TI_urlEntity, $right.RequestURL, Id, TI_urlEntity
Source
github.com/Azure/Azure-Sentinel

'Identifies requests in CommonSecurityLog to malware loader or command-and-control URLs that HoneyLabs pulled out of payloads captured by its honeypot sensors. A hit usually means a host inside your estate reached out to attacker-controlled infrastructure, so treat it as post-compromise until proven otherwise. Indicator Confidence reflects how much evidence HoneyLabs holds (90 = 100+ observed attacks, 60 = a single sighting); raise the threshold in the query to trade coverage for precision.'

MITRE ATT&CK Coverage

Rule Body

id: afe2a5ff-f4fe-4dee-8b6b-fd28d29d6738
name: HoneyLabs TI Map URL Entity to CommonSecurityLog
version: 1.0.0
kind: Scheduled
description: |
  'Identifies requests in CommonSecurityLog to malware loader or command-and-control URLs that HoneyLabs pulled out of payloads captured by its honeypot sensors. A hit usually means a host inside your estate reached out to attacker-controlled infrastructure, so treat it as post-compromise until proven otherwise. Indicator Confidence reflects how much evidence HoneyLabs holds (90 = 100+ observed attacks, 60 = a single sighting); raise the threshold in the query to trade coverage for precision.'
severity: High
requiredDataConnectors:
  - connectorId: ThreatIntelligenceTaxii
    dataTypes:
      - ThreatIntelligenceIndicator
  - connectorId: CEF
    dataTypes:
      - CommonSecurityLog
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
  - Execution
relevantTechniques:
  - T1071
  - T1105
query: |
  let dt_lookBack = 1h;
  let ioc_lookBack = 14d;
  let HoneyLabs_URLs = ThreatIntelIndicators
    | where TimeGenerated >= ago(ioc_lookBack)
    | where IsActive == true and ValidUntil > now()
    | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id
    | where SourceSystem startswith 'HoneyLabs'
    | where ObservableKey == 'url:value'
    | extend TI_urlEntity = tostring(ObservableValue),
             HoneyLabsConfidence = Confidence,
             IndicatorDescription = tostring(Data.description);
  HoneyLabs_URLs
  | join kind=innerunique (
      CommonSecurityLog
      | where TimeGenerated >= ago(dt_lookBack)
      | where isnotempty(RequestURL)
      | extend CommonSecurityLog_TimeGenerated = TimeGenerated
  ) on $left.TI_urlEntity == $right.RequestURL
  | where CommonSecurityLog_TimeGenerated < ValidUntil
  | summarize CommonSecurityLog_TimeGenerated = arg_max(CommonSecurityLog_TimeGenerated, *) by Id, TI_urlEntity
  | project CommonSecurityLog_TimeGenerated, IndicatorDescription, Id, ValidUntil,
    HoneyLabsConfidence, TI_urlEntity, SourceIP, DestinationIP, DeviceName, DeviceAction,
    SourceUserName, RequestClientApplication
  | extend timestamp = CommonSecurityLog_TimeGenerated
entityMappings:
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: TI_urlEntity
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIP

Stages and Predicates

Parameters

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

let HoneyLabs_URLs is inlined into the numbered stages below.

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

Stage 1: source

ThreatIntelIndicators

Stage 2: where

| where TimeGenerated >= ago(ioc_lookBack)

Stage 3: where

| where IsActive == true and ValidUntil > now()

Stage 4: summarize

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

Stage 5: where

| where SourceSystem startswith 'HoneyLabs'

Stage 6: where

| where ObservableKey == 'url:value'

Stage 7: extend

| extend TI_urlEntity = tostring(ObservableValue),
           HoneyLabsConfidence = Confidence,
           IndicatorDescription = tostring(Data.description)

Stage 8: join

HoneyLabs_URLs
| join kind=innerunique (
    CommonSecurityLog
    | where TimeGenerated >= ago(dt_lookBack)
    | where isnotempty(RequestURL)
    | extend CommonSecurityLog_TimeGenerated = TimeGenerated
) on $left.TI_urlEntity == $right.RequestURL

Stage 9: where

where CommonSecurityLog_TimeGenerated < ValidUntil

Stage 10: summarize

summarize CommonSecurityLog_TimeGenerated by Id, TI_urlEntity

Stage 11: project

project CommonSecurityLog_TimeGenerated, DestinationIP, DeviceAction, DeviceName, HoneyLabsConfidence, Id, IndicatorDescription, RequestClientApplication, SourceIP, SourceUserName, TI_urlEntity, ValidUntil

Stage 12: extend

extend timestamp

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
CommonSecurityLog_TimeGeneratedproject
DestinationIPproject
DeviceActionproject
DeviceNameproject
HoneyLabsConfidenceproject
Idproject
IndicatorDescriptionproject
RequestClientApplicationproject
SourceIPproject
SourceUserNameproject
TI_urlEntityproject
ValidUntilproject
timestampextend

Imperva - Request from unexpected countries

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

'Detects request attempts from unexpected countries.'

MITRE ATT&CK Coverage

Rule Body

id: 58300723-22e0-4096-b33a-aa9b992c3564
name: Imperva - Request from unexpected countries
description: |
  'Detects request attempts from unexpected countries.'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: ImpervaWAFCloudAPI
    dataTypes:
      - ImpervaWAFCloud
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1190
  - T1133
query: |
  let bl_country = dynamic(['CH', 'KR']);
  ImpervaWAFCloud
  | where Country in (bl_country)
  | where DvcAction !startswith 'REQ_BLOCKED' or DvcAction !startswith 'REQ_BAD_'
  | extend IPCustomEntity = SrcIpAddr
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPCustomEntity
version: 1.0.0
kind: Scheduled

Stages and Predicates

Parameters

let bl_country = dynamic(['CH', 'KR']);

Stage 1: source

ImpervaWAFCloud

Stage 2: where

| where Country in (bl_country)

Stage 3: where

| where DvcAction !startswith 'REQ_BLOCKED' or DvcAction !startswith 'REQ_BAD_'

Stage 4: extend

| extend IPCustomEntity = SrcIpAddr

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Countryin
  • CH
  • KR
field:"Country" kind:in

Output Fields

These fields are emitted when the rule matches.

FieldSource
IPCustomEntityextend

Mail redirect via ExO transport rule

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

Identifies when Exchange Online transport rule configured to forward emails. This could be an adversary mailbox configured to collect mail from multiple user accounts.

MITRE ATT&CK Coverage

TacticTechniques
Collection
Exfiltration

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: 500415fb-bba7-4227-a08a-9857fb61b6a7
name: Mail redirect via ExO transport rule
description: |
  'Identifies when Exchange Online transport rule configured to forward emails.
  This could be an adversary mailbox configured to collect mail from multiple user accounts.'
severity: Medium
status: Available 
requiredDataConnectors:
  - connectorId: Office365
    dataTypes:
      - OfficeActivity (Exchange)
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Collection
  - Exfiltration
relevantTechniques:
  - T1114
  - T1020
query: |
  OfficeActivity
  | where OfficeWorkload == "Exchange"
  | where Operation in~ ("New-TransportRule", "Set-TransportRule")
  | mv-apply DynamicParameters = todynamic(Parameters) on (summarize ParsedParameters = make_bag(pack(tostring(DynamicParameters.Name), DynamicParameters.Value)))
  | extend RuleName = case(
      Operation =~ "Set-TransportRule", OfficeObjectId,
      Operation =~ "New-TransportRule", ParsedParameters.Name,
      "Unknown")
  | mv-expand ExpandedParameters = todynamic(Parameters)
  | where ExpandedParameters.Name in~ ("BlindCopyTo", "RedirectMessageTo") and isnotempty(ExpandedParameters.Value)
  | extend RedirectTo = ExpandedParameters.Value
  | extend ClientIPValues = extract_all(@'\[?(::ffff:)?(?P<IPAddress>(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P<Port>\d+))?', dynamic(["IPAddress", "Port"]), ClientIP)[0]
  | extend From = ParsedParameters.From
  | project TimeGenerated, RedirectTo, IPAddress = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1]), UserId, From, Operation, RuleName, Parameters
  | 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: IPAddress
version: 2.0.4
kind: Scheduled

Stages and Predicates

Stage 1: source

OfficeActivity

Stage 2: where

| where OfficeWorkload == "Exchange"

Stage 3: where

| where Operation in~ ("New-TransportRule", "Set-TransportRule")

Stage 4: kusto:mv-apply

| mv-apply DynamicParameters = todynamic(Parameters) on (summarize ParsedParameters = make_bag(pack(tostring(DynamicParameters.Name), DynamicParameters.Value)))

Stage 5: extend

| extend RuleName = case(
    Operation =~ "Set-TransportRule", OfficeObjectId,
    Operation =~ "New-TransportRule", ParsedParameters.Name,
    "Unknown")
RuleName =
ifOperation =~ "Set-TransportRule"OfficeObjectId
elifOperation =~ "New-TransportRule"ParsedParameters.Name
else"Unknown"

Stage 6: mv-expand

| mv-expand ExpandedParameters = todynamic(Parameters)

Stage 7: where

| where ExpandedParameters.Name in~ ("BlindCopyTo", "RedirectMessageTo") and isnotempty(ExpandedParameters.Value)

Stage 8: extend (3 consecutive steps)

| extend RedirectTo = ExpandedParameters.Value
| extend ClientIPValues = extract_all(@'\[?(::ffff:)?(?P<IPAddress>(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P<Port>\d+))?', dynamic(["IPAddress", "Port"]), ClientIP)[0]
| extend From = ParsedParameters.From

Stage 9: project

| project TimeGenerated, RedirectTo, IPAddress = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1]), UserId, From, Operation, RuleName, Parameters

Stage 10: extend

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

Stage 11: summarize aggregation inside the mv-apply branch

summarize

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
ExpandedParameters.Namein
  • BlindCopyTo transforms: kusto_dynamic_path
  • RedirectMessageTo transforms: kusto_dynamic_path
field:"ExpandedParameters.Name" kind:in
ExpandedParameters.Valueis_not_null
  • (no value, null check)
field:"ExpandedParameters.Value" kind:is_not_null
OfficeWorkloadeq
  • Exchange corpus 2 (kusto 2)
field:"m365::Workload" kind:eq value:"Exchange"
Operationin
  • New-TransportRule corpus 2 (kusto 2)
  • Set-TransportRule corpus 3 (kusto 3)
field:"Operation" kind:in

NRT GitHub Two Factor Auth Disable

#
Severity
medium
Source
github.com/Azure/Azure-Sentinel

Two-factor authentication is a process where a user is prompted during the sign-in process for an additional form of identification, such as to enter a code on their cellphone or to provide a fingerprint scan. Two factor authentication reduces the risk of account takeover. Attacker will want to disable such security tools in order to go undetected.

MITRE ATT&CK Coverage

TacticTechniques
Defense Impairment

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: 594c653d-719a-4c23-b028-36e3413e632e
name: NRT GitHub Two Factor Auth Disable
description: |
  'Two-factor authentication is a process where a user is prompted during the sign-in process for an additional form of identification, such as to enter a code on their cellphone or to provide a fingerprint scan. Two factor authentication reduces the risk of account takeover. Attacker will want to disable such security tools in order to go undetected. '
severity: Medium
requiredDataConnectors: []
tactics:
  - DefenseEvasion
relevantTechniques:
  - T1562
query: |
  GitHubAuditData
  | where Action == "org.disable_two_factor_requirement"
  | project TimeGenerated, Action, Actor, Country, Repository
  | extend Name = iif(Actor contains "@", split(Actor, "@")[0], Actor)
  | extend UPNSuffix = iif(Actor contains "@", split(Actor, "@")[1], "")
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: Actor
      - identifier: Name
        columnName: Name
      - identifier: UPNSuffix
        columnName: UPNSuffix
version: 1.0.3
kind: NRT

Stages and Predicates

Stage 1: source

GitHubAuditData

Stage 2: where

| where Action == "org.disable_two_factor_requirement"

Stage 3: project

| project TimeGenerated, Action, Actor, Country, Repository

Stage 4: extend

| extend Name = iif(Actor contains "@", split(Actor, "@")[0], Actor)

Stage 5: extend

| extend UPNSuffix = iif(Actor contains "@", split(Actor, "@")[1], "")

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Actioneq
  • org.disable_two_factor_requirement corpus 2 (kusto 2)
field:"Action" kind:eq value:"org.disable_two_factor_requirement"

Output Fields

These fields are emitted when the rule matches.

FieldSource
Actionproject
Actorproject
Countryproject
Repositoryproject
TimeGeneratedproject
Nameextend
UPNSuffixextend

Pathlock TDnR - Pathlock Security Radar Internal Events

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

Detects internal events generated by the Pathlock Security Radar platform, forwarded to Microsoft Sentinel. These events include platform health indicators, configuration changes, and self-monitoring alerts that may require SOC attention.

MITRE ATT&CK Coverage

TacticTechniques
Discovery

Rule Body

id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c34
name: Pathlock TDnR - Pathlock Security Radar Internal Events
kind: Scheduled
description: >-
  Detects internal events generated by the Pathlock Security Radar platform, forwarded to Microsoft
  Sentinel. These events include platform health indicators, configuration changes, and
  self-monitoring alerts that may require SOC attention.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: Pathlock_TDnR
    dataTypes:
      - Pathlock_TDnR_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Discovery
relevantTechniques:
  - T1082
query: |
  Pathlock_TDnR_CL
  | where DataSource == "INTERNAL"
  | 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 == "INTERNAL"

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 - SAP Read Access Logging Audit

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

Detects SAP Read Access Logging (RAL) audit changelog events, forwarded by Pathlock Threat Detection and Response. RAL audit events capture changes to which sensitive data fields are being logged, and modifications may indicate attempts to disable read access monitoring for sensitive data.

MITRE ATT&CK Coverage

Rule Body

id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c43
name: Pathlock TDnR - SAP Read Access Logging Audit
kind: Scheduled
description: >-
  Detects SAP Read Access Logging (RAL) audit changelog events, forwarded by Pathlock Threat
  Detection and Response. RAL audit events capture changes to which sensitive data fields are being
  logged, and modifications may indicate attempts to disable read access monitoring for sensitive data.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: Pathlock_TDnR
    dataTypes:
      - Pathlock_TDnR_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Collection
relevantTechniques:
  - T1213
query: |
  Pathlock_TDnR_CL
  | where DataSource == "RAL_AUDIT"
  | 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 == "RAL_AUDIT"

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

Silverfort - NoPacBreach Incident

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

'The NoPac vulnerability involves privilege escalation, allowing attackers to gain unauthorized access and potentially take control of an entire Active Directory domain'

MITRE ATT&CK Coverage

Rule Body

id: bdfd2c45-10a0-44e7-a90a-ba7b6bdd9ff2
name: Silverfort - NoPacBreach Incident
description: |
  'The NoPac vulnerability involves privilege escalation, allowing attackers to gain unauthorized access and potentially take control of an entire Active Directory domain'
severity: High
requiredDataConnectors:
  - connectorId: SilverfortAma
    dataTypes:
      - CommonSecurityLog
tactics:
  - PrivilegeEscalation
relevantTechniques:
  - T1068 #Exploitation for Privilege Escalation
  - T1548 #Abuse Elevation Control Mechanism
query: |-
  CommonSecurityLog 
  | where DeviceVendor has 'Silverfort'
  | where DeviceProduct has 'Admin Console'
  | where DeviceEventClassID == "NewIncident"
  | where Message has "NoPacBreach"
  | extend UserName = parse_json(replace('^""|""$', '', Message))['userName']
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: UserName
queryPeriod: 15m
queryFrequency: 15m
triggerOperator: gt
triggerThreshold: 0
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

CommonSecurityLog

Stage 2: where

| where DeviceVendor has 'Silverfort'

Stage 3: where

| where DeviceProduct has 'Admin Console'

Stage 4: where

| where DeviceEventClassID == "NewIncident"

Stage 5: where

| where Message has "NoPacBreach"

Stage 6: extend

| extend UserName = parse_json(replace('^""|""$', '', Message))['userName']

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
UserNameextend

Tape Erase Job Started

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

Detects when tape erase operations start. This might indicate data destruction activity.

Rule Body

id: 06ba509c-606e-4967-baee-21815ae61f8e
name: Tape Erase Job Started
description: Detects when tape erase operations start. This might indicate data destruction
  activity.
severity: High
status: Available
requiredDataConnectors:
- connectorId: Syslog
  dataTypes:
  - Syslog
- connectorId: SyslogAma
  dataTypes:
  - Syslog
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
  aggregationKind: AlertPerResult
tactics: []
relevantTechniques: []
query: "Veeam_GetSecurityEvents\n| where instanceId == 115\n| extend JobName = extract(\"\
  JobName=\\\"([^\\\"]*)\\\"\", 1, SyslogMessage)\n| project\n    Date = format_datetime(TimeGenerated,\
  \ 'dd.MM.yyyy HH:mm'),\n    DataSource = original_host,\n    EventId = instanceId,\n\
  \    [\"Job Name\"] = JobName,\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 == 115

Stage 3: extend

| extend JobName = extract("JobName=\"([^\"]*)\"", 1, SyslogMessage)

Stage 4: project

| project
    Date = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),
    DataSource = original_host,
    EventId = instanceId,
    ["Job Name"] = JobName,
    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
Job Nameproject
MessageDetailsproject
Severityproject

TI Map Domain entity to EmailUrlInfo

#
Severity
medium
Time window
14d
Group by
$left.DomainName, $right.UrlDomain, Id, NetworkMessageId, ObservableValue, Url
Source
github.com/Azure/Azure-Sentinel

Identifies a match in EmailUrlInfo table from any Domain IOC from TI.

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Telemetry Coverage

Rule Body

id: b56e2290-c65b-45a5-9636-3651e85bbe5d
name: TI Map Domain entity to EmailUrlInfo
description: |
  'Identifies a match in EmailUrlInfo table from any Domain IOC from TI.'
severity: Medium
requiredDataConnectors:
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - EmailUrlInfo
  - connectorId: ThreatIntelligence
    dataTypes:
      - ThreatIntelIndicators
  - connectorId: ThreatIntelligenceTaxii
    dataTypes:
      - ThreatIntelIndicators
  - connectorId: MicrosoftDefenderThreatIntelligence
    dataTypes:
      - ThreatIntelIndicators
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1566
query: |
    let dt_lookBack = 1h; // Define the lookback period for email data as 1 hour
    let ioc_lookBack = 14d; // Define the lookback period for threat intelligence data as 14 days
    let EmailUrlInfo_ = EmailUrlInfo| where isnotempty(Url) or isnotempty(UrlDomain) // Filter for non-empty URLs or URL domains
    | where TimeGenerated >= ago(dt_lookBack) // Filter for records within the lookback period
    | extend Url = tolower(Url), UrlDomain = tolower(UrlDomain) // Convert URLs and domains to lowercase
    | extend EmailUrlInfo_TimeGenerated = TimeGenerated; // Create a new column for the time generated
    let EmailEvents_ = EmailEvents
    | where TimeGenerated >= ago(dt_lookBack); // Filter email events within the lookback period
    let TI_Urls = ThreatIntelIndicators
    | where TimeGenerated >= ago(ioc_lookBack) // Filter threat intelligence indicators within the lookback period
    | summarize arg_max(TimeGenerated, *) by Id, ObservableValue // Get latest record per Id FIRST
    | where IsActive == true and ValidUntil > now() // THEN filter for active indicators that haven't expired
    | extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))
    | where isnotempty(IndicatorType) and IndicatorType == "url"
    | extend Url = tolower(ObservableValue)
    | where isnotempty(Url) // Filter for non-empty URLs
    | join kind=innerunique (EmailUrlInfo_) on Url // Join with email URL info on URL
    | extend Description = tostring(parse_json(Data).description)
    | extend ActivityGroupNames = extract("ActivityGroup:([^,]+)", 1, tostring(parse_json(Data).labels))
    | project EmailUrlInfo_TimeGenerated, Description, ActivityGroupNames, Id, ValidUntil, Confidence, Url, UrlLocation, NetworkMessageId; // Select relevant columns
    let TI_Domains = ThreatIntelIndicators
    | where TimeGenerated >= ago(ioc_lookBack) // Filter threat intelligence indicators within the lookback period
    | where isnotempty(ObservableValue) // Filter for non-empty domain names
    | extend DomainName = tolower(ObservableValue) // Convert domain names to lowercase
    | where ObservableKey == "domain-name:value"
    | summarize arg_max(TimeGenerated, *) by Id, ObservableValue // Get latest record per Id FIRST
    | where IsActive == true // THEN filter for active indicators
    | extend TrafficLightProtocolLevel = AdditionalFields.TLPLevel
    | extend IndicatorId = tostring(split(Id, "--")[2]);
    let TI_Domains_UrlInfo = TI_Domains
    | project-reorder *, IsActive, Tags, TrafficLightProtocolLevel, DomainName, Type
    | join kind=innerunique (EmailUrlInfo_) on $left.DomainName == $right.UrlDomain // Join with email URL info on domain name
    | extend ExpirationDateTime = ValidUntil
    | extend Description = Data.description
    | extend ThreatType =  Data.type
    | extend ConfidenceScore = Confidence
    | extend ActivityGroupNames = replace_string(tostring(parse_json(tostring(Data.labels))[0]),"ActivityGroup:","")
    | where ExpirationDateTime > now() // Filter for indicators that haven't expired
    | project EmailUrlInfo_TimeGenerated, Description, ActivityGroupNames, IndicatorId, ThreatType, ExpirationDateTime, ConfidenceScore, UrlDomain, UrlLocation, NetworkMessageId; // Select relevant columns
    union TI_Urls, TI_Domains_UrlInfo // Combine URL and domain threat intelligence data
    | extend timestamp = EmailUrlInfo_TimeGenerated // Add a timestamp column
    | join kind=inner (EmailEvents_) on NetworkMessageId // Join with email events on network message ID
    | where DeliveryAction !has "Blocked" // Filter out blocked delivery actions
    | extend Name = tostring(split(RecipientEmailAddress, '@', 0)[0]), UPNSuffix = tostring(split(RecipientEmailAddress, '@', 1)[0]); // Extract name and UPN suffix from recipient email address"
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: RecipientEmailAddress
      - identifier: Name
        columnName: Name
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: Url
version: 1.0.7
kind: Scheduled

Stages and Predicates

Parameters

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

Let Binding: EmailUrlInfo_

let EmailUrlInfo_ = EmailUrlInfo| where isnotempty(Url) or isnotempty(UrlDomain)
| where TimeGenerated >= ago(dt_lookBack)
| extend Url = tolower(Url), UrlDomain = tolower(UrlDomain)
| extend EmailUrlInfo_TimeGenerated = TimeGenerated;

Let Binding: EmailEvents_

let EmailEvents_ = EmailEvents
| where TimeGenerated >= ago(dt_lookBack);

Union (2 Sources)

Each leg below queries one source; the rule matches if any leg does. Sources: TI_Urls, TI_Domains_UrlInfo

Leg 1: TI_Urls

Leg 2: TI_Domains_UrlInfo

Applied to the Combined Result

| extend timestamp = EmailUrlInfo_TimeGenerated | join kind=inner (EmailEvents_) on NetworkMessageId | where DeliveryAction !has "Blocked" | extend Name = tostring(split(RecipientEmailAddress, '@', 0)[0]), UPNSuffix = tostring(split(RecipientEmailAddress, '@', 1)[0]);

Indicators

These rows show field, operator, and value matches.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
DeliveryActionmatchBlockedexcludes:DeliveryAction field:"DeliveryAction" value:"Blocked"

Output Fields

These fields are emitted when the rule matches.

FieldSource
ActivityGroupNamesproject
ConfidenceScoreproject
Descriptionproject
EmailUrlInfo_TimeGeneratedproject
ExpirationDateTimeproject
IndicatorIdproject
NetworkMessageIdproject
ThreatTypeproject
UrlDomainproject
UrlLocationproject
timestampextend
Nameextend
UPNSuffixextend

TIE LSASS Memory

#
Severity
high
Time window
2h
Source
github.com/Azure/Azure-Sentinel

'Searches for OS Credentials dumping attacks.'

MITRE ATT&CK Coverage

TacticTechniques
Credential Access

Rule Body

id: 7851f57c-98b6-43c6-9747-9bb7cf11f21c
name: TIE LSASS Memory
description: |
  'Searches for OS Credentials dumping attacks.'
severity: High
requiredDataConnectors:
  - connectorId: TenableIE
    dataTypes:
      - Tenable_IE_CL
queryFrequency: 2h
queryPeriod: 2h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CredentialAccess
relevantTechniques:
  - T1003.001
query: |
  // For the query to work properly, make sure you have imported the afad_parser.yaml parser into the workspace
  // Retrieve the parser here: https://aka.ms/sentinel-TenableApp-afad-parser
  // Then, create the Kusto Function with alias afad_parser
  afad_parser
    | where MessageType == 2 and Codename == "OS Credential Dumping: LSASS Memory"
    | extend HostName = tostring(split(Host, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(Host, '.'), 1, -1), '.'))
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: DnsDomain
version: 1.0.1
kind: Scheduled

Stages and Predicates

Stage 1: source

afad_parser

Stage 2: where

| where MessageType == 2 and Codename == "OS Credential Dumping: LSASS Memory"

Stage 3: extend

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

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Codenameeq
  • OS Credential Dumping: LSASS Memory corpus 3 (kusto 3)
field:"Codename" kind:eq value:"OS Credential Dumping: LSASS Memory"
MessageTypeeq
  • 2 corpus 21 (kusto 21)
field:"MessageType" kind:eq value:"2"

Output Fields

These fields are emitted when the rule matches.

FieldSource
DnsDomainextend
HostNameextend

Whisper Security - Tor Exit Node Communication

#
Status
available
Severity
medium
Time window
1d
Group by
$left.DestinationIP, $right.indicator
Source
github.com/Azure/Azure-Sentinel

Identifies network traffic communicating with known Tor exit nodes. While Tor usage may be legitimate in some environments, unexpected Tor communication can indicate data exfiltration, C2 tunneling, or policy violations.

MITRE ATT&CK Coverage

TacticTechniques
Command & Control

Rule Body

id: f5567c93-91de-577a-b35e-c2807715493d
name: Whisper Security - Tor Exit Node Communication
description: |
  Identifies network traffic communicating with known Tor exit nodes. While Tor usage may be legitimate in some environments, unexpected Tor communication can indicate data exfiltration, C2 tunneling, or policy violations.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: WhisperSecurityConnector
  dataTypes:
  - WhisperThreatIntel_CL
queryFrequency: 1h
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- CommandAndControl
relevantTechniques:
- T1090
query: |
  // MITRE ATT&CK: T1090.003 - Proxy: Multi-hop Proxy
  // Tactic: Command and Control
  // Detects communication with Tor exit nodes using Whisper threat intelligence
  let timeRange = 1d;
  let torIndicators = WhisperThreatIntel_CL
      | where TimeGenerated > ago(timeRange)
      | where isTor == true
      | project indicator, threatScore, threatLevel, feedNames, lastSeen;
  let CommonSecurityLogSafe = union isfuzzy=true CommonSecurityLog, (datatable(TimeGenerated:datetime, DestinationIP:string, SourceIP:string, DeviceProduct:string, DeviceAction:string, DeviceVendor:string, Activity:string)[]);
  CommonSecurityLogSafe
      | where TimeGenerated > ago(timeRange)
      | where isnotempty(DestinationIP)
      | join kind=inner (torIndicators) on $left.DestinationIP == $right.indicator
      | extend SourceIP = SourceIP, DestinationIP = DestinationIP
      | project TimeGenerated, SourceIP, DestinationIP, DeviceProduct, DeviceAction, DeviceVendor, Activity, threatScore, threatLevel, feedNames
entityMappings:
- entityType: IP
  fieldMappings:
  - identifier: Address
    columnName: SourceIP
- entityType: IP
  fieldMappings:
  - identifier: Address
    columnName: DestinationIP
customDetails:
  ThreatScore: threatScore
  ThreatLevel: threatLevel
  FeedNames: feedNames
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: PT5H
    matchingMethod: AllEntities
version: 1.0.0
kind: Scheduled

Stages and Predicates

Parameters

let timeRange = 1d;

Let Binding: torIndicators

let torIndicators = WhisperThreatIntel_CL
    | where TimeGenerated > ago(timeRange)
    | where isTor == true
    | project indicator, threatScore, threatLevel, feedNames, lastSeen;

Union isfuzzy=true (2 Sources)

Each leg below queries one source; the rule matches if any leg does. Sources: CommonSecurityLog, datatable(TimeGenerated:datetime,

Leg 1: CommonSecurityLog

Leg 2: datatable(TimeGenerated:datetime,

(datatable(TimeGenerated:datetime, DestinationIP:string, SourceIP:string, DeviceProduct:string, DeviceAction:string, DeviceVendor:string, Activity:string)[])

Applied to the Combined Result

| where TimeGenerated > ago(timeRange) | where isnotempty(DestinationIP) | join kind=inner (torIndicators) on $left.DestinationIP == $right.indicator | extend SourceIP = SourceIP, DestinationIP = DestinationIP | project TimeGenerated, SourceIP, DestinationIP, DeviceProduct, DeviceAction, DeviceVendor, Activity, threatScore, threatLevel, feedNames

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
DestinationIPis_not_null
  • (no value, null check)
field:"DestinationIP" kind:is_not_null
isToreq
  • true
field:"isTor" kind:eq value:"true"

Output Fields

These fields are emitted when the rule matches.

FieldSource
Activityproject
DestinationIPproject
DeviceActionproject
DeviceProductproject
DeviceVendorproject
SourceIPproject
TimeGeneratedproject
feedNamesproject
threatLevelproject
threatScoreproject

Zero Networks Segement - Machine Removed from protection

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

'Detects when a machine is removed from protection.'

MITRE ATT&CK Coverage

TacticTechniques
Defense Impairment

Rule Body

id: a4ce12ca-d01d-460a-b15e-6c74ef328b82
name: Zero Networks Segement - Machine Removed from protection
description: |
  'Detects when a machine is removed from protection.'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: ZeroNetworksSegmentAuditFunction
    dataTypes:
      - ZNSegmentAudit_CL
  - connectorId: ZeroNetworksSegmentAuditNativePoller
    dataTypes:
      - ZNSegmentAuditNativePoller_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - DefenseEvasion
relevantTechniques:
  - T1562
query: |
  ZNSegmentAudit
  | where AuditTypeId == 4
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: PerformedByName
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: DestinationEntityName
version: 1.0.1
kind: Scheduled

Stages and Predicates

Stage 1: source

ZNSegmentAudit

Stage 2: where

| where AuditTypeId == 4

Indicators

These rows show field, operator, and value matches.

Back to all Kusto rules