Detection rules › Kusto

Kusto rules: 00

A potentially malicious web request was executed against a web server

#
Status
available
Severity
medium
Time window
1d
Group by
ClientIp, Hostname, SessionBlockedStarted, TimeKey
Source
github.com/Azure/Azure-Sentinel

'Detects unobstructed Web Application Firewall (WAF) activity in sessions where the WAF blocked incoming requests by computing the ratio between blocked requests and unobstructed WAF requests in these sessions (BlockvsSuccessRatio metric). A high ratio value for a given client IP and hostname calls for further investigation of the WAF data in that session, due to the significantly high number of blocked requests and a few unobstructed logs that may be malicious but have passed undetected through the WAF. The successCode variable defines what the detection thinks is a successful status code and should be altered to fit the environment.'

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Rule Body

id: 46ac55ae-47b8-414a-8f94-89ccd1962178
name: A potentially malicious web request was executed against a web server
description: |
  'Detects unobstructed Web Application Firewall (WAF) activity in sessions where the WAF blocked incoming requests by computing the ratio between blocked requests and unobstructed WAF requests in these sessions (BlockvsSuccessRatio metric).
  A high ratio value for a given client IP and hostname calls for further investigation of the WAF data in that session, due to the significantly high number of blocked requests and a few unobstructed logs that may be malicious but have passed undetected through the WAF. The successCode variable defines what the detection thinks is a successful status code and should be altered to fit the environment.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: WAF
    dataTypes:
      - AzureDiagnostics
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1190
query: |
  let queryperiod = 1d;
  let mode = dynamic(['Blocked', 'Detected']);
  let successCode = dynamic(['200', '101','204', '400','504','304','401','500']);
  let sessionBin = 30m;
  AGWFirewallLogs
  | where TimeGenerated > ago(queryperiod)
  | where Action  in (mode)
  | sort by Hostname asc, ClientIp asc, TimeGenerated asc
  | extend SessionBlockedStarted = row_window_session(TimeGenerated, queryperiod, 10m, ((ClientIp != prev(ClientIp)) or (Hostname != prev(Hostname))))
  | summarize SessionBlockedEnded = max(TimeGenerated), SessionBlockedCount = count() by Hostname, ClientIp, SessionBlockedStarted
  | extend TimeKey = range(bin(SessionBlockedStarted, sessionBin), bin(SessionBlockedEnded, sessionBin), sessionBin)
  | mv-expand TimeKey to typeof(datetime)
  | join kind = inner(
      AGWAccessLogs
      | where TimeGenerated > ago(queryperiod)
      | where  (isempty(HttpStatus) or HttpStatus in (successCode))
      | extend TimeKey = bin(TimeGenerated, sessionBin)
      | extend Hostname = coalesce(Host,OriginalHost)
      | extend ClientIp = tostring(ClientIp)
  ) on TimeKey, Hostname, ClientIp
  | where TimeGenerated between (SessionBlockedStarted..SessionBlockedEnded)
  | extend
      OriginalRequestUriWithArgs = column_ifexists("OriginalRequestUriWithArgs", ""),
      ServerStatus = column_ifexists("ServerStatus", "")
  | summarize
      SuccessfulAccessCount = count(),
      UserAgents = make_set(UserAgent, 250),
      RequestURIs = make_set(RequestUri, 250),
      OriginalRequestURIs = make_set(OriginalRequestUriWithArgs, 250),
      SuccessCodes = make_set(HttpStatus, 250),
      SuccessCodes_BackendServer = make_set(ServerStatus, 250),
      take_any(SessionBlockedEnded, SessionBlockedCount)
      by Hostname, ClientIp, SessionBlockedStarted 
  | where SessionBlockedCount > SuccessfulAccessCount
  | extend BlockvsSuccessRatio = SessionBlockedCount/toreal(SuccessfulAccessCount)
  | sort by BlockvsSuccessRatio desc, SessionBlockedStarted asc
  | project-reorder SessionBlockedStarted, SessionBlockedEnded, Hostname, ClientIp, SessionBlockedCount, SuccessfulAccessCount, BlockvsSuccessRatio, SuccessCodes, RequestURIs, OriginalRequestURIs, UserAgents
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: ClientIp
version: 1.0.6
kind: Scheduled

Stages and Predicates

Parameters

let queryperiod = 1d;
let mode = dynamic(['Blocked', 'Detected']);
let successCode = dynamic(['200', '101','204', '400','504','304','401','500']);
let sessionBin = 30m;

Stage 1: source

AGWFirewallLogs

Stage 2: where

| where TimeGenerated > ago(queryperiod)

Stage 3: where

| where Action  in (mode)

Stage 4: sort

| sort by Hostname asc, ClientIp asc, TimeGenerated asc

Stage 5: extend

| extend SessionBlockedStarted = row_window_session(TimeGenerated, queryperiod, 10m, ((ClientIp != prev(ClientIp)) or (Hostname != prev(Hostname))))

Stage 6: summarize

| summarize SessionBlockedEnded = max(TimeGenerated), SessionBlockedCount = count() by Hostname, ClientIp, SessionBlockedStarted

Stage 7: extend

| extend TimeKey = range(bin(SessionBlockedStarted, sessionBin), bin(SessionBlockedEnded, sessionBin), sessionBin)

Stage 8: mv-expand

| mv-expand TimeKey to typeof(datetime)

Stage 9: join

| join kind = inner(
    AGWAccessLogs
    | where TimeGenerated > ago(queryperiod)
    | where  (isempty(HttpStatus) or HttpStatus in (successCode))
    | extend TimeKey = bin(TimeGenerated, sessionBin)
    | extend Hostname = coalesce(Host,OriginalHost)
    | extend ClientIp = tostring(ClientIp)
) on TimeKey, Hostname, ClientIp

Stage 10: where

where TimeGenerated >= "SessionBlockedStarted" and TimeGenerated <= "SessionBlockedEnded"

Stage 11: extend

extend OriginalRequestUriWithArgs, ServerStatus

Stage 12: summarize

summarize OriginalRequestURIs, RequestURIs, SuccessCodes, SuccessCodes_BackendServer, SuccessfulAccessCount, UserAgents by Hostname, ClientIp, SessionBlockedStarted

Stage 13: where

where SessionBlockedCount > SuccessfulAccessCount

Stage 14: extend

extend BlockvsSuccessRatio

Stage 15: sort

sort by BlockvsSuccessRatio, SessionBlockedStarted

Stage 16: project-reorder

project-reorder

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
ClientIpsummarize
Hostnamesummarize
OriginalRequestURIssummarize
RequestURIssummarize
SessionBlockedStartedsummarize
SuccessCodessummarize
SuccessCodes_BackendServersummarize
SuccessfulAccessCountsummarize
UserAgentssummarize
BlockvsSuccessRatioextend

Acronis - Multiple Endpoints Infected by Ransomware

#
Severity
high
Time window
1d
Group by
DeviceName
Source
github.com/Azure/Azure-Sentinel

Detects when three or more distinct endpoints report ransomware detections within a single day.

MITRE ATT&CK Coverage

TacticTechniques
Impact

Rule Body

id: a783ade7-bf43-416d-b809-8f5b06d87790
name: Acronis - Multiple Endpoints Infected by Ransomware
description: Detects when three or more distinct endpoints report ransomware detections within a single day.
severity: High
query: |
    CommonSecurityLog
    | where DeviceVendor == "Acronis"
    | where DeviceEventClassID == "ActiveProtectionBlocksSuspiciousActivity"
    | summarize ActiveProtectionBlocksSuspiciousActivity = count() by DeviceName
queryFrequency: 1h
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 2
tactics:
    - Impact
relevantTechniques:
    - T1486
incidentConfiguration:
    createIncident: true
    groupingConfiguration:
        enabled: true
        reopenClosedIncident: true
        lookbackDuration: P1D
        matchingMethod: AnyAlert
eventGroupingSettings:
    aggregationKind: SingleAlert
customDetails:
  DeviceName: DeviceName
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: DeviceName
version: 1.0.0
kind: Scheduled
requiredDataConnectors: []

Stages and Predicates

Stage 1: source

CommonSecurityLog

Stage 2: where

| where DeviceVendor == "Acronis"

Stage 3: where

| where DeviceEventClassID == "ActiveProtectionBlocksSuspiciousActivity"

Stage 4: summarize

| summarize ActiveProtectionBlocksSuspiciousActivity = count() by DeviceName

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
ActiveProtectionBlocksSuspiciousActivitysummarize
DeviceNamesummarize

Bitglass - Suspicious file uploads

#
Status
available
Severity
high
Time window
15m
Group by
User
Source
github.com/Azure/Azure-Sentinel

'Detects suspicious file upload activity.'

MITRE ATT&CK Coverage

TacticTechniques
Exfiltration

Rule Body

id: 4b272e82-19f1-40d1-bfdf-74fbb6353e8b
name: Bitglass - Suspicious file uploads
description: |
  'Detects suspicious file upload activity.'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: Bitglass
    dataTypes:
      - Bitglass
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Exfiltration
relevantTechniques:
  - T1567
