Detection rules › Kusto

Kusto rules: 0d

AWSCloudTrail - Full Admin policy created and then attached to Roles, Users or Groups

#
Status
available
Severity
medium
Time window
14d
Group by
AccountName, AccountUPNSuffix, EventName, EventSource, PolicyName, RecipientAccountId, SourceIpAddress, UserIdentityArn, UserIdentityType
Source
github.com/Azure/Azure-Sentinel

Identifies creation of IAM policies that grant full administrative access and subsequent attachment to a role, user, or group. This sequence can be used to elevate a low-privilege identity to administrative access and should be investigated immediately. AWS IAM Policy Grammar: https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_grammar.html and AWS IAM API at https://docs.aws.amazon.com/IAM/latest/APIReference/API_Operations.html

MITRE ATT&CK Coverage

TacticTechniques
Privilege Escalation

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: 826bb2f8-7894-4785-9a6b-a8a855d8366f
name: AWSCloudTrail - Full Admin policy created and then attached to Roles, Users or Groups
description: |
  Identifies creation of IAM policies that grant full administrative access and subsequent attachment to a role, user, or group. This sequence can be used to elevate a low-privilege identity to administrative access and should be investigated immediately.
  AWS IAM Policy Grammar: https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_grammar.html 
  and AWS IAM API at https://docs.aws.amazon.com/IAM/latest/APIReference/API_Operations.html
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: AWS
    dataTypes:
      - AWSCloudTrail
  - connectorId: AWSS3
    dataTypes:
      - AWSCloudTrail
queryFrequency: 1d
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - PrivilegeEscalation
relevantTechniques:
  - T1484
query: |
  let EventNameList = dynamic(["AttachUserPolicy","AttachRolePolicy","AttachGroupPolicy"]);
  let createPolicy = dynamic(["CreatePolicy", "CreatePolicyVersion"]);
  let timeframe = 1d;
  let lookback = 14d;
  // Creating Master table with all the events to use with materialize for better performance
  let EventInfo = AWSCloudTrail
  | where TimeGenerated >= ago(lookback)
  | where EventName in (EventNameList) or EventName in (createPolicy)
  | extend UserIdentityArn = iif(isempty(UserIdentityArn), tostring(parse_json(Resources)[0].ARN), UserIdentityArn)
  | extend UserName = tostring(split(UserIdentityArn, '/')[-1])
  | extend AccountName = case( UserIdentityPrincipalid == "Anonymous", "Anonymous", isempty(UserIdentityUserName), UserName, UserIdentityUserName)
  | extend AccountName = iif(AccountName contains "@", tostring(split(AccountName, '@', 0)[0]), AccountName),
    AccountUPNSuffix = iif(AccountName contains "@", tostring(split(AccountName, '@', 1)[0]), "");
  //Checking for Policy creation event with Full Admin Privileges since lookback period.
  let FullAdminPolicyEvents =  materialize(  EventInfo
  | where TimeGenerated >= ago(lookback)
  | where EventName in (createPolicy)
  | extend PolicyName = tostring(parse_json(RequestParameters).policyName)
  | extend Statement = parse_json(tostring((parse_json(RequestParameters).policyDocument))).Statement
  | mvexpand Statement
  | extend Action = parse_json(Statement).Action , Effect = tostring(parse_json(Statement).Effect), Resource = tostring(parse_json(Statement).Resource)
  | mvexpand Action
  | extend Action = tostring(Action)
  | where Effect =~ "Allow" and Action == "*" and Resource == "*"
  | distinct TimeGenerated, EventName, PolicyName, SourceIpAddress, UserIdentityArn, UserIdentityUserName, RecipientAccountId, AccountName, AccountUPNSuffix
  | extend UserIdentityUserName = iff(isnotempty(UserIdentityUserName), UserIdentityUserName, tostring(split(UserIdentityArn,'/')[-1]))
  | project-rename StartTime = TimeGenerated  );
  let PolicyAttach = materialize(  EventInfo
  | where TimeGenerated >= ago(timeframe)
  | where EventName in (EventNameList)
  | extend PolicyName = tostring(split(tostring(parse_json(RequestParameters).policyArn),"/")[1])
  | summarize AttachEventCount=count(), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by EventSource, EventName,   UserIdentityType , UserIdentityArn, SourceIpAddress, RecipientAccountId, AccountName, AccountUPNSuffix, PolicyName
  | extend AttachEvent = pack("StartTime", StartTime, "EndTime", EndTime, "EventName", EventName, "UserIdentityType",   UserIdentityType, "AccountName", AccountName, "AccountUPNSuffix", AccountUPNSuffix, "RecipientAccountId", RecipientAccountId, "UserIdentityArn", UserIdentityArn, "SourceIpAddress", SourceIpAddress)
  | project EventSource, PolicyName, AttachEvent, RecipientAccountId, AccountName, AccountUPNSuffix, AttachEventCount
  );
  // Joining the list of PolicyNames and checking if it has been attached to any Roles/Users/Groups.
  // These Roles/Users/Groups will be Privileged and can be used by adversaries as pivot point for privilege escalation via multiple ways.
  FullAdminPolicyEvents
  | join kind=leftouter
  (
      PolicyAttach
  )
  on PolicyName
  | project-away PolicyName1
  | extend timestamp = StartTime
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: AccountUPNSuffix
      - identifier: CloudAppAccountId
        columnName: RecipientAccountId
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIpAddress
customDetails:
  PolicyName: PolicyName
  EventName: EventName
  RecipientAccountId: RecipientAccountId
  UserIdentityArn: UserIdentityArn
alertDetailsOverride:
  alertDisplayNameFormat: 'AWS Full Admin privilege escalation policy activity: {{PolicyName}} by {{AccountName}}'
  alertDescriptionFormat: 'Detected {{EventName}} for policy {{PolicyName}} in account {{RecipientAccountId}}.'
version: 1.0.5
kind: Scheduled

Stages and Predicates

Parameters

let EventNameList = dynamic(["AttachUserPolicy","AttachRolePolicy","AttachGroupPolicy"]);
let createPolicy = dynamic(["CreatePolicy", "CreatePolicyVersion"]);
let timeframe = 1d;
let lookback = 14d;

let EventInfo and let FullAdminPolicyEvents are inlined into the numbered stages below.

Let Binding: PolicyAttach used in Stage 17

let PolicyAttach = materialize(  EventInfo
| where TimeGenerated >= ago(timeframe)
| where EventName in (EventNameList)
| extend PolicyName = tostring(split(tostring(parse_json(RequestParameters).policyArn),"/")[1])
| summarize AttachEventCount=count(), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by EventSource, EventName,   UserIdentityType , UserIdentityArn, SourceIpAddress, RecipientAccountId, AccountName, AccountUPNSuffix, PolicyName
| extend AttachEvent = pack("StartTime", StartTime, "EndTime", EndTime, "EventName", EventName, "UserIdentityType",   UserIdentityType, "AccountName", AccountName, "AccountUPNSuffix", AccountUPNSuffix, "RecipientAccountId", RecipientAccountId, "UserIdentityArn", UserIdentityArn, "SourceIpAddress", SourceIpAddress)
| project EventSource, PolicyName, AttachEvent, RecipientAccountId, AccountName, AccountUPNSuffix, AttachEventCount
);

Stages 1 to 16 define let FullAdminPolicyEvents as the rule's main pipeline source. Stages 17 to 20 use it.

Stage 1: source

AWSCloudTrail

Stage 2: where

| where TimeGenerated >= ago(lookback)

Stage 3: where

| where EventName in (EventNameList) or EventName in (createPolicy)

Stage 4: extend (4 consecutive steps)

