Detection rules › Kusto

Kusto rules: 20

AWSCloudTrail - CloudFormation policy created then used for privilege escalation

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

Identifies creation of IAM policies that grant CloudFormation and IAM permissions commonly used for privilege escalation, followed by attachment activity to a user, role, or group. Review whether the new policy and any subsequent attachment were authorized because this pattern can enable an attacker to create or elevate access.

MITRE ATT&CK Coverage

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: efdc3cff-f006-426f-97fd-4657862f7b9a
name: AWSCloudTrail - CloudFormation policy created then used for privilege escalation
description: |
  Identifies creation of IAM policies that grant CloudFormation and IAM permissions commonly used for privilege
  escalation, followed by attachment activity to a user, role, or group. Review whether the new policy and any
  subsequent attachment were authorized because this pattern can enable an attacker to create or elevate access.
severity: High
status: Available
requiredDataConnectors:
  - connectorId: AWS
    dataTypes:
      - AWSCloudTrail
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - DefenseEvasion
  - PrivilegeEscalation
  - Persistence
relevantTechniques:
  - T1484
  - T1098.003
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), Condition = tostring(parse_json(Statement).Condition)
  | extend Action = tostring(Action)
  | where Effect =~ "Allow" and (((Action has "iam:*" or Action has "iam:PassRole") and Action has "cloudformation:*") or ((Action has "iam:*" or Action has "iam:PassRole") and Action contains "cloudformation:DescribeStacks" and Action contains "cloudformation:CreateStack") or ((Action contains "iam:*" or Action contains "iam:PassRole") and Action contains "cloudformation:Describe*" and Action contains "cloudformation:Create*")) and Resource == "*" and Condition == ""
  | distinct TimeGenerated, EventName, PolicyName, SourceIpAddress, UserIdentityArn,  RecipientAccountId, AccountName, AccountUPNSuffix
  | project-rename StartTime = TimeGenerated  );
  let PolicyAttach = materialize(  EventInfo
  | where TimeGenerated >= ago(timeframe)
  | where EventName in (EventNameList) and isempty(ErrorCode) and isempty(ErrorMessage)
  | extend PolicyName = tostring(split(tostring(parse_json(RequestParameters).policyArn),"/")[1])
  | summarize AttachEventCount=count(), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by EventSource, EventName,   RecipientAccountId, AccountName, AccountUPNSuffix, UserIdentityType , UserIdentityArn, SourceIpAddress, PolicyName
  | extend AttachEvent = pack("StartTime", StartTime, "EndTime", EndTime, "EventName", EventName, "UserIdentityType",   UserIdentityType, "SourceIpAddress", SourceIpAddress, "AccountName", AccountName, "AccountUPNSuffix", AccountUPNSuffix, "RecipientAccountId", RecipientAccountId, "UserIdentityArn", UserIdentityArn)
  | project EventSource, PolicyName, AttachEvent, AttachEventCount, RecipientAccountId, AccountName, AccountUPNSuffix
  );
  // 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
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 CloudFormation privilege escalation policy activity: {{PolicyName}} by {{AccountName}}'
  alertDescriptionFormat: 'Detected {{EventName}} for policy {{PolicyName}} from {{SourceIpAddress}}.'
version: 1.0.4
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 15

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

Stages 1 to 14 define let FullAdminPolicyEvents as the rule's main pipeline source. Stages 15 to 17 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), Condition = tostring(parse_json(Statement).Condition)

Stage 11: extend

| extend Action = tostring(Action)

Stage 12: where

| where Effect =~ "Allow" and (((Action has "iam:*" or Action has "iam:PassRole") and Action has "cloudformation:*") or ((Action has "iam:*" or Action has "iam:PassRole") and Action contains "cloudformation:DescribeStacks" and Action contains "cloudformation:CreateStack") or ((Action contains "iam:*" or Action contains "iam:PassRole") and Action contains "cloudformation:Describe*" and Action contains "cloudformation:Create*")) and Resource == "*" and Condition == ""

Stage 13: distinct

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

Stage 14: project-rename

| project-rename StartTime = TimeGenerated

Stage 15: join

FullAdminPolicyEvents
| join kind=leftouter
(
    PolicyAttach
)
on PolicyName

Stage 16: project-away

project-away PolicyName1

Stage 17: summarize aggregation inside the join branch

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

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Actioncontains
  • cloudformation:Create*
  • cloudformation:CreateStack
  • cloudformation:Describe*
  • cloudformation:DescribeStacks
  • iam:*
  • iam:PassRole
field:"Action" kind:contains
Actionmatch
  • cloudformation:* transforms: term
  • iam:* transforms: term
  • iam:PassRole transforms: term
field:"Action" kind:match
Effecteq
  • Allow
field:"Effect" kind:eq value:"Allow"
ErrorCodeis_null
  • (no value, null check)
field:"ErrorCode" kind:is_null
ErrorMessageis_null
  • (no value, null check)
field:"ErrorMessage" kind:is_null
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

Azure DevOps Build Variable Modified by New User

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

'Variables can be configured and used at any stage of the build process in Azure DevOps to inject values. An attacker with the required permissions could modify or add to these variables to conduct malicious activity such as changing paths or remote endpoints called during the build. As variables are often changed by users, just detecting these changes would have a high false positive rate. This detection looks for modifications to variable groups where that user has not been observed modifying them before.'

MITRE ATT&CK Coverage

TacticTechniques
Defense Impairment

Rule Body

id: 3b9a44d7-c651-45ed-816c-eae583a6f2f1
name: Azure DevOps Build Variable Modified by New User
description: |
  'Variables can be configured and used at any stage of the build process in Azure DevOps to inject values. An attacker with the required permissions could modify or add to these variables to conduct malicious activity such as changing paths or remote endpoints called during the build.
  As variables are often changed by users, just detecting these changes would have a high false positive rate. This detection looks for modifications to variable groups where that user has not been observed modifying them before.'
severity: Medium
status: Available
requiredDataConnectors: []
queryFrequency: 1d
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - DefenseEvasion
relevantTechniques:
  - T1578
query: |
  let lookback = 14d;
  let timeframe = 1d;
  let historical_data =
  ADOAuditLogs
  | where TimeGenerated > ago(lookback) and TimeGenerated < ago(timeframe)
  | where OperationName =~ "Library.VariableGroupModified"
  | extend variables = Data.Variables
  | extend VariableGroupId = tostring(Data.VariableGroupId)
  | extend UserKey = strcat(VariableGroupId, "-", ActorUserId)
  | project UserKey;
  ADOAuditLogs
  | where TimeGenerated > ago(timeframe)
  | where OperationName =~ "Library.VariableGroupModified"
  | extend VariableGroupName = tostring(Data.VariableGroupName)
  | extend VariableGroupId = tostring(Data.VariableGroupId)
  | extend UserKey = strcat(VariableGroupId, "-", ActorUserId)
  | where UserKey !in (historical_data)
  | project-away UserKey
  | project-reorder TimeGenerated, VariableGroupName, ActorUPN, IpAddress, UserAgent
  | extend timestamp = TimeGenerated
  | extend AccountName = tostring(split(ActorUPN, "@")[0]), AccountUPNSuffix = tostring(split(ActorUPN, "@")[1])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: ActorUPN
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: AccountUPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IpAddress
version: 1.0.6
kind: Scheduled

Stages and Predicates

Parameters

let lookback = 14d;
let timeframe = 1d;

Let Binding: historical_data used in Stage 5

let historical_data = ADOAuditLogs
| where TimeGenerated > ago(lookback) and TimeGenerated < ago(timeframe)
| where OperationName =~ "Library.VariableGroupModified"
| extend variables = Data.Variables
| extend VariableGroupId = tostring(Data.VariableGroupId)
| extend UserKey = strcat(VariableGroupId, "-", ActorUserId)
| project UserKey;