query: |
  Bitglass
  | where EventType =~ 'swgwebdlp'
  | where HttpRequestMethod =~ 'POST'
  | where isnotempty(DlpPattern)
  | summarize count() by User, bin(TimeGenerated, 15m)
  | where count_ > 1
  | extend AccountCustomEntity = User
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountCustomEntity
version: 1.0.1
kind: Scheduled

Stages and Predicates

Stage 1: source

Bitglass

Stage 2: where

| where EventType =~ 'swgwebdlp'

Stage 3: where

| where HttpRequestMethod =~ 'POST'

Stage 4: where

| where isnotempty(DlpPattern)

Stage 5: summarize

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

Stage 6: where

| where count_ > 1

Stage 7: extend

| extend AccountCustomEntity = User

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
Usersummarize
AccountCustomEntityextend

BloodHound Attack Path Finding - Large Default Group With SyncLapsPassword Privileges

#

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 - Large Default Group With SyncLapsPassword Privileges

Rule Body

id: af134cb3-a3e7-421c-bcef-0c6c6432a273
name: BloodHound Attack Path Finding - Large Default Group With SyncLapsPassword Privileges
description: BloodHound Attack Path Finding - Large Default Group With SyncLapsPassword
  Privileges
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 == "LargeDefaultGroupsSyncLAPSPassword"
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 == "LargeDefaultGroupsSyncLAPSPassword"

Indicators

These rows show field, operator, and value matches.

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

Cisco ASA - threat detection message fired

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

'Identifies when the Cisco ASA Threat Detection engine fired an alert based on malicious activity occurring on the network inicated by DeviceEventClassID 733101-733105 Resources: https://www.cisco.com/c/en/us/td/docs/security/asa/syslog/b_syslog/syslogs9.html Details on how to further troubleshoot/investigate: https://www.cisco.com/c/en/us/support/docs/security/asa-5500-x-series-next-generation-firewalls/113685-asa-threat-detection.html'

MITRE ATT&CK Coverage

Rule Body

id: 795edf2d-cf3e-45b5-8452-fe6c9e6a582e
name: Cisco ASA - threat detection message fired
description: |
  'Identifies when the Cisco ASA Threat Detection engine fired an alert based on malicious activity occurring on the network inicated by DeviceEventClassID 733101-733105
  Resources: https://www.cisco.com/c/en/us/td/docs/security/asa/syslog/b_syslog/syslogs9.html
  Details on how to further troubleshoot/investigate: https://www.cisco.com/c/en/us/support/docs/security/asa-5500-x-series-next-generation-firewalls/113685-asa-threat-detection.html'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: CiscoAsaAma
    dataTypes:
      - CommonSecurityLog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Discovery
  - Impact
relevantTechniques:
  - T1046
  - T1498
query: |
  CommonSecurityLog
  | where isempty(CommunicationDirection)
  | where DeviceEventClassID in ("733101","733102","733103","733104","733105")
  | extend HostName = tostring(split(DeviceName, ".")[0]), DomainIndex = toint(indexof(DeviceName, '.'))
  | extend HostNameDomain = iff(DomainIndex != -1, substring(DeviceName, DomainIndex + 1), DeviceName)
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: DeviceName
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIP
version: 1.0.3
kind: Scheduled

Stages and Predicates

Stage 1: source

CommonSecurityLog

Stage 2: where

| where isempty(CommunicationDirection)

Stage 3: where

| where DeviceEventClassID in ("733101","733102","733103","733104","733105")

Stage 4: extend

| extend HostName = tostring(split(DeviceName, ".")[0]), DomainIndex = toint(indexof(DeviceName, '.'))

Stage 5: extend

| extend HostNameDomain = iff(DomainIndex != -1, substring(DeviceName, DomainIndex + 1), DeviceName)
HostNameDomain =
ifDomainIndex != -1substring(DeviceName, (DomainIndex + 1))
elseDeviceName

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
CommunicationDirectionis_null
  • (no value, null check)
field:"CommunicationDirection" kind:is_null
DeviceEventClassIDin
  • 733101
  • 733102
  • 733103
  • 733104
  • 733105
field:"DeviceEventClassID" kind:in

Output Fields

These fields are emitted when the rule matches.

FieldSource
DomainIndexextend
HostNameextend
HostNameDomainextend

Configuration Backup Job Settings Updated

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

Detects when configuration backup job settings are updated in Veeam Backup & Replication. This might indicate configuration changes that require review.

Rule Body

id: 90039769-0c32-442b-bb12-5b61224a77a0
name: Configuration Backup Job Settings Updated
description: Detects when configuration backup job settings are updated in Veeam Backup
  & Replication. This might indicate configuration changes that require review.
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 == 31500\n| project\n    Date\
  \ = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),\n    DataSource = original_host,\n\
  \    EventId = instanceId,\n    UserName = 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 == 31500

Stage 3: project

| project
    Date = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),
    DataSource = original_host,
    EventId = instanceId,
    UserName = 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
UserNameproject

ContraForce - Privileged access change

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

'Identifies administrative access changes made in the ContraForce workspace: workspace role assignments, changes and removals, organization role changes, member additions and removals, and service provider (MSP) access grants or revocations. Review each change to confirm it was expected and authorized. The affected member and the old and new roles are carried in the event metadata.'

MITRE ATT&CK Coverage

Rule Body

id: 3f4bd2a1-7c58-4a83-9c2e-6d1f0a8b5c31
name: ContraForce - Privileged access change
description: |
  'Identifies administrative access changes made in the ContraForce workspace: workspace role
  assignments, changes and removals, organization role changes, member additions and removals,
  and service provider (MSP) access grants or revocations. Review each change to confirm it was
  expected and authorized. The affected member and the old and new roles are carried in the
  event metadata.'
severity: Medium
requiredDataConnectors:
  - connectorId: ContraForceEvents
    dataTypes:
      - ContraForceEvents_CL
queryFrequency: 5m
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - PrivilegeEscalation
  - Persistence
relevantTechniques:
  - T1078
  - T1098
query: |
  ContraForceEvents_CL
  | where ingestion_time() > ago(5m)
  | where ActivityType in ("WorkspaceRoleAssigned", "WorkspaceRoleChanged", "WorkspaceRoleRemoved", "OrganizationalRoleChanged", "MemberAdded", "MemberRemoved", "ServiceProviderAdded", "ServiceProviderRemoved")
  | extend Meta = parse_json(Metadata)
  | extend MemberEmail = tostring(Meta.MemberEmail),
           MemberType = tostring(Meta.MemberType),
           FromRole = tostring(Meta.FromRole),
           ToRole = tostring(Meta.ToRole),
           Members = tostring(Meta.Members)
  | project TimeGenerated, EventId, CfWorkspaceId, ActivityType, ActivityAction,
            ActorName, ActorEmail, TargetResourceType, TargetResourceId, TargetResourceName,
            MemberEmail, MemberType, FromRole, ToRole, Members
eventGroupingSettings:
  aggregationKind: AlertPerResult
alertDetailsOverride:
  alertDisplayNameFormat: 'ContraForce access change: {{ActivityType}} on {{TargetResourceName}}'
  alertDescriptionFormat: '{{ActorName}} performed {{ActivityType}} on {{TargetResourceName}}. Member email, old and new roles are in the alert custom details.'
customDetails:
  EventId: EventId
  ActorEmail: ActorEmail
  MemberEmail: MemberEmail
  FromRole: FromRole
  ToRole: ToRole
  CfWorkspaceId: CfWorkspaceId
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: PT2H
    matchingMethod: Selected
    groupByCustomDetails:
      - EventId
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

ContraForceEvents_CL

Stage 2: where

| where ingestion_time() > ago(5m)

Stage 3: where

| where ActivityType in ("WorkspaceRoleAssigned", "WorkspaceRoleChanged", "WorkspaceRoleRemoved", "OrganizationalRoleChanged", "MemberAdded", "MemberRemoved", "ServiceProviderAdded", "ServiceProviderRemoved")

Stage 4: extend

| extend Meta = parse_json(Metadata)

Stage 5: extend

| extend MemberEmail = tostring(Meta.MemberEmail),
         MemberType = tostring(Meta.MemberType),
         FromRole = tostring(Meta.FromRole),
         ToRole = tostring(Meta.ToRole),
         Members = tostring(Meta.Members)

Stage 6: project

| project TimeGenerated, EventId, CfWorkspaceId, ActivityType, ActivityAction,
          ActorName, ActorEmail, TargetResourceType, TargetResourceId, TargetResourceName,
          MemberEmail, MemberType, FromRole, ToRole, Members

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
ActivityTypein
  • MemberAdded
  • MemberRemoved
  • OrganizationalRoleChanged
  • ServiceProviderAdded
  • ServiceProviderRemoved
  • WorkspaceRoleAssigned
  • WorkspaceRoleChanged
  • WorkspaceRoleRemoved
field:"ActivityType" kind:in

Output Fields

These fields are emitted when the rule matches.

FieldSource
ActivityActionproject
ActivityTypeproject
ActorEmailproject
ActorNameproject
CfWorkspaceIdproject
EventIdproject
FromRoleproject
MemberEmailproject
MemberTypeproject
Membersproject
TargetResourceIdproject
TargetResourceNameproject
TargetResourceTypeproject
TimeGeneratedproject
ToRoleproject