| extend UserIdentityArn = iif(isempty(UserIdentityArn), tostring(parse_json(Resources)[0].ARN), UserIdentityArn)
| extend UserName = tostring(split(UserIdentityArn, '/')[-1])
| extend AccountName = case( UserIdentityPrincipalid == "Anonymous", "Anonymous", isempty(UserIdentityUserName), UserName, UserIdentityUserName)
| extend AccountName = iif(AccountName contains "@", tostring(split(AccountName, '@', 0)[0]), AccountName),
  AccountUPNSuffix = iif(AccountName contains "@", tostring(split(AccountName, '@', 1)[0]), "")

Stage 5: where

| where TimeGenerated >= ago(lookback)

Stage 6: where

| where EventName in (createPolicy)

Stage 7: extend

| extend PolicyName = tostring(parse_json(RequestParameters).policyName)

Stage 8: extend

| extend Statement = parse_json(tostring((parse_json(RequestParameters).policyDocument))).Statement

Stage 9: mv-expand

| mvexpand Statement

Stage 10: extend

| extend Action = parse_json(Statement).Action , Effect = tostring(parse_json(Statement).Effect), Resource = tostring(parse_json(Statement).Resource)

Stage 11: mv-expand

| mvexpand Action

Stage 12: extend

| extend Action = tostring(Action)

Stage 13: where

| where Effect =~ "Allow" and Action == "*" and Resource == "*"

Stage 14: distinct

| distinct TimeGenerated, EventName, PolicyName, SourceIpAddress, UserIdentityArn, UserIdentityUserName, RecipientAccountId, AccountName, AccountUPNSuffix

Stage 15: extend

| extend UserIdentityUserName = iff(isnotempty(UserIdentityUserName), UserIdentityUserName, tostring(split(UserIdentityArn,'/')[-1]))
UserIdentityUserName =
ifisnotempty(UserIdentityUserName)UserIdentityUserName
elsetostring(split(UserIdentityArn, '/')[(- 1)])

Stage 16: project-rename

| project-rename StartTime = TimeGenerated

Stage 17: join

FullAdminPolicyEvents
| join kind=leftouter
(
    PolicyAttach
)
on PolicyName

Stage 18: project-away

project-away PolicyName1

Stage 19: extend

extend timestamp

Stage 20: summarize aggregation inside the join branch

summarize by EventSource, EventName, UserIdentityType, UserIdentityArn, SourceIpAddress, RecipientAccountId, AccountName, AccountUPNSuffix, PolicyName

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Actioneq
  • *
field:"Action" kind:eq value:"*"
Effecteq
  • Allow
field:"Effect" kind:eq value:"Allow"
EventNamein
  • AttachGroupPolicy corpus 17 (kusto 15, panther 2)
  • AttachGroupPolicy
  • AttachRolePolicy corpus 17 (kusto 15, panther 2)
  • AttachRolePolicy
  • AttachUserPolicy corpus 17 (kusto 15, panther 2)
  • AttachUserPolicy
  • CreatePolicy corpus 14 (kusto 12, panther 2)
  • CreatePolicy
  • CreatePolicyVersion corpus 14 (kusto 12, panther 2)
  • CreatePolicyVersion
field:"aws::eventName" kind:in
Resourceeq
  • *
field:"Resource" kind:eq value:"*"

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountNamesummarize
AccountUPNSuffixsummarize
EventNamesummarize
EventSourcesummarize
PolicyNamesummarize
RecipientAccountIdsummarize
SourceIpAddresssummarize
UserIdentityArnsummarize
UserIdentityTypesummarize

Cloud Gateway Settings Updated

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

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

Rule Body

id: 9f0d4171-9a9f-42e4-acbf-9093dd4203de
name: Cloud Gateway Settings Updated
description: Detects when cloud gateway 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 == 24131\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 == 24131

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

Conditional Access - A Conditional Access policy was updated

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

A Conditional Access policy was updated in Entra ID.

MITRE ATT&CK Coverage

TacticTechniques
Defense Impairment

Telemetry Coverage

PlatformRecord / event type
AzureUpdate conditional access policy

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: ccca6b88-a7b6-41c9-9be2-fc3daeb65b26
name: Conditional Access - A Conditional Access policy was updated
version: 1.0.1
kind: Scheduled
description: A Conditional Access policy was updated in Entra ID.
severity: Informational
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AuditLogs
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
relevantTechniques:
- T1562
query: |+
  // A Conditional Access policy was updated.
  AuditLogs
  | where OperationName == "Update conditional access policy"
  | extend
      policy = tostring(TargetResources[0].displayName),
      modifiedBy = tostring(InitiatedBy.user.userPrincipalName),
      oldPolicy = tostring(TargetResources[0].modifiedProperties[0].oldValue),
      newPolicy = tostring(TargetResources[0].modifiedProperties[0].newValue)
  | extend
      accountName = tostring(split(modifiedBy, "@")[0]),
      upnSuffix = tostring(split(modifiedBy, "@")[1])
  | project
      TimeGenerated,
      OperationName,
      policy,
      modifiedBy,
      accountName,
      upnSuffix,
      oldPolicy,
      newPolicy,
      Result

entityMappings:
- entityType: Account
  fieldMappings:
  - identifier: Name
    columnName: accountName
  - identifier: UPNSuffix
    columnName: upnSuffix
suppressionEnabled: false
suppressionDuration: 5h
eventGroupingSettings:
  aggregationKind: AlertPerResult
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: false
    reopenClosedIncident: false
    lookbackDuration: PT1H
    matchingMethod: AllEntities
    groupByEntities: []
    groupByAlertDetails: []
    groupByCustomDetails: []

Stages and Predicates

Stage 1: source

AuditLogs

Stage 2: where

| where OperationName == "Update conditional access policy"

Stage 3: extend

| extend
    policy = tostring(TargetResources[0].displayName),
    modifiedBy = tostring(InitiatedBy.user.userPrincipalName),
    oldPolicy = tostring(TargetResources[0].modifiedProperties[0].oldValue),
    newPolicy = tostring(TargetResources[0].modifiedProperties[0].newValue)

Stage 4: extend

| extend
    accountName = tostring(split(modifiedBy, "@")[0]),
    upnSuffix = tostring(split(modifiedBy, "@")[1])

Stage 5: project

| project
    TimeGenerated,
    OperationName,
    policy,
    modifiedBy,
    accountName,
    upnSuffix,
    oldPolicy,
    newPolicy,
    Result

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
OperationNameeq
  • Update conditional access policy corpus 6 (kusto 6)
field:"OperationName" kind:eq value:"Update conditional access policy"

Output Fields

These fields are emitted when the rule matches.

FieldSource
OperationNameproject
Resultproject
TimeGeneratedproject
accountNameproject
modifiedByproject
newPolicyproject
oldPolicyproject
policyproject
upnSuffixproject

Create Incident for XDR Alerts

#

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

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

'This Query creates an incident based on Trend Vision One Workbench Alerts and maps the impacted entities for Microsoft Sentinel usage.'

Rule Body

id: 0febd8cc-1b8d-45ed-87b3-e1e8a57d14cd
name: Create Incident for XDR Alerts
description: |
  'This Query creates an incident based on Trend Vision One Workbench Alerts and maps the impacted entities for Microsoft Sentinel usage.'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: TrendMicroXDR
    dataTypes:
      - TrendMicro_XDR_WORKBENCH_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