Stage 1: source

ADOAuditLogs

Stage 2: where

| where TimeGenerated > ago(timeframe)

Stage 3: where

| where OperationName =~ "Library.VariableGroupModified"

Stage 4: extend (3 consecutive steps)

| extend VariableGroupName = tostring(Data.VariableGroupName)
| extend VariableGroupId = tostring(Data.VariableGroupId)
| extend UserKey = strcat(VariableGroupId, "-", ActorUserId)

Stage 5: where

| where UserKey !in (historical_data)

Stage 6: project-away

| project-away UserKey

Stage 7: project-reorder

| project-reorder TimeGenerated, VariableGroupName, ActorUPN, IpAddress, UserAgent

Stage 8: extend

| extend timestamp = TimeGenerated

Stage 9: extend

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

Indicators

These rows show field, operator, and value matches.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
UserKeyeqhistorical_dataexcludes:UserKey field:"UserKey" value:"historical_data"

Output Fields

These fields are emitted when the rule matches.

FieldSource
VariableGroupNameextend
VariableGroupIdextend
timestampextend
AccountNameextend
AccountUPNSuffixextend

BloodHound Attack Path Finding - Large Default Groups With WriteOwnerLimitedRights 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 Groups With WriteOwnerLimitedRights Privileges

Rule Body

id: b26a44aa-aec3-4cfe-aa75-e41104f97a9a
name: BloodHound Attack Path Finding - Large Default Groups With WriteOwnerLimitedRights
  Privileges
description: BloodHound Attack Path Finding - Large Default Groups With WriteOwnerLimitedRights
  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 == "LargeDefaultGroupsWriteOwnerLimitedRights"
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 == "LargeDefaultGroupsWriteOwnerLimitedRights"

Indicators

These rows show field, operator, and value matches.

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

Brute force attack against an Entra-authenticated Windows device

#
Status
available
Severity
medium
Time window
20m
Group by
Anomalies, Baseline, FailureToSuccessDiff, Score, UserDisplayName, UserPrincipalName
Source
github.com/Azure/Azure-Sentinel

Identifies evidence of brute force activity against Windows devices authenticated via Entra ID (including Entra-joined, hybrid-joined, and Windows 365 Cloud PCs) by detecting multiple authentication failures followed by a successful authentication within a defined time window.

MITRE ATT&CK Coverage

TacticTechniques
Credential Access

Telemetry Coverage

Rule Body

id: 3fbc20a4-04c4-464e-8fcb-6667f53e4987
name: Brute force attack against an Entra-authenticated Windows device
description: |
  'Identifies evidence of brute force activity against Windows devices authenticated via Entra ID (including Entra-joined, hybrid-joined, and Windows 365 Cloud PCs) by detecting multiple authentication failures followed by a successful authentication within a defined time window.'
severity: Medium
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - SigninLogs
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - CredentialAccess
relevantTechniques:
  - T1110
query: |
  let authenticationWindow = 20m;
  let sensitivity = 2.5;
  SigninLogs
  | where AppDisplayName =~ "Windows Sign In"
  | extend FailureOrSuccess = iff(ResultType in ("0", "50125", "50140", "70043", "70044"), "Success", "Failure")
  | summarize FailureCount = countif(FailureOrSuccess=="Failure"), SuccessCount = countif(FailureOrSuccess=="Success"), IPAddresses = make_set(IPAddress,1000)
                            by bin(TimeGenerated, authenticationWindow), UserDisplayName, UserPrincipalName
  | extend FailureSuccessDiff = FailureCount - SuccessCount
  | where FailureSuccessDiff > 0
  | sort by UserPrincipalName asc, TimeGenerated asc
  | serialize
  | summarize Diff = make_list(FailureSuccessDiff, 10000), TimeStamp = make_list(TimeGenerated, 10000) by UserDisplayName, UserPrincipalName//, tostring(IPAddresses)
  | extend (Anomalies, Score, Baseline) = series_decompose_anomalies(Diff, sensitivity, -1, 'linefit')  
  | mv-expand Diff to typeof(double), TimeStamp to typeof(datetime), Anomalies to typeof(double), Score to typeof(double), Baseline to typeof(long)
  | where Anomalies > 0
  | summarize by UserDisplayName, UserPrincipalName, Anomalies, Score, Baseline, FailureToSuccessDiff = Diff
  | join kind=leftouter (
        SigninLogs
        | where AppDisplayName =~ "Windows Sign In"
        | extend OS = DeviceDetail.operatingSystem, Browser = DeviceDetail.browser
        | extend StatusCode = tostring(Status.errorCode), StatusDetails = tostring(Status.additionalDetails)
        | extend State = tostring(LocationDetails.state), City = tostring(LocationDetails.city)
        | summarize StartTime = min(TimeGenerated), 
                    EndTime = max(TimeGenerated), 
                    IPAddresses = make_set(IPAddress,100), 
                    OS = make_set(OS,20), 
                    Browser = make_set(Browser,20), 
                    City = make_set(City,100), 
                    ResultType = make_set(ResultType,100)
                by UserDisplayName, UserPrincipalName, UserId, AppDisplayName
    ) on UserDisplayName, UserPrincipalName
  | project-away UserDisplayName1, UserPrincipalName1
  | extend IPAddressFirst = tostring(IPAddresses[0])
  | extend Name = tostring(split(UserPrincipalName,'@',0)[0]), UPNSuffix = tostring(split(UserPrincipalName,'@',1)[0])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: UserPrincipalName
      - identifier: Name
        columnName: Name
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: Account
    fieldMappings:
      - identifier: AadUserId
        columnName: UserId
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPAddressFirst    
version: 2.0.3
kind: Scheduled

Stages and Predicates

Parameters

let authenticationWindow = 20m;
let sensitivity = 2.5;

Stage 1: source

SigninLogs

Stage 2: where

| where AppDisplayName =~ "Windows Sign In"

Stage 3: extend

| extend FailureOrSuccess = iff(ResultType in ("0", "50125", "50140", "70043", "70044"), "Success", "Failure")
FailureOrSuccess =
ifResultType in (0, 50125, 50140, 70043, 70044)"Success"
else"Failure"

Stage 4: summarize

| summarize FailureCount = countif(FailureOrSuccess=="Failure"), SuccessCount = countif(FailureOrSuccess=="Success"), IPAddresses = make_set(IPAddress,1000)
                          by bin(TimeGenerated, authenticationWindow), UserDisplayName, UserPrincipalName

Stage 5: extend

| extend FailureSuccessDiff = FailureCount - SuccessCount

Stage 6: where

| where FailureSuccessDiff > 0

Stage 7: sort

| sort by UserPrincipalName asc, TimeGenerated asc

Stage 8: kusto:serialize

| serialize

Stage 9: summarize

| summarize Diff = make_list(FailureSuccessDiff, 10000), TimeStamp = make_list(TimeGenerated, 10000) by UserDisplayName, UserPrincipalName

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

Stage 10: extend

| extend (Anomalies, Score, Baseline) = series_decompose_anomalies(Diff, sensitivity, -1, 'linefit')

Stage 11: mv-expand

| mv-expand Diff to typeof(double), TimeStamp to typeof(datetime), Anomalies to typeof(double), Score to typeof(double), Baseline to typeof(long)

Stage 12: where

| where Anomalies > 0

Stage 13: summarize

| summarize by UserDisplayName, UserPrincipalName, Anomalies, Score, Baseline, FailureToSuccessDiff = Diff

Stage 14: join

