Detection rules › Kusto

Kusto rules: 26

ApexOne - Multiple deny or terminate actions on single IP

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

'Detects multiple deny or terminate actions on single IP.'

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Rule Body

id: cd94e078-67b7-11ec-90d6-0242ac120003
name: ApexOne - Multiple deny or terminate actions on single IP
description: |
  'Detects multiple deny or terminate actions on single IP.'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: CefAma
    dataTypes:
      - CommonSecurityLog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1190
query: |
  let threshold = 5;
  TMApexOneEvent
  | where EventMessage has "Behavior Monitoring"
  | where DvcAction == 2 or DvcAction == 3
  | summarize count() by SrcIpAddr, bin(TimeGenerated, 10m)
  | where count_ > threshold
  | extend IPCustomEntity = SrcIpAddr
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPCustomEntity
version: 1.0.3
kind: Scheduled

Stages and Predicates

Parameters

let threshold = 5;

Stage 1: source

TMApexOneEvent

Stage 2: where

| where EventMessage has "Behavior Monitoring"

Stage 3: where

| where DvcAction == 2 or DvcAction == 3

Stage 4: summarize

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

Stage 5: where

| where count_ > threshold

Stage 6: 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

Application ID URI Changed

#
Severity
medium
Time window
1d
Author
Microsoft Security Research
Source
github.com/Azure/Azure-Sentinel

Detects changes to an Application ID URI. Monitor these changes to make sure that they were authorized. Ref: https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-applications#appid-uri-added-modified-or-removed

MITRE ATT&CK Coverage

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: 9fb2ee72-959f-4c2b-bc38-483affc539e4
name: Application ID URI Changed
description: |
  'Detects changes to an Application ID URI.
    Monitor these changes to make sure that they were authorized.
    Ref: https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-applications#appid-uri-added-modified-or-removed'
severity: Medium
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AuditLogs
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Persistence
  - PrivilegeEscalation
relevantTechniques:
  - T1078.004
tags:
  - AADSecOpsGuide
query: |
  AuditLogs
    | where Category == "ApplicationManagement"
    | where OperationName has_any ("Update Application", "Update Service principal")
    | where TargetResources has "AppIdentifierUri"
    | 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 mod_props = TargetResources[0].modifiedProperties
    | extend TargetAppName = tostring(TargetResources[0].displayName)
    | mv-expand mod_props
    | where mod_props.displayName has "AppIdentifierUri"
    | extend OldURI = tostring(mod_props.oldValue)
    | extend NewURI = tostring(mod_props.newValue)
    | extend UpdatedBy = iif(isnotempty(InitiatingAppName), InitiatingAppName, InitiatingUserPrincipalName)
    | extend InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1])
    | project-reorder TimeGenerated, InitiatingAppName, InitiatingAppServicePrincipalId, InitiatingAadUserId, InitiatingUserPrincipalName, InitiatingIPAddress

entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: InitiatingUserPrincipalName
      - identifier: Name
        columnName: InitiatingAccountName
      - identifier: UPNSuffix
        columnName: InitiatingAccountUPNSuffix
  - entityType: Account
    fieldMappings:
      - identifier: AadUserId
        columnName: InitiatingAadUserId
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: OldURI
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: NewURI
  - 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

Stage 1: source

AuditLogs

Stage 2: where

| where Category == "ApplicationManagement"

Stage 3: where

| where OperationName has_any ("Update Application", "Update Service principal")

Stage 4: where

| where TargetResources has "AppIdentifierUri"

Stage 5: extend (7 consecutive steps)

| 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 mod_props = TargetResources[0].modifiedProperties
| extend TargetAppName = tostring(TargetResources[0].displayName)

Stage 6: mv-expand

| mv-expand mod_props

Stage 7: where

| where mod_props.displayName has "AppIdentifierUri"

Stage 8: extend (4 consecutive steps)

| extend OldURI = tostring(mod_props.oldValue)
| extend NewURI = tostring(mod_props.newValue)
| extend UpdatedBy = iif(isnotempty(InitiatingAppName), InitiatingAppName, InitiatingUserPrincipalName)
| extend InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1])

Stage 9: project-reorder

| project-reorder TimeGenerated, InitiatingAppName, InitiatingAppServicePrincipalId, InitiatingAadUserId, InitiatingUserPrincipalName, InitiatingIPAddress

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Categoryeq
  • ApplicationManagement
field:"Category" kind:eq value:"ApplicationManagement"
OperationNamematch
  • Update Application transforms: term corpus 3 (kusto 3)
  • Update Service principal transforms: term corpus 2 (kusto 2)
field:"OperationName" kind:match
TargetResourcesmatch
  • AppIdentifierUri transforms: term
field:"azure_ad::target_resources" kind:match value:"AppIdentifierUri"
mod_props.displayNamematch
  • AppIdentifierUri transforms: term, kusto_dynamic_path
field:"mod_props.displayName" kind:match value:"AppIdentifierUri"

Output Fields

These fields are emitted when the rule matches.

FieldSource
InitiatingAppNameextend
InitiatingAppServicePrincipalIdextend
InitiatingUserPrincipalNameextend
InitiatingAadUserIdextend
InitiatingIPAddressextend
mod_propsextend
TargetAppNameextend
OldURIextend
NewURIextend
UpdatedByextend
InitiatingAccountNameextend
InitiatingAccountUPNSuffixextend

AV detections related to Dev-0530 actors

#
Severity
high
Time window
1d
Group by
$left.CompromisedEntity, $right.DeviceName, AlertSeverity, CompromisedEntity, Description, DeviceId, DisplayName, Entities, ProductName, PublicIP, TenantId, ThreatFamilyName, ThreatName
Author
Microsoft Security Research
Source
github.com/Azure/Azure-Sentinel

This query looks for Microsoft Defender AV detections related to Dev-0530 actors. In Microsoft Sentinel the SecurityAlerts table includes only the Device Name of the affected device, this query joins the DeviceInfo table to clearly connect other information such as Device group, ip, logged on users etc. This would allow the Microsoft Sentinel analyst to have more context related to the alert, if available.

MITRE ATT&CK Coverage

TacticTechniques
Impact

Telemetry Coverage

Rule Body

id: 5f171045-88ab-4634-baae-a7b6509f483b
name: AV detections related to Dev-0530 actors
description: |
  'This query looks for Microsoft Defender AV detections related to  Dev-0530 actors.
  In Microsoft Sentinel the SecurityAlerts table includes only the Device Name of the affected device, this query joins the DeviceInfo table to clearly connect other information such as Device group, ip, logged on users etc. This would allow the Microsoft Sentinel analyst to have more context related to the alert, if available.'
severity: High
requiredDataConnectors:
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - SecurityAlert
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Impact
relevantTechniques:
  - T1486
tags:
  -  Dev-0530 actors
query: |
  let Dev0530_threats = dynamic(["Trojan:Win32/SiennaPurple.A", "Ransom:Win32/SiennaBlue.A", "Ransom:Win32/SiennaBlue.B"]);
  SecurityAlert
  | where ProviderName == "MDATP"
  | extend ThreatName = tostring(parse_json(ExtendedProperties).ThreatName)
  | extend ThreatFamilyName = tostring(parse_json(ExtendedProperties).ThreatFamilyName)
  | where ThreatName in~ (Dev0530_threats) or ThreatFamilyName in~ (Dev0530_threats)
  | extend CompromisedEntity = tolower(CompromisedEntity)
  | join kind=inner (DeviceInfo
      | extend DeviceName = tolower(DeviceName)
  ) on $left.CompromisedEntity == $right.DeviceName
  | summarize by bin(TimeGenerated, 1d), DisplayName, ThreatName, ThreatFamilyName, PublicIP, AlertSeverity, Description, tostring(LoggedOnUsers), DeviceId, TenantId, CompromisedEntity, tostring(LoggedOnUsers), ProductName, Entities
  | extend HostName = tostring(split(CompromisedEntity, ".")[0]), DomainIndex = toint(indexof(CompromisedEntity, '.'))
  | extend HostNameDomain = iff(DomainIndex != -1, substring(CompromisedEntity, DomainIndex + 1), CompromisedEntity)
  | project-away DomainIndex
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: CompromisedEntity
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: HostNameDomain
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: PublicIP
version: 1.0.5
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: Microsoft Security Research
    support:
        tier: Community
    categories:
        domains: [ "Security - Others" ]

Stages and Predicates

Parameters