relevantTechniques:
suppressionDuration: 5h
suppressionEnabled: false
query: |
  TrendMicro_XDR_WORKBENCH_CL
  | extend Severity = case(severity_s == "low", "Informational",
                          severity_s == "medium", "Low",
                          severity_s == "high", "Medium",
                          "High"
                          )
  | extend 
      UserAccountName_s = todynamic(column_ifexists("UserAccountName_s", "[]")),
      UserAccountNTDomain_s = todynamic(column_ifexists("UserAccountNTDomain_s", "[]")),
      FileName_s = todynamic(column_ifexists("FileName_s", "[]")),
      FileDirectory_s = todynamic(column_ifexists("FileDirectory_s", "[]")),
      ProcessCommandLine_s = todynamic(column_ifexists("ProcessCommandLine_s", "[]")),
      RegistryKey_s = todynamic(column_ifexists("RegistryKey_s", "[]")),
      RegistryValue_s = todynamic(column_ifexists("RegistryValue_s", "[]")),
      RegistryValueName_s = todynamic(column_ifexists("RegistryValueName_s", "[]"))
alertRuleTemplateName: null
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: 5m
    matchingMethod: Selected
    groupByCustomDetails: 
      - WorkbenchID
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: UserAccountName_s
      - identifier: NTDomain
        columnName: UserAccountNTDomain_s
  - entityType: File
    fieldMappings:
      - identifier: Name
        columnName: FileName_s
      - identifier: Directory
        columnName: FileDirectory_s
  - entityType: Process
    fieldMappings:
      - identifier: CommandLine
        columnName: ProcessCommandLine_s
  - entityType: RegistryKey
    fieldMappings:
      - identifier: Key
        columnName: RegistryKey_s
  - entityType: RegistryValue
    fieldMappings:
      - identifier: Name
        columnName: ProcessCommandLine_s
      - identifier: Value
        columnName: RegistryValue_s
alertDetailsOverride:
  alertDisplayNameFormat: '{{workbenchName_s}}'
  alertDescriptionFormat: '{{description_s}}'
  alertSeverityColumnName: Severity
customDetails:
  XDRCustomerID: xdrCustomerID_g
  WorkbenchID: workbenchId_s
  WorkbenchName: workbenchName_s
  WorkbenchLink: workbenchLink_s
  PriorityScore: priorityScore_d
  CreatedAt: createdTime_t
  Severity: severity_s
  ImpactScopeSummary: impactScope_Summary_s
  Provider: alertProvider_s
eventGroupingSettings:
  aggregationKind: AlertPerResult
version: 1.0.4
kind: Scheduled

Stages and Predicates

Stage 1: source

TrendMicro_XDR_WORKBENCH_CL

Stage 2: extend

| extend Severity = case(severity_s == "low", "Informational",
                        severity_s == "medium", "Low",
                        severity_s == "high", "Medium",
                        "High"
                        )
Severity =
ifseverity_s == "low""Informational"
elifseverity_s == "medium""Low"
elifseverity_s == "high""Medium"
else"High"

Stage 3: extend

| extend 
    UserAccountName_s = todynamic(column_ifexists("UserAccountName_s", "[]")),
    UserAccountNTDomain_s = todynamic(column_ifexists("UserAccountNTDomain_s", "[]")),
    FileName_s = todynamic(column_ifexists("FileName_s", "[]")),
    FileDirectory_s = todynamic(column_ifexists("FileDirectory_s", "[]")),
    ProcessCommandLine_s = todynamic(column_ifexists("ProcessCommandLine_s", "[]")),
    RegistryKey_s = todynamic(column_ifexists("RegistryKey_s", "[]")),
    RegistryValue_s = todynamic(column_ifexists("RegistryValue_s", "[]")),
    RegistryValueName_s = todynamic(column_ifexists("RegistryValueName_s", "[]"))

Output Fields

These fields are emitted when the rule matches.

FieldSource
Severityextend
FileDirectory_sextend
FileName_sextend
ProcessCommandLine_sextend
RegistryKey_sextend
RegistryValueName_sextend
RegistryValue_sextend
UserAccountNTDomain_sextend
UserAccountName_sextend

Dataverse - New Dataverse application user activity type

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

Identifies new or previously unseen activity types associated with Dataverse application (non-interactive) user.

MITRE ATT&CK Coverage

TacticTechniques
Privilege Escalation
Credential Access
Execution

Telemetry Coverage

Rule Body

id: 5c768e7d-7e5e-4d57-80d4-3f50c96fbf70
kind: Scheduled
name: Dataverse - New Dataverse application user activity type
description: Identifies new or previously unseen activity types associated with Dataverse
  application (non-interactive) user.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: Dataverse
    dataTypes:
      - DataverseActivity
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CredentialAccess
  - Execution
  - PrivilegeEscalation
relevantTechniques:
  - T1635
  - T0871
  - T1078
query: |
  let query_frequency = 1h;
  let query_lookback = 14d;
  let app_user_regex = "^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}\\.com$";
  let guid_regex = "([0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12})";
  let application_users = DataverseActivity
      | where UserId !endswith "@onmicrosoft.com" and UserId != "Unknown"
      | summarize by UserId
      | where split(UserId, "@")[1] matches regex app_user_regex;
  let historical_app_activity = application_users
      | join kind = inner (
          DataverseActivity
          | where TimeGenerated between(ago(query_lookback) .. ago(query_frequency))
          | summarize by UserId, EntityName, Message, InstanceUrl)
          on
          UserId;
  let current_activity = application_users
      | join kind= inner (
          DataverseActivity
          | where TimeGenerated >= ago(query_frequency)
          | summarize by UserId, EntityName, Message, InstanceUrl)
          on
          UserId;
  current_activity
  | join kind = leftanti (historical_app_activity) on UserId, Message, EntityName, InstanceUrl
  | summarize NewActivities = make_set(strcat(Message, " ", EntityName), 1000) by UserId, InstanceUrl
  | extend
      AadUserId = extract(guid_regex, 1, tostring(split(UserId, "@")[0])),
      CloudAppId = int(32780)
  | project
      UserId,
      NewActivities,
      InstanceUrl,
      AadUserId,
      CloudAppId
eventGroupingSettings:
  aggregationKind: SingleAlert
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: AadUserId
        columnName: AadUserId
  - entityType: CloudApplication
    fieldMappings:
      - identifier: AppId
        columnName: CloudAppId
      - identifier: InstanceName
        columnName: InstanceUrl
alertDetailsOverride:
  alertDisplayNameFormat: 'Dataverse - Unusual non-interactive account activity in
    {{InstanceUrl}} '
  alertDescriptionFormat: '{{UserId}} generated new activities in {{InstanceUrl}}
    which had not been seen previously in the Dataverse.'
version: 3.2.0

Stages and Predicates

Parameters

let query_frequency = 1h;
let query_lookback = 14d;
let app_user_regex = "^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}\\.com$";
let guid_regex = "([0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12})";

let application_users and let current_activity are inlined into the numbered stages below.

Let Binding: historical_app_activity used in Stage 6

let historical_app_activity = application_users
    | join kind = inner (
        DataverseActivity
        | where TimeGenerated between(ago(query_lookback) .. ago(query_frequency))
        | summarize by UserId, EntityName, Message, InstanceUrl)
        on
        UserId;

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

Stage 1: source

DataverseActivity

Stage 2: where

where not (UserId endswith "@onmicrosoft.com") and UserId != "Unknown"

Stage 3: summarize

summarize by UserId

Stage 4: where

where /* macro: (split(UserId, "@")[1] matches regex "^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}\\.com$") */

Stage 5: join

| join kind= inner (
        DataverseActivity
        | where TimeGenerated >= ago(query_frequency)
        | summarize by UserId, EntityName, Message, InstanceUrl)
        on
        UserId

Stage 6: join (negated)

join kind=leftanti (historical_app_activity) on UserId, Message, EntityName, InstanceUrl

Stage 7: summarize

summarize NewActivities by UserId, InstanceUrl

Stage 8: extend