Dataverse - New sign-in from an unauthorized domain

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

Identifies Dataverse sign-in activity originating from users with UPN suffixes that have not been seen previously in the last 14 days and are not present on a predefined list of authorized domains. Common internal Power Platform system users are excluded by default.

MITRE ATT&CK Coverage

Telemetry Coverage

Rule Body

id: 4c1c9aee-8e44-4bb9-bd53-f3e7d6761282
kind: Scheduled
name: Dataverse - New sign-in from an unauthorized domain
description: Identifies Dataverse sign-in activity originating from users with UPN
  suffixes that have not been seen previously in the last 14 days and are not present
  on a predefined list of authorized domains. Common internal Power Platform system
  users are excluded by default.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: Dataverse
    dataTypes:
      - DataverseActivity
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1078
  - T1190
  - T1133
query: |
  // Allow list of UPN suffixes allowed by the organization.
  let allowed_domains = dynamic([
      'onmicrosoft.com',
      'microsoft.com'
      ]);
  // All list of users allowed by the organization
  let allowed_users = dynamic([
      'user1@mydomain.com',
      'user2@mydomain.com'
      ]);
  let query_frequency = 1h;
  let query_lookback = 14d;
  let historical_users = DataverseActivity
      | where TimeGenerated between(ago(query_lookback) .. ago(query_frequency))
      | where Message == 'UserSignIn'
      | summarize by UserId;
  DataverseActivity
  | where TimeGenerated >= ago (query_frequency)
  | where Message == 'UserSignIn'
  | join kind=leftanti (historical_users) on UserId
  | summarize FirstEvent = min(TimeGenerated), LastEvent = max(TimeGenerated) by UserId, ClientIp, InstanceUrl
  | where isnotempty(ClientIp)
  | extend CloudAppId = int(32780)
  | extend AccountName = tostring(split(UserId, '@')[0])
  | extend UPNSuffix = tostring(split(UserId, '@')[1])
  | where UPNSuffix !in (allowed_domains) and UserId !in (allowed_users)
  | project
      FirstEvent,
      LastEvent,
      UserId,
      ClientIp,
      InstanceUrl,
      AccountName,
      UPNSuffix,
      CloudAppId
eventGroupingSettings:
  aggregationKind: SingleAlert
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: ClientIp
  - entityType: CloudApplication
    fieldMappings:
      - identifier: AppId
        columnName: CloudAppId
      - identifier: InstanceName
        columnName: InstanceUrl
alertDetailsOverride:
  alertDisplayNameFormat: Dataverse - Unauthorized sign-in activity
  alertDescriptionFormat: New user sign-in activity was detected in {{InstanceUrl}}
    originating from user {{UserId}}. This user's UPN suffix is not on the authorized
    list of domains.
version: 3.2.0

Stages and Predicates

Parameters

let query_frequency = 1h;
let query_lookback = 14d;

Let Binding: allowed_domains

let allowed_domains = dynamic([
    'onmicrosoft.com',
    'microsoft.com'
    ]);

Let Binding: allowed_users

let allowed_users = dynamic([
    'user1@mydomain.com',
    'user2@mydomain.com'
    ]);

Let Binding: historical_users used in Stage 4

let historical_users = DataverseActivity
    | where TimeGenerated between(ago(query_lookback) .. ago(query_frequency))
    | where Message == 'UserSignIn'
    | summarize by UserId;

Stage 1: source

DataverseActivity

Stage 2: where

| where TimeGenerated >= ago (query_frequency)

Stage 3: where

| where Message == 'UserSignIn'

Stage 4: join (negated)

join kind=leftanti (historical_users) on UserId

Stage 5: summarize

summarize FirstEvent, LastEvent by UserId, ClientIp, InstanceUrl

Stage 6: where

where isnotempty(ClientIp)

Stage 7: extend (3 consecutive steps)

extend AccountName, CloudAppId, UPNSuffix

Stage 8: where

where not (UPNSuffix in ("microsoft.com", "onmicrosoft.com")) and not (UserId in ("user1@mydomain.com", "user2@mydomain.com"))

Stage 9: project

project AccountName, ClientIp, CloudAppId, FirstEvent, InstanceUrl, LastEvent, UPNSuffix, UserId

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
ClientIpis_not_null
  • (no value, null check)
field:"ClientIp" kind:is_not_null
Messageeq
  • UserSignIn corpus 3 (kusto 3)
field:"Message" kind:eq value:"UserSignIn"

Exclusions

The rule actively suppresses these predicates.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountNameproject
ClientIpproject
CloudAppIdproject
FirstEventproject
InstanceUrlproject
LastEventproject
UPNSuffixproject
UserIdproject

Google SecOps - Detection Alerts

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

Creates one incident per active Google Security Operations detection alert in Microsoft Sentinel. Covers all rule types and severity levels with no additional filtering, providing broad visibility across all Google SecOps alerts. Use alongside focused rules for complete detection coverage.

MITRE ATT&CK Coverage

TacticTechniques
Initial Access
Privilege Escalation
Stealth
Lateral Movement

Rule Body

id: f6b0c254-8f7d-4a1b-d5c2-0e4a6b9f2d8a
name: Google SecOps - Detection Alerts
description: |
  Creates one incident per active Google Security Operations detection alert in Microsoft Sentinel. Covers all rule types and severity levels with no additional filtering, providing broad visibility across all Google SecOps alerts. Use alongside focused rules for complete detection coverage.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: GSDetectionAlerts
    dataTypes:
      - DetectionAlerts_CL
queryFrequency: 10m
queryPeriod: 10m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
  - DefenseEvasion
  - LateralMovement
  - PrivilegeEscalation
  - CommandAndControl
relevantTechniques:
  - T1078
  - T1021
  - T1566
query: |
  GoogleSecOpsDetectionAlerts
  | where alertState == "ALERTING"
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: varPrincipalIp
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: varTargetIp
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: varSourceIp
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: varCorrelationIp
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: urlBackToProduct
customDetails:
  alert_identifier: id
  RuleName: ruleName
  RuleId: ruleId
  RuleType: ruleType
  Severity: severity
  RiskScore: riskScore
  AlertState: alertState
  DetectionType: detectionType
  DetectionTime: detectionTime
  PrincipalIP: varPrincipalIp
  TargetIP: varTargetIp
  SourceIP: varSourceIp
  CorrelationIP: varCorrelationIp
  PrincipalHostname: varPrincipalHostname
  TargetHostname: varTargetHostname
  SourceHostname: varSourceHostname
  PrincipalUser: varPrincipalUserUserid
  SourceUser: varSourceUserUserid
  TargetUser: varTargetUserUserid
alertDetailsOverride:
  alertDisplayNameFormat: "Google SecOps Alert: {{ruleName}} : {{id}}"
  alertDescriptionFormat: "Google SecOps detection alert. Rule: {{ruleName}}. Severity: {{severity}}. Type: {{ruleType}}."
eventGroupingSettings:
  aggregationKind: AlertPerResult
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: P1D
    matchingMethod: Selected
    groupByCustomDetails:
      - alert_identifier
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

GoogleSecOpsDetectionAlerts

Stage 2: where

| where alertState == "ALERTING"

Indicators

These rows show field, operator, and value matches.

High-Risk Admin Activity

#
Status
available
Severity
medium
Time window
1h
Group by
actor_alternateId_s, actor_displayName_s, authenticationContext_externalSessionId_s, client_device_s, client_geographicalContext_city_s, client_geographicalContext_country_s, client_geographicalContext_geolocation_lat_d, client_geographicalContext_geolocation_lon_d, client_ipAddress_s, client_userAgent_browser_s, client_userAgent_os_s, client_userAgent_rawUserAgent_s, displayMessage_s, outcome_reason_s, outcome_result_s
Source
github.com/Azure/Azure-Sentinel

The Okta risk engine auto-assigns risk levels to each login attempt. This rule identifies successful admin operations that correlate with successful high-risk Okta authentication or session start events.

MITRE ATT&CK Coverage

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: 9f82a735-ae43-4c03-afb4-d5d153e1ace1
name: High-Risk Admin Activity
description: |
  The Okta risk engine auto-assigns risk levels to each login attempt. This rule identifies successful admin operations that correlate with successful high-risk Okta authentication or session start events.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: OktaSSO
    dataTypes:
      - Okta_CL
  - connectorId: OktaSSOv2
    dataTypes:
      - OktaSSO
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Persistence
  - PrivilegeEscalation
relevantTechniques:
  - T1098
  - T1078.004