| join kind=leftouter (
      SigninLogs
      | where AppDisplayName =~ "Windows Sign In"
      | extend OS = DeviceDetail.operatingSystem, Browser = DeviceDetail.browser
      | extend StatusCode = tostring(Status.errorCode), StatusDetails = tostring(Status.additionalDetails)
      | extend State = tostring(LocationDetails.state), City = tostring(LocationDetails.city)
      | summarize StartTime = min(TimeGenerated), 
                  EndTime = max(TimeGenerated), 
                  IPAddresses = make_set(IPAddress,100), 
                  OS = make_set(OS,20), 
                  Browser = make_set(Browser,20), 
                  City = make_set(City,100), 
                  ResultType = make_set(ResultType,100)
              by UserDisplayName, UserPrincipalName, UserId, AppDisplayName
  ) on UserDisplayName, UserPrincipalName

Stage 15: project-away

project-away UserDisplayName1, UserPrincipalName1

Stage 16: extend

extend IPAddressFirst

Stage 17: extend

extend Name, UPNSuffix

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
Anomaliessummarize
Baselinesummarize
FailureToSuccessDiffsummarize
Scoresummarize
UserDisplayNamesummarize
UserPrincipalNamesummarize
IPAddressFirstextend
Nameextend
UPNSuffixextend

CYFIRMA - High severity Malicious Network Indicators Associated with Malware - Block Recommended Rule

#

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

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

"This analytics rule detects network-based indicators identified by CYFIRMA threat intelligence as associated with Command & Control (C2) infrastructure. These may represent attacker-controlled endpoints used to maintain persistence, exfiltrate data, or receive commands from malware-infected hosts."

MITRE ATT&CK Coverage

Rule Body

id: c7c02bbf-b775-4e85-a3c7-7f8a9318fd2f
name: CYFIRMA - High severity Malicious Network Indicators Associated with Malware - Block Recommended Rule
description: |
  "This analytics rule detects network-based indicators identified by CYFIRMA threat intelligence as associated with Command & Control (C2) infrastructure.
  These may represent attacker-controlled endpoints used to maintain persistence, exfiltrate data, or receive commands from malware-infected hosts."
version: 1.0.1
kind: Scheduled
severity: High
enabled: false
requiredDataConnectors:
  - connectorId: CyfirmaCyberIntelligenceDC
    dataTypes:
      - CyfirmaIndicators_CL
query: |
  //Network Indicators Associated with Malware - Block Recommended Rule
  let timeFrame= 5m;
  CyfirmaIndicators_CL 
  | where ConfidenceScore >= 80
      and TimeGenerated between (ago(timeFrame) .. now())
      and pattern !contains 'file:hashes' and RecommendedActions has 'Block' and Roles has 'Malware'
  | extend IPv4 = extract(@"ipv4-addr:value\s*=\s*'([^']+)'", 1, pattern)
  | extend IPv6 = extract(@"ipv6-addr:value\s*=\s*'([^']+)'", 1, pattern)
  | extend URL = extract(@"url:value\s*=\s*'([^']+)'", 1, pattern)
  | extend Domain = extract(@"domain-name:value\s*=\s*'([^']+)'", 1, pattern)
  | extend parsed = parse_json(extensions)
  | extend extensionKeys = bag_keys(parsed)
  | mv-expand extensionKeys
  | extend extensionKeyStr = tostring(extensionKeys)
  | extend ext = parsed[extensionKeyStr]
  | extend props = ext.properties
  | extend 
      extension_id = extensionKeyStr,
      ASN_Owner = props.asn_owner,
      ASN = props.asn,
      ProviderName = 'CYFIRMA',
      ProductName = 'DeCYFIR/DeTCT'
  | project
      IPv4,
      IPv6,
      URL,
      Domain,
      ThreatActors,
      RecommendedActions,
      Sources,
      Roles,
      Country,
      IPAbuse,
      name,
      Description,
      ConfidenceScore,
      IndicatorID,
      created,
      modified,
      valid_from,
      Tags,
      ThreatType,
      TimeGenerated,
      SecurityVendors,
      ProductName,
      ProviderName
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: GreaterThan
triggerThreshold: 0
suppressionDuration: 5m
suppressionEnabled: true
tactics:
  - InitialAccess
  - Execution
  - CommandAndControl
relevantTechniques:
  - T1566
  - T1189
  - T1204
  - T1071
  - T1090
  - T1059
  - T1568
  - T1566.002
  - T1204.001
  - T1071.001
  - T1568.002
alertDetailsOverride:
  alertDisplayNameFormat: "High-Confidence Malicious Network Indicators Associated with Malware - Block Recommended - {{name}} "
  alertDescriptionFormat: "{{Description}} - {{name}} "
  alertDynamicProperties:
    - alertProperty: ProductName
      value: ProductName
    - alertProperty: ProviderName
      value: ProviderName
customDetails:
  ThreatActors: ThreatActors
  Sources: Sources
  RecommendedActions: RecommendedActions
  Roles: Roles
  Country: Country
  Description: Description
  ConfidenceScore: ConfidenceScore
  SecurityVendors: SecurityVendors
  IndicatorID: IndicatorID
  Created: created
  Modified: modified
  ValidFrom: valid_from
  Tags: Tags
  ThreatType: ThreatType
  TimeGenerated: TimeGenerated
  IPAbuse: IPAbuse
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPv4
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPv6
  - entityType: DNS
    fieldMappings:
      - identifier: DomainName
        columnName: Domain
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: URL
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: false
    reopenClosedIncident: false
    lookbackDuration: PT5H
    matchingMethod: AllEntities
eventGroupingSettings:
  aggregationKind: AlertPerResult

Stages and Predicates

Parameters

let timeFrame = 5m;

Stage 1: source

CyfirmaIndicators_CL

Stage 2: where

| where ConfidenceScore >= 80
    and TimeGenerated between (ago(timeFrame) .. now())
    and pattern !contains 'file:hashes' and RecommendedActions has 'Block' and Roles has 'Malware'

Stage 3: extend (6 consecutive steps)

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

Stage 4: mv-expand

| mv-expand extensionKeys

Stage 5: extend (4 consecutive steps)

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

Stage 6: project

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

Indicators

These rows show field, operator, and value matches.

Exclusions

The rule actively suppresses these predicates.

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

Output Fields

These fields are emitted when the rule matches.

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

Detect Rare scheduled task created

#
Author
Robbe Van den Daele
Source
github.com/HybridBrothers/Hunting-Queries-Detection-Rules

Persistence via Scheduled Tasks is a well-known technique used by adversaries to make sure their malware programs keep running an the compromised device. With this detection rule, you can search for scheduled tasks being created by processes that did not performed this before. > [!WARNING] > This detection rule is the base for the detection. You will need to add environment specific finetuning in order to limit the BP detections on legitimate processes

MITRE ATT&CK Coverage

References

Telemetry Coverage

Rule Body

let lolbins = toscalar(externaldata(FileName:string, Description:string, Author:string, Date:datetime, Command:string, CommandDescription:string, CommandUsecase:string, CommandCategory:string)
    ["https://lolbas-project.github.io/api/lolbas.csv"] with(format="csv", ignoreFirstRecord=true)
    | extend FileName = tolower(FileName)
    | summarize make_set(FileName)
);
// Setting up the rare ones.
let rareScheduledTaskRegistrations = toscalar(
    DeviceProcessEvents
    | where Timestamp > ago(14d)
    | where ProcessCommandLine has_all ("schtasks",  "/create")
    | summarize count() by ProcessCommandLine
    | where count_ < 5
    | summarize make_set(ProcessCommandLine)
);
DeviceProcessEvents
| where Timestamp > ago(1d)
| where ProcessCommandLine has_all ("schtasks",  "/create")
| extend ProcessCommandLine = replace_regex(ProcessCommandLine, @"C:\\Users\\[^\\]+", "C:\\Users\\<USERNAME>")
// Only take the ones in the last day we find rare within the organization.
| where ProcessCommandLine in (rareScheduledTaskRegistrations)
| invoke FileProfile(InitiatingProcessSHA256, 1000)
| where not(GlobalPrevalence > 1000 and tolower(InitiatingProcessFileName) !in (lolbins))