extend AadUserId, CloudAppId

Stage 9: project

project AadUserId, CloudAppId, InstanceUrl, NewActivities, UserId

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
UserIdends_with
  • @onmicrosoft.com
field:"UserId" kind:ends_with value:"@onmicrosoft.com"
UserIdne
  • Unknown corpus 2 (kusto 2)
field:"user" kind:ne value:"Unknown"

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
UserIdends_with@onmicrosoft.comexcludes:UserId field:"UserId" value:"@onmicrosoft.com"
UserIdneUnknownexcludes:UserId field:"UserId" value:"Unknown"

Output Fields

These fields are emitted when the rule matches.

FieldSource
AadUserIdproject
CloudAppIdproject
InstanceUrlproject
NewActivitiesproject
UserIdproject

Detect potential file enumeration activity (ASIM Web Session)

#
Status
available
Severity
medium
Time window
1h
Group by
DstHostname, SrcHostname, SrcIpAddr, SrcUsername
Source
github.com/Azure/Azure-Sentinel

'This detection method identifies potential cases of file enumeration activity. The query is designed to identify client sources that generate multiple requests resulting in 404 error codes'

MITRE ATT&CK Coverage

TacticTechniques
Credential Access
Discovery
Command & Control

Rule Body

id: b3731ce1-1f04-47c4-95c2-9827408c4375
name: Detect potential file enumeration activity (ASIM Web Session)
description: |
  'This detection method identifies potential cases of file enumeration activity. The query is designed to identify client sources that generate multiple requests resulting in 404 error codes'
severity: Medium
status: Available 
tags:
  - Schema: WebSession
    SchemaVersion: 0.2.6
requiredDataConnectors: []
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Discovery
  - CommandAndControl
  - CredentialAccess
relevantTechniques:
  - T1083
  - T1071
  - T1110
query: |
  // HTTP response status codes indicate whether a specific HTTP request has been successfully completed.
  // Please refer this for more details: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
  let HTTPErrorCode=dynamic(["404"]);
  let threshold = 10; // You can update this threshold to the value that suites your environment
  // let FileNotFoundRequests = 
  _Im_WebSession (starttime=ago(1h))
  | where EventResultDetails in~ (HTTPErrorCode)
  // Filter the logs to include only HTTP GET requests with an HTTP status code of 404 and '/' in the URL
  | where HttpRequestMethod =~ "GET" and Url contains "/"
  | summarize
      RequestCount = count(),
      FileCount=dcount(Url),
      EventStartTime=min(TimeGenerated),
      EventEndTime=max(TimeGenerated),
      RequestURLs = make_set(Url, 100),
      DestinationIPList=make_set(DstIpAddr, 100)
      by SrcIpAddr, SrcUsername, SrcHostname, DstHostname
  | where RequestCount > threshold  // Adjust the threshold as per your requirements
  | extend Name = iif(SrcUsername contains "@", tostring(split(SrcUsername,'@',0)[0]),SrcUsername), UPNSuffix = iif(SrcUsername contains "@",tostring(split(SrcUsername,'@',1)[0]),"")
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SrcIpAddr
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: Name
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: DstHostname
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: SrcHostname
eventGroupingSettings:
  aggregationKind: AlertPerResult
customDetails:
  RequestCount: RequestCount
  RequestURLs: RequestURLs
  FileCount: FileCount
  EventStartTime: EventStartTime
  EventEndTime: EventEndTime
  DestinationIPList: DestinationIPList
alertDetailsOverride:
  alertDisplayNameFormat: "User '{{SrcUsername}}' with IP '{{SrcIpAddr}}' has been observed with performing file enumeration activity"
  alertDescriptionFormat: "User generated multiple requests '{{RequestCount}}' that has resulted in error code '404', suggesting the possibility of file enumeration activity. It's important to investigate the source and patterns of these extensive 404 errors to identify potential security threats. Details about this error code could be found [here](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status)"
version: 1.0.0
kind: Scheduled

Stages and Predicates

Parameters

let HTTPErrorCode = dynamic(["404"]);
let threshold = 10;

Stage 1: source

_Im_WebSession (starttime=ago(1h))

Stage 2: where

| where EventResultDetails in~ (HTTPErrorCode)

Stage 3: where

| where HttpRequestMethod =~ "GET" and Url contains "/"

Stage 4: summarize

| summarize
    RequestCount = count(),
    FileCount=dcount(Url),
    EventStartTime=min(TimeGenerated),
    EventEndTime=max(TimeGenerated),
    RequestURLs = make_set(Url, 100),
    DestinationIPList=make_set(DstIpAddr, 100)
    by SrcIpAddr, SrcUsername, SrcHostname, DstHostname

Stage 5: where

| where RequestCount > threshold

Stage 6: extend

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

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
DestinationIPListsummarize
DstHostnamesummarize
EventEndTimesummarize
EventStartTimesummarize
FileCountsummarize
RequestCountsummarize
RequestURLssummarize
SrcHostnamesummarize
SrcIpAddrsummarize
SrcUsernamesummarize
Nameextend
UPNSuffixextend

External Repository Settings Updated

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

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

Rule Body

id: d035188d-d856-4a74-8226-f3bdf65033c4
name: External Repository Settings Updated
description: Detects when external repository 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 == 32100\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 == 32100

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

Fortinet - Beacon pattern detected

#
Severity
low
Time window
1d
Group by
DestinationIP, DestinationPort, EndTime, SourceIP, StartTime, sum_ReceivedBytes, sum_SentBytes
Author
Microsoft Security Research
Source
github.com/Azure/Azure-Sentinel

'Identifies patterns in the time deltas of contacts between internal and external IPs in Fortinet network data that are consistent with beaconing. Accounts for randomness (jitter) and seasonality such as working hours that may have been introduced into the beacon pattern. The lookback is set to 1d, the minimum granularity in time deltas is set to 60 seconds and the minimum number of beacons required to emit a detection is set to 4. Increase the lookback period to capture beacons with larger periodicities. The jitter tolerance is set to 0.2 - This means we account for an overall 20% deviation from the infered beacon periodicity. Seasonality is dealt with automatically using series_outliers. Note: In large environments it may be necessary to reduce the lookback period to get fast query times.'

MITRE ATT&CK Coverage

Rule Body

id: 3255ec41-6bd6-4f35-84b1-c032b18bbfcb
name: Fortinet - Beacon pattern detected
description: |
  'Identifies patterns in the time deltas of contacts between internal and external IPs in Fortinet network data that are consistent with beaconing.
   Accounts for randomness (jitter) and seasonality such as working hours that may have been introduced into the beacon pattern.
   The lookback is set to 1d, the minimum granularity in time deltas is set to 60 seconds and the minimum number of beacons required to emit a  detection is set to 4.
   Increase the lookback period to capture beacons with larger periodicities.
   The jitter tolerance is set to 0.2 - This means we account for an overall 20% deviation from the infered beacon periodicity. Seasonality is dealt with  automatically using series_outliers.
   Note: In large environments it may be necessary to reduce the lookback period to get fast query times.'
severity: Low
requiredDataConnectors:
  - connectorId: Fortinet
    dataTypes:
      - CommonSecurityLog
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
relevantTechniques:
  - T1071
  - T1571