query: |
  let AdminActivity = dynamic(["iam.role.create","iam.role.permissions.add","user.session.access_admin_app","user.mfa.factor.suspend", "user.account.privilege.grant", "group.privilege.grant", "system.api_token.create", "user.session.impersonation.grant"]);
  // Define the list of allowed admins to exclude from the alert. This can be modified to include specific admin accounts that are known to perform high-risk activities as part of their job function.
  let AllowedAdmins = dynamic([]);
  let AdminOperations = OktaSSO
  | where actor_alternateId_s !in (AllowedAdmins)
  | where eventType_s in (AdminActivity)
  | where outcome_result_s =~ 'SUCCESS' 
  | summarize  StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by  actor_alternateId_s, actor_displayName_s, client_userAgent_os_s, client_userAgent_browser_s, client_device_s, client_userAgent_rawUserAgent_s, client_ipAddress_s, client_geographicalContext_country_s, client_geographicalContext_city_s, displayMessage_s, outcome_result_s, outcome_reason_s, column_ifexists('debugContext_debugData_logOnlySecurityData_s', ""), column_ifexists('debugContext_debugData_threatSuspected_s', ""), client_geographicalContext_geolocation_lat_d, client_geographicalContext_geolocation_lon_d, authenticationContext_externalSessionId_s;
  let HighRiskEvents = OktaSSO
  | where eventType_s in ('policy.evaluate_sign_on' , 'user.session.start')
  | extend debugContext_debugData_logOnlySecurityData_s = column_ifexists('debugContext_debugData_logOnlySecurityData_s', '{}')
  | where parse_json(tostring(parse_json(debugContext_debugData_logOnlySecurityData_s).risk)).level =~ "HIGH"
  | where outcome_result_s =~ 'SUCCESS'
  | extend reasons = tostring(parse_json(tostring(parse_json(debugContext_debugData_logOnlySecurityData_s).risk)).reasons)
  | summarize  StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by  actor_alternateId_s, actor_displayName_s, client_userAgent_os_s, client_userAgent_browser_s, client_device_s, client_userAgent_rawUserAgent_s, client_ipAddress_s, client_geographicalContext_country_s, client_geographicalContext_city_s, displayMessage_s, outcome_result_s, outcome_reason_s, column_ifexists('debugContext_debugData_logOnlySecurityData_s', ""), column_ifexists('debugContext_debugData_threatSuspected_s', ""), client_geographicalContext_geolocation_lat_d, client_geographicalContext_geolocation_lon_d, authenticationContext_externalSessionId_s, reasons;
  AdminOperations
  | join kind=inner (HighRiskEvents) on actor_displayName_s, client_ipAddress_s, authenticationContext_externalSessionId_s
  | project StartTime, EndTime, actor_alternateId_s, actor_displayName_s, client_userAgent_os_s, client_userAgent_browser_s, client_device_s, client_userAgent_rawUserAgent_s, client_ipAddress_s, client_geographicalContext_country_s, client_geographicalContext_city_s, displayMessage_s, outcome_result_s, outcome_reason_s, column_ifexists('debugContext_debugData_logOnlySecurityData_s', ""), column_ifexists('debugContext_debugData_threatSuspected_s', ""), client_geographicalContext_geolocation_lat_d, client_geographicalContext_geolocation_lon_d, authenticationContext_externalSessionId_s, reasons
  | extend AccountName = tostring(split(actor_alternateId_s, "@")[0]), AccountUPNSuffix = tostring(split(actor_alternateId_s, "@")[1])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: AccountUPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: client_ipAddress_s
customDetails:
  SessionId: authenticationContext_externalSessionId_s
  AdminDisplayName: actor_displayName_s
  AdminDeviceOS: client_userAgent_os_s
  AdminDevice: client_device_s
  UserAgent: client_userAgent_rawUserAgent_s
alertDetailsOverride:
  alertDisplayNameFormat: 'High-Risk admin activity by {{actor_displayName_s}} from IP {{client_ipAddress_s}}'
  alertDescriptionFormat: 'High-risk Okta activity by {{actor_displayName_s}} from IP {{client_ipAddress_s}} correlated with admin action during session {{authenticationContext_externalSessionId_s}}'
version: 1.1.3
kind: Scheduled

Stages and Predicates

Parameters

let AllowedAdmins = dynamic([]);

let AdminOperations is inlined into the numbered stages below.

Let Binding: AdminActivity used in Stage 3

let AdminActivity = dynamic(["iam.role.create","iam.role.permissions.add","user.session.access_admin_app","user.mfa.factor.suspend", "user.account.privilege.grant", "group.privilege.grant", "system.api_token.create", "user.session.impersonation.grant"]);

Let Binding: HighRiskEvents used in Stage 6

let HighRiskEvents = OktaSSO
| where eventType_s in ('policy.evaluate_sign_on' , 'user.session.start')
| extend debugContext_debugData_logOnlySecurityData_s = column_ifexists('debugContext_debugData_logOnlySecurityData_s', '{}')
| where parse_json(tostring(parse_json(debugContext_debugData_logOnlySecurityData_s).risk)).level =~ "HIGH"
| where outcome_result_s =~ 'SUCCESS'
| extend reasons = tostring(parse_json(tostring(parse_json(debugContext_debugData_logOnlySecurityData_s).risk)).reasons)
| summarize  StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by  actor_alternateId_s, actor_displayName_s, client_userAgent_os_s, client_userAgent_browser_s, client_device_s, client_userAgent_rawUserAgent_s, client_ipAddress_s, client_geographicalContext_country_s, client_geographicalContext_city_s, displayMessage_s, outcome_result_s, outcome_reason_s, column_ifexists('debugContext_debugData_logOnlySecurityData_s', ""), column_ifexists('debugContext_debugData_threatSuspected_s', ""), client_geographicalContext_geolocation_lat_d, client_geographicalContext_geolocation_lon_d, authenticationContext_externalSessionId_s, reasons;

Stages 1 to 5 define let AdminOperations as the rule's main pipeline source. Stages 6 to 8 use it.

Stage 1: source

OktaSSO

Stage 2: where

| where actor_alternateId_s !in (AllowedAdmins)

Stage 3: where

| where eventType_s in (AdminActivity)

Stage 4: where

| where outcome_result_s =~ 'SUCCESS'

Stage 5: summarize

| summarize  StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by  actor_alternateId_s, actor_displayName_s, client_userAgent_os_s, client_userAgent_browser_s, client_device_s, client_userAgent_rawUserAgent_s, client_ipAddress_s, client_geographicalContext_country_s, client_geographicalContext_city_s, displayMessage_s, outcome_result_s, outcome_reason_s, column_ifexists('debugContext_debugData_logOnlySecurityData_s', ""), column_ifexists('debugContext_debugData_threatSuspected_s', ""), client_geographicalContext_geolocation_lat_d, client_geographicalContext_geolocation_lon_d, authenticationContext_externalSessionId_s

Stage 6: join

AdminOperations
| join kind=inner (HighRiskEvents) on actor_displayName_s, client_ipAddress_s, authenticationContext_externalSessionId_s

Stage 7: project

project EndTime, StartTime, actor_alternateId_s, actor_displayName_s, authenticationContext_externalSessionId_s, client_device_s, client_geographicalContext_city_s, client_geographicalContext_country_s, client_geographicalContext_geolocation_lat_d, client_geographicalContext_geolocation_lon_d, client_ipAddress_s, client_userAgent_browser_s, client_userAgent_os_s, client_userAgent_rawUserAgent_s, displayMessage_s, outcome_reason_s, outcome_result_s, reasons

Stage 8: extend

extend AccountName, AccountUPNSuffix

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
debugContext_debugData_logOnlySecurityData_s.risk.leveleq
  • HIGH transforms: kusto_accessor_expr:{"expression":"parse_json(tostring(parse_json(debugContext_debugData_logOnlySecurityData_s).risk)).level","field":"debugContext_debugData_logOnlySecurityData_s.risk.level"}, kusto_dynamic_path
field:"debugContext_debugData_logOnlySecurityData_s.risk.level" kind:eq value:"HIGH"
eventType_sin
  • group.privilege.grant
  • iam.role.create
  • iam.role.permissions.add
  • policy.evaluate_sign_on
  • system.api_token.create
  • user.account.privilege.grant
  • user.mfa.factor.suspend
  • user.session.access_admin_app
  • user.session.impersonation.grant
  • user.session.start
field:"okta::eventType" kind:in
outcome_result_seq
  • SUCCESS corpus 3 (kusto 2, elastic 1)
  • SUCCESS
field:"okta::outcome.result" kind:eq

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
actor_alternateId_seq[]excludes:actor_alternateId_s field:"actor_alternateId_s" value:"[]"

Output Fields

These fields are emitted when the rule matches.

FieldSource
EndTimeproject
StartTimeproject
actor_alternateId_sproject
actor_displayName_sproject
authenticationContext_externalSessionId_sproject
client_device_sproject
client_geographicalContext_city_sproject
client_geographicalContext_country_sproject
client_geographicalContext_geolocation_lat_dproject
client_geographicalContext_geolocation_lon_dproject
client_ipAddress_sproject
client_userAgent_browser_sproject
client_userAgent_os_sproject
client_userAgent_rawUserAgent_sproject
displayMessage_sproject
outcome_reason_sproject
outcome_result_sproject
reasonsproject
AccountNameextend
AccountUPNSuffixextend

Palo Alto - potential beaconing detected

#
Status
available
Severity
low
Time window
1h
Group by
DestinationIP, DestinationPort, DeviceName, SourceIP, SourceUserID, TimeDeltainSeconds
Source
github.com/Azure/Azure-Sentinel