let Dev0530_threats = dynamic(["Trojan:Win32/SiennaPurple.A", "Ransom:Win32/SiennaBlue.A", "Ransom:Win32/SiennaBlue.B"]);

Stage 1: source

SecurityAlert

Stage 2: where

| where ProviderName == "MDATP"

Stage 3: extend

| extend ThreatName = tostring(parse_json(ExtendedProperties).ThreatName)

Stage 4: extend

| extend ThreatFamilyName = tostring(parse_json(ExtendedProperties).ThreatFamilyName)

Stage 5: where

| where ThreatName in~ (Dev0530_threats) or ThreatFamilyName in~ (Dev0530_threats)

Stage 6: extend

| extend CompromisedEntity = tolower(CompromisedEntity)

Stage 7: join

| join kind=inner (DeviceInfo
    | extend DeviceName = tolower(DeviceName)
) on $left.CompromisedEntity == $right.DeviceName

Stage 8: summarize

summarize by DisplayName, ThreatName, ThreatFamilyName, PublicIP, AlertSeverity, Description, DeviceId, TenantId, CompromisedEntity, ProductName, Entities

Stage 9: extend

extend DomainIndex, HostName

Stage 10: extend

extend HostNameDomain
HostNameDomain =
ifDomainIndex != -1substring(CompromisedEntity, (DomainIndex + 1))
elseCompromisedEntity

Stage 11: project-away

project-away DomainIndex

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
ProviderNameeq
  • MDATP corpus 3 (kusto 3)
field:"ProviderName" kind:eq value:"MDATP"
ThreatFamilyNamein
  • Ransom:Win32/SiennaBlue.A
  • Ransom:Win32/SiennaBlue.B
  • Trojan:Win32/SiennaPurple.A
field:"ThreatFamilyName" kind:in
ThreatNamein
  • Ransom:Win32/SiennaBlue.A
  • Ransom:Win32/SiennaBlue.B
  • Trojan:Win32/SiennaPurple.A
field:"ThreatName" kind:in

Output Fields

These fields are emitted when the rule matches.

FieldSource
AlertSeveritysummarize
CompromisedEntitysummarize
Descriptionsummarize
DeviceIdsummarize
DisplayNamesummarize
Entitiessummarize
ProductNamesummarize
PublicIPsummarize
TenantIdsummarize
ThreatFamilyNamesummarize
ThreatNamesummarize
HostNameextend
HostNameDomainextend

AWSCloudTrail - S3 Object Exfiltration from Anonymous User

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

Detects S3 GetObject activity performed by anonymous principals, which can indicate unauthorized object access and potential data exfiltration from public or misconfigured buckets.

MITRE ATT&CK Coverage

TacticTechniques
Collection

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: 15d3bf4e-8708-41c8-a836-8b0aa5be730e
name: AWSCloudTrail - S3 Object Exfiltration from Anonymous User
description: |
  Detects S3 GetObject activity performed by anonymous principals, which can indicate unauthorized object access
  and potential data exfiltration from public or misconfigured buckets.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: AWS
    dataTypes:
      - AWSCloudTrail
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Collection
relevantTechniques:
  - T1530
query: |
  AWSCloudTrail
  | where ((UserIdentityAccountId =~ @'ANONYMOUS_PRINCIPAL' or UserIdentityPrincipalid == "Anonymous") and EventName == 'GetObject')
  | extend
     BucketName = parse_json(RequestParameters).bucketName,
     ObjectKey = parse_json(RequestParameters).key
  | extend UserName = substring(UserIdentityPrincipalid, indexof_regex(UserIdentityPrincipalid, ":") + 1)
  | extend Name = split(UserName,'@')[0],UpnSuffix = split(UserName,'@')[1]
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: Name
      - identifier: UPNSuffix
        columnName: UpnSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIpAddress
customDetails:
  BucketName: BucketName
  ObjectKey: ObjectKey
alertDetailsOverride:
  alertDisplayNameFormat: 'AWS S3 object access by anonymous principal in bucket {{BucketName}}'
  alertDescriptionFormat: 'Detected anonymous GetObject activity for bucket {{BucketName}} and object {{ObjectKey}} from {{SourceIpAddress}}.'
version: 1.0.1
kind: Scheduled

Stages and Predicates

Stage 1: source

AWSCloudTrail

Stage 2: where

| where ((UserIdentityAccountId =~ @'ANONYMOUS_PRINCIPAL' or UserIdentityPrincipalid == "Anonymous") and EventName == 'GetObject')

Stage 3: extend (3 consecutive steps)

| extend
   BucketName = parse_json(RequestParameters).bucketName,
   ObjectKey = parse_json(RequestParameters).key
| extend UserName = substring(UserIdentityPrincipalid, indexof_regex(UserIdentityPrincipalid, ":") + 1)
| extend Name = split(UserName,'@')[0],UpnSuffix = split(UserName,'@')[1]

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
BucketNameextend
ObjectKeyextend
UserNameextend
Nameextend
UpnSuffixextend

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

Rule Body

id: 12ecc3cf-a1db-4fa4-bcb2-605dd3a274e0
name: BloodHound Attack Path Finding - Large Default Groups With Limited Ownership
  Privileges
description: BloodHound Attack Path Finding - Large Default Groups With Limited Ownership
  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 == "LargeDefaultGroupsOwnsLimitedRights"
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 == "LargeDefaultGroupsOwnsLimitedRights"

Indicators

These rows show field, operator, and value matches.

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

Brute force attack against user credentials

#
Status
available
Severity
medium
Time window
1h
Group by
User, UserId, UserType
Source
github.com/Azure/Azure-Sentinel

'Identifies evidence of brute force activity against a user based on multiple authentication failures and at least one successful authentication within a given time window. This query limits IPAddresses to 100 and may not potentially cover all IPAddresses. The default failure threshold is 10, success threshold is 1, and the default time window is 20 minutes.'

MITRE ATT&CK Coverage

TacticTechniques
Credential Access

Rule Body

id: 5a6ce089-e756-40fb-b022-c8e8864a973a
name: Brute force attack against user credentials
description: |
  'Identifies evidence of brute force activity against a user based on multiple authentication failures and at least one successful authentication within a given time window. This query limits IPAddresses to 100 and may not potentially cover all IPAddresses.
  The default failure threshold is 10, success threshold is 1, and the default time window is 20 minutes.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: SalesforceServiceCloudCCPDefinition
    dataTypes:
      - SalesforceServiceCloud
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CredentialAccess
relevantTechniques:
  - T1110
query: |
  let failureCountThreshold = 10;
  let successCountThreshold = 1;
  let Failures =
  SalesforceServiceCloud
  | where EventType == "Login" and LoginStatus != "LOGIN_NO_ERROR"
  | summarize
        FailureStartTime = min(TimeGenerated),
        FailureEndTime = max(TimeGenerated),
        IpAddresses = make_set (ClientIp, 100),
        FailureCount = count() by User, UserId, UserType;
    SalesforceServiceCloud
    | where EventType == "Login" and LoginStatus == "LOGIN_NO_ERROR"
    | summarize
            SuccessStartTime = min(TimeGenerated),
            SuccessEndTime = max(TimeGenerated),
            IpAddresses = make_set (ClientIp, 100),
            SuccessCount = count() by User, UserId, UserType
    | join kind=leftouter Failures on UserId
    | where FailureCount >= failureCountThreshold and SuccessCount >= successCountThreshold
    | where FailureEndTime < SuccessStartTime
    | project User, EventStartTime = FailureStartTime, EventEndTime = SuccessEndTime, IpAddresses
customDetails:
  EventStartTime: FailureStartTime
  EventEndTime: SuccessEndTime
  IPAddresses: IpAddresses
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: User
version: 1.0.4
kind: Scheduled

Stages and Predicates

Parameters

let failureCountThreshold = 10;
let successCountThreshold = 1;

Let Binding: Failures used in Stage 4

let Failures = SalesforceServiceCloud
| where EventType == "Login" and LoginStatus != "LOGIN_NO_ERROR"
| summarize
      FailureStartTime = min(TimeGenerated),
      FailureEndTime = max(TimeGenerated),
      IpAddresses = make_set (ClientIp, 100),
      FailureCount = count() by User, UserId, UserType;

Stage 1: source

SalesforceServiceCloud

Stage 2: where

| where EventType == "Login" and LoginStatus == "LOGIN_NO_ERROR"

Stage 3: summarize