query: |
    let starttime = 1d;
    let TimeDeltaThresholdInSeconds = 60; // we ignore beacons diffs that fall below this threshold
    let TotalBeaconsThreshold = 4; // minimum number of beacons required in a session to surface a row
    let JitterTolerance = 0.2; // tolerance to jitter, e.g. - 0.2 = 20% jitter is tolerated either side of the periodicity
    CommonSecurityLog
    | where DeviceVendor == "Fortinet"
    | where TimeGenerated > ago(starttime)
    // eliminate bad data
    | where isnotempty(SourceIP) and isnotempty(DestinationIP) and SourceIP != "0.0.0.0"
    // filter out deny, close, rst and SNMP to reduce data volume
    | where DeviceAction !in ("close", "client-rst", "server-rst", "deny") and DestinationPort != 161
    // map input fields
    | project TimeGenerated , SourceIP, DestinationIP, DestinationPort, ReceivedBytes, SentBytes, DeviceAction
    // where destination IPs are public
    | where ipv4_is_private(DestinationIP) == false
    // sort into source->destination 'sessions'
    | sort by SourceIP asc, DestinationIP asc, DestinationPort asc, TimeGenerated asc
    | serialize
    // time diff the contact times between source and destination to get a list of deltas
    | extend nextTimeGenerated = next(TimeGenerated, 1), nextSourceIP = next(SourceIP, 1), nextDestIP = next(DestinationIP, 1), nextDestPort = next(DestinationPort, 1)
    | extend TimeDeltainSeconds = datetime_diff("second",nextTimeGenerated,TimeGenerated)
    | where SourceIP == nextSourceIP and DestinationIP == nextDestIP and DestinationPort == nextDestPort
    // remove small time deltas below the set threshold
    | where TimeDeltainSeconds > TimeDeltaThresholdInSeconds
    // summarize the deltas by source->destination
    | summarize count(), StartTime=min(TimeGenerated), EndTime=max(TimeGenerated), sum(ReceivedBytes), sum(SentBytes), makelist(TimeDeltainSeconds), makeset(DeviceAction) by SourceIP, DestinationIP, DestinationPort
    // get some statistical properties of the delta distribution and smooth any outliers (e.g. laptop shut overnight, working hours)
    | extend series_stats(list_TimeDeltainSeconds), outliers=series_outliers(list_TimeDeltainSeconds)
    // expand the deltas and the outliers
    | mvexpand list_TimeDeltainSeconds to typeof(double), outliers to typeof(double)
    // replace outliers with the average of the distribution
    | extend list_TimeDeltainSeconds_normalized=iff(outliers > 1.5 or outliers < -1.5, series_stats_list_TimeDeltainSeconds_avg , list_TimeDeltainSeconds)
    // summarize with the smoothed distribution
    | summarize BeaconCount=count(), makelist(list_TimeDeltainSeconds), list_TimeDeltainSeconds_normalized=makelist(list_TimeDeltainSeconds_normalized), makeset(set_DeviceAction) by StartTime, EndTime, SourceIP, DestinationIP, DestinationPort, sum_ReceivedBytes, sum_SentBytes
    // get stats on the smoothed distribution
    | extend series_stats(list_TimeDeltainSeconds_normalized)
    // match jitter tolerance on smoothed distrib
    | extend MaxJitter = (series_stats_list_TimeDeltainSeconds_normalized_avg*JitterTolerance)
    | where series_stats_list_TimeDeltainSeconds_normalized_stdev < MaxJitter
    // where the minimum beacon threshold is satisfied and there was some data transfer
    | where BeaconCount > TotalBeaconsThreshold and (sum_SentBytes > 0 or sum_ReceivedBytes > 0)
    // final projection
    | project StartTime, EndTime, SourceIP, DestinationIP, DestinationPort, BeaconCount, TimeDeltasInSeconds=list_list_TimeDeltainSeconds, Periodicity=series_stats_list_TimeDeltainSeconds_normalized_avg, ReceivedBytes=sum_ReceivedBytes, SentBytes=sum_SentBytes, Actions=set_set_DeviceAction
    // where periodicity is order of magnitude larger than time delta threshold (eliminates FPs whose periodicity is close to the values we ignored)
    | where Periodicity >= (10*TimeDeltaThresholdInSeconds)
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: DestinationIP
version: 1.0.5
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: Microsoft Security Research
    support:
        tier: Community
    categories:
        domains: [ "Security - Network" ]

Stages and Predicates

Parameters

let starttime = 1d;
let TimeDeltaThresholdInSeconds = 60;
let TotalBeaconsThreshold = 4;
let JitterTolerance = 0.2;

Stage 1: source

CommonSecurityLog

Stage 2: where

| where DeviceVendor == "Fortinet"

Stage 3: where

| where TimeGenerated > ago(starttime)

Stage 4: where

| where isnotempty(SourceIP) and isnotempty(DestinationIP) and SourceIP != "0.0.0.0"

Stage 5: where

| where DeviceAction !in ("close", "client-rst", "server-rst", "deny") and DestinationPort != 161

Stage 6: project

| project TimeGenerated , SourceIP, DestinationIP, DestinationPort, ReceivedBytes, SentBytes, DeviceAction

Stage 7: where

| where ipv4_is_private(DestinationIP) == false

Stage 8: sort

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

Stage 9: kusto:serialize

| serialize

Stage 10: extend

| extend nextTimeGenerated = next(TimeGenerated, 1), nextSourceIP = next(SourceIP, 1), nextDestIP = next(DestinationIP, 1), nextDestPort = next(DestinationPort, 1)

Stage 11: extend

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

Stage 12: where

| where SourceIP == nextSourceIP and DestinationIP == nextDestIP and DestinationPort == nextDestPort

Stage 13: where

| where TimeDeltainSeconds > TimeDeltaThresholdInSeconds

Stage 14: summarize

| summarize count(), StartTime=min(TimeGenerated), EndTime=max(TimeGenerated), sum(ReceivedBytes), sum(SentBytes), makelist(TimeDeltainSeconds), makeset(DeviceAction) by SourceIP, DestinationIP, DestinationPort

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

Stage 15: extend

| extend series_stats(list_TimeDeltainSeconds), outliers=series_outliers(list_TimeDeltainSeconds)

Stage 16: mv-expand

| mvexpand list_TimeDeltainSeconds to typeof(double), outliers to typeof(double)

Stage 17: extend

| extend list_TimeDeltainSeconds_normalized=iff(outliers > 1.5 or outliers < -1.5, series_stats_list_TimeDeltainSeconds_avg , list_TimeDeltainSeconds)
list_TimeDeltainSeconds_normalized =
if(outliers > 1.5 or outliers < -1.5)series_stats_list_TimeDeltainSeconds_avg
elselist_TimeDeltainSeconds

Stage 18: summarize

| summarize BeaconCount=count(), makelist(list_TimeDeltainSeconds), list_TimeDeltainSeconds_normalized=makelist(list_TimeDeltainSeconds_normalized), makeset(set_DeviceAction) by StartTime, EndTime, SourceIP, DestinationIP, DestinationPort, sum_ReceivedBytes, sum_SentBytes

Stage 19: extend

| extend series_stats(list_TimeDeltainSeconds_normalized)

Stage 20: extend

| extend MaxJitter = (series_stats_list_TimeDeltainSeconds_normalized_avg*JitterTolerance)

Stage 21: where

| where series_stats_list_TimeDeltainSeconds_normalized_stdev < MaxJitter

Stage 22: where

| where BeaconCount > TotalBeaconsThreshold and (sum_SentBytes > 0 or sum_ReceivedBytes > 0)

Stage 23: project

| project StartTime, EndTime, SourceIP, DestinationIP, DestinationPort, BeaconCount, TimeDeltasInSeconds=list_list_TimeDeltainSeconds, Periodicity=series_stats_list_TimeDeltainSeconds_normalized_avg, ReceivedBytes=sum_ReceivedBytes, SentBytes=sum_SentBytes, Actions=set_set_DeviceAction

Stage 24: where

| where Periodicity >= (10*TimeDeltaThresholdInSeconds)

Indicators