Stages and Predicates

Let Binding: lolbins used in Stage 7

let lolbins = toscalar(externaldata(FileName:string, Description:string, Author:string, Date:datetime, Command:string, CommandDescription:string, CommandUsecase:string, CommandCategory:string)
    ["https://lolbas-project.github.io/api/lolbas.csv"] with(format="csv", ignoreFirstRecord=true)
    | extend FileName = tolower(FileName)
    | summarize make_set(FileName)
);

Let Binding: rareScheduledTaskRegistrations used in Stage 5

let rareScheduledTaskRegistrations = toscalar(
    DeviceProcessEvents
    | where Timestamp > ago(14d)
    | where ProcessCommandLine has_all ("schtasks",  "/create")
    | summarize count() by ProcessCommandLine
    | where count_ < 5
    | summarize make_set(ProcessCommandLine)
);

Stage 1: source

DeviceProcessEvents

Stage 2: where

| where Timestamp > ago(1d)

Stage 3: where

| where ProcessCommandLine has_all ("schtasks",  "/create")

Stage 4: extend

| extend ProcessCommandLine = replace_regex(ProcessCommandLine, @"C:\\Users\\[^\\]+", "C:\\Users\\<USERNAME>")

Stage 5: where

| where ProcessCommandLine in (rareScheduledTaskRegistrations)

Stage 6: invoke

| invoke FileProfile(InitiatingProcessSHA256, 1000)

Stage 7: where

| where not(GlobalPrevalence > 1000 and tolower(InitiatingProcessFileName) !in (lolbins))

Indicators

These rows show field, operator, and value matches.

Exclusions

The rule actively suppresses these predicates.

Output Fields

These fields are emitted when the rule matches.

FieldSource
ProcessCommandLineextend

Multiple scans in the network (Microsoft Defender for IoT)

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

'This alert leverages Defender for IoT to detect multiple scans on the network indicating new devices, functionality, application misconfiguration, or malicious reconnaissance activity on the network.'

MITRE ATT&CK Coverage

TacticTechniques
Discovery

Rule Body

id: 493916d5-a094-4bfa-bdd1-d983a063ea3d
name: Multiple scans in the network (Microsoft Defender for IoT)
description: |
  'This alert leverages Defender for IoT to detect multiple scans on the network indicating new devices, functionality, application misconfiguration, or malicious reconnaissance activity on the network.'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: IoT
    dataTypes:
      - SecurityAlert (ASC for IoT)
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Discovery
relevantTechniques:
  - T0842
query: |
  SecurityAlert
  | where ProviderName == "IoTSecurity"
  | where AlertName has "Scan"
  | extend ExtendedProperties = parse_json(ExtendedProperties)
  | where tostring(ExtendedProperties.isNew) == "True"
  | extend DeviceId = tostring(ExtendedProperties.DeviceId), 
           SourceDeviceAddress = tostring(ExtendedProperties.SourceDeviceAddress), 
           DestDeviceAddress = tostring(ExtendedProperties.DestinationDeviceAddress), 
           RemediationSteps = tostring(parse_json(RemediationSteps)[0]), 
           Protocol = tostring(ExtendedProperties.Protocol), 
           AlertManagementUri = tostring(ExtendedProperties.AlertManagementUri)
  | project
    TimeGenerated,
    DeviceId,
    ProductName,
    ProductComponentName,
    AlertSeverity,
    AlertName,
    Description,
    Protocol,
    SourceDeviceAddress,
    DestDeviceAddress,
    RemediationSteps,
    Tactics,
    Entities,
    VendorOriginalId,
    AlertLink,
    AlertManagementUri,
    Techniques
entityMappings:
sentinelEntitiesMappings:
  - columnName: Entities
eventGroupingSettings:
  aggregationKind: AlertPerResult
customDetails:
  Sensor: DeviceId
  Protocol: Protocol
  VendorOriginalId: VendorOriginalId
  AlertManagementUri: AlertManagementUri
alertDetailsOverride:
  alertDisplayNameFormat: (MDIoT) {{AlertName}}
  alertDescriptionFormat: (MDIoT) {{Description}}
  alertTacticsColumnName: Tactics
  alertSeverityColumnName: AlertSeverity
  alertDynamicProperties: 
    - alertProperty: ProductName
      value: ProductName
    - alertProperty: RemediationSteps
      value: RemediationSteps
    - alertProperty: Techniques
      value: Techniques
    - alertProperty: ProductComponentName
      value: ProductComponentName
    - alertProperty: AlertLink
      value: AlertLink
version: 1.0.3
kind: Scheduled

Stages and Predicates

Stage 1: source

SecurityAlert

Stage 2: where

| where ProviderName == "IoTSecurity"

Stage 3: where

| where AlertName has "Scan"

Stage 4: extend

| extend ExtendedProperties = parse_json(ExtendedProperties)

Stage 5: where

| where tostring(ExtendedProperties.isNew) == "True"

Stage 6: extend

| extend DeviceId = tostring(ExtendedProperties.DeviceId), 
         SourceDeviceAddress = tostring(ExtendedProperties.SourceDeviceAddress), 
         DestDeviceAddress = tostring(ExtendedProperties.DestinationDeviceAddress), 
         RemediationSteps = tostring(parse_json(RemediationSteps)[0]), 
         Protocol = tostring(ExtendedProperties.Protocol), 
         AlertManagementUri = tostring(ExtendedProperties.AlertManagementUri)

Stage 7: project

| project
  TimeGenerated,
  DeviceId,
  ProductName,
  ProductComponentName,
  AlertSeverity,
  AlertName,
  Description,
  Protocol,
  SourceDeviceAddress,
  DestDeviceAddress,
  RemediationSteps,
  Tactics,
  Entities,
  VendorOriginalId,
  AlertLink,
  AlertManagementUri,
  Techniques

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
AlertNamematch
  • Scan transforms: term
field:"AlertName" kind:match value:"Scan"
ExtendedProperties.isNeweq
  • True transforms: tostring, kusto_dynamic_path corpus 15 (kusto 15)
field:"ExtendedProperties.isNew" kind:eq value:"True"
ProviderNameeq
  • IoTSecurity corpus 15 (kusto 15)
field:"ProviderName" kind:eq value:"IoTSecurity"

Output Fields

These fields are emitted when the rule matches.

FieldSource
AlertLinkproject
AlertManagementUriproject
AlertNameproject
AlertSeverityproject
Descriptionproject
DestDeviceAddressproject
DeviceIdproject
Entitiesproject
ProductComponentNameproject
ProductNameproject
Protocolproject
RemediationStepsproject
SourceDeviceAddressproject
Tacticsproject
Techniquesproject
TimeGeneratedproject
VendorOriginalIdproject

OCI - Multiple instances launched

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

'Detects when multiple instances were launched.'

MITRE ATT&CK Coverage

TacticTechniques
Impact

Rule Body

id: a79cf2b9-a511-4282-ba5d-812e14b07831
name: OCI - Multiple instances launched
description: |
  'Detects when multiple instances were launched.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: OracleCloudInfrastructureLogsConnector
    dataTypes:
      - OCILogs
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Impact
relevantTechniques:
  - T1496
query: |
  let threshold = 5;
  OCILogs
  | where data_eventName_s =~ 'LaunchInstance'
  | summarize count() by SrcIpAddr, bin(TimeGenerated, 10m)
  | where count_ >= threshold
  | extend IPCustomEntity = SrcIpAddr
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPCustomEntity
version: 1.0.0
kind: Scheduled