'Identifies beaconing patterns from Palo Alto Network traffic logs based on recurrent timedelta patterns. The query leverages various KQL functions to calculate time deltas and then compares it with total events observed in a day to find percentage of beaconing. This outbound beaconing pattern to untrusted public networks should be investigated for any malware callbacks or data exfiltration attempts. Reference Blog: https://medium.com/@HuntOperator/detect-beaconing-with-flare-elastic-stack-and-intrusion-detection-systems-110dc74e0c56 https://techcommunity.microsoft.com/t5/microsoft-sentinel-blog/detect-network-beaconing-via-intra-request-time-delta-patterns/ba-p/779586'

MITRE ATT&CK Coverage

Rule Body

id: f0be259a-34ac-4946-aa15-ca2b115d5feb
name: Palo Alto - potential beaconing detected
description: |
  'Identifies beaconing patterns from Palo Alto Network traffic logs based on recurrent timedelta patterns.
  The query leverages various KQL functions to calculate time deltas and then compares it with total events observed in a day to find percentage of beaconing.
  This outbound beaconing pattern to untrusted public networks should be investigated for any malware callbacks or data exfiltration attempts.
  Reference Blog:
  https://medium.com/@HuntOperator/detect-beaconing-with-flare-elastic-stack-and-intrusion-detection-systems-110dc74e0c56
  https://techcommunity.microsoft.com/t5/microsoft-sentinel-blog/detect-network-beaconing-via-intra-request-time-delta-patterns/ba-p/779586'
severity: Low
status: Available
requiredDataConnectors:
  - connectorId: CefAma
    dataTypes:
      - CommonSecurityLog
queryFrequency: 1d
queryPeriod: 2d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
relevantTechniques:
  - T1071
  - T1571
query: |
  let starttime = 2d;
  let endtime = 1d;
  let TimeDeltaThreshold = 25;
  let TotalEventsThreshold = 30;
  let MostFrequentTimeDeltaThreshold = 25;
  let PercentBeaconThreshold = 80;
  CommonSecurityLog
  | where DeviceVendor == "Palo Alto Networks" and Activity == "TRAFFIC"
  | where TimeGenerated between (startofday(ago(starttime))..startofday(ago(endtime)))
  | where ipv4_is_private(DestinationIP)== false
  | project TimeGenerated, DeviceName, SourceUserID, SourceIP, SourcePort, DestinationIP, DestinationPort, ReceivedBytes, SentBytes
  | sort by SourceIP asc,TimeGenerated asc, DestinationIP asc, DestinationPort asc
  | serialize
  | extend nextTimeGenerated = next(TimeGenerated, 1), nextSourceIP = next(SourceIP, 1)
  | extend TimeDeltainSeconds = datetime_diff('second',nextTimeGenerated,TimeGenerated)
  | where SourceIP == nextSourceIP
  //Whitelisting criteria/ threshold criteria
  | where TimeDeltainSeconds > TimeDeltaThreshold
  | summarize count(), sum(ReceivedBytes), sum(SentBytes)
  by TimeDeltainSeconds, bin(TimeGenerated, 1h), DeviceName, SourceUserID, SourceIP, DestinationIP, DestinationPort
  | summarize (MostFrequentTimeDeltaCount, MostFrequentTimeDeltainSeconds) = arg_max(count_, TimeDeltainSeconds), TotalEvents=sum(count_), TotalSentBytes = sum(sum_SentBytes), TotalReceivedBytes = sum(sum_ReceivedBytes)
  by bin(TimeGenerated, 1h), DeviceName, SourceUserID, SourceIP, DestinationIP, DestinationPort
  | where TotalEvents > TotalEventsThreshold and MostFrequentTimeDeltaCount > MostFrequentTimeDeltaThreshold
  | extend BeaconPercent = MostFrequentTimeDeltaCount/toreal(TotalEvents) * 100
  | where BeaconPercent > PercentBeaconThreshold
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: SourceUserID
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: DeviceName
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: DestinationIP
version: 1.0.7
kind: Scheduled

Stages and Predicates

Parameters

let starttime = 2d;
let endtime = 1d;
let TimeDeltaThreshold = 25;
let TotalEventsThreshold = 30;
let MostFrequentTimeDeltaThreshold = 25;
let PercentBeaconThreshold = 80;

Stage 1: source

CommonSecurityLog

Stage 2: where

| where DeviceVendor == "Palo Alto Networks" and Activity == "TRAFFIC"

Stage 3: where

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

Stage 4: where

| where ipv4_is_private(DestinationIP)== false

Stage 5: project

| project TimeGenerated, DeviceName, SourceUserID, SourceIP, SourcePort, DestinationIP, DestinationPort, ReceivedBytes, SentBytes

Stage 6: sort

| sort by SourceIP asc,TimeGenerated asc, DestinationIP asc, DestinationPort asc

Stage 7: kusto:serialize

| serialize

Stage 8: extend

| extend nextTimeGenerated = next(TimeGenerated, 1), nextSourceIP = next(SourceIP, 1)

Stage 9: extend

| extend TimeDeltainSeconds = datetime_diff('second',nextTimeGenerated,TimeGenerated)

Stage 10: where

| where SourceIP == nextSourceIP

Stage 11: where

| where TimeDeltainSeconds > TimeDeltaThreshold

Stage 12: summarize

| summarize count(), sum(ReceivedBytes), sum(SentBytes)
by TimeDeltainSeconds, bin(TimeGenerated, 1h), DeviceName, SourceUserID, SourceIP, DestinationIP, DestinationPort

Stage 13: summarize

| summarize (MostFrequentTimeDeltaCount, MostFrequentTimeDeltainSeconds) = arg_max(count_, TimeDeltainSeconds), TotalEvents=sum(count_), TotalSentBytes = sum(sum_SentBytes), TotalReceivedBytes = sum(sum_ReceivedBytes)
by bin(TimeGenerated, 1h), DeviceName, SourceUserID, SourceIP, DestinationIP, DestinationPort

Stage 14: where

| where TotalEvents > TotalEventsThreshold and MostFrequentTimeDeltaCount > MostFrequentTimeDeltaThreshold

Stage 15: extend

| extend BeaconPercent = MostFrequentTimeDeltaCount/toreal(TotalEvents) * 100

Stage 16: where

| where BeaconPercent > PercentBeaconThreshold

Indicators

These rows show field, operator, and value matches.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
DestinationIPcidr_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:DestinationIP

Output Fields

These fields are emitted when the rule matches.

FieldSource
DestinationIPsummarize
DestinationPortsummarize
DeviceNamesummarize
MostFrequentTimeDeltaCountsummarize
MostFrequentTimeDeltainSecondssummarize
SourceIPsummarize
SourceUserIDsummarize
TotalEventssummarize
TotalReceivedBytessummarize
TotalSentBytessummarize
BeaconPercentextend

Pathlock TDnR - Emergency User (AdminTrack) Activity

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

Detects activity from emergency user accounts tracked by Pathlock AdminTrack in SAP, forwarded by Pathlock Threat Detection and Response. Emergency user (firefighter) account usage should always be reviewed as these accounts carry broad privileges and any unauthorized or unreviewed use may indicate insider threat or account takeover.

MITRE ATT&CK Coverage

Rule Body

id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c50
name: Pathlock TDnR - Emergency User (AdminTrack) Activity
kind: Scheduled
description: >-
  Detects activity from emergency user accounts tracked by Pathlock AdminTrack in SAP,
  forwarded by Pathlock Threat Detection and Response. Emergency user (firefighter) account usage
  should always be reviewed as these accounts carry broad privileges and any unauthorized or
  unreviewed use may indicate insider threat or account takeover.
severity: High
status: Available
requiredDataConnectors:
  - connectorId: Pathlock_TDnR
    dataTypes:
      - Pathlock_TDnR_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Persistence
  - PrivilegeEscalation
relevantTechniques:
  - T1078
  - T1548
query: |
  Pathlock_TDnR_CL
  | where DataSource == "SAST_AT"
  | 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 == "SAST_AT"

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 BTP Cloud Foundry Events

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

Detects security events from SAP BTP Cloud Foundry environments, forwarded by Pathlock Threat Detection and Response. Anomalous BTP Cloud Foundry activity may indicate unauthorized application deployments, service account abuse, or data exfiltration from cloud-native SAP workloads.

MITRE ATT&CK Coverage

TacticTechniques
Discovery

Rule Body

id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c22
name: Pathlock TDnR - SAP BTP Cloud Foundry Events
kind: Scheduled
description: >-
  Detects security events from SAP BTP Cloud Foundry environments, forwarded by Pathlock Threat
  Detection and Response. Anomalous BTP Cloud Foundry activity may indicate unauthorized application
  deployments, service account abuse, or data exfiltration from cloud-native SAP workloads.
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 == "CLOUD_FOUNDRY_LOGS"
  | 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 == "CLOUD_FOUNDRY_LOGS"

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 - Spool Job Changes

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

Detects changes to SAP spool jobs, forwarded by Pathlock Threat Detection and Response. Unauthorized spool job manipulations may indicate tampering with scheduled output, redirection of sensitive reports, or data collection activity involving SAP output management.