These rows show field, operator, and value matches.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
DeviceActioninclient-rst, close, deny, server-rstexcludes:DeviceAction
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
Actionsproject
BeaconCountproject
DestinationIPproject
DestinationPortproject
EndTimeproject
Periodicityproject
ReceivedBytesproject
SentBytesproject
SourceIPproject
StartTimeproject
TimeDeltasInSecondsproject

Global VM Exclusions Changed

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

Detects when global VM exclusions are updated in Veeam Backup & Replication.

Rule Body

id: 2034c441-2850-4167-80ed-b4438155836d
name: Global VM Exclusions Changed
description: Detects when global VM exclusions are updated in Veeam Backup & Replication.
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 == 40600\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 == 40600

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

Imperva - Forbidden HTTP request method in request

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

'Detects connections with unexpected HTTP request method.'

MITRE ATT&CK Coverage

Rule Body

id: 7ebc9e24-319c-4786-9151-c898240463bc
name: Imperva - Forbidden HTTP request method in request
description: |
  'Detects connections with unexpected HTTP request method.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: ImpervaWAFCloudAPI
    dataTypes:
      - ImpervaWAFCloud
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1190
  - T1133
query: |
  let bl_http_methods = dynamic(['PUT', 'HEAD', 'OPTIONS', 'TRACE', 'POST']);
  ImpervaWAFCloud
  | where HttpRequestMethod in~ (bl_http_methods)
  | extend IPCustomEntity = SrcIpAddr, UrlCustomEntity = UrlOriginal
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPCustomEntity
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: UrlCustomEntity
version: 1.0.0
kind: Scheduled

Stages and Predicates

Parameters

let bl_http_methods = dynamic(['PUT', 'HEAD', 'OPTIONS', 'TRACE', 'POST']);

Stage 1: source

ImpervaWAFCloud

Stage 2: where

| where HttpRequestMethod in~ (bl_http_methods)

Stage 3: extend

| extend IPCustomEntity = SrcIpAddr, UrlCustomEntity = UrlOriginal

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
HttpRequestMethodin
  • HEAD
  • OPTIONS
  • POST
  • PUT
  • TRACE
field:"HttpRequestMethod" kind:in

Output Fields

These fields are emitted when the rule matches.

FieldSource
IPCustomEntityextend
UrlCustomEntityextend

Imperva - Malicious user agent

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

'Detects requests containing known malicious user agent strings.'

MITRE ATT&CK Coverage

Rule Body

id: 905794a9-bc46-42b9-974d-5a2dd58110c5
name: Imperva - Malicious user agent
description: |
  'Detects requests containing known malicious user agent strings.'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: ImpervaWAFCloudAPI
    dataTypes:
      - ImpervaWAFCloud
queryFrequency: 10m
queryPeriod: 10m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1190
  - T1133
query: |
  ImpervaWAFCloud
  | where HttpUserAgentOriginal has_any ('Nikto', 'hydra', 'advanced email extractor', 'BFAC', 'brutus', 'cgichk', 'cisco-torch', 'scanner', 'datacha0s', 'dirbuster', 'grabber', 'havij', 'internet ninja', 'masscan', 'morfeus', 'mysqloit', 'n-stealth', 'nessus', 'netsparker', 'nmap nse', 'nmap scripting engine', 'nmap-nse', 'nsauditor', 'openvas', 'pangolin', 'qualys was', 'security scan', 'springenwerk', 'sql power injector', 'sqlmap', 'sqlninja', 'w3af.sf.net', 'w3af.sourceforge.net', 'w3af.org', 'webbandit', 'webinspect', 'webvulnscan', 'xmlrpc exploit', 'WPScan', 'XSpider', 'Webster', 'fantomCrew', 'fantomBrowser')
  | summarize count() by SrcIpAddr, bin(TimeGenerated, 5m)
  | extend IPCustomEntity = SrcIpAddr
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPCustomEntity
version: 1.0.1
kind: Scheduled

Stages and Predicates

Stage 1: source

ImpervaWAFCloud

Stage 2: where

| where HttpUserAgentOriginal has_any ('Nikto', 'hydra', 'advanced email extractor', 'BFAC', 'brutus', 'cgichk', 'cisco-torch', 'scanner', 'datacha0s', 'dirbuster', 'grabber', 'havij', 'internet ninja', 'masscan', 'morfeus', 'mysqloit', 'n-stealth', 'nessus', 'netsparker', 'nmap nse', 'nmap scripting engine', 'nmap-nse', 'nsauditor', 'openvas', 'pangolin', 'qualys was', 'security scan', 'springenwerk', 'sql power injector', 'sqlmap', 'sqlninja', 'w3af.sf.net', 'w3af.sourceforge.net', 'w3af.org', 'webbandit', 'webinspect', 'webvulnscan', 'xmlrpc exploit', 'WPScan', 'XSpider', 'Webster', 'fantomCrew', 'fantomBrowser')

Stage 3: summarize

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

Stage 4: extend

| extend IPCustomEntity = SrcIpAddr

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
HttpUserAgentOriginalmatch
  • BFAC transforms: term
  • Nikto transforms: term
  • WPScan transforms: term
  • Webster transforms: term
  • XSpider transforms: term
  • advanced email extractor transforms: term
  • brutus transforms: term
  • cgichk transforms: term
  • cisco-torch transforms: term
  • datacha0s transforms: term
  • dirbuster transforms: term
  • fantomBrowser transforms: term
  • fantomCrew transforms: term
  • grabber transforms: term
  • havij transforms: term
  • hydra transforms: term
  • internet ninja transforms: term
  • masscan transforms: term
  • morfeus transforms: term
  • mysqloit transforms: term
  • n-stealth transforms: term
  • nessus transforms: term
  • netsparker transforms: term
  • nmap nse transforms: term
  • nmap scripting engine transforms: term
  • nmap-nse transforms: term
  • nsauditor transforms: term
  • openvas transforms: term
  • pangolin transforms: term
  • qualys was transforms: term
  • scanner transforms: term
  • security scan transforms: term
  • springenwerk transforms: term
  • sql power injector transforms: term
  • sqlmap transforms: term
  • sqlninja transforms: term
  • w3af.org transforms: term
  • w3af.sf.net transforms: term
  • w3af.sourceforge.net transforms: term
  • webbandit transforms: term
  • webinspect transforms: term
  • webvulnscan transforms: term
  • xmlrpc exploit transforms: term
field:"HttpUserAgentOriginal" kind:match

Output Fields

These fields are emitted when the rule matches.

FieldSource
SrcIpAddrsummarize
IPCustomEntityextend

Pathlock TDnR - Generic SAP Change Documents

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

Detects generic SAP change document events across all object classes, forwarded by Pathlock Threat Detection and Response. This broad datasource captures cross-functional object modifications that may indicate unauthorized data tampering or configuration changes.

MITRE ATT&CK Coverage

TacticTechniques
Defense Impairment
Impact

Rule Body

id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c09
name: Pathlock TDnR - Generic SAP Change Documents
kind: Scheduled
description: >-
  Detects generic SAP change document events across all object classes, forwarded by Pathlock Threat
  Detection and Response. This broad datasource captures cross-functional object modifications
  that may indicate unauthorized data tampering or configuration changes.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: Pathlock_TDnR
    dataTypes:
      - Pathlock_TDnR_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Impact
  - DefenseEvasion
relevantTechniques:
  - T1565
  - T1562