Stages and Predicates

Parameters

let threshold = 5;

Stage 1: source

OCILogs

Stage 2: where

| where data_eventName_s =~ 'LaunchInstance'

Stage 3: summarize

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

Stage 4: where

| where count_ >= threshold

Stage 5: extend

| extend IPCustomEntity = SrcIpAddr

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
SrcIpAddrsummarize
IPCustomEntityextend

Refactor AWS policy based on activities in the last 60 days

#

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
30m
Source
github.com/Azure/Azure-Sentinel

This is a recommended update to IAM policy on AWS. Review the policy and apply it according to change control process. Authomize will have a recommended policy to be downloaded.

MITRE ATT&CK Coverage

TacticTechniques
Privilege Escalation

Rule Body

id: 642de064-c67b-4eb7-98bd-3f8cd51f282c
name: Refactor AWS policy based on activities in the last 60 days
kind: Scheduled
description: This is a recommended update to IAM policy on AWS. Review the policy and apply it according to change control process. Authomize will have a recommended policy to be downloaded.
severity: High
status: Available
queryFrequency: 30m
queryPeriod: 30m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - PrivilegeEscalation
relevantTechniques:
  - T1078
query: |-
  Authomize_v2_CL
  | where ingestion_time() >= ago(30m)
  | extend EventID = id_s, Policy = policy_name_s, Severity = severity_s,Description = description_s,Recommendation = recommendation_s,URL = url_s,Tactics = tactics_s
  | where Policy has "Refactor AWS policy based on activities in the last 60 days."
  | project  EventID, Policy, Severity, Description, Recommendation, URL, Category, Tactics
entityMappings:
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: URL
requiredDataConnectors:
- connectorId: Authomize
  dataTypes: [ "Authomize_v2_CL" ]
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: 5h
    matchingMethod: AnyAlert
    groupByEntities: []
    groupByAlertDetails: []
    groupByCustomDetails: []
eventGroupingSettings:
  aggregationKind: SingleAlert
alertDetailsOverride:
  alertnameFormat: Alert from Authomize - Refactor AWS policy based on activities in the last 60 days
  alertDescriptionFormat: Refactor AWS policy based on activities in the last 60 days. This is a recommended update to IAM policy on AWS. Review the policy and apply it according to change control process. Authomize will have a recommended policy to be downloaded.
  alertSeverity: Severity
  alertTactics: Tactics
  alertDynamicProperties:
  - alertProperty: AlertLink
    value: URL
customDetails:
  AuthomizeEventID: EventID
  EventName: Policy
  EventDescription: Description
  EventRecommendation: Recommendation
  ReferencedURL: URL
suppressionDuration: 5h
suppressionEnabled: false
version: 1.0.2

Stages and Predicates

Stage 1: source

Authomize_v2_CL

Stage 2: where

| where ingestion_time() >= ago(30m)

Stage 3: extend

| extend EventID = id_s, Policy = policy_name_s, Severity = severity_s,Description = description_s,Recommendation = recommendation_s,URL = url_s,Tactics = tactics_s

Stage 4: where

| where Policy has "Refactor AWS policy based on activities in the last 60 days."

Stage 5: project

| project  EventID, Policy, Severity, Description, Recommendation, URL, Category, Tactics

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Policymatch
  • Refactor AWS policy based on activities in the last 60 days. transforms: term
field:"policy_name_s" kind:match value:"Refactor AWS policy based on activities in the last 60 days."

Output Fields

These fields are emitted when the rule matches.

FieldSource
Categoryproject
Descriptionproject
EventIDproject
Policyproject
Recommendationproject
Severityproject
Tacticsproject
URLproject

SAP BTP - Cloud Integration access policy tampering

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

Identifies changes to access policies in SAP Cloud Integration. Access policies control authorization for integration artifacts, defining which users and roles can access specific integration flows and related content. Unauthorized access policy manipulation could indicate: - Attacker granting themselves access to sensitive integration artifacts - Removal of security controls to enable further malicious activity - Defense evasion by modifying artifact references to hide unauthorized access

MITRE ATT&CK Coverage

Rule Body

id: 9e6f4b2c-0d3e-5a8f-c9b7-2f5d8a1e4c6b
kind: Scheduled
name: SAP BTP - Cloud Integration access policy tampering
description: |
  Identifies changes to access policies in SAP Cloud Integration. Access policies control
  authorization for integration artifacts, defining which users and roles can access specific
  integration flows and related content.
  
  Unauthorized access policy manipulation could indicate:
  - Attacker granting themselves access to sensitive integration artifacts
  - Removal of security controls to enable further malicious activity
  - Defense evasion by modifying artifact references to hide unauthorized access
severity: High
status: Available
requiredDataConnectors:
  - connectorId: SAPBTPAuditEvents
    dataTypes:
      - SAPBTPAuditLog_CL
queryFrequency: 15m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - DefenseEvasion
  - PrivilegeEscalation
relevantTechniques:
  - T1548
  - T1222
query: |
  let accessPolicyTypes = dynamic(["Access Policy", "Artifact Reference"]);
  let monitoredActions = dynamic(["Create", "Change", "Delete"]);
  SAPBTPAuditLog_CL
  | where Category == "audit.security-events"
  | extend data_s = tostring(Message.data),
           ipAddress = tostring(Message.ip)
  | extend parsedData = parse_json(data_s)
  | extend action = tostring(parsedData.action),
           objectType = tostring(parsedData.objectType),
           objectId = tostring(parsedData.objectId),
           policyMessage = tostring(parsedData.attributes.message)
  | where objectType in (accessPolicyTypes)
  | where action in (monitoredActions)
  | extend normalizedAction = case(
      action == "Create", "created",
      action == "Change", "modified",
      action == "Delete", "deleted",
      action
  )
  | extend MessageText = case(
      objectType == "Access Policy", strcat("Access policy '", objectId, "' was ", normalizedAction),
      objectType == "Artifact Reference", strcat("Artifact reference '", objectId, "' was ", normalizedAction),
      strcat(objectType, " '", objectId, "' was ", normalizedAction)
  )
  | project
      UpdatedOn,
      UserName,
      MessageText,
      ObjectType = objectType,
      ObjectId = objectId,
      Action = action,
      PolicyMessage = policyMessage,
      Tenant,
      ipAddress,
      CloudApp = "SAP Cloud Integration"
  | extend AccountName = split(UserName, "@")[0], UPNSuffix = split(UserName, "@")[1]
eventGroupingSettings:
  aggregationKind: SingleAlert
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: ipAddress
  - entityType: CloudApplication
    fieldMappings:
      - identifier: Name
        columnName: CloudApp
alertDetailsOverride:
  alertDisplayNameFormat: 'SAP Cloud Integration: {{MessageText}}'
  alertDescriptionFormat: |
    {{MessageText}} by {{UserName}} from IP {{ipAddress}}.
    
    This could indicate:
    - Legitimate access policy administration
    - Unauthorized privilege escalation attempt
    - Attacker modifying security controls to access sensitive integrations
customDetails:
  ObjectType: ObjectType
  ObjectId: ObjectId
  Action: Action
  PolicyMessage: PolicyMessage
  SourceIP: ipAddress
version: 1.0.1

Stages and Predicates

Parameters

let accessPolicyTypes = dynamic(["Access Policy", "Artifact Reference"]);
let monitoredActions = dynamic(["Create", "Change", "Delete"]);

Stage 1: source

SAPBTPAuditLog_CL

Stage 2: where

| where Category == "audit.security-events"

Stage 3: extend (3 consecutive steps)