| summarize
          SuccessStartTime = min(TimeGenerated),
          SuccessEndTime = max(TimeGenerated),
          IpAddresses = make_set (ClientIp, 100),
          SuccessCount = count() by User, UserId, UserType

Stage 4: join

| join kind=leftouter Failures on UserId

Stage 5: where

where FailureCount >= 10 and SuccessCount >= 1

Stage 6: where

where FailureEndTime < SuccessStartTime

Stage 7: project

project EventEndTime, EventStartTime, IpAddresses, User

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
EventEndTimeproject
EventStartTimeproject
IpAddressesproject
Userproject

Cisco WSA - Unexpected uploads

#
Status
available
Severity
high
Time window
10m
Group by
AmpFileName, SrcUserName
Source
github.com/Azure/Azure-Sentinel

'Detects unexpected file uploads.'

MITRE ATT&CK Coverage

TacticTechniques
Exfiltration

Rule Body

id: 32c460ad-2d40-43e9-8ead-5cdd1d7a3163
name: Cisco WSA - Unexpected uploads
description: |
  'Detects unexpected file uploads.'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: SyslogAma
    datatypes:
      - Syslog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Exfiltration
relevantTechniques:
  - T1567
query: |
  let f_cnt_upload_threshold = 5;
  let f_upload_size_threshold = 5000000;
  CiscoWSAEvent
  | where HttpRequestMethod in~ ('POST', 'PUT')
  | where isnotempty(AmpFileName)
  | where UrlCategory in~ ('IW_fts', 'IW_osb')
  | where DstBytes > f_upload_size_threshold
  | summarize count() by AmpFileName, SrcUserName, bin(TimeGenerated, 10m)
  | where count_ >= f_cnt_upload_threshold
  | extend AccountCustomEntity = SrcUserName
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountCustomEntity
version: 1.0.3
kind: Scheduled

Stages and Predicates

Parameters

let f_cnt_upload_threshold = 5;
let f_upload_size_threshold = 5000000;

Stage 1: source

CiscoWSAEvent

Stage 2: where

| where HttpRequestMethod in~ ('POST', 'PUT')

Stage 3: where

| where isnotempty(AmpFileName)

Stage 4: where

| where UrlCategory in~ ('IW_fts', 'IW_osb')

Stage 5: where

| where DstBytes > f_upload_size_threshold

Stage 6: summarize

| summarize count() by AmpFileName, SrcUserName, bin(TimeGenerated, 10m)

Stage 7: where

| where count_ >= f_cnt_upload_threshold

Stage 8: extend

| extend AccountCustomEntity = SrcUserName

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AmpFileNamesummarize
SrcUserNamesummarize
AccountCustomEntityextend

CYFIRMA - High severity Malicious Phishing Network Indicators - Monitor Recommended Rule

#

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

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

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

MITRE ATT&CK Coverage

Rule Body

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

Stage 3: extend (6 consecutive steps)

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

Stage 4: mv-expand

| mv-expand extensionKeys

Stage 5: extend (4 consecutive steps)

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

Stage 6: project

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

Indicators

These rows show field, operator, and value matches.

Exclusions

The rule actively suppresses these predicates.

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

Output Fields

These fields are emitted when the rule matches.

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

CYFIRMA - Social and Public Exposure - Exposure of PII/CII in Public Domain 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.

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

"This analytics rule detects high severity alerts from CYFIRMA indicating exposure of Personally Identifiable Information (PII) or Confidential Information (CII) in public or unsecured sources. Such leaks may include email addresses, credentials, phone numbers, or other sensitive personal or organizational data. These exposures can lead to identity theft, phishing, credential compromise, or regulatory non-compliance. Investigate promptly and initiate remediation steps including user notifications and credential resets."

MITRE ATT&CK Coverage

Rule Body

id: 52d71822-41e4-4c21-b36f-400294f2b43a
name: CYFIRMA - Social and Public Exposure - Exposure of PII/CII in Public Domain Rule
description: |
  "This analytics rule detects high severity alerts from CYFIRMA indicating exposure of Personally Identifiable Information (PII) or Confidential Information (CII) in public or unsecured sources. 
  Such leaks may include email addresses, credentials, phone numbers, or other sensitive personal or organizational data. 
  These exposures can lead to identity theft, phishing, credential compromise, or regulatory non-compliance. 
  Investigate promptly and initiate remediation steps including user notifications and credential resets." 
version: 1.0.1
kind: Scheduled
severity: High
requiredDataConnectors:
  - connectorId: CyfirmaDigitalRiskAlertsConnector
    dataTypes:
      - CyfirmaSPEPIIAndCIIAlerts_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - InitialAccess
  - Exfiltration
  - Collection
  - CredentialAccess
relevantTechniques:
  - T1078
  - T1003
  - T1213
  - T1537
query: |
  // High severity - Social and Public Exposure - Exposure of PII/CII in Public Domain
  let timeFrame = 5m;
  CyfirmaSPEPIIAndCIIAlerts_CL
  | where severity == 'Critical' and TimeGenerated between (ago(timeFrame) .. now())
  | extend
      Description=description,
      FirstSeen=first_seen,
      LastSeen=last_seen,
      RiskScore=risk_score,
      AlertUID=alert_uid,
      UID=uid,
      AssetType=asset_type,
      AssetValue=signature,
      Source=source,
      Impact=impact,
      Recommendation=recommendation,
      PostedDate=posted_date,
      ProviderName='CYFIRMA',
      ProductName='DeCYFIR/DeTCT',
      AlertTitle=Alert_title
  | project
      TimeGenerated,
      Description,
      RiskScore,
      FirstSeen,
      LastSeen,
      AlertUID,
      UID,
      AssetType,
      AssetValue,
      Source,
      Impact,
      Recommendation,
      PostedDate,
      ProductName,
      ProviderName,
      AlertTitle
customDetails:
  AssetType: AssetType
  AssetValue: AssetValue
  RiskScore: RiskScore
  Source: Source
  Impact: Impact
  Description: Description
  Recommendation: Recommendation
  FirstSeen: FirstSeen
  LastSeen: LastSeen
  AlertUID: AlertUID
  UID: UID
  TimeGenerated: TimeGenerated
alertDetailsOverride:
  alertDisplayNameFormat: "CYFIRMA - High Severity: Exposure of PII/CII in Public Domain - {{AlertTitle}} "
  alertDescriptionFormat: "{{Description}} "
  alertDynamicProperties:
    - alertProperty: ProductName
      value: ProductName
    - alertProperty: ProviderName
      value: ProviderName
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

CyfirmaSPEPIIAndCIIAlerts_CL

Stage 2: where

| where severity == 'Critical' and TimeGenerated between (ago(timeFrame) .. now())

Stage 3: extend

| extend
    Description=description,
    FirstSeen=first_seen,
    LastSeen=last_seen,
    RiskScore=risk_score,
    AlertUID=alert_uid,
    UID=uid,
    AssetType=asset_type,
    AssetValue=signature,
    Source=source,
    Impact=impact,
    Recommendation=recommendation,
    PostedDate=posted_date,
    ProviderName='CYFIRMA',
    ProductName='DeCYFIR/DeTCT',
    AlertTitle=Alert_title

Stage 4: project

| project
    TimeGenerated,
    Description,
    RiskScore,
    FirstSeen,
    LastSeen,
    AlertUID,
    UID,
    AssetType,
    AssetValue,
    Source,
    Impact,
    Recommendation,
    PostedDate,
    ProductName,
    ProviderName,
    AlertTitle

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AlertTitleproject
AlertUIDproject
AssetTypeproject
AssetValueproject
Descriptionproject
FirstSeenproject
Impactproject
LastSeenproject
PostedDateproject
ProductNameproject
ProviderNameproject
Recommendationproject
RiskScoreproject
Sourceproject
TimeGeneratedproject
UIDproject

GTI - High Relevance Alert Detected

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

Triggers an incident when a GTI Relevance System Alert is assessed as RELEVANCE_LEVEL_HIGH with a HIGH confidence level. High relevance means Google Threat Intelligence has determined the threat directly or very closely matches your organisation profile - regardless of threat type. These are the most actionable alerts and warrant priority investigation. Each unique Alert ID is grouped into a single incident.

MITRE ATT&CK Coverage

Rule Body