MITRE ATT&CK Coverage

Rule Body

id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c61
name: Pathlock TDnR - Spool Job Changes
kind: Scheduled
description: >-
  Detects changes to SAP spool jobs, forwarded by Pathlock Threat Detection and Response.
  Unauthorized spool job manipulations may indicate tampering with scheduled output, redirection
  of sensitive reports, or data collection activity involving SAP output management.
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 == "SPOOL_REQUEST"
  | 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 == "SPOOL_REQUEST"

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

Rare client observed with high reverse DNS lookup count - Anomaly based (ASIM DNS Solution)

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

This rule makes use of the series decompose anomaly method to identify clients with high reverse DNS counts. This helps in detecting the possible initial phases of an attack, like discovery and reconnaissance. It utilizes ASIM normalization and is applied to any source that supports the ASIM DNS schema.

MITRE ATT&CK Coverage

TacticTechniques
Reconnaissance

Telemetry Coverage

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

Rule Body

id: 0fe6bde4-b215-480c-99b4-84a96edcdbd7
name: Rare client observed with high reverse DNS lookup count - Anomaly based (ASIM DNS Solution)
description: |
  'This rule makes use of the series decompose anomaly method to identify clients with high reverse DNS counts. This helps in detecting the possible initial phases of an attack, like discovery and reconnaissance. It utilizes [ASIM](https://aka.ms/AboutASIM) normalization and is applied to any source that supports the ASIM DNS schema.'
severity: Medium
status: Available 
tags:
  - Schema: ASimDns
    SchemaVersion: 0.1.6
requiredDataConnectors: []
queryFrequency: 1d
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Reconnaissance
relevantTechniques:
  - T1590
query: |
  let threshold = 2.5;
  let SearchDomain = dynamic(["in-addr.arpa"]);
  let min_t = ago(14d);
  let max_t = now();
  let timeframe = 1d;
  let DNSEvents=(stime: datetime, etime: datetime) {
    _Im_Dns(starttime=stime, endtime=etime, domain_has_any=SearchDomain)
  };
  DNSEvents(stime=min_t, etime=max_t)
  | make-series QueryCount=dcount(DnsQuery) on TimeGenerated from min_t to max_t step timeframe by SrcIpAddr
  | extend (anomalies, score, baseline) = series_decompose_anomalies(QueryCount, threshold, -1, 'linefit')
  | mv-expand anomalies, score, baseline, TimeGenerated, QueryCount
  | extend
    anomalies = toint(anomalies),
    score = toint(score),
    baseline = toint(baseline),
    EventTime = todatetime(TimeGenerated),
    Total = tolong(QueryCount)
  | where EventTime >= ago(timeframe)
  | where score >= threshold * 2
  | join kind = inner (DNSEvents(stime=ago(timeframe), etime=max_t)
    | summarize DNSQueries=make_set(DnsQuery, 1000) by SrcIpAddr)
    on SrcIpAddr
  | project-away SrcIpAddr1
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SrcIpAddr
eventGroupingSettings:
  aggregationKind: AlertPerResult
customDetails:
  DNSQueries: DNSQueries
  AnomalyScore: score
  baseline: baseline
  Total: Total
alertDetailsOverride:
  alertDisplayNameFormat: "[Anomaly] Rare client has been observed as making high reverse DNS lookup count  - client IP: '{{SrcIpAddr}}'"
  alertDescriptionFormat: "Client has been identified as making high reverse DNS counts which could be carrying out reconnaissance or discovery activity.\n\nReverse DNS lookup count baseline for this client: '{{baseline}}'\n\nCurrent reverse DNS lookup count by this client showing as: '{{Total}}'\n\nDNS queries requested by this client inlcude: '{{DNSQueries}}'"
version: 1.0.2
kind: Scheduled

Stages and Predicates

Parameters

let threshold = 2.5;
let SearchDomain = dynamic(["in-addr.arpa"]);
let min_t = ago(14d);
let max_t = now();
let timeframe = 1d;

Let Binding: DNSEvents used in Stages 1, 8

let DNSEvents = (stime: datetime, etime: datetime) {
  _Im_Dns(starttime=stime, endtime=etime, domain_has_any=SearchDomain)
};

Stage 1: source

DNSEvents(stime=min_t, etime=max_t)

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

Stage 2: make-series

| make-series QueryCount=dcount(DnsQuery) on TimeGenerated from min_t to max_t step timeframe by SrcIpAddr

Stage 3: extend

| extend (anomalies, score, baseline) = series_decompose_anomalies(QueryCount, threshold, -1, 'linefit')

Stage 4: mv-expand

| mv-expand anomalies, score, baseline, TimeGenerated, QueryCount

Stage 5: extend

| extend
  anomalies = toint(anomalies),
  score = toint(score),
  baseline = toint(baseline),
  EventTime = todatetime(TimeGenerated),
  Total = tolong(QueryCount)

Stage 6: where

| where EventTime >= ago(timeframe)

Stage 7: where

| where score >= threshold * 2

Stage 8: join

| join kind = inner (DNSEvents(stime=ago(timeframe), etime=max_t)
  | summarize DNSQueries=make_set(DnsQuery, 1000) by SrcIpAddr)
  on SrcIpAddr

Stage 9: project-away

project-away SrcIpAddr1

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
scorege
  • 5
field:"score" kind:ge value:"5"

Output Fields

These fields are emitted when the rule matches.

FieldSource
QueryCountsummarize
SrcIpAddrsummarize
anomaliesextend
baselineextend
scoreextend
EventTimeextend
Totalextend

TI Map URL Entity to UrlClickEvents

#
Severity
medium
Time window
14d
Group by
$left.TI_Url, $right.Url, $right.UrlChain, IndicatorId, ReportId
Source
github.com/Azure/Azure-Sentinel

This query identifies any URL indicators of compromise (IOCs) from threat intelligence (TI) by searching for matches in UrlClickEvents.

MITRE ATT&CK Coverage

TacticTechniques
Command & Control

Telemetry Coverage

Rule Body

id: 23391c84-87d8-452f-a84c-47a62f01e115
name: TI Map URL Entity to UrlClickEvents
description: |
  'This query identifies any URL indicators of compromise (IOCs) from threat intelligence (TI) by searching for matches in UrlClickEvents.'
severity: Medium
requiredDataConnectors:
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - UrlClickEvents
  - connectorId: ThreatIntelligence
    dataTypes:
      - ThreatIntelligenceIndicator
  - connectorId: ThreatIntelligenceTaxii
    dataTypes:
      - ThreatIntelligenceIndicator
  - connectorId: MicrosoftDefenderThreatIntelligence
    dataTypes:
      - ThreatIntelligenceIndicator
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
relevantTechniques:
  - T1071
query: |
  let dt_lookBack = 1h;
  let ioc_lookBack = 14d;
  let UrlClickEvents_ = materialize(UrlClickEvents
      | where TimeGenerated >= ago(dt_lookBack)
      | extend UrlClickEvents_TimeGenerated = TimeGenerated);
  let ChainReportID = UrlClickEvents_
      | mv-expand todynamic(UrlChain)
      | extend UrlChain = tolower(UrlChain)
      | project ReportId, Url, UrlChain;
  // Url is not always in UrlChain, so we need to check both
  let ClickedUrls = 
    (union isfuzzy=false (ChainReportID), (ChainReportID | project Url = UrlChain))
    | distinct Url
    | summarize make_list(Url);
  let TI = materialize(ThreatIntelligenceIndicator
    | where TimeGenerated >= ago(ioc_lookBack)
    | where isnotempty(Url) and tolower(Url) in (ClickedUrls)
    | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
    | where Active == true and ExpirationDateTime > now()
    | project-rename TI_Url = Url, TI_Type = Type
    );
  (union isfuzzy=false (TI | join kind=innerunique (ChainReportID) on $left.TI_Url == $right.UrlChain),
    (TI | join kind=innerunique (ChainReportID) on $left.TI_Url == $right.Url))
  | project-away UrlChain
  | join kind=innerunique (UrlClickEvents_) on ReportId
  | where UrlClickEvents_TimeGenerated < ExpirationDateTime
  | summarize UrlClickEvents_TimeGenerated = arg_max(UrlClickEvents_TimeGenerated, *) by IndicatorId
  | project UrlClickEvents_TimeGenerated, AccountUpn, Description, ActivityGroupNames, IndicatorId, ThreatType, ExpirationDateTime, ConfidenceScore, Url, NetworkMessageId
  | extend timestamp = UrlClickEvents_TimeGenerated
  | extend timestamp = UrlClickEvents_TimeGenerated, Name = tostring(split(AccountUpn, '@', 0)[0]), UPNSuffix = tostring(split(AccountUpn, '@', 1)[0])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: AccountUpn
      - identifier: Name
        columnName: Name
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: Url
version: 1.0.2
kind: Scheduled

Stages and Predicates

Parameters

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

Let Binding: UrlClickEvents_

let UrlClickEvents_ = materialize(UrlClickEvents
    | where TimeGenerated >= ago(dt_lookBack)
    | extend UrlClickEvents_TimeGenerated = TimeGenerated);