query: |
  Pathlock_TDnR_CL
  | where DataSource == "CHANGEDOC_GENERIC"
  | project TimeGenerated, Sysid, DataSource, Eventid, Instance, Hostname, Bname,
            Tcode, Report, Area, Subid, SrcIp, DestIp, AffectedUser, LogLine,
            MsgType, MsgId, MsgNo, MessageV1, MessageV2, MessageV3, MessageV4, CentralTs
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: Bname
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: Hostname
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SrcIp
eventGroupingSettings:
  aggregationKind: SingleAlert
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: 5h
    matchingMethod: AnyAlert
    groupByEntities: []
    groupByAlertDetails: []
    groupByCustomDetails: []
suppressionDuration: 5h
suppressionEnabled: false
version: 1.0.0

Stages and Predicates

Stage 1: source

Pathlock_TDnR_CL

Stage 2: where

| where DataSource == "CHANGEDOC_GENERIC"

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

StealthTalk - After hours work

#
Status
available
Severity
low
Time window
2d
Group by
UserId
Source
github.com/Azure/Azure-Sentinel

Identifies systematic off-hours activity for a single StealthTalk user - repeated authentications outside the user's configured working hours OR on weekends, observed across at least two distinct calendar days within a 48-hour window. The pattern is a common indicator of credential misuse, insider threat, or compromise of the account by an attacker operating in a different timezone. An "off-hours event" is one where IsWeekend=true OR DeviationMinutes >= 180 (i.e. >= 3 hours after the configured working-hours end). Three or more such events on at least two distinct days are required for an incident to fire - a single late-evening login is not enough.

MITRE ATT&CK Coverage

TacticTechniques
Initial Access
Persistence
Stealth

Rule Body

id: e3a8b2f1-5c7d-4d89-9b6e-0f1a2c3d4e5f
name: StealthTalk - After hours work
description: |
  Identifies systematic off-hours activity for a single StealthTalk user - repeated authentications
  outside the user's configured working hours OR on weekends, observed across at least two
  distinct calendar days within a 48-hour window. The pattern is a common indicator of credential
  misuse, insider threat, or compromise of the account by an attacker operating in a different
  timezone.

  An "off-hours event" is one where IsWeekend=true OR DeviationMinutes >= 180 (i.e. >= 3 hours
  after the configured working-hours end). Three or more such events on at least two distinct
  days are required for an incident to fire - a single late-evening login is not enough.
severity: Low
requiredDataConnectors:
  - connectorId: StealthTalkAnomalousAuth
    dataTypes:
      - StealthTalkAnomalousAuth_CL
queryFrequency: 1h
queryPeriod: 2d
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - InitialAccess
  - DefenseEvasion
  - Persistence
relevantTechniques:
  - T1078
query: |
  let LookbackPeriod    = 48h;
  let MinAttempts       = 3;
  let MinDistinctDays   = 2;
  let OffHoursThreshold = 180;
  StealthTalkAnomalousAuth_CL
  | where TimeGenerated >= ago(LookbackPeriod)
  | where EventType == "OffHoursLogin"
  | where IsWeekend == true or DeviationMinutes >= OffHoursThreshold
  | summarize
      AttemptCount   = count(),
      DistinctDays   = dcount(startofday(TimeGenerated)),
      FirstSeen      = min(TimeGenerated),
      LastSeen       = max(TimeGenerated),
      DeviceIds      = make_set(DeviceId),
      MaxDeviation   = max(DeviationMinutes),
      WeekendsCount  = countif(IsWeekend == true),
      AppVersions    = make_set(AppVersion)
    by UserId
  | where AttemptCount >= MinAttempts and DistinctDays >= MinDistinctDays
  | extend
      AlertName    = "AfterHoursWork",
      AlertDetails = strcat(
          "User ", UserId,
          " performed ", AttemptCount, " off-hours logins",
          " across ", DistinctDays, " distinct days.",
          " Max deviation from working hours: ", MaxDeviation, " min.",
          " Weekend logins: ", WeekendsCount, "."
      )
  | project
      TimeGenerated = LastSeen,
      UserId, AttemptCount, DistinctDays, MaxDeviation,
      WeekendsCount, DeviceIds, AppVersions, FirstSeen, LastSeen,
      AlertName, AlertDetails
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: UserId
customDetails:
  AttemptCount: AttemptCount
  DistinctDays: DistinctDays
  MaxDeviation: MaxDeviation
  WeekendsCount: WeekendsCount
  FirstSeen: FirstSeen
  LastSeen: LastSeen
alertDetailsOverride:
  alertDisplayNameFormat: 'StealthTalk: After-Hours Work - {{UserId}} ({{AttemptCount}} events / {{DistinctDays}} days)'
  alertDescriptionFormat: '{{AlertDetails}}'
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: 5h
    matchingMethod: Selected
    groupByEntities:
      - Account
suppressionEnabled: false
suppressionDuration: 5h
version: 1.0.0
kind: Scheduled

Stages and Predicates

Parameters

let LookbackPeriod = 48h;
let MinAttempts = 3;
let MinDistinctDays = 2;
let OffHoursThreshold = 180;

Stage 1: source

StealthTalkAnomalousAuth_CL

Stage 2: where

| where TimeGenerated >= ago(LookbackPeriod)

Stage 3: where

| where EventType == "OffHoursLogin"

Stage 4: where

| where IsWeekend == true or DeviationMinutes >= OffHoursThreshold

Stage 5: summarize

| summarize
    AttemptCount   = count(),
    DistinctDays   = dcount(startofday(TimeGenerated)),
    FirstSeen      = min(TimeGenerated),
    LastSeen       = max(TimeGenerated),
    DeviceIds      = make_set(DeviceId),
    MaxDeviation   = max(DeviationMinutes),
    WeekendsCount  = countif(IsWeekend == true),
    AppVersions    = make_set(AppVersion)
  by UserId

Stage 6: where

| where AttemptCount >= MinAttempts and DistinctDays >= MinDistinctDays

Stage 7: extend

| extend
    AlertName    = "AfterHoursWork",
    AlertDetails = strcat(
        "User ", UserId,
        " performed ", AttemptCount, " off-hours logins",
        " across ", DistinctDays, " distinct days.",
        " Max deviation from working hours: ", MaxDeviation, " min.",
        " Weekend logins: ", WeekendsCount, "."
    )

Stage 8: project

| project
    TimeGenerated = LastSeen,
    UserId, AttemptCount, DistinctDays, MaxDeviation,
    WeekendsCount, DeviceIds, AppVersions, FirstSeen, LastSeen,
    AlertName, AlertDetails

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AlertDetailsproject
AlertNameproject
AppVersionsproject
AttemptCountproject
DeviceIdsproject
DistinctDaysproject
FirstSeenproject
LastSeenproject
MaxDeviationproject
TimeGeneratedproject
UserIdproject
WeekendsCountproject

URL Added to Application from Unknown Domain

#
Severity
high
Time window
2h
Author
Microsoft Security Research
Source
github.com/Azure/Azure-Sentinel

Detects a URL being added to an application where the domain is not one that is associated with the tenant. The query uses domains seen in sign in logs to determine if the domain is associated with the tenant. Applications associated with URLs not controlled by the organization can pose a security risk. Ref: https://learn.microsoft.com/en-gb/entra/architecture/security-operations-applications#application-configuration-changes

MITRE ATT&CK Coverage

Telemetry Coverage

PlatformRecord / event type
AzureUpdate application

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: 017e095a-94d8-430c-a047-e51a11fb737b
name: URL Added to Application from Unknown Domain
description: |
  'Detects a URL being added to an application where the domain is not one that is associated with the tenant.
    The query uses domains seen in sign in logs to determine if the domain is associated with the tenant.
    Applications associated with URLs not controlled by the organization can pose a security risk.
    Ref: https://learn.microsoft.com/en-gb/entra/architecture/security-operations-applications#application-configuration-changes'
severity: High
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AuditLogs
queryFrequency: 2h
queryPeriod: 2h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Persistence
  - PrivilegeEscalation