id: f6a7b8c9-d0e1-2345-fabc-456789012345
name: GTI - High Relevance Alert Detected
description: |
  Triggers an incident when a GTI Relevance System Alert is assessed as RELEVANCE_LEVEL_HIGH
  with a HIGH confidence level. High relevance means Google Threat Intelligence has determined
  the threat directly or very closely matches your organisation profile - regardless of threat type.
  These are the most actionable alerts and warrant priority investigation.
  Each unique Alert ID is grouped into a single incident.
severity: High
status: Available
requiredDataConnectors:
  - connectorId: GoogleThreatIntelligenceRelevanceSystemAlertsAPI
    dataTypes:
      - RelevanceSystemAlerts_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
  - Exfiltration
  - Impact
  - CredentialAccess
  - PrivilegeEscalation
relevantTechniques:
  - T1566
  - T1068
  - T1078
  - T1567
  - T1486
  - T1552
query: |
  GTIRelevanceSystemAlerts
  | where RelevanceLevel == "HIGH"
      and IsRelevant == true
  | extend
      DetailSeverity = coalesce(DataLeakSeverity, InsiderThreatSeverity, InitialAccessBrokerSeverity, "")
  | project
      TimeGenerated,
      AlertId,
      AlertName           = DisplayName,
      AlertState          = State,
      DetailType,
      DetailSeverity,
      RelevanceLevel,
      RelevanceConfidence,
      RelevanceReasoning,
      CommonThemes        = RelevanceEvidenceCommonThemes,
      DistinctThemes      = RelevanceEvidenceDistinctThemes,
      SeverityLevel,
      SeverityConfidence,
      SeverityReasoning,
      PriorityLevel,
      PriorityReasoning,
      FindingCount,
      AiSummary,
      AuditCreateTime,
      AuditUpdateTime
alertDetailsOverride:
  alertDisplayNameFormat: 'GTI High Relevance Alert: {{AlertName}}'
  alertDescriptionFormat: |
    Relevance: {{RelevanceLevel}}

    Why it matches your organisation:
    {{RelevanceReasoning}}

    Summary: {{AiSummary}}
customDetails:
  AlertId: AlertId
  AlertState: AlertState
  DetailType: DetailType
  RelevanceLevel: RelevanceLevel
  RelevanceConfidence: RelevanceConfidence
  PriorityLevel: PriorityLevel
  SeverityLevel: SeverityLevel
  FindingCount: FindingCount
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: P5D
    matchingMethod: Selected
    groupByCustomDetails:
      - AlertId
eventGroupingSettings:
  aggregationKind: AlertPerResult
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

GTIRelevanceSystemAlerts

Stage 2: where

| where RelevanceLevel == "HIGH"
    and IsRelevant == true

Stage 3: extend

| extend
    DetailSeverity = coalesce(DataLeakSeverity, InsiderThreatSeverity, InitialAccessBrokerSeverity, "")

Stage 4: project

| project
    TimeGenerated,
    AlertId,
    AlertName           = DisplayName,
    AlertState          = State,
    DetailType,
    DetailSeverity,
    RelevanceLevel,
    RelevanceConfidence,
    RelevanceReasoning,
    CommonThemes        = RelevanceEvidenceCommonThemes,
    DistinctThemes      = RelevanceEvidenceDistinctThemes,
    SeverityLevel,
    SeverityConfidence,
    SeverityReasoning,
    PriorityLevel,
    PriorityReasoning,
    FindingCount,
    AiSummary,
    AuditCreateTime,
    AuditUpdateTime

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AiSummaryproject
AlertIdproject
AlertNameproject
AlertStateproject
AuditCreateTimeproject
AuditUpdateTimeproject
CommonThemesproject
DetailSeverityproject
DetailTypeproject
DistinctThemesproject
FindingCountproject
PriorityLevelproject
PriorityReasoningproject
RelevanceConfidenceproject
RelevanceLevelproject
RelevanceReasoningproject
SeverityConfidenceproject
SeverityLevelproject
SeverityReasoningproject
TimeGeneratedproject

Microsoft Entra ID UserAgent OS Missmatch

#
Status
available
Severity
medium
Time window
1d
Group by
DeviceOS, UserAgentOS, UserPrincipalName
Source
github.com/Azure/Azure-Sentinel

This query extracts the operating system from the UserAgent header and compares this to the DeviceDetail information present in Microsoft Entra ID.

MITRE ATT&CK Coverage

TacticTechniques
Stealth

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: 6a638d80-f6b2-473b-9087-3cac78a84b40
name: Microsoft Entra ID UserAgent OS Missmatch
description: |
  This query extracts the operating system from the UserAgent header and compares this to the DeviceDetail information present in Microsoft Entra ID.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - SigninLogs
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AADNonInteractiveUserSignInLogs
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - DefenseEvasion
relevantTechniques:
  - T1036
query: |
  let timeframe = 1d;
  let ExtractOSFromUA=(ua:string) {
      case(
          ua has "Windows NT 6.0", "Windows Vista/Windows Server 2008",
          ua has "Windows NT 6.1", "Windows 7/Windows Server 2008R2",
          ua has "Windows NT 6.1", "Windows 7/Windows Server 2008",
          ua has "Windows NT 6.2", "Windows 8/Windows Server 2012",
          ua has "Windows NT 6.3", "Windows 8.1/Windows Server 2012R2",
          ua has "Windows NT 10.0", "Windows 10",
          ua has "Windows NT 11.0", "Windows 11",
          ua has "Windows Phone", "WindowsPhone",
          ua has "Android", "Android",
          ua has "iPhone;", "IOS",
          ua has "iPad;", "IOS",
          ua has "Polycom/", "Polycom",
          ua has "Darwin/", "MacOS",
          ua has "Mac OS X", "MacOS",
          ua has "macOS", "MacOS",
          ua has "ubuntu", "Linux",
          ua has "Linux", "Linux",
          ua has "curl", "CLI",
          ua has "python", "CLI",
          "Unknown"
      )
  };
  // Query to obtain 'simplified' user agents in a given timespan.
  union withsource=tbl_name AADNonInteractiveUserSignInLogs, SigninLogs
  | where TimeGenerated >= ago(timeframe)
  | extend UserAgentOS=tolower(ExtractOSFromUA(UserAgent))
  | where not(isempty(UserAgent))
  | where not(isempty(AppId))
  | where ResultType == 0
  | extend DeviceOS=tolower(DeviceDetail_dynamic.operatingSystem)
  | where not(isempty(DeviceOS))
  | where not(UserAgentOS == "unknown")
  // Look for matches both ways, since sometimes the browser OS is more specific and sometimes the DeviceOS is more specific.
  | where not(UserAgentOS contains DeviceOS) and not(DeviceOS contains UserAgentOS)
  | where not(DeviceOS == "ios" and UserAgentOS == "macos") // This can happen for 'request desktop site'
  | where not(DeviceOS == "android" and UserAgentOS == "linux") // Android and Linux sometimes confused
  | summarize count(), arg_min(TimeGenerated,*) by DeviceOS, UserAgentOS, UserPrincipalName
  // Begin allow-list.
  // End allow-list.
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: UserPrincipalName
version: 1.0.2
kind: Scheduled

Stages and Predicates

Parameters

let timeframe = 1d;

Let Binding: ExtractOSFromUA

let ExtractOSFromUA = (ua:string) {
    case(
        ua has "Windows NT 6.0", "Windows Vista/Windows Server 2008",
        ua has "Windows NT 6.1", "Windows 7/Windows Server 2008R2",
        ua has "Windows NT 6.1", "Windows 7/Windows Server 2008",
        ua has "Windows NT 6.2", "Windows 8/Windows Server 2012",
        ua has "Windows NT 6.3", "Windows 8.1/Windows Server 2012R2",
        ua has "Windows NT 10.0", "Windows 10",
        ua has "Windows NT 11.0", "Windows 11",
        ua has "Windows Phone", "WindowsPhone",
        ua has "Android", "Android",
        ua has "iPhone;", "IOS",
        ua has "iPad;", "IOS",
        ua has "Polycom/", "Polycom",
        ua has "Darwin/", "MacOS",
        ua has "Mac OS X", "MacOS",
        ua has "macOS", "MacOS",
        ua has "ubuntu", "Linux",
        ua has "Linux", "Linux",
        ua has "curl", "CLI",
        ua has "python", "CLI",
        "Unknown"
    )
};

Union withsource=tbl_name (2 Sources)

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

Leg 1: AADNonInteractiveUserSignInLogs

Leg 2: SigninLogs

Applied to the Combined Result