Let Binding: ChainReportID

let ChainReportID = UrlClickEvents_
    | mv-expand todynamic(UrlChain)
    | extend UrlChain = tolower(UrlChain)
    | project ReportId, Url, UrlChain;

Let Binding: ClickedUrls

let ClickedUrls = (union isfuzzy=false (ChainReportID), (ChainReportID | project Url = UrlChain))
  | distinct Url
  | summarize make_list(Url);

Union isfuzzy=false (2 Sources)

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

Leg 1: TI

TI | join kind=innerunique (ChainReportID) on $left.TI_Url == $right.UrlChain

Leg 2: TI

TI | join kind=innerunique (ChainReportID) on $left.TI_Url == $right.Url

Applied to the Combined Result

| project-away UrlChain
| join kind=innerunique (UrlClickEvents_) on ReportId
| where UrlClickEvents_TimeGenerated < ExpirationDateTime
| summarize UrlClickEvents_TimeGenerated = arg_max(UrlClickEvents_TimeGenerated, *) by IndicatorId
| project UrlClickEvents_TimeGenerated, AccountUpn, Description, ActivityGroupNames, IndicatorId, ThreatType, ExpirationDateTime, ConfidenceScore, Url, NetworkMessageId
| extend timestamp = UrlClickEvents_TimeGenerated
| extend timestamp = UrlClickEvents_TimeGenerated, Name = tostring(split(AccountUpn, '@', 0)[0]), UPNSuffix = tostring(split(AccountUpn, '@', 1)[0])

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Activeeq
  • true
field:"Active" kind:eq value:"true"
Urlin
  • ClickedUrls transforms: tolower
field:"Url" kind:in value:"ClickedUrls"
Urlis_not_null
  • (no value, null check)
field:"Url" kind:is_not_null

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountUpnproject
ActivityGroupNamesproject
ConfidenceScoreproject
Descriptionproject
ExpirationDateTimeproject
IndicatorIdproject
NetworkMessageIdproject
ThreatTypeproject
Urlproject
UrlClickEvents_TimeGeneratedproject
timestampextend
Nameextend
UPNSuffixextend

Tomcat - Sql injection patterns

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

'Detects possible sql injection patterns'

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Rule Body

id: ce84741e-4875-11ec-81d3-0242ac130003
name: Tomcat - Sql injection patterns
description: |
  'Detects possible sql injection patterns'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: CustomLogsAma
    datatypes:
      - Tomcat_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1190
query: |
  let commands = dynamic(["1/*'*/", "1'||'asd'||'", "'1'='1", "1' or '1'='1", "1 or 1=1", "1=1", "1/*!1111'*/", "'or''='"]);
  TomcatEvent
  | where UrlOriginal has_any (commands)
  | extend UrlCustomEntity = UrlOriginal
entityMappings:
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: UrlCustomEntity
version: 1.0.2
kind: Scheduled

Stages and Predicates

Let Binding: commands used in Stage 2

let commands = dynamic(["1/*'*/", "1'||'asd'||'", "'1'='1", "1' or '1'='1", "1 or 1=1", "1=1", "1/*!1111'*/", "'or''='"]);

Stage 1: source

TomcatEvent

Stage 2: where

| where UrlOriginal has_any (commands)

Stage 3: extend

| extend UrlCustomEntity = UrlOriginal

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
UrlOriginalmatch
  • '1'='1 transforms: term
  • 'or''=' transforms: term
  • 1 or 1=1 transforms: term
  • 1' or '1'='1 transforms: term
  • 1'||'asd'||' transforms: term
  • 1/*!1111'*/ transforms: term
  • 1/*'*/ transforms: term
  • 1=1 transforms: term
field:"UrlOriginal" kind:match

Output Fields

These fields are emitted when the rule matches.

FieldSource
UrlCustomEntityextend

Uniqkey - Excessive credential access

#
Severity
medium
Time window
1h
Group by
ActorEmail, ActorType
Source
github.com/Azure/Azure-Sentinel

Detects an actor reading an abnormally large number of credentials from Uniqkey within a single hour (default threshold: more than 50 access events). Bulk credential reads are rarely part of normal daily work and can indicate a compromised session harvesting secrets, or an insider collecting credentials ahead of departure. Tune the threshold to your organization's baseline, and consider a lower threshold for actors of type scim or system that normally never read credentials interactively.

MITRE ATT&CK Coverage

TacticTechniques
Credential Access

Rule Body

id: 755c7adb-1015-4be3-9e56-55e72009c088
name: Uniqkey - Excessive credential access
version: 1.0.0
kind: Scheduled
description: |-
  Detects an actor reading an abnormally large number of credentials from Uniqkey within a single hour (default threshold: more than 50 access events). Bulk credential reads are rarely part of normal daily work and can indicate a compromised session harvesting secrets, or an insider collecting credentials ahead of departure. Tune the threshold to your organization's baseline, and consider a lower threshold for actors of type scim or system that normally never read credentials interactively.
severity: Medium
requiredDataConnectors:
  - connectorId: UniqkeyEventsConnector
    dataTypes:
      - UniqkeyEvents_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
relevantTechniques:
- T1555
query: |-
  let threshold = 50;
  UniqkeyEvents_CL
  | where Category == "credential_access"
  | summarize AccessCount = count(), DistinctTargets = dcount(TargetId), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), SrcIpAddr = take_any(SrcIpAddr) by ActorEmail, ActorType
  | where AccessCount > threshold
  | project StartTime, EndTime, ActorEmail, ActorType, AccessCount, DistinctTargets, SrcIpAddr
entityMappings:
- entityType: Account
  fieldMappings:
  - identifier: FullName
    columnName: ActorEmail
- entityType: IP
  fieldMappings:
  - identifier: Address
    columnName: SrcIpAddr
suppressionEnabled: false
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: 1h
    matchingMethod: AllEntities
suppressionDuration: 5h
eventGroupingSettings:
  aggregationKind: AlertPerResult

Stages and Predicates

Parameters

let threshold = 50;

Stage 1: source

UniqkeyEvents_CL

Stage 2: where

| where Category == "credential_access"

Stage 3: summarize

| summarize AccessCount = count(), DistinctTargets = dcount(TargetId), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), SrcIpAddr = take_any(SrcIpAddr) by ActorEmail, ActorType

Stage 4: where

| where AccessCount > threshold

Stage 5: project

| project StartTime, EndTime, ActorEmail, ActorType, AccessCount, DistinctTargets, SrcIpAddr

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccessCountproject
ActorEmailproject
ActorTypeproject
DistinctTargetsproject
EndTimeproject
SrcIpAddrproject
StartTimeproject

Vectra RUX - Create Incident for Escalated Host Detection or Unresolved Priority Host

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

This query creates a Microsoft Sentinel incident when a detection on a host entity has been escalated in Vectra (investigation_status = escalated). Escalation indicates that a human analyst or MDR service has reviewed the detection and determined it requires immediate host attention or notification. One incident is created per detection - grouping is based on alert display name so that repeated rule evaluations against the same detection do not create duplicate incidents. This query creates a Microsoft Sentinel incident when Vectra AI identifies a host entity with one or more detections marked as unresolved and prioritized (unresolved_priority = true). Vectra's AI engine assigns priority based on attack rating, velocity, breadth, and host privilege level. This rule surfaces host-based threats that require analyst attention. Incidents are grouped per host entity and remain open while the threat persists. Once all associated detections are resolved in Vectra, the incident can be closed in Microsoft Sentinel.

MITRE ATT&CK Coverage

Rule Body

id: 231904f5-b670-4223-9bec-2e9aeca9cf5b
name: Vectra RUX - Create Incident for Escalated Host Detection or Unresolved Priority Host
version: 1.0.0
kind: Scheduled
description: |
  This query creates a Microsoft Sentinel incident when a detection on a host entity has been escalated in Vectra
  (investigation_status = escalated). Escalation indicates that a human analyst or MDR service
  has reviewed the detection and determined it requires immediate host attention or notification.
  One incident is created per detection - grouping is based on alert display name so that
  repeated rule evaluations against the same detection do not create duplicate incidents.

  This query creates a Microsoft Sentinel incident when Vectra AI identifies a host entity with one or more detections
  marked as unresolved and prioritized (unresolved_priority = true). Vectra's AI engine assigns
  priority based on attack rating, velocity, breadth, and host privilege level. This rule surfaces
  host-based threats that require analyst attention.

  Incidents are grouped per host entity and remain open while the threat persists. Once all associated
  detections are resolved in Vectra, the incident can be closed in Microsoft Sentinel.
status: Available
requiredDataConnectors:
  - connectorId: VectraRUXConnector
    dataTypes:
      - VectraDetectionsCombined
severity: High
queryFrequency: 10m
queryPeriod: 10m
suppressionDuration: PT1H
suppressionEnabled: false
triggerOperator: GreaterThan
triggerThreshold: 0
tactics:
  - Discovery
  - LateralMovement
  - CredentialAccess
  - Exfiltration
  - CommandAndControl
  - Persistence
relevantTechniques:
  - T1046
  - T1021
  - T1003
  - T1041
  - T1071