| extend data_s = tostring(Message.data),
         ipAddress = tostring(Message.ip)
| extend parsedData = parse_json(data_s)
| extend action = tostring(parsedData.action),
         objectType = tostring(parsedData.objectType),
         objectId = tostring(parsedData.objectId),
         policyMessage = tostring(parsedData.attributes.message)

Stage 4: where

| where objectType in (accessPolicyTypes)

Stage 5: where

| where action in (monitoredActions)

Stage 6: extend

| extend normalizedAction = case(
    action == "Create", "created",
    action == "Change", "modified",
    action == "Delete", "deleted",
    action
)
normalizedAction =
ifaction == "Create""created"
elifaction == "Change""modified"
elifaction == "Delete""deleted"
elseaction

Stage 7: extend

| extend MessageText = case(
    objectType == "Access Policy", strcat("Access policy '", objectId, "' was ", normalizedAction),
    objectType == "Artifact Reference", strcat("Artifact reference '", objectId, "' was ", normalizedAction),
    strcat(objectType, " '", objectId, "' was ", normalizedAction)
)
MessageText =
ifobjectType == "Access Policy"strcat("Access policy '", objectId, "' was ", normalizedAction)
elifobjectType == "Artifact Reference"strcat("Artifact reference '", objectId, "' was ", normalizedAction)
elsestrcat(objectType, " '", objectId, "' was ", normalizedAction)

Stage 8: project

| project
    UpdatedOn,
    UserName,
    MessageText,
    ObjectType = objectType,
    ObjectId = objectId,
    Action = action,
    PolicyMessage = policyMessage,
    Tenant,
    ipAddress,
    CloudApp = "SAP Cloud Integration"

Stage 9: extend

| extend AccountName = split(UserName, "@")[0], UPNSuffix = split(UserName, "@")[1]

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Categoryeq
  • audit.security-events
field:"Category" kind:eq value:"audit.security-events"
actionin
  • Change
  • Create
  • Delete
field:"action" kind:in
objectTypein
  • Access Policy
  • Artifact Reference
field:"objectType" kind:in

Output Fields

These fields are emitted when the rule matches.

FieldSource
Actionproject
CloudAppproject
MessageTextproject
ObjectIdproject
ObjectTypeproject
PolicyMessageproject
Tenantproject
UpdatedOnproject
UserNameproject
ipAddressproject
AccountNameextend
UPNSuffixextend

Secret Exposure with Anomalous SPN Sign-In

#
Status
available
Severity
high
Time window
1455m
Group by
$left.ApplicationId, $right.ServicePrincipalId, ApplicationId, StraTenantId
Source
github.com/Azure/Azure-Sentinel

Detects applications with open hardcoded-secret findings (CWE-798, CWE-259, CWE-321, CWE-522) that have associated service principal sign-in anomalies. Correlates StratoSecure secret findings with AAD SPN sign-in failures.

MITRE ATT&CK Coverage

Telemetry Coverage

Rule Body

id: a91e677a-46b5-428e-8a7d-29a2bb33bb6f
name: Secret Exposure with Anomalous SPN Sign-In
description: |
  Detects applications with open hardcoded-secret findings (CWE-798, CWE-259,
  CWE-321, CWE-522) that have associated service principal sign-in anomalies.
  Correlates StratoSecure secret findings with AAD SPN sign-in failures.
severity: High
status: Available
requiredDataConnectors:
  - connectorId: StratoSecurePush
    dataTypes:
      - StratoSecure_Findings_CL
queryFrequency: 1h
queryPeriod: 1455m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CredentialAccess
  - DefenseEvasion
relevantTechniques:
  - T1552
  - T1078.004
query: |
  let lookback = 1d;
  let SecretFindings = StratoSecure_Findings_CL
      | where TimeGenerated >= ago(lookback)
      | where FixStatus =~ "open"
      | where CweId in ("CWE-798", "CWE-259", "CWE-321", "CWE-522")
      | where Severity in ("Critical", "High")
      | summarize FindingCount = count(), RuleIds = make_set(RuleId), Repos = make_set(Repository)
          by ApplicationId, StraTenantId;
  let AnomalousSignins = AADServicePrincipalSignInLogs
      | where TimeGenerated >= ago(lookback)
      | where ResultType != "0" or ConditionalAccessStatus =~ "failure"
      | summarize FailedAttempts = count(), DistinctIPs = dcount(IPAddress),
          FirstAttempt = min(TimeGenerated), LastAttempt = max(TimeGenerated)
          by ServicePrincipalId, ServicePrincipalName
      | where FailedAttempts > 5 or DistinctIPs > 3;
  SecretFindings
      | join kind=inner AnomalousSignins on $left.ApplicationId == $right.ServicePrincipalId
      | project TimeGenerated = LastAttempt, ApplicationId, ServicePrincipalName, StraTenantId,
          FindingCount, RuleIds, Repos, FailedAttempts, DistinctIPs
entityMappings:
  - entityType: CloudApplication
    fieldMappings:
      - identifier: AppId
        columnName: ApplicationId
version: 1.0.0
kind: Scheduled

Stages and Predicates

Parameters

let lookback = 1d;

let SecretFindings is inlined into the numbered stages below.

Let Binding: AnomalousSignins used in Stage 7

let AnomalousSignins = AADServicePrincipalSignInLogs
    | where TimeGenerated >= ago(lookback)
    | where ResultType != "0" or ConditionalAccessStatus =~ "failure"
    | summarize FailedAttempts = count(), DistinctIPs = dcount(IPAddress),
        FirstAttempt = min(TimeGenerated), LastAttempt = max(TimeGenerated)
        by ServicePrincipalId, ServicePrincipalName
    | where FailedAttempts > 5 or DistinctIPs > 3;

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

Stage 1: source

StratoSecure_Findings_CL

Stage 2: where

| where TimeGenerated >= ago(lookback)

Stage 3: where

| where FixStatus =~ "open"

Stage 4: where

| where CweId in ("CWE-798", "CWE-259", "CWE-321", "CWE-522")

Stage 5: where

| where Severity in ("Critical", "High")

Stage 6: summarize

| summarize FindingCount = count(), RuleIds = make_set(RuleId), Repos = make_set(Repository)
        by ApplicationId, StraTenantId

Stage 7: join

SecretFindings
| join kind=inner AnomalousSignins on $left.ApplicationId == $right.ServicePrincipalId

Stage 8: project

project ApplicationId, DistinctIPs, FailedAttempts, FindingCount, Repos, RuleIds, ServicePrincipalName, StraTenantId, TimeGenerated

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
ApplicationIdproject
DistinctIPsproject
FailedAttemptsproject
FindingCountproject
Reposproject
RuleIdsproject
ServicePrincipalNameproject
StraTenantIdproject
TimeGeneratedproject

Tailscale Premium: DERP relay traffic surge

#
Status
available
Severity
low
Time window
15m
Group by
SrcNodeName, SrcOs, SrcTags, SrcUser
Source
github.com/Azure/Azure-Sentinel

Identifies when a source node has more than 75 percent of its recent flows falling back to a DERP relay (Tailscale IsRelayed flag, traffic via 127.3.3.40). Operational signal useful for spotting policy drift.

MITRE ATT&CK Coverage

TacticTechniques
Command & Control

Rule Body