| where TimeGenerated >= ago(timeframe) | extend UserAgentOS=tolower(ExtractOSFromUA(UserAgent)) | where not(isempty(UserAgent)) | where not(isempty(AppId)) | where ResultType == 0 | extend DeviceOS=tolower(DeviceDetail_dynamic.operatingSystem) | where not(isempty(DeviceOS)) | where not(UserAgentOS == "unknown") | where not(UserAgentOS contains DeviceOS) and not(DeviceOS contains UserAgentOS) | where not(DeviceOS == "ios" and UserAgentOS == "macos") | where not(DeviceOS == "android" and UserAgentOS == "linux") | summarize count(), arg_min(TimeGenerated,*) by DeviceOS, UserAgentOS, UserPrincipalName

Indicators

These rows show field, operator, and value matches.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
UserAgentis_null(no value, null check)excludes:UserAgent
AppIdis_null(no value, null check)excludes:AppId
DeviceOSis_null(no value, null check)excludes:DeviceOS
UserAgentOSequnknownexcludes:UserAgentOS field:"UserAgentOS" value:"unknown"
DeviceOScross_field_compareUserAgentOSexcludes:DeviceOS field:"DeviceOS" value:"UserAgentOS"
UserAgentOScross_field_compareDeviceOSexcludes:UserAgentOS field:"UserAgentOS" value:"DeviceOS"
DeviceOSeqiosexcludes:DeviceOS field:"DeviceOS" value:"ios"
UserAgentOSeqmacosexcludes:UserAgentOS field:"UserAgentOS" value:"macos"
DeviceOSeqandroidexcludes:DeviceOS field:"DeviceOS" value:"android"
UserAgentOSeqlinuxexcludes:UserAgentOS field:"UserAgentOS" value:"linux"

Output Fields

These fields are emitted when the rule matches.

FieldSource
DeviceOSsummarize
UserAgentOSsummarize
UserPrincipalNamesummarize

Pathlock TDnR - Outbound SAP SMTP Email

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

Detects outbound SMTP email activity from SAP systems, forwarded by Pathlock Threat Detection and Response. Anomalous outbound email patterns may indicate data exfiltration, unauthorized use of SAP email functions to send sensitive business data outside the organization.

MITRE ATT&CK Coverage

Rule Body

id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c39
name: Pathlock TDnR - Outbound SAP SMTP Email
kind: Scheduled
description: >-
  Detects outbound SMTP email activity from SAP systems, forwarded by Pathlock Threat Detection
  and Response. Anomalous outbound email patterns may indicate data exfiltration, unauthorized
  use of SAP email functions to send sensitive business data outside the organization.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: Pathlock_TDnR
    dataTypes:
      - Pathlock_TDnR_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Exfiltration
relevantTechniques:
  - T1048
query: |
  Pathlock_TDnR_CL
  | where DataSource == "OUTBOUND_SMTP_MAIL"
  | 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 == "OUTBOUND_SMTP_MAIL"

Stage 3: project

| project TimeGenerated, Sysid, DataSource, Eventid, Instance, Hostname, Bname,
          Tcode, Report, Area, Subid, SrcIp, DestIp, AffectedUser, LogLine,
          MsgType, MsgId, MsgNo, MessageV1, MessageV2, MessageV3, MessageV4, CentralTs

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AffectedUserproject
Areaproject
Bnameproject
CentralTsproject
DataSourceproject
DestIpproject
Eventidproject
Hostnameproject
Instanceproject
LogLineproject
MessageV1project
MessageV2project
MessageV3project
MessageV4project
MsgIdproject
MsgNoproject
MsgTypeproject
Reportproject
SrcIpproject
Subidproject
Sysidproject
Tcodeproject
TimeGeneratedproject

Pathlock TDnR - User Authorization Buffer Manipulation

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

Detects unauthorized manipulations of the SAP user authorization buffer (USRBF2), forwarded by Pathlock Threat Detection and Response. Direct manipulation of the authorization buffer bypasses the normal role/profile assignment process and can grant arbitrary privileges to any user without leaving a trace in standard authorization management logs.

MITRE ATT&CK Coverage

TacticTechniques
Privilege Escalation
Defense Impairment

Rule Body

id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c72
name: Pathlock TDnR - User Authorization Buffer Manipulation
kind: Scheduled
description: >-
  Detects unauthorized manipulations of the SAP user authorization buffer (USRBF2), forwarded by
  Pathlock Threat Detection and Response. Direct manipulation of the authorization buffer bypasses
  the normal role/profile assignment process and can grant arbitrary privileges to any user without
  leaving a trace in standard authorization management logs.
severity: High
status: Available
requiredDataConnectors:
  - connectorId: Pathlock_TDnR
    dataTypes:
      - Pathlock_TDnR_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - PrivilegeEscalation
  - DefenseEvasion
relevantTechniques:
  - T1548
  - T1562
query: |
  Pathlock_TDnR_CL
  | where DataSource == "USER_AUTH_BUFFER"
  | 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 == "USER_AUTH_BUFFER"

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

Power Automate - Unusual bulk deletion of flow resources

#
Status
available
Severity
medium
Time window
14d
Group by
ActorName, ActorUserId, UserUpn
Source
github.com/Azure/Azure-Sentinel

Identifies bulk deletion of Power Automate flows that exceed a predefined threshold defined in the query and deviate from activity patterns observed in the last 14 days.

MITRE ATT&CK Coverage

Telemetry Coverage

PlatformRecord / event type
Microsoft 365DeleteFlow

Rule Body

id: 56cb646e-56a0-4f0e-8866-9bc1dd15da78
kind: Scheduled
name: Power Automate - Unusual bulk deletion of flow resources
description: Identifies bulk deletion of Power Automate flows that exceed a predefined
  threshold defined in the query and deviate from activity patterns observed in the
  last 14 days.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: PowerAutomate
    dataTypes:
      - PowerAutomateActivity
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Impact
  - DefenseEvasion
relevantTechniques:
  - T1485
  - T0828
  - T1562
query: |
  // minThreshold: Minimum number of apps to be deleted to be considered an anomaly;
  // This is to prevent one-off isolated delete flow to be considered outlier.
  // The Min Threshold can be reduced or increased according to the traffic in the organization.
  let minThreshold=10;
  let interval = 1h;
  let startTime = ago(14d);
  let endTime = now();
  let query_frequency = 1h;
  let flow_deletion_events = PowerAutomateActivity
      | where TimeGenerated >= startTime
      | where EventOriginalType =~ "DeleteFlow"
      | extend IngestionTimeGenerated = TimeGenerated;
  flow_deletion_events
  | make-series DeletedFlowCount=count() on IngestionTimeGenerated from startTime to endTime step interval by ActorName, UserUpn, ActorUserId
  | extend(Anomalies, AnomalyScore, ExpectedUsage) = series_decompose_anomalies(DeletedFlowCount)
  | mv-expand
      DeletedFlowCount to typeof(double),
      IngestionTimeGenerated to typeof(datetime),
      Anomalies to typeof(double),
      AnomalyScore to typeof(double),
      ExpectedUsage to typeof(long)
  | where IngestionTimeGenerated >= ago(query_frequency)
  | where Anomalies != 0 and DeletedFlowCount >= minThreshold
  | lookup (flow_deletion_events
      | where IngestionTimeGenerated >= ago(query_frequency))
      on ActorName, UserUpn, ActorUserId
  | extend
      AccountName = tostring(split(ActorName, "@")[0]),
      UPNSuffix = tostring(split(ActorName, "@")[1]),
      PowerAutomateAppId = 27592
  | project
      TimeGenerated,
      ActorName,
      DeletedFlowCount,
      ExpectedUsage,
      Anomalies,
      AnomalyScore,
      AccountName,
      UPNSuffix,
      PowerAutomateAppId,
      UserUpn,
      ActorUserId
eventGroupingSettings:
  aggregationKind: SingleAlert
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: CloudApplication
    fieldMappings:
      - identifier: AppId
        columnName: PowerAutomateAppId
alertDetailsOverride:
  alertDisplayNameFormat: Power Automate - unusual bulk deletion of {{DeletedFlowCount}}
    flows
  alertDescriptionFormat: User {{ActorName}}  deleted {{DeletedFlowCount}} flows in
    the last hour, surpassing the bulk delete threshold. This is anomalous compared
    to the past 14 days.
customDetails:
  DeletedFlowCount: DeletedFlowCount
version: 3.2.0