relevantTechniques:
  - T1078.004
tags:
  - AADSecOpsGuide
query: |
  let domains =
    SigninLogs
    | where ResultType == 0
    | extend domain = split(UserPrincipalName, "@")[1]
    | extend domain = tostring(split(UserPrincipalName, "@")[1])
    | summarize by tolower(tostring(domain));
    AuditLogs
    | where Category =~ "ApplicationManagement"
    | where Result =~ "success"
    | where OperationName =~ 'Update Application'
    | mv-expand TargetResources
    | mv-expand TargetResources.modifiedProperties
    | where TargetResources_modifiedProperties.displayName =~ "AppAddress"
    | extend Key = tostring(TargetResources_modifiedProperties.displayName)
    | extend NewValue = TargetResources_modifiedProperties.newValue
    | extend OldValue = TargetResources_modifiedProperties.oldValue
    | where isnotempty(Key) and isnotempty(NewValue)
    | project-reorder Key, NewValue, OldValue
    | extend NewUrls = extract_all('"Address":([^,]*)', tostring(NewValue))
    | extend OldUrls = extract_all('"Address":([^,]*)', tostring(OldValue))
    | extend AddedUrls = set_difference(NewUrls, OldUrls)
    | where array_length(AddedUrls) > 0
    | extend UserAgent = iif(tostring(AdditionalDetails[0].key) == "User-Agent", tostring(AdditionalDetails[0].value), "")
    | extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
    | extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
    | extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
    | extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
    | extend InitiatingIPAddress = tostring(InitiatedBy.user.ipAddress)
    | extend InitiatedBy = tostring(iff(isnotempty(InitiatingUserPrincipalName),InitiatingUserPrincipalName, InitiatingAppName))
    | extend AppDisplayName = tostring(TargetResources.displayName)
    | where isnotempty(AddedUrls)
    | mv-expand AddedUrls
    | extend AddedUrls = trim(@'"', tostring(AddedUrls))
    | extend Domain = extract("^(?:https?:\\/\\/)?(?:[^@\\/\\n]+@)?(?:www\\.)?([^:\\/?\\n]+)/", 1, replace_string(tolower(AddedUrls), '"', ""))
    | where isnotempty(Domain)
    | extend Domain = strcat(split(Domain, ".")[-2], ".", split(Domain, ".")[-1])
    | where Domain !in (domains)
    | project-reorder TimeGenerated, AppDisplayName, AddedUrls, InitiatedBy, UserAgent, InitiatingIPAddress
    | extend InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1])
entityMappings:
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: AddedUrls
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: InitiatingAppName
      - identifier: AadUserId
        columnName: InitiatingAppServicePrincipalId
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: InitiatingUserPrincipalName
      - identifier: Name
        columnName: InitiatingAccountName
      - identifier: UPNSuffix
        columnName: InitiatingAccountUPNSuffix
  - entityType: Account
    fieldMappings:
      - identifier: AadUserId
        columnName: InitiatingAadUserId
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: InitiatingIPAddress
version: 1.1.1
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: Microsoft Security Research
    support:
        tier: Community
    categories:
        domains: [ "Security - Others" ]

Stages and Predicates

Let Binding: domains used in Stage 20

let domains = SigninLogs
  | where ResultType == 0
  | extend domain = split(UserPrincipalName, "@")[1]
  | extend domain = tostring(split(UserPrincipalName, "@")[1])
  | summarize by tolower(tostring(domain));

Stage 1: source

AuditLogs

Stage 2: where

| where Category =~ "ApplicationManagement"

Stage 3: where

| where Result =~ "success"

Stage 4: where

| where OperationName =~ 'Update Application'

Stage 5: mv-expand

| mv-expand TargetResources

Stage 6: mv-expand

| mv-expand TargetResources.modifiedProperties

Stage 7: where

| where TargetResources_modifiedProperties.displayName =~ "AppAddress"

Stage 8: extend (3 consecutive steps)

| extend Key = tostring(TargetResources_modifiedProperties.displayName)
| extend NewValue = TargetResources_modifiedProperties.newValue
| extend OldValue = TargetResources_modifiedProperties.oldValue

Stage 9: where

| where isnotempty(Key) and isnotempty(NewValue)

Stage 10: project-reorder

| project-reorder Key, NewValue, OldValue

Stage 11: extend (3 consecutive steps)

| extend NewUrls = extract_all('"Address":([^,]*)', tostring(NewValue))
| extend OldUrls = extract_all('"Address":([^,]*)', tostring(OldValue))
| extend AddedUrls = set_difference(NewUrls, OldUrls)

Stage 12: where

| where array_length(AddedUrls) > 0

Stage 13: extend (8 consecutive steps)

| extend UserAgent = iif(tostring(AdditionalDetails[0].key) == "User-Agent", tostring(AdditionalDetails[0].value), "")
| extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
| extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
| extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend InitiatingIPAddress = tostring(InitiatedBy.user.ipAddress)
| extend InitiatedBy = tostring(iff(isnotempty(InitiatingUserPrincipalName),InitiatingUserPrincipalName, InitiatingAppName))
| extend AppDisplayName = tostring(TargetResources.displayName)

Stage 14: where

| where isnotempty(AddedUrls)

Stage 15: mv-expand

| mv-expand AddedUrls

Stage 16: extend

| extend AddedUrls = trim(@'"', tostring(AddedUrls))

Stage 17: extend

| extend Domain = extract("^(?:https?:\\/\\/)?(?:[^@\\/\\n]+@)?(?:www\\.)?([^:\\/?\\n]+)/", 1, replace_string(tolower(AddedUrls), '"', ""))

Stage 18: where

| where isnotempty(Domain)

Stage 19: extend

| extend Domain = strcat(split(Domain, ".")[-2], ".", split(Domain, ".")[-1])

Stage 20: where

| where Domain !in (domains)

Stage 21: project-reorder

| project-reorder TimeGenerated, AppDisplayName, AddedUrls, InitiatedBy, UserAgent, InitiatingIPAddress

Stage 22: extend

| extend InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1])

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
AddedUrlsis_not_null
  • (no value, null check)
field:"AddedUrls" kind:is_not_null
Categoryeq
  • ApplicationManagement corpus 13 (kusto 13)
field:"Category" kind:eq value:"ApplicationManagement"
Domainis_not_null
  • (no value, null check)
field:"Domain" kind:is_not_null
Keyis_not_null
  • (no value, null check)
field:"Key" kind:is_not_null
NewValueis_not_null
  • (no value, null check)
field:"Details" kind:is_not_null
OperationNameeq
  • Update Application corpus 3 (kusto 3)
field:"OperationName" kind:eq value:"Update Application"
Resulteq
  • success corpus 23 (kusto 23)
field:"Result" kind:eq value:"success"
TargetResources_modifiedProperties.displayNameeq
  • AppAddress transforms: kusto_dynamic_path corpus 2 (kusto 2)
field:"TargetResources_modifiedProperties.displayName" kind:eq value:"AppAddress"

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
Domaineqdomainsexcludes:Domain field:"Domain" value:"domains"

Output Fields

These fields are emitted when the rule matches.

FieldSource
Keyextend
NewValueextend
OldValueextend
NewUrlsextend
OldUrlsextend
AddedUrlsextend
UserAgentextend
InitiatingAppNameextend
InitiatingAppServicePrincipalIdextend
InitiatingUserPrincipalNameextend
InitiatingAadUserIdextend
InitiatingIPAddressextend
InitiatedByextend
AppDisplayNameextend
Domainextend
InitiatingAccountNameextend
InitiatingAccountUPNSuffixextend

Back to all Kusto rules