id: 0a1c8d12-e7d3-4890-8b89-8d6dbc1be2f0
name: "Tailscale Premium: DERP relay traffic surge"
description: Identifies when a source node has more than 75 percent of its recent flows falling back to a DERP relay (Tailscale IsRelayed flag, traffic via 127.3.3.40). Operational signal useful for spotting policy drift.
description-detailed: |
  Identifies when a source node has more than 75 percent of its recent flows falling back to a DERP relay (Tailscale's IsRelayed flag, traffic via 127.3.3.40). Sustained high relay rate indicates direct WireGuard peer-to-peer is failing - causes include NAT/firewall changes, a network blocking UDP 41641, or potential evasion attempts. Operational signal but useful for spotting policy drift. Requires Tailscale Premium or Enterprise.
severity: Low
status: Available
requiredDataConnectors:
  - connectorId: TailscalePremiumCCF
    dataTypes:
      - Tailscale_Network_CL
queryFrequency: 15m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
relevantTechniques:
  - T1572
query: |
  let minFlows = 20;
  let relayedPctThreshold = 75.0;
  Tailscale_Network_CL
  | where TimeGenerated > ago(15m)
  | summarize
      TotalFlows = count(),
      RelayedFlows = countif(IsRelayed)
      by SrcNodeName, SrcUser, SrcOs, SrcTags=tostring(SrcTags)
  | where TotalFlows >= minFlows
  | extend RelayedPct = round(100.0 * RelayedFlows / TotalFlows, 1)
  | where RelayedPct > relayedPctThreshold
  | project SrcNodeName, SrcUser, SrcOs, SrcTags, TotalFlows, RelayedFlows, RelayedPct
  | order by RelayedPct desc
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: SrcNodeName
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: SrcUser
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: PT6H
    matchingMethod: AllEntities
kind: Scheduled
version: 1.0.0

Stages and Predicates

Parameters

let minFlows = 20;
let relayedPctThreshold = 75.0;

Stage 1: source

Tailscale_Network_CL

Stage 2: where

| where TimeGenerated > ago(15m)

Stage 3: summarize

| summarize
    TotalFlows = count(),
    RelayedFlows = countif(IsRelayed)
    by SrcNodeName, SrcUser, SrcOs, SrcTags=tostring(SrcTags)

Stage 4: where

| where TotalFlows >= minFlows

Stage 5: extend

| extend RelayedPct = round(100.0 * RelayedFlows / TotalFlows, 1)

Stage 6: where

| where RelayedPct > relayedPctThreshold

Stage 7: project

| project SrcNodeName, SrcUser, SrcOs, SrcTags, TotalFlows, RelayedFlows, RelayedPct

Stage 8: sort

| order by RelayedPct desc

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
RelayedPctgt
  • 75.0 transforms: number
field:"RelayedPct" kind:gt value:"75.0"
TotalFlowsge
  • 20 transforms: number
field:"TotalFlows" kind:ge value:"20"

Output Fields

These fields are emitted when the rule matches.

FieldSource
RelayedFlowsproject
RelayedPctproject
SrcNodeNameproject
SrcOsproject
SrcTagsproject
SrcUserproject
TotalFlowsproject

TI Map URL Entity to PaloAlto Data

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

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

MITRE ATT&CK Coverage

TacticTechniques
Command & Control

Rule Body

id: 106813db-679e-4382-a51b-1bfc463befc3
name: TI Map URL Entity to PaloAlto Data
description: |
  'This query identifies any URL indicators of compromise (IOCs) from threat intelligence (TI) by searching for matches in PaloAlto Data.'
severity: Medium
requiredDataConnectors:
  - connectorId: PaloAltoNetworks
    dataTypes:
      - CommonSecurityLog
  - connectorId: ThreatIntelligence
    dataTypes:
      - ThreatIntelligenceIndicator
  - connectorId: ThreatIntelligenceTaxii
    dataTypes:
      - ThreatIntelligenceIndicator
  - connectorId: MicrosoftDefenderThreatIntelligence
    dataTypes:
      - ThreatIntelligenceIndicator
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
relevantTechniques:
  - T1071
query: |
  let dt_lookBack = 1h;
  let ioc_lookBack = 14d;
  ThreatIntelligenceIndicator
  // Picking up only IOC's that contain the entities we want
  | where isnotempty(Url)
  | where TimeGenerated >= ago(ioc_lookBack)
  | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
  | where Active == true and ExpirationDateTime > now()
  // using innerunique to keep perf fast and result set low, we only need one match to indicate potential malicious activity that needs to be investigated
  | join kind=innerunique (
    CommonSecurityLog
    | extend IngestionTime = ingestion_time()
    | where IngestionTime > ago(dt_lookBack)
    // Select on Palo Alto logs
    | where DeviceVendor =~ "Palo Alto Networks"
    | where DeviceEventClassID =~ 'url'
    //Uncomment the line below to only alert on allowed connections
    //| where DeviceAction !~ "block-url"
    //Select logs where URL data is populated
    | extend PA_Url = column_ifexists("RequestURL", "None")
    | extend PA_Url = iif(isempty(PA_Url), extract("([^\"]+)", 1, tolower(AdditionalExtensions)), trim('"', PA_Url))
    | extend PA_Url = iif(PA_Url !startswith "http://" and ApplicationProtocol !~ "ssl", strcat('http://', PA_Url), iif(PA_Url !startswith "https://" and ApplicationProtocol =~ "ssl", strcat('https://', PA_Url), PA_Url))
    | where isnotempty(PA_Url)
    | extend CommonSecurityLog_TimeGenerated = TimeGenerated
  ) on $left.Url == $right.PA_Url
  | where CommonSecurityLog_TimeGenerated < ExpirationDateTime
  | summarize CommonSecurityLog_TimeGenerated = arg_max(CommonSecurityLog_TimeGenerated, *) by IndicatorId, PA_Url
  | project timestamp = CommonSecurityLog_TimeGenerated, Description, ActivityGroupNames, IndicatorId, ThreatType, ExpirationDateTime, ConfidenceScore, DeviceAction, SourceIP, PA_Url, DeviceName
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: DeviceName
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIP
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: PA_Url
version: 1.2.5
kind: Scheduled

Stages and Predicates

Parameters

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

Stage 1: source

ThreatIntelligenceIndicator

Stage 2: where

| where isnotempty(Url)

Stage 3: where

| where TimeGenerated >= ago(ioc_lookBack)

Stage 4: summarize

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

Stage 5: where

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

Stage 6: join

| join kind=innerunique (
  CommonSecurityLog
  | extend IngestionTime = ingestion_time()
  | where IngestionTime > ago(dt_lookBack)
  | where DeviceVendor =~ "Palo Alto Networks"
  | where DeviceEventClassID =~ 'url'
  | extend PA_Url = column_ifexists("RequestURL", "None")
  | extend PA_Url = iif(isempty(PA_Url), extract("([^\"]+)", 1, tolower(AdditionalExtensions)), trim('"', PA_Url))
  | extend PA_Url = iif(PA_Url !startswith "http://" and ApplicationProtocol !~ "ssl", strcat('http://', PA_Url), iif(PA_Url !startswith "https://" and ApplicationProtocol =~ "ssl", strcat('https://', PA_Url), PA_Url))
  | where isnotempty(PA_Url)
  | extend CommonSecurityLog_TimeGenerated = TimeGenerated
) on $left.Url == $right.PA_Url

Stage 7: where

where CommonSecurityLog_TimeGenerated < ExpirationDateTime

Stage 8: summarize

summarize CommonSecurityLog_TimeGenerated by IndicatorId, PA_Url

Stage 9: project

project ActivityGroupNames, ConfidenceScore, Description, DeviceAction, DeviceName, ExpirationDateTime, IndicatorId, PA_Url, SourceIP, ThreatType, timestamp

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
ActivityGroupNamesproject
ConfidenceScoreproject
Descriptionproject
DeviceActionproject
DeviceNameproject
ExpirationDateTimeproject
IndicatorIdproject
PA_Urlproject
SourceIPproject
ThreatTypeproject
timestampproject

Vectra AI Detect - Suspected Compromised Host

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

'Create an incident when a Host is suspected to be compromised. The higher the severity level is, the more immediate attention it requires as Vectra AI engine is more confident that this is a real threat. Level of severity are: Low, Medium, High, Critical). Recommended configuration is to trigger an alert for at least High and Critical.'

MITRE ATT&CK Coverage

Rule Body

id: 60eb6cf0-3fa1-44c1-b1fe-220fbee23d63
name: Vectra AI Detect - Suspected Compromised Host
description: |
  'Create an incident when a Host is suspected to be compromised. 
  The higher the severity level is, the more immediate attention it requires as Vectra AI engine is more confident that this is a real threat. 
  Level of severity are: Low, Medium, High, Critical). Recommended configuration is to trigger an alert for at least High and Critical.'