query: |
  VectraDetectionsCombined
  | where ["Entity Type"] == "host"
  | summarize arg_max(TimeGenerated, *) by ["Detection ID"]
  | where ["Investigation Status"] == "escalated" or ["Unresolved Priority"] == true
  | extend
      detection_name     = ["D Type Vname"],
      detection_category = ["Detection Category"],
      entity_url         = replace_string(replace_string(URL, "api/v3.4/", ""), "api/v3.5/", ""),
      mitre_techniques   = tostring(Mitre),
      detection_tags     = tostring(Tags),
      assigned_to        = ["Assigned To"],
      entity_name = ["Entity Name"],
      entity_uid = ["Entity UID"],
      entity_id = ["Entity ID"],
      detection_id = ["Detection ID"],
      external_reference = ["External Reference ID"],
      investigation_status = ["Investigation Status"],
      entity_type = ["Entity Type"]
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: entity_name
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: P7D
    matchingMethod: Selected
    groupByEntities:
      - Host
    groupByAlertDetails:
      - DisplayName
customDetails:
  detection_name: detection_name
  assigned_to: assigned_to
  entity_uid: entity_uid
  entity_url: entity_url
  entity_id: entity_id
  detection_id: detection_id
  external_reference: external_reference
  investigation_status: investigation_status
  category: detection_category
  tags: detection_tags
  mitre_techniques: mitre_techniques
  entity_type: entity_type
alertDetailsOverride:
  alertDisplayNameFormat: "Vectra AI - {{detection_name}} on {{entity_name}}"
  alertDescriptionFormat: >
    Vectra AI has escalated detection {{detection_name}} ({{detection_category}}) on host or identified
    host {{entity_name}} as a priority threat. See custom details for full context.
  alertDynamicProperties:
    - alertProperty: AlertLink
      value: entity_url
    - alertProperty: Techniques
      value: mitre_techniques
eventGroupingSettings:
  aggregationKind: AlertPerResult

Stages and Predicates

Stage 1: source

VectraDetectionsCombined

Stage 2: where

| where ["Entity Type"] == "host"

Stage 3: summarize

| summarize arg_max(TimeGenerated, *) by ["Detection ID"]

Stage 4: where

| where ["Investigation Status"] == "escalated" or ["Unresolved Priority"] == true

Stage 5: extend

| extend
    detection_name     = ["D Type Vname"],
    detection_category = ["Detection Category"],
    entity_url         = replace_string(replace_string(URL, "api/v3.4/", ""), "api/v3.5/", ""),
    mitre_techniques   = tostring(Mitre),
    detection_tags     = tostring(Tags),
    assigned_to        = ["Assigned To"],
    entity_name = ["Entity Name"],
    entity_uid = ["Entity UID"],
    entity_id = ["Entity ID"],
    detection_id = ["Detection ID"],
    external_reference = ["External Reference ID"],
    investigation_status = ["Investigation Status"],
    entity_type = ["Entity Type"]

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Entity Typeeq
  • host transforms: kusto_literal_column
field:"Entity Type" kind:eq value:"host"
Investigation Statuseq
  • escalated transforms: kusto_literal_column
field:"Investigation Status" kind:eq value:"escalated"
Unresolved Priorityeq
  • true transforms: kusto_literal_column
field:"Unresolved Priority" kind:eq value:"true"

Output Fields

These fields are emitted when the rule matches.

FieldSource
assigned_toextend
detection_categoryextend
detection_idextend
detection_nameextend
detection_tagsextend
entity_idextend
entity_nameextend
entity_typeextend
entity_uidextend
entity_urlextend
external_referenceextend
investigation_statusextend
mitre_techniquesextend

Virtual Lab Settings Updated

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

Detects when virtual lab settings are updated in Veeam Backup & Replication. This might indicate configuration changes that require review.

Rule Body

id: a21eccd5-0148-4f88-a5d7-a8f86e9e4d8e
name: Virtual Lab Settings Updated
description: Detects when virtual lab settings are updated in Veeam Backup & Replication.
  This might indicate configuration changes that require review.
severity: Low
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 == 30700\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 == 30700

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

Whisper Security - Newly Registered Domain on Threat ASN

#
Status
available
Severity
high
Time window
1d
Group by
$left.parsedAsn, $right.asn
Source
github.com/Azure/Azure-Sentinel

Identifies newly registered domains (under 7 days old) hosted on autonomous systems with elevated threat scores. This combination often indicates adversary infrastructure staging for phishing, malware delivery, or C2 operations.

MITRE ATT&CK Coverage

TacticTechniques
Resource Development

Rule Body

id: 4f80ee8e-901f-538a-8603-cc4b49e80164
name: Whisper Security - Newly Registered Domain on Threat ASN
description: |
  Identifies newly registered domains (under 7 days old) hosted on autonomous systems with elevated threat scores. This combination often indicates adversary infrastructure staging for phishing, malware delivery, or C2 operations.
severity: High
status: Available
requiredDataConnectors:
- connectorId: WhisperSecurityConnector
  dataTypes:
  - WhisperASNReputation_CL
  - WhisperInfraContext_CL
queryFrequency: 1h
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- ResourceDevelopment
relevantTechniques:
- T1583
query: |
  // MITRE ATT&CK: T1583.001 - Acquire Infrastructure: Domains
  // Tactic: Resource Development
  // Correlates newly registered domains with high-threat ASN reputation data
  let maxDomainAgeDays = 7;
  let asnThreatThreshold = 50;
  // Defensive: WhisperInfraChainPipeline currently emits domainAge=-1 as a
  // sentinel when the create date is unknown (tracked bug - proper fix needs
  // CALL whisper.history() integration). Filter the sentinel out so the rule
  // does not fire on every domain with an ASN.
  let newDomains = WhisperInfraContext_CL
      | where TimeGenerated > ago(1d)
      | where domainAge >= 0 and domainAge < maxDomainAgeDays
      | where isnotempty(asns)
      | extend parsedAsn = tostring(split(asns, ",")[0])
      | extend parsedIp = tostring(split(ipAddresses, ",")[0])
      | project TimeGenerated, indicator, domainAge, parsedAsn, parsedIp, registrar, countries;
  let threatAsns = WhisperASNReputation_CL
      | where TimeGenerated > ago(1d)
      | where maxThreatScore > asnThreatThreshold
      | project asn, asnName, reputationScore, reputationLevel, maxThreatScore;
  newDomains
      | join kind=inner (threatAsns) on $left.parsedAsn == $right.asn
      | extend DnsDomain = indicator, IPAddress = parsedIp
      | project TimeGenerated, DnsDomain, IPAddress, domainAge, registrar, countries, asn, asnName, reputationScore, reputationLevel, maxThreatScore
entityMappings:
- entityType: DNS
  fieldMappings:
  - identifier: DomainName
    columnName: DnsDomain
- entityType: IP
  fieldMappings:
  - identifier: Address
    columnName: IPAddress
customDetails:
  DomainAgeDays: domainAge
  ASNName: asnName
  ReputationScore: reputationScore
  MaxThreatScore: maxThreatScore
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: PT5H
    matchingMethod: AllEntities
version: 1.0.0
kind: Scheduled

Stages and Predicates

Parameters

let maxDomainAgeDays = 7;
let asnThreatThreshold = 50;

let newDomains is inlined into the numbered stages below.

Let Binding: threatAsns used in Stage 8

let threatAsns = WhisperASNReputation_CL
    | where TimeGenerated > ago(1d)
    | where maxThreatScore > asnThreatThreshold
    | project asn, asnName, reputationScore, reputationLevel, maxThreatScore;

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

Stage 1: source

WhisperInfraContext_CL

Stage 2: where

| where TimeGenerated > ago(1d)

Stage 3: where

| where domainAge >= 0 and domainAge < maxDomainAgeDays

Stage 4: where

| where isnotempty(asns)

Stage 5: extend

| extend parsedAsn = tostring(split(asns, ",")[0])

Stage 6: extend

| extend parsedIp = tostring(split(ipAddresses, ",")[0])

Stage 7: project

| project TimeGenerated, indicator, domainAge, parsedAsn, parsedIp, registrar, countries

Stage 8: join

newDomains
| join kind=inner (threatAsns) on $left.parsedAsn == $right.asn

Stage 9: extend

extend DnsDomain, IPAddress

Stage 10: project

project DnsDomain, IPAddress, TimeGenerated, asn, asnName, countries, domainAge, maxThreatScore, registrar, reputationLevel, reputationScore

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
asnsis_not_null
  • (no value, null check)
field:"asns" kind:is_not_null
domainAgege
  • 0 transforms: number
field:"domainAge" kind:ge value:"0"
domainAgelt
  • 7 transforms: number
field:"domainAge" kind:lt value:"7"
maxThreatScoregt
  • 50 transforms: number
field:"maxThreatScore" kind:gt value:"50"

Output Fields

These fields are emitted when the rule matches.

FieldSource
DnsDomainproject
IPAddressproject
TimeGeneratedproject
asnproject
asnNameproject
countriesproject
domainAgeproject
maxThreatScoreproject
registrarproject
reputationLevelproject
reputationScoreproject

Back to all Kusto rules