Stages and Predicates

Parameters

let minThreshold = 10;
let interval = 1h;
let startTime = ago(14d);
let endTime = now();
let query_frequency = 1h;

let flow_deletion_events is inlined into the numbered stages below.

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

Stage 1: source

PowerAutomateActivity

Stage 2: where

| where TimeGenerated >= startTime

Stage 3: where

| where EventOriginalType =~ "DeleteFlow"

Stage 4: extend

| extend IngestionTimeGenerated = TimeGenerated

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

Stage 5: summarize

flow_deletion_events
| make-series DeletedFlowCount=count() on IngestionTimeGenerated from startTime to endTime step interval by ActorName, UserUpn, ActorUserId

Stage 6: extend

| extend(Anomalies, AnomalyScore, ExpectedUsage) = series_decompose_anomalies(DeletedFlowCount)

Stage 7: mv-expand

| mv-expand
    DeletedFlowCount to typeof(double),
    IngestionTimeGenerated to typeof(datetime),
    Anomalies to typeof(double),
    AnomalyScore to typeof(double),
    ExpectedUsage to typeof(long)

Stage 8: where

| where IngestionTimeGenerated >= ago(query_frequency)

Stage 9: where

| where Anomalies != 0 and DeletedFlowCount >= minThreshold

Stage 10: kusto:lookup

| lookup (flow_deletion_events
    | where IngestionTimeGenerated >= ago(query_frequency))
    on ActorName, UserUpn, ActorUserId

Stage 11: extend

extend AccountName, PowerAutomateAppId, UPNSuffix

Stage 12: project

project AccountName, ActorName, ActorUserId, Anomalies, AnomalyScore, DeletedFlowCount, ExpectedUsage, PowerAutomateAppId, TimeGenerated, UPNSuffix, UserUpn

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountNameproject
ActorNameproject
ActorUserIdproject
Anomaliesproject
AnomalyScoreproject
DeletedFlowCountproject
ExpectedUsageproject
PowerAutomateAppIdproject
TimeGeneratedproject
UPNSuffixproject
UserUpnproject

RecordedFuture Threat Hunting Hash All Actors

#
Severity
medium
Time window
1d
Group by
Id, lowerHash
Source
github.com/Azure/Azure-Sentinel

Recorded Future Threat Hunting hash correlation for all actors.

MITRE ATT&CK Coverage

Telemetry Coverage

Rule Body

id: 6db6a8e6-2959-440b-ba57-a505875fcb37
name: RecordedFuture Threat Hunting Hash All Actors
description: |
  'Recorded Future Threat Hunting hash correlation for all actors.'
severity: Medium
requiredDataConnectors:
  - connectorId: ThreatIntelligenceUploadIndicatorsAPI
    dataTypes:
      - ThreatIntelIndicators
queryFrequency: 15m
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
  - Execution
  - Persistence
relevantTechniques:
  - T1189
  - T1059
  - T1554
customDetails:
  ActorInformation: RecordedFuturePortalLink
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: 1h
    matchingMethod: AllEntities
eventGroupingSettings:
  aggregationKind: AlertPerResult
alertDetailsOverride:
  alertDisplayNameFormat: '{{Description}}'
  alertDescriptionFormat: '**{{Description}}**\n\nCorrelation found on {{Hash}} from the {{Type}} table.\n'
  alertDynamicProperties:
    - alertProperty: AlertLink
      value: RecordedFuturePortalLink
query: |
  let ioc_lookBack = 1d;
  // The source table (imFileEvent) is a ASIM parser table, but can be replaced by any infrastructure table containing Hash data.
  // The following workbook: Recorded Future - Hash Correlation will help researching available data and selecting tables and columns
  imFileEvent
  | where isnotempty(Hash)
  | extend lowerHash=tolower(Hash)
  | join kind=inner (
  ThreatIntelIndicators
  // Only look for IOCs
  | where ObservableKey contains'file:hashes'
  | where isnotempty(ObservableValue)
  // Only look at Recorded Future Threat Hunt Indicators.
  | where Data.description startswith "Recorded Future - Threat Hunt"
  // Only work with the latest indicators
  | where TimeGenerated >= ago(ioc_lookBack)
  | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id
  | where IsActive == true and ValidUntil > now()
  | extend lowerHash=tolower(ObservableValue)
  ) on lowerHash
  // select column from the source table to match with Recorded Future ThreatIntelIndicator $left.Hash
  | mv-expand Label=Data.labels
  | extend RecordedFuturePortalLink = parse_json(tostring(Label)).RecordedFuturePortalLink
  // Hash and HashType is defaulted to data from Recorded Future, it can be changed to data from the infrastructure table.
  | project Hash=ObservableValue, HashType=extract("file:hashes.('[^']*')", 1, ObservableKey), Description=Data.description, Type, TimeGenerated, RecordedFuturePortalLink

entityMappings:
  - entityType: FileHash
    fieldMappings:
      - identifier: Value
        columnName: Hash
      - identifier: Algorithm
        columnName: HashType
version: 1.1.1
kind: Scheduled

Stages and Predicates

Parameters

let ioc_lookBack = 1d;

Stage 1: source

imFileEvent

Stage 2: where

| where isnotempty(Hash)

Stage 3: extend

| extend lowerHash=tolower(Hash)

Stage 4: join

| join kind=inner (
ThreatIntelIndicators
| where ObservableKey contains'file:hashes'
| where isnotempty(ObservableValue)
| where Data.description startswith "Recorded Future - Threat Hunt"
| where TimeGenerated >= ago(ioc_lookBack)
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id
| where IsActive == true and ValidUntil > now()
| extend lowerHash=tolower(ObservableValue)
) on lowerHash

Stage 5: mv-expand

mv-expand Label

Stage 6: extend

extend RecordedFuturePortalLink

Stage 7: project

project Description, Hash, HashType, RecordedFuturePortalLink, TimeGenerated, Type

Stage 8: summarize aggregation inside the join branch

summarize by Id

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
Idsummarize

Registry Run Keys - Suspicious Registry Run Keys

#
Group by
NormalizedRegistryValueData
Author
Cyb3rMonk
Source
github.com/Cyb3r-Monk/Threat-Hunting-and-Detection

Below query looks for suspicious additions to Run, RunOnce and several other registry keys. The query analyzes all values in the specified registry keys and finds anomalous ones based on commonality in the environment and excludes possible legitimate activities like software installations. The query might require tuning according to the environment.

MITRE ATT&CK Coverage

Telemetry Coverage

Rule Body