severity: Informational
status: Available
requiredDataConnectors:
  - connectorId: CefAma
    dataTypes:
      - CommonSecurityLog
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
  aggregationKind: AlertPerResult
tactics:
  - CredentialAccess
  - Discovery
  - LateralMovement
  - Collection
  - CommandAndControl
  - Exfiltration
  - Impact
relevantTechniques:
  - T1003
  - T1087
  - T1021
  - T1119
  - T1071
  - T1041
  - T1499
query: |
  // Edit this variable to only keep the Severity level where an incident needs to be created (Defaults are: "High", "Critical" ). Possible values are: "Low", "Medium", "High", "Critical"
  let configured_level = dynamic(["High", "Critical"]);
  CommonSecurityLog
  | where DeviceVendor == "Vectra Networks"
  | where DeviceProduct == "X Series"
  | where DeviceEventClassID == "hsc"
  | project-rename threat_score = FlexNumber1
  | project-rename certainty_score = FlexNumber2
  | project-rename vectra_URL = DeviceCustomString4
  | project-rename detection_name = DeviceEventClassID
  | project-rename score_decreases = DeviceCustomString3
  | extend level = case( threat_score <  50 and certainty_score < 50, "Low",
                        threat_score < 50 and certainty_score >= 50 , "Medium", 
                        threat_score >= 50 and certainty_score <= 50, "High", 
                        threat_score >= 50 and certainty_score >= 50, "Critical",
                        "UNKNOWN")
  | extend Severity = case(level == "Info", "Informational",level == "Critical", "High", level)
  | where level in (configured_level) 
  //keep only the event with the highest threat score per Host
  | summarize arg_max(TimeGenerated, *) by SourceHostName
  | sort by TimeGenerated
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: SourceHostName
alertDetailsOverride:
  alertDisplayNameFormat: Vectra AI Detect - Host {{SourceHostName}} reaches {{level}} severity
  alertDescriptionFormat: |
    The host {{SourceHostName}} has a Threat score of {{threat_score}} and a
    certainty of {{certainty_score}}
  alertSeverityColumnName: Severity
  alertDynamicProperties:
    - alertProperty: AlertLink
      value: vectra_URL
    - alertProperty: ProductName
      value: DeviceProduct
    - alertProperty: ProviderName
      value: DeviceVendor
    - alertProperty: ConfidenceScore
      value: certainty_score
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: true
    lookbackDuration: 7d
    matchingMethod: AllEntities
customDetails:
  ScoreDecrease: score_decreases
version: 1.0.9
kind: Scheduled

Stages and Predicates

Parameters

let configured_level = dynamic(["High", "Critical"]);

Stage 1: source

CommonSecurityLog

Stage 2: where

| where DeviceVendor == "Vectra Networks"

Stage 3: where

| where DeviceProduct == "X Series"

Stage 4: where

| where DeviceEventClassID == "hsc"

Stage 5: project-rename

| project-rename threat_score = FlexNumber1

Stage 6: project-rename

| project-rename certainty_score = FlexNumber2

Stage 7: project-rename

| project-rename vectra_URL = DeviceCustomString4

Stage 8: project-rename

| project-rename detection_name = DeviceEventClassID

Stage 9: project-rename

| project-rename score_decreases = DeviceCustomString3

Stage 10: extend

| extend level = case( threat_score <  50 and certainty_score < 50, "Low",
                      threat_score < 50 and certainty_score >= 50 , "Medium", 
                      threat_score >= 50 and certainty_score <= 50, "High", 
                      threat_score >= 50 and certainty_score >= 50, "Critical",
                      "UNKNOWN")
level =
ifthreat_score < 50 and certainty_score < 50"Low"
elifthreat_score < 50 and certainty_score >= 50"Medium"
elifthreat_score >= 50 and certainty_score <= 50"High"
elifthreat_score >= 50 and certainty_score >= 50"Critical"
else"UNKNOWN"

Stage 11: extend

| extend Severity = case(level == "Info", "Informational",level == "Critical", "High", level)
Severity =
iflevel == "Info""Informational"
eliflevel == "Critical""High"
elselevel

Stage 12: where

| where level in (configured_level)

Stage 13: summarize

| summarize arg_max(TimeGenerated, *) by SourceHostName

Stage 14: sort

| sort by TimeGenerated

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
SourceHostNamesummarize

Veeam ONE VM with No Replica (Hyper-V)

#

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

Detects Hyper-V VMs with no replica configured.

Rule Body

id: 6117e317-4b90-4bcc-a71f-0c1f29d82ce3
name: Veeam ONE VM with No Replica (Hyper-V)
description: Detects Hyper-V VMs with no replica configured.
severity: High
status: Available
requiredDataConnectors:
- connectorId: VeeamCustomTablesDataConnector
  dataTypes:
  - VeeamOneTriggeredAlarms_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
  aggregationKind: AlertPerResult
tactics: []
relevantTechniques: []
query: VeeamOneTriggeredAlarms_CL | where PredefinedAlarmId == 332
version: 1.0.1
kind: Scheduled
customDetails:
  PredefinedAlarmId: PredefinedAlarmId
  TriggeredAlarmId: TriggeredAlarmId
  Description: Description
  Name: Name
  VoneHostName: VoneHostName
  TriggeredTime: TriggeredTime
  Status: Status
  Comment: Comment
  ObjectId: ObjectId
  ObjectName: ObjectName
  ObjectType: ObjectType

Stages and Predicates

Stage 1: source

VeeamOneTriggeredAlarms_CL

Stage 2: where

| where PredefinedAlarmId == 332

Indicators

These rows show field, operator, and value matches.

Web sites blocked by Eset

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

'Create alert on web sites blocked by Eset.'

MITRE ATT&CK Coverage

Rule Body

id: 84ad2f8a-b64c-49bc-b669-bdb4fd3071e9
name: Web sites blocked by Eset
description: |
  'Create alert on web sites blocked by Eset.'
severity: Low
status: Available
requiredDataConnectors:
  - connectorId: EsetSMC
    dataTypes:
      - eset_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Exfiltration
  - CommandAndControl
  - InitialAccess
relevantTechniques:
  - T1189
  - T1567
  - T1071.001
query: |
  eset_CL
  | where event_type_s == 'FilteredWebsites_Event'
  | extend AccountCustomEntity = username_s, URLCustomEntity = object_uri_s, HostCustomEntity = hostname_s, IPCustomEntity = ipv4_s
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: AccountCustomEntity
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: HostCustomEntity
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPCustomEntity
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: URLCustomEntity
version: 1.0.1
kind: Scheduled

Stages and Predicates

Stage 1: source

eset_CL

Stage 2: where

| where event_type_s == 'FilteredWebsites_Event'

Stage 3: extend

| extend AccountCustomEntity = username_s, URLCustomEntity = object_uri_s, HostCustomEntity = hostname_s, IPCustomEntity = ipv4_s

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountCustomEntityextend
HostCustomEntityextend
IPCustomEntityextend
URLCustomEntityextend

Back to all Kusto rules