// Author: Cyb3rMonk(https://twitter.com/Cyb3rMonk, https://mergene.medium.com)
// Link to original post: https://mergene.medium.com/threat-hunting-with-data-science-registry-run-keys-9ae329d1ad85
// Description: This query looks for suspicious additions to Run, RunOnce and several other registry keys. 
//              The query analyzes all values in the specified registry keys and finds anomalous ones based on
//              commonality in the environment and excludes possible legitimate activities like software installations.
//              The query might requiure tuning according to the environment.
let dataset= materialize (
DeviceRegistryEvents 
| where ActionType == "RegistryValueSet" 
// registry keys to be monitored
| where RegistryKey has @"Microsoft\Windows\CurrentVersion\RunOnce"
    or RegistryKey has @"Microsoft\Windows\CurrentVersion\RunOnceEx"
    or RegistryKey has @"Microsoft\Windows\CurrentVersion\Run"
    or RegistryKey has @"\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
    or (RegistryKey has @"\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders")
    or (RegistryKey has @"Software\Microsoft\Windows\CurrentVersion\Policies\Explorer" and RegistryValueName == "Run")
    or (RegistryKey has @"\Session Manager" and RegistryValueName == "BootExecute")
    or (RegistryKey has @"Microsoft\Windows NT\CurrentVersion\Winlogon" and RegistryValueName == "Userinit")
    or (RegistryKey has @"Microsoft\Windows NT\CurrentVersion\Winlogon" and RegistryValueName == "Shell")
    or (RegistryKey has @"Microsoft\Windows NT\CurrentVersion\Windows" and RegistryValueName == "load")
    or RegistryKey has @"Microsoft\Windows NT\CurrentVersion\Winlogon\Notify" 
    or RegistryKey has @"\Software\Microsoft\Windows\CurrentVersion\RunServices"
    or RegistryKey has @"\Software\Microsoft\Windows\CurrentVersion\RunServicesOnce"
    or RegistryKey has @"\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer\Run"
        // below is related to the persistence using CLSID (junction folders, etc.)
    or (RegistryKey has @"SOFTWARE\Classes\CLSID" and RegistryKey endswith "InprocServer32" and isempty(RegistryValueName))
// exclude Config.Msi folder items
| where RegistryValueData !endswith @".rbf /restore"
// create NormalizedRegistryValueData field
| extend NormalizedRegistryValueData = replace(@'(C|D):\\Users\\.*?\\', @'C:\\Users\\userxx\\',RegistryValueData )
| extend NormalizedRegistryValueData = replace(@'\{.*\}', @'\{xxxxxxxxxx\}',NormalizedRegistryValueData ) //{fe07d7-d438-4dd9-bb0f-5721658f4f}
| extend NormalizedRegistryValueData = replace(@'\\[A-Za-z0-9-]+-[A-Za-z0-9]+\\', @'\\xxxxxxxxxx\\',NormalizedRegistryValueData ) //\fe07d7-d438-4dd9-bb0f-5721658f4f\
| extend NormalizedRegistryValueData = replace(@'\d+\.\d+\.\d+\.\d+', @'X.Y.Z.T',NormalizedRegistryValueData )
| extend NormalizedRegistryValueData = replace(@'-\d+\.\d+\.\d+', @'-X.Y.Z',NormalizedRegistryValueData )
| extend NormalizedRegistryValueData = replace(@'_\d+\.log', @'_XYZT.log',NormalizedRegistryValueData )
| extend NormalizedRegistryValueData = replace(@'--quiet|--passive', @'',NormalizedRegistryValueData )
| extend NormalizedRegistryValueData = replace(@'installSessionId\s[A-Za-z0-9-]+', @'installSessionId xxxxxx',NormalizedRegistryValueData )
| extend NormalizedRegistryValueData = replace(@'C:\\ProgramData\\.*?\\Microsoft\\Teams\\',@'C:\\ProgramData\\userxxx\\Microsoft\\Teams\\',NormalizedRegistryValueData)
);
dataset
| summarize dcount_device = dcount(DeviceId), total_count = count() by NormalizedRegistryValueData
| where dcount_device <=5 and total_count <20
| join kind=inner (dataset| where Timestamp > ago(1d)) on NormalizedRegistryValueData
| invoke FileProfile(InitiatingProcessSHA1,1000)
| where GlobalPrevalence <100
         or isempty(GlobalPrevalence)
         // inlcude processes that are involved in malicious attacks(e.g. office macro creating the registry key)
         or InitiatingProcessFileName in~ ("powershell.exe","reg.exe", "regedit.exe", "cmd.exe","winword.exe","excel.exe","powerpnt.exe")

Stages and Predicates

let dataset is inlined into the numbered stages below.

Stage 1: source

let dataset

Stage 2: source

DeviceRegistryEvents

Stage 3: where

where ActionType == "RegistryValueSet"

Stage 4: where

where ((RegistryKey endswith "InprocServer32" and RegistryKey has @"SOFTWARE\Classes\CLSID" and isempty(RegistryValueName)) or (RegistryKey has @"Microsoft\Windows NT\CurrentVersion\Windows" and RegistryValueName == "load") or (RegistryKey has @"Microsoft\Windows NT\CurrentVersion\Winlogon" and RegistryValueName == "Shell") or (RegistryKey has @"Microsoft\Windows NT\CurrentVersion\Winlogon" and RegistryValueName == "Userinit") or (RegistryKey has @"Software\Microsoft\Windows\CurrentVersion\Policies\Explorer" and RegistryValueName == "Run") or (RegistryKey has @"\Session Manager" and RegistryValueName == "BootExecute") or RegistryKey has @"Microsoft\Windows NT\CurrentVersion\Winlogon\Notify" or RegistryKey has @"Microsoft\Windows\CurrentVersion\Run" or RegistryKey has @"Microsoft\Windows\CurrentVersion\RunOnce" or RegistryKey has @"Microsoft\Windows\CurrentVersion\RunOnceEx" or RegistryKey has @"\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders" or RegistryKey has @"\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders" or RegistryKey has @"\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer\Run" or RegistryKey has @"\Software\Microsoft\Windows\CurrentVersion\RunServices" or RegistryKey has @"\Software\Microsoft\Windows\CurrentVersion\RunServicesOnce")

Stage 5: where

where not (RegistryValueData endswith ".rbf /restore")

Stage 6: extend (9 consecutive steps)

extend NormalizedRegistryValueData

Stage 7: summarize

summarize dcount_device, total_count by NormalizedRegistryValueData

Stage 8: where

where dcount_device <= 5 and total_count < 20

Stage 9: join

join kind=inner (dataset) on NormalizedRegistryValueData

Stage 10: invoke

invoke

Stage 11: where

where (isempty(GlobalPrevalence) or GlobalPrevalence < 100 or InitiatingProcessFileName in~ ("cmd.exe", "excel.exe", "powerpnt.exe", "powershell.exe", "reg.exe", "regedit.exe", "winword.exe"))

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
ActionTypeeq
  • RegistryValueSet
field:"ActionType" kind:eq value:"RegistryValueSet"
GlobalPrevalenceis_null
  • (no value, null check)
field:"GlobalPrevalence" kind:is_null
GlobalPrevalencelt
  • 100 transforms: number
field:"GlobalPrevalence" kind:lt value:"100"
InitiatingProcessFileNamein
  • cmd.exe
  • excel.exe
  • powerpnt.exe
  • powershell.exe
  • reg.exe
  • regedit.exe
  • winword.exe
field:"InitiatingProcessFileName" kind:in
RegistryKeyends_with
  • InprocServer32
field:"RegistryKey" kind:ends_with value:"InprocServer32"
RegistryKeymatch
  • Microsoft\Windows NT\CurrentVersion\Windows transforms: term
  • Microsoft\Windows NT\CurrentVersion\Winlogon transforms: term
  • Microsoft\Windows NT\CurrentVersion\Winlogon\Notify transforms: term
  • Microsoft\Windows\CurrentVersion\Run transforms: term
  • Microsoft\Windows\CurrentVersion\RunOnce transforms: term
  • Microsoft\Windows\CurrentVersion\RunOnceEx transforms: term
  • SOFTWARE\Classes\CLSID transforms: term
  • Software\Microsoft\Windows\CurrentVersion\Policies\Explorer transforms: term
  • \Microsoft\Windows\CurrentVersion\Explorer\Shell Folders transforms: term
  • \Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders transforms: term
  • \Session Manager transforms: term
  • \Software\Microsoft\Windows\CurrentVersion\Policies\Explorer\Run transforms: term
  • \Software\Microsoft\Windows\CurrentVersion\RunServices transforms: term
  • \Software\Microsoft\Windows\CurrentVersion\RunServicesOnce transforms: term
field:"RegistryKey" kind:match
RegistryValueNameeq
  • BootExecute
  • Run
  • Shell
  • Userinit
  • load
field:"RegistryValueName" kind:eq
RegistryValueNameis_null
  • (no value, null check)
field:"RegistryValueName" kind:is_null
dcount_devicele
  • 5 transforms: number
field:"dcount_device" kind:le value:"5"
total_countlt
  • 20 transforms: number
field:"total_count" kind:lt value:"20"

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
RegistryValueDataends_with.rbf /restoreexcludes:RegistryValueData field:"RegistryValueData" value:".rbf /restore"

Output Fields

These fields are emitted when the rule matches.

FieldSource
NormalizedRegistryValueDatasummarize
dcount_devicesummarize
total_countsummarize

SAP BTP - User added to Cloud Identity Service privileged Administrators list

#
Status
available
Severity
high
Time window
15m
Group by
Tenant, UpdatedOn, UserName, callerMail, ipAddress, targetUser
Source
github.com/Azure/Azure-Sentinel

Identifies when a user is granted privileged administrator permissions in SAP Cloud Identity Service. These permissions include managing Identity Providers, Service Providers, Users, Groups, and Access controls.

MITRE ATT&CK Coverage

TacticTechniques
Privilege Escalation
Persistence
Lateral Movement

Rule Body

id: 7d4e9f2a-8b1c-4a5d-9e3f-6c2b1a0d8e7f
kind: Scheduled
name: SAP BTP - User added to Cloud Identity Service privileged Administrators list
description: Identifies when a user is granted privileged administrator permissions
  in SAP Cloud Identity Service. These permissions include managing Identity Providers,
  Service Providers, Users, Groups, and Access controls.
severity: High
status: Available
requiredDataConnectors:
  - connectorId: SAPBTPAuditEvents
    dataTypes:
      - SAPBTPAuditLog_CL
queryFrequency: 15m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - LateralMovement
  - PrivilegeEscalation
relevantTechniques:
  - T0859
  - T1078
query: |
  let monitored_permissions = dynamic(["ManageIdP", "ManageSP", "ManageUsers", "ReadUsers", "ManageAccess", "ManageGroups"]);
  SAPBTPAuditLog_CL
  | extend data_s = tostring(Message.data)
  | extend action = extract(@"action=""([^""]+)""", 1, data_s),
           state = extract(@"state=""([^""]+)""", 1, data_s),
           changedAttribute = extract(@"changedAttribute=""([^""]+)""", 1, data_s),
           newValue = extract(@"newValue=""([^""]+)""", 1, data_s),
           targetUser = extract(@"userIdentifier=""([^""]+)""", 1, data_s),
           callerMail = extract(@"callerMail=""([^""]+)""", 1, data_s),
           ipAddress = extract(@"ipAddress=""([^""]+)""", 1, data_s)
  | where action == "grantPermissions"
  | where state == "successful"
  | where changedAttribute == "authorizations"
  | where isnotempty(newValue)
  | mv-expand permission = monitored_permissions
  | where newValue contains tostring(permission)
  | summarize 
      GrantedPermissions = make_set(newValue, 10),
      MatchedPermissions = make_set(tostring(permission), 10)
      by UpdatedOn, UserName, callerMail, targetUser, Tenant, ipAddress
  | project UpdatedOn, UserName, callerMail, targetUser, GrantedPermissions, MatchedPermissions, Tenant, ipAddress, CloudApp = "SAP Cloud Identity Service"
  | 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: CloudApplication
    fieldMappings:
      - identifier: Name
        columnName: CloudApp
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: ipAddress
version: 1.0.1

Stages and Predicates

Parameters

let monitored_permissions = dynamic(["ManageIdP", "ManageSP", "ManageUsers", "ReadUsers", "ManageAccess", "ManageGroups"]);

Stage 1: source

SAPBTPAuditLog_CL

Stage 2: extend

| extend data_s = tostring(Message.data)

Stage 3: extend

| extend action = extract(@"action=""([^""]+)""", 1, data_s),
         state = extract(@"state=""([^""]+)""", 1, data_s),
         changedAttribute = extract(@"changedAttribute=""([^""]+)""", 1, data_s),
         newValue = extract(@"newValue=""([^""]+)""", 1, data_s),
         targetUser = extract(@"userIdentifier=""([^""]+)""", 1, data_s),
         callerMail = extract(@"callerMail=""([^""]+)""", 1, data_s),
         ipAddress = extract(@"ipAddress=""([^""]+)""", 1, data_s)

Stage 4: where

| where action == "grantPermissions"

Stage 5: where

| where state == "successful"

Stage 6: where

| where changedAttribute == "authorizations"

Stage 7: where

| where isnotempty(newValue)

Stage 8: mv-expand

| mv-expand permission = monitored_permissions

Stage 9: where

| where newValue contains tostring(permission)

Stage 10: summarize

| summarize 
    GrantedPermissions = make_set(newValue, 10),
    MatchedPermissions = make_set(tostring(permission), 10)
    by UpdatedOn, UserName, callerMail, targetUser, Tenant, ipAddress

Stage 11: project

| project UpdatedOn, UserName, callerMail, targetUser, GrantedPermissions, MatchedPermissions, Tenant, ipAddress, CloudApp = "SAP Cloud Identity Service"

Stage 12: extend

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

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
CloudAppproject
GrantedPermissionsproject
MatchedPermissionsproject
Tenantproject
UpdatedOnproject
UserNameproject
callerMailproject
ipAddressproject
targetUserproject
AccountNameextend
UPNSuffixextend

SAST Auth Finding Correlated with Brute Force

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

Detects applications with open SAST authentication findings that are simultaneously experiencing brute force login attempts in Azure AD. Correlates StratoSecure SAST findings with SigninLogs failure spikes.

MITRE ATT&CK Coverage

TacticTechniques
Initial Access
Credential Access

Telemetry Coverage

Rule Body

id: 4ae9f294-410d-4c11-9072-0d8fcf5162d8
name: SAST Auth Finding Correlated with Brute Force
description: |
  Detects applications with open SAST authentication findings that are
  simultaneously experiencing brute force login attempts in Azure AD.
  Correlates StratoSecure SAST findings with SigninLogs failure spikes.
severity: High
status: Available
requiredDataConnectors:
  - connectorId: StratoSecurePush
    dataTypes:
      - StratoSecure_Findings_CL
queryFrequency: 1h
queryPeriod: 75m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CredentialAccess
  - InitialAccess
relevantTechniques:
  - T1110
  - T1078
query: |
  let lookback = 1h;
  let bruteForceThreshold = 20;
  let AuthFindings = StratoSecure_Findings_CL
      | where TimeGenerated >= ago(lookback)
      | where FixStatus =~ "open"
      | where Severity in ("Critical", "High")
      | where RuleId has_any ("auth", "jwt", "login", "session", "token", "password", "credential")
      | summarize FindingCount = count(), RuleIds = make_set(RuleId), Repos = make_set(Repository)
          by ApplicationId, StraTenantId;
  let BruteForce = SigninLogs
      | where TimeGenerated >= ago(lookback)
      | where ResultType != "0"
      | summarize FailedAttempts = count(), DistinctUsers = dcount(UserPrincipalName),
          IPList = make_set(IPAddress, 20), FirstAttempt = min(TimeGenerated), LastAttempt = max(TimeGenerated)
          by AppId, AppDisplayName
      | where FailedAttempts >= bruteForceThreshold;
  AuthFindings
      | join kind=inner BruteForce on $left.ApplicationId == $right.AppId
      | project TimeGenerated = LastAttempt, ApplicationId, AppDisplayName, StraTenantId,
          FindingCount, RuleIds, Repos, FailedAttempts, DistinctUsers, IPList
entityMappings:
  - entityType: CloudApplication
    fieldMappings:
      - identifier: AppId
        columnName: ApplicationId
version: 1.0.0
kind: Scheduled

Stages and Predicates

Parameters

let lookback = 1h;
let bruteForceThreshold = 20;

let AuthFindings is inlined into the numbered stages below.

Let Binding: BruteForce used in Stage 7

let BruteForce = SigninLogs
    | where TimeGenerated >= ago(lookback)
    | where ResultType != "0"
    | summarize FailedAttempts = count(), DistinctUsers = dcount(UserPrincipalName),
        IPList = make_set(IPAddress, 20), FirstAttempt = min(TimeGenerated), LastAttempt = max(TimeGenerated)
        by AppId, AppDisplayName
    | where FailedAttempts >= bruteForceThreshold;

Stages 1 to 6 define let AuthFindings 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 Severity in ("Critical", "High")

Stage 5: where

| where RuleId has_any ("auth", "jwt", "login", "session", "token", "password", "credential")

Stage 6: summarize

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

Stage 7: join

AuthFindings
| join kind=inner BruteForce on $left.ApplicationId == $right.AppId

Stage 8: project

project AppDisplayName, ApplicationId, DistinctUsers, FailedAttempts, FindingCount, IPList, Repos, RuleIds, StraTenantId, TimeGenerated

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
FailedAttemptsge
  • 20 transforms: number
field:"FailedAttempts" kind:ge value:"20"
FixStatuseq
  • open corpus 5 (kusto 5)
field:"FixStatus" kind:eq value:"open"
ResultTypene
  • 0
field:"ResultType" kind:ne value:"0"
RuleIdmatch
  • auth transforms: term
  • credential transforms: term
  • jwt transforms: term
  • login transforms: term
  • password transforms: term
  • session transforms: term
  • token transforms: term
field:"RuleId" kind:match
Severityin
  • Critical corpus 7 (kusto 7)
  • High corpus 7 (kusto 7)
field:"Severity" kind:in

Output Fields

These fields are emitted when the rule matches.

FieldSource
AppDisplayNameproject
ApplicationIdproject
DistinctUsersproject
FailedAttemptsproject
FindingCountproject
IPListproject
Reposproject
RuleIdsproject
StraTenantIdproject
TimeGeneratedproject

Back to all Kusto rules