Detection rules › Kusto

Kusto rules: 0n

Account created or deleted by non-approved user

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

Identifies accounts that were created or deleted by a defined list of non-approved user principal names. Add to this list before running the query for accurate results. Ref : https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-user-accounts

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Telemetry Coverage

PlatformRecord / event type
AzureAdd user
AzureDelete user

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: 6d63efa6-7c25-4bd4-a486-aa6bf50fde8a
name: Account created or deleted by non-approved user
description: |
  'Identifies accounts that were created or deleted by a defined list of non-approved user principal names. Add to this list before running the query for accurate results.
  Ref : https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-user-accounts'
severity: Medium
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AuditLogs
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - InitialAccess
relevantTechniques:
  - T1078.004
tags:
  - AADSecOpsGuide
query: |
  // Add non-approved user principal names or apps to the list below to search for their account creation/deletion activity
  // ex: dynamic(["UPN1", "upn123"])
  let nonapproved_users = dynamic([]);
  let nonapproved_apps = dynamic([]);
  AuditLogs
  | where OperationName =~ "Add user" or OperationName =~ "Delete user"
  | where Result =~ "success"
  | 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(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))
  | where InitiatingUserPrincipalName has_any (nonapproved_users) or InitiatingAppName has_any (nonapproved_apps)
  | extend InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: InitiatingAppName
      - identifier: AadUserId
        columnName: InitiatingAppServicePrincipalId
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: InitiatingUserPrincipalName
      - identifier: Name
        columnName: InitiatingAccountName
      - identifier: UPNSuffix
        columnName: InitiatingAccountUPNSuffix
  - entityType: Account
    fieldMappings:
      - identifier: AadUserId
        columnName: InitiatingAadUserId
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: InitiatingIpAddress
version: 1.1.1
kind: Scheduled

Stages and Predicates

Parameters

let nonapproved_users = dynamic([]);
let nonapproved_apps = dynamic([]);

Stage 1: source

AuditLogs

Stage 2: where

| where OperationName =~ "Add user" or OperationName =~ "Delete user"

Stage 3: where

| where Result =~ "success"

Stage 4: extend (5 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(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))

Stage 5: where

| where InitiatingUserPrincipalName has_any (nonapproved_users) or InitiatingAppName has_any (nonapproved_apps)

Stage 6: extend

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

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
InitiatingAppNamematch
  • [] transforms: term
field:"InitiatingAppName" kind:match value:"[]"
InitiatingUserPrincipalNamematch
  • [] transforms: term
field:"InitiatingUserPrincipalName" kind:match value:"[]"
OperationNameeq
  • Add user corpus 4 (kusto 4)
  • Delete user corpus 3 (kusto 3)
field:"OperationName" kind:eq
Resulteq
  • success corpus 23 (kusto 23)
field:"Result" kind:eq value:"success"

Output Fields

These fields are emitted when the rule matches.

FieldSource
InitiatingAppNameextend
InitiatingAppServicePrincipalIdextend
InitiatingUserPrincipalNameextend
InitiatingAadUserIdextend
InitiatingIpAddressextend
InitiatingAccountNameextend
InitiatingAccountUPNSuffixextend

BloodHound Attack Path Finding - Logic App Contributor Role on Tier Zero Logic App

#

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 - Logic App Contributor Role on Tier Zero Logic App

Rule Body

id: 25ffa891-4832-485d-9461-f8c90df1d3ff
name: BloodHound Attack Path Finding - Logic App Contributor Role on Tier Zero Logic
  App
description: BloodHound Attack Path Finding - Logic App Contributor Role on Tier Zero
  Logic App
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 == "AzureT0LogicAppContributor"
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 == "AzureT0LogicAppContributor"

Indicators

These rows show field, operator, and value matches.

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

Bulk Changes to Privileged Account Permissions

#
Status
available
Severity
high
Time window
1h
Group by
$left.TimeGenerated, $right.TimeWindow
Source
github.com/Azure/Azure-Sentinel

Identifies when changes to multiple users permissions are changed at once. Investigate immediately if not a planned change. This setting could enable an attacker access to Azure subscriptions in your environment. Ref : https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-privileged-identity-management

MITRE ATT&CK Coverage

TacticTechniques
Privilege Escalation

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: 218f60de-c269-457a-b882-9966632b9dc6
name: Bulk Changes to Privileged Account Permissions
description: |
  'Identifies when changes to multiple users permissions are changed at once. Investigate immediately if not a planned change. This setting could enable an attacker access to Azure subscriptions in your environment.
  Ref : https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-privileged-identity-management'
severity: High
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AuditLogs
queryFrequency: 2h
queryPeriod: 2h
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - PrivilegeEscalation
relevantTechniques:
  - T1078.004
tags:
  - AADSecOpsGuide
query: |
  let AdminRecords = AuditLogs
  | where Category =~ "RoleManagement"
  | where ActivityDisplayName has_any ("Add eligible member to role", "Add member to role")
  | mv-apply TargetResource = TargetResources on 
    (
        where TargetResource.type =~ "User"
        | extend TargetUserPrincipalName = tostring(TargetResource.userPrincipalName),
                 props = TargetResource.modifiedProperties
    )
  | mv-apply Property = props on 
    (
        where Property.displayName =~ "Role.DisplayName"
        | extend RoleName = trim('"',tostring(Property.newValue))
    )
  | where RoleName contains "Admin";
  AdminRecords
  | summarize dcount(TargetUserPrincipalName) by bin(TimeGenerated, 1h)
  | where dcount_TargetUserPrincipalName > 9
  | join kind=rightsemi  (
    AdminRecords
    | extend TimeWindow = bin(TimeGenerated, 1h)
  ) on $left.TimeGenerated == $right.TimeWindow
  | extend InitiatedByUser = iff(isnotempty(InitiatedBy.user.userPrincipalName), tostring(InitiatedBy.user.userPrincipalName), "")
  | 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(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))
  | extend TargetName = tostring(split(TargetUserPrincipalName,'@',0)[0]), TargetUPNSuffix = tostring(split(TargetUserPrincipalName,'@',1)[0])
  | extend InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: InitiatingAppName
      - identifier: AadUserId
        columnName: InitiatingAppServicePrincipalId
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: TargetUserPrincipalName
      - identifier: Name
        columnName: TargetName
      - identifier: UPNSuffix
        columnName: TargetUPNSuffix
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: InitiatingUserPrincipalName
      - identifier: Name
        columnName: InitiatingAccountName
      - identifier: UPNSuffix
        columnName: InitiatingAccountUPNSuffix
  - entityType: Account
    fieldMappings:
      - identifier: AadUserId
        columnName: InitiatingAadUserId
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: InitiatingIpAddress
customDetails:
    InitiatedByUser: InitiatedByUser
    TargetUser: TargetUserPrincipalName
version: 1.0.7
kind: Scheduled

Stages and Predicates

let AdminRecords is inlined into the numbered stages below.

Stage 0: let

let AdminRecords = AuditLogs <inlined as stages below>;

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

Stage 1: source

AuditLogs

Stage 2: where

| where Category =~ "RoleManagement"

Stage 3: where

| where ActivityDisplayName has_any ("Add eligible member to role", "Add member to role")

Stage 4: kusto:mv-apply

| mv-apply TargetResource = TargetResources on 
  (
      where TargetResource.type =~ "User"
      | extend TargetUserPrincipalName = tostring(TargetResource.userPrincipalName),
               props = TargetResource.modifiedProperties
  )

Stage 5: kusto:mv-apply

| mv-apply Property = props on 
  (
      where Property.displayName =~ "Role.DisplayName"
      | extend RoleName = trim('"',tostring(Property.newValue))
  )

Stage 6: where

| where RoleName contains "Admin"

Stage 7: summarize

AdminRecords
| summarize dcount(TargetUserPrincipalName) by bin(TimeGenerated, 1h)

Stage 8: where

| where dcount_TargetUserPrincipalName > 9

Stage 9: join

| join kind=rightsemi  (
  AdminRecords
  | extend TimeWindow = bin(TimeGenerated, 1h)
) on $left.TimeGenerated == $right.TimeWindow

Stage 10: extend (8 consecutive steps)

extend InitiatedByUser, InitiatingAadUserId, InitiatingAccountName, InitiatingAccountUPNSuffix, InitiatingAppName, InitiatingAppServicePrincipalId, InitiatingIpAddress, InitiatingUserPrincipalName, TargetName, TargetUPNSuffix
InitiatedByUser =
ifisnotempty(InitiatedBy.user.userPrincipalName)tostring(InitiatedBy.user.userPrincipalName)
else""

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
ActivityDisplayNamematch
  • Add eligible member to role transforms: term corpus 9 (kusto 9)
  • Add eligible member to role transforms: term
  • Add member to role transforms: term corpus 9 (kusto 9)
  • Add member to role transforms: term
field:"azure_ad::activity_display_name" kind:match
Categoryeq
  • RoleManagement corpus 17 (kusto 16, sigma 1)
field:"Category" kind:eq value:"RoleManagement"
Property.displayNameeq
  • Role.DisplayName transforms: kusto_dynamic_path
field:"Property.displayName" kind:eq value:"Role.DisplayName"
RoleNamecontains
  • Admin
field:"RoleName" kind:contains value:"Admin"
TargetResource.typeeq
  • User transforms: kusto_dynamic_path
field:"TargetResource.type" kind:eq value:"User"
dcount_TargetUserPrincipalNamegt
  • 9 transforms: number
field:"dcount_TargetUserPrincipalName" kind:gt value:"9"

Output Fields

These fields are emitted when the rule matches.

FieldSource
InitiatedByUserextend
InitiatingAppNameextend
InitiatingAppServicePrincipalIdextend
InitiatingUserPrincipalNameextend
InitiatingAadUserIdextend
InitiatingIpAddressextend
TargetNameextend
TargetUPNSuffixextend
InitiatingAccountNameextend
InitiatingAccountUPNSuffixextend

Cisco ASA - average attack detection rate increase

#
Status
available
Severity
low
Time window
6h
Group by
DeviceEventClassID, DeviceName, DropRate, SourceIP, SourceOfDropRateCount
Source
github.com/Azure/Azure-Sentinel

'This will help you determine if Cisco ASA devices are under heavier attack than normal over the last hour versus the previous 6 hours based on DeviceEventClassID 733100 References: https://www.cisco.com/c/en/us/td/docs/security/asa/syslog/b_syslog/syslogs9.html Details on how to further troubleshoot/investigate: https://www.cisco.com/c/en/us/support/docs/security/asa-5500-x-series-next-generation-firewalls/113685-asa-threat-detection.html'

MITRE ATT&CK Coverage

Rule Body

id: 79f29feb-6a9d-4cdf-baaa-2daf480a5da1
name: Cisco ASA - average attack detection rate increase
description: |
  'This will help you determine if Cisco ASA devices are under heavier attack than normal over the last hour versus the previous 6 hours based on DeviceEventClassID 733100
  References: https://www.cisco.com/c/en/us/td/docs/security/asa/syslog/b_syslog/syslogs9.html
  Details on how to further troubleshoot/investigate: https://www.cisco.com/c/en/us/support/docs/security/asa-5500-x-series-next-generation-firewalls/113685-asa-threat-detection.html'
severity: Low
status: Available
requiredDataConnectors:
  - connectorId: CiscoAsaAma
    dataTypes:
      - CommonSecurityLog
queryFrequency: 1h
queryPeriod: 6h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Discovery
  - Impact
relevantTechniques:
  - T1046
  - T1498
query: |
  let timeframe = 1h;
  let last1h = CommonSecurityLog
  | where TimeGenerated >= ago(timeframe)
  | where isempty(CommunicationDirection)
  | where DeviceEventClassID == "733100"
  | extend SourceOfDropRateCount = tostring(split(tostring(split(Message, "]")[0]),"[ ")[1])
  | extend splitMessage = split(Message, ".")
  | extend DropRate = tostring(split(tostring(splitMessage[0]),"] ")[1])
  | extend CurrentBurstRate = split(tostring(split(tostring(splitMessage[1]),"  ")[0]),"is ")
  | extend CurrentBurstRatePerSec = toint(split(tostring(CurrentBurstRate[1])," ")[0])
  | extend MaxConfiguredBurstRate = toint(CurrentBurstRate[2])
  | extend CurrentAvgRate = split(tostring(split(tostring(splitMessage[1]),"  ")[1]),"is ")
  | extend CurrentAvgRatePerSec = toint(split(tostring(CurrentAvgRate[1])," ")[0])
  | extend MaxConfiguredAvgRate = toint(CurrentAvgRate[2])
  | extend CumulativeTotal = toint(split(tostring(split(tostring(splitMessage[1]),"  ")[2]),"is ")[1])
  | summarize last1hCumTotal = sum(CumulativeTotal), last1hAvgRatePerSec = avg(CurrentAvgRatePerSec), last1hAvgBurstRatePerSec = avg(CurrentBurstRatePerSec) by DeviceName, DeviceEventClassID, SourceIP, SourceOfDropRateCount, DropRate;
  let prev6h = CommonSecurityLog
  | where TimeGenerated between (ago(6h) .. ago(1h))
  | where isempty(CommunicationDirection)
  | where DeviceEventClassID == "733100"
  | extend SourceOfDropRateCount = tostring(split(tostring(split(Message, "]")[0]),"[ ")[1])
  | extend splitMessage = split(Message, ".")
  | extend DropRate = tostring(split(tostring(splitMessage[0]),"] ")[1])
  | extend CurrentBurstRate = split(tostring(split(tostring(splitMessage[1]),"  ")[0]),"is ")
  | extend prevCurrentBurstRatePerSec = toint(split(tostring(CurrentBurstRate[1])," ")[0])
  | extend prevMaxConfiguredBurstRate = toint(CurrentBurstRate[2])
  | extend CurrentAvgRate = split(tostring(split(tostring(splitMessage[1]),"  ")[1]),"is ")
  | extend prevCurrentAvgRatePerSec = toint(split(tostring(CurrentAvgRate[1])," ")[0])
  | extend prevMaxConfiguredAvgRate = toint(CurrentAvgRate[2])
  | extend prevCumulativeTotal = toint(split(tostring(split(tostring(splitMessage[1]),"  ")[2]),"is ")[1])
  | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), prev6hCumTotal = sum(prevCumulativeTotal), prev6hAvgRatePerSec = avg(prevCurrentAvgRatePerSec), prev6hAvgBurstRatePerSec = avg(prevCurrentBurstRatePerSec)
  by DeviceName, DeviceEventClassID, SourceIP, SourceOfDropRateCount, DropRate;
  last1h | join (
    prev6h
  ) on DeviceName, DeviceEventClassID, SourceIP, SourceOfDropRateCount, DropRate
  | project StartTimeUtc, EndTimeUtc, DeviceName, DeviceEventClassID, SourceIP, SourceOfDropRateCount, DropRate, last1hCumTotal, prev6hCumTotal, prev6hAvgCumTotal = prev6hCumTotal/6, last1hAvgRatePerSec, prev6hAvgRatePerSec, last1hAvgBurstRatePerSec, prev6hAvgBurstRatePerSec
  // Select only events that indicate a doubling of the expected rate in the last hour over the previous 6 hours
  | where last1hCumTotal > 2*prev6hAvgCumTotal or last1hAvgRatePerSec > 2*prev6hAvgRatePerSec or last1hAvgBurstRatePerSec > 2*prev6hAvgBurstRatePerSec
  | extend HostName = tostring(split(DeviceName, ".")[0]), DomainIndex = toint(indexof(DeviceName, '.'))
  | extend HostNameDomain = iff(DomainIndex != -1, substring(DeviceName, DomainIndex + 1), DeviceName)
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: DeviceName
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: HostNameDomain
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIP
version: 1.0.3
kind: Scheduled

Stages and Predicates

Parameters

let timeframe = 1h;

let last1h is inlined into the numbered stages below.

Let Binding: prev6h used in Stage 7

let prev6h = CommonSecurityLog
| where TimeGenerated between (ago(6h) .. ago(1h))
| where isempty(CommunicationDirection)
| where DeviceEventClassID == "733100"
| extend SourceOfDropRateCount = tostring(split(tostring(split(Message, "]")[0]),"[ ")[1])
| extend splitMessage = split(Message, ".")
| extend DropRate = tostring(split(tostring(splitMessage[0]),"] ")[1])
| extend CurrentBurstRate = split(tostring(split(tostring(splitMessage[1]),"  ")[0]),"is ")
| extend prevCurrentBurstRatePerSec = toint(split(tostring(CurrentBurstRate[1])," ")[0])
| extend prevMaxConfiguredBurstRate = toint(CurrentBurstRate[2])
| extend CurrentAvgRate = split(tostring(split(tostring(splitMessage[1]),"  ")[1]),"is ")
| extend prevCurrentAvgRatePerSec = toint(split(tostring(CurrentAvgRate[1])," ")[0])
| extend prevMaxConfiguredAvgRate = toint(CurrentAvgRate[2])
| extend prevCumulativeTotal = toint(split(tostring(split(tostring(splitMessage[1]),"  ")[2]),"is ")[1])
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), prev6hCumTotal = sum(prevCumulativeTotal), prev6hAvgRatePerSec = avg(prevCurrentAvgRatePerSec), prev6hAvgBurstRatePerSec = avg(prevCurrentBurstRatePerSec)
by DeviceName, DeviceEventClassID, SourceIP, SourceOfDropRateCount, DropRate;

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

Stage 1: source

CommonSecurityLog

Stage 2: where

| where TimeGenerated >= ago(timeframe)

Stage 3: where

| where isempty(CommunicationDirection)

Stage 4: where

| where DeviceEventClassID == "733100"

Stage 5: extend (10 consecutive steps)

| extend SourceOfDropRateCount = tostring(split(tostring(split(Message, "]")[0]),"[ ")[1])
| extend splitMessage = split(Message, ".")
| extend DropRate = tostring(split(tostring(splitMessage[0]),"] ")[1])
| extend CurrentBurstRate = split(tostring(split(tostring(splitMessage[1]),"  ")[0]),"is ")
| extend CurrentBurstRatePerSec = toint(split(tostring(CurrentBurstRate[1])," ")[0])
| extend MaxConfiguredBurstRate = toint(CurrentBurstRate[2])
| extend CurrentAvgRate = split(tostring(split(tostring(splitMessage[1]),"  ")[1]),"is ")
| extend CurrentAvgRatePerSec = toint(split(tostring(CurrentAvgRate[1])," ")[0])
| extend MaxConfiguredAvgRate = toint(CurrentAvgRate[2])
| extend CumulativeTotal = toint(split(tostring(split(tostring(splitMessage[1]),"  ")[2]),"is ")[1])

Stage 6: summarize

| summarize last1hCumTotal = sum(CumulativeTotal), last1hAvgRatePerSec = avg(CurrentAvgRatePerSec), last1hAvgBurstRatePerSec = avg(CurrentBurstRatePerSec) by DeviceName, DeviceEventClassID, SourceIP, SourceOfDropRateCount, DropRate

Stage 7: join

last1h
| join (
  prev6h
) on DeviceName, DeviceEventClassID, SourceIP, SourceOfDropRateCount, DropRate

Stage 8: project

project DeviceEventClassID, DeviceName, DropRate, EndTimeUtc, SourceIP, SourceOfDropRateCount, StartTimeUtc, last1hAvgBurstRatePerSec, last1hAvgRatePerSec, last1hCumTotal, prev6hAvgBurstRatePerSec, prev6hAvgCumTotal, prev6hAvgRatePerSec, prev6hCumTotal

Stage 9: where

where (last1hAvgBurstRatePerSec > (prev6hAvgBurstRatePerSec * 2) or last1hAvgRatePerSec > (prev6hAvgRatePerSec * 2) or last1hCumTotal > (prev6hAvgCumTotal * 2))

Stage 10: extend

extend DomainIndex, HostName

Stage 11: extend

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

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
DeviceEventClassIDproject
DeviceNameproject
DropRateproject
EndTimeUtcproject
SourceIPproject
SourceOfDropRateCountproject
StartTimeUtcproject
last1hAvgBurstRatePerSecproject
last1hAvgRatePerSecproject
last1hCumTotalproject
prev6hAvgBurstRatePerSecproject
prev6hAvgCumTotalproject
prev6hAvgRatePerSecproject
prev6hCumTotalproject
DomainIndexextend
HostNameextend
HostNameDomainextend

CYFIRMA - Attack Surface - Weak Certificate Exposure - High 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 alert indicates that a weak or insecure SSL/TLS certificate has been detected on a public-facing asset monitored by Cyfirma. Such certificates do not meet modern encryption standards and are considered insecure, especially for handling sensitive transactions. This exposure increases the risk of man-in-the-middle attacks and loss of data integrity or confidentiality. Immediate remediation is advised by replacing weak certificates with strong, industry-compliant ones."

MITRE ATT&CK Coverage

TacticTechniques
Reconnaissance
Resource Development
Initial Access
Defense Impairment
Credential Access

Rule Body

id: 3b5a1c0e-7f3a-4d47-8416-6c0b8b91e9ce 
name: CYFIRMA - Attack Surface - Weak Certificate Exposure - High Rule
description: |
  "This alert indicates that a weak or insecure SSL/TLS certificate has been detected on a public-facing asset monitored by Cyfirma. 
  Such certificates do not meet modern encryption standards and are considered insecure, especially for handling sensitive transactions. 
  This exposure increases the risk of man-in-the-middle attacks and loss of data integrity or confidentiality. 
  Immediate remediation is advised by replacing weak certificates with strong, industry-compliant ones."
version: 1.0.1
kind: Scheduled
severity: High
requiredDataConnectors:
  - connectorId: CyfirmaAttackSurfaceAlertsConnector
    dataTypes:
      - CyfirmaASCertificatesAlerts_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - DefenseEvasion
  - ResourceDevelopment
  - Reconnaissance
  - InitialAccess
  - CredentialAccess
relevantTechniques:
  - T1553
  - T1588
  - T1595
  - T1190
  - T1552
query: |
  // High Severity Weak Certificate Exposure Detected
  let timeFrame = 5m;
  CyfirmaASCertificatesAlerts_CL
  | where severity == 'Critical' and TimeGenerated between (ago(timeFrame) .. now())
  | extend
      Description=description,
      FirstSeen=first_seen,
      LastSeen=last_seen,
      ValidFrom=valid_from,
      ValidTo=valid_to,
      RiskScore=risk_score,
      Domain=sub_domain,
      TopDomain=top_domain,
      Protocols=protocols,
      SelfSigned=self_signed,
      AlertUID=alert_uid,
      UID=uid,
      CertificateData= cert_data,
      CertificateHash=cert_hash,
      IssuedBy=issued_by,
      IssuedTo=issued_to,
      ProviderName='CYFIRMA',
      ProductName='DeCYFIR/DeTCT'
  | project
      TimeGenerated,
      Description,
      Domain,
      TopDomain,
      RiskScore,
      FirstSeen,
      LastSeen,
      ValidFrom,
      ValidTo,
      Protocols,
      SelfSigned,
      CertificateData,
      CertificateHash,
      IssuedBy,
      IssuedTo,
      ProviderName,
      ProductName
entityMappings:
  - entityType: DNS
    fieldMappings:
      - identifier: DomainName
        columnName: Domain
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: TopDomain
      - identifier: DnsDomain
        columnName: Domain
customDetails:
  TimeGenerated: TimeGenerated
  CertificateData: CertificateData
  CertificateHash: CertificateHash
  FirstSeen: FirstSeen
  LastSeen: LastSeen
  ValidFrom: ValidFrom
  ValidTo: ValidTo
  Protocols: Protocols
  SelfSigned: SelfSigned
  IssuedBy: IssuedBy
  IssuedTo: IssuedTo
  Description: Description
  RiskScore: RiskScore
  TopDomain: TopDomain
  Domain: Domain
alertDetailsOverride:
  alertDisplayNameFormat: CYFIRMA - High Severity Weak Certificate Exposure Detected for this Domain  - {{Domain}}
  alertDescriptionFormat: CYFIRMA - High Severity Weak Certificate Exposure Detected  - {{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

CyfirmaASCertificatesAlerts_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,
    ValidFrom=valid_from,
    ValidTo=valid_to,
    RiskScore=risk_score,
    Domain=sub_domain,
    TopDomain=top_domain,
    Protocols=protocols,
    SelfSigned=self_signed,
    AlertUID=alert_uid,
    UID=uid,
    CertificateData= cert_data,
    CertificateHash=cert_hash,
    IssuedBy=issued_by,
    IssuedTo=issued_to,
    ProviderName='CYFIRMA',
    ProductName='DeCYFIR/DeTCT'

Stage 4: project

| project
    TimeGenerated,
    Description,
    Domain,
    TopDomain,
    RiskScore,
    FirstSeen,
    LastSeen,
    ValidFrom,
    ValidTo,
    Protocols,
    SelfSigned,
    CertificateData,
    CertificateHash,
    IssuedBy,
    IssuedTo,
    ProviderName,
    ProductName

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
CertificateDataproject
CertificateHashproject
Descriptionproject
Domainproject
FirstSeenproject
IssuedByproject
IssuedToproject
LastSeenproject
ProductNameproject
Protocolsproject
ProviderNameproject
RiskScoreproject
SelfSignedproject
TimeGeneratedproject
TopDomainproject
ValidFromproject
ValidToproject

Datazag - impersonation domain resolved in DNS

#
Status
available
Severity
high
Time window
14d
Group by
Confidence, DatazagAlertRef, Id, IndicatorDomain, IndicatorEvidence, IndicatorRecordId, IndicatorStixId, IndicatorValidUntil
Source
github.com/Azure/Azure-Sentinel

Matches active Datazag impersonation indicators against DNS activity normalized by the ASIM Dns schema, so the rule works across any normalized DNS source without modification. A match means a host inside the estate resolved a domain Datazag scored as brand impersonation, platform impersonation or attacker infrastructure. Requires the ASIM DNS parsers.

MITRE ATT&CK Coverage

TacticTechniques
Initial Access
Command & Control

Telemetry Coverage

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

Rule Body

id: 8f3c1a4e-6b52-4d19-9c07-2a5e8d0f7b41
name: Datazag - impersonation domain resolved in DNS
description: |
  Matches active Datazag impersonation indicators against DNS activity normalized by the ASIM
  Dns schema, so the rule works across any normalized DNS source without modification. A match
  means a host inside the estate resolved a domain Datazag scored as brand impersonation,
  platform impersonation or attacker infrastructure. Requires the ASIM DNS parsers.
severity: High
status: Available
requiredDataConnectors:
  - connectorId: ThreatIntelligenceTaxii
    dataTypes:
      - ThreatIntelIndicators
tactics:
  - InitialAccess
  - CommandAndControl
relevantTechniques:
  - T1566
  - T1071
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
query: |
  let datazag_identity = "identity--55a5a448-c6f1-5128-bc71-4d85b719131e";
  let ioc_lookback = 14d;
  let dns_lookback = 1h;
  let confidence_floor = 85;
  let DatazagDomains =
      ThreatIntelIndicators
      | where TimeGenerated >= ago(ioc_lookback)
      | where tostring(Data.created_by_ref) == datazag_identity
      | where ObservableKey == "domain-name:value"
      | where isnotempty(ObservableValue)
      | summarize arg_max(TimeGenerated, *) by Id
      | where IsDeleted == false
      | where tostring(Data.revoked) != "true"
      | where isempty(ValidUntil) or ValidUntil > now()
      | where Confidence >= confidence_floor
      | extend IndicatorDomain = tolower(trim_end(@"\.", ObservableValue))
      | project
          IndicatorDomain,
          IndicatorStixId     = tostring(Data.id),
          IndicatorRecordId   = Id,
          DatazagAlertRef     = tostring(Data.external_references[0].external_id),
          Confidence,
          IndicatorEvidence   = tostring(Data.labels),
          IndicatorValidUntil = ValidUntil;
  let DnsActivity =
      _Im_Dns
      | where TimeGenerated >= ago(dns_lookback)
      | where isnotempty(DnsQuery)
      | extend IndicatorDomain = tolower(trim_end(@"\.", DnsQuery))
      | project
          DnsTime         = TimeGenerated,
          IndicatorDomain,
          DnsQuery,
          SrcIpAddr       = column_ifexists("SrcIpAddr", ""),
          SrcHostname     = column_ifexists("SrcHostname", ""),
          SrcUsername     = column_ifexists("SrcUsername", ""),
          SrcUsernameType = column_ifexists("SrcUsernameType", ""),
          EventVendor     = column_ifexists("EventVendor", ""),
          EventProduct    = column_ifexists("EventProduct", "");
  DatazagDomains
  | join kind=innerunique DnsActivity on IndicatorDomain
  | summarize
      FirstSeen   = min(DnsTime),
      LastSeen    = max(DnsTime),
      QueryCount  = count(),
      SourceIps   = make_set(SrcIpAddr, 50),
      SourceHosts = make_set(SrcHostname, 50),
      Upns        = make_set_if(SrcUsername, SrcUsernameType == "UPN", 50),
      SeenBy      = make_set(strcat(EventVendor, " ", EventProduct), 10)
      by
      IndicatorDomain, IndicatorStixId, IndicatorRecordId, DatazagAlertRef,
      Confidence, IndicatorEvidence, IndicatorValidUntil
  | extend
      SourceIp   = tostring(SourceIps[0]),
      SourceHost = tostring(SourceHosts[0]),
      AccountUpn = tostring(Upns[0])
  | order by LastSeen desc
eventGroupingSettings:
  aggregationKind: AlertPerResult
alertDetailsOverride:
  alertDisplayNameFormat: 'Datazag: {{IndicatorDomain}} resolved from inside the estate'
  alertDescriptionFormat: |
    {{IndicatorDomain}} was resolved from inside the estate. Datazag scored this indicator
    at confidence {{Confidence}}. Supporting evidence: {{IndicatorEvidence}}
customDetails:
  DatazagAlertRef: DatazagAlertRef
  DatazagIndicatorRef: IndicatorStixId
  DatazagConfidence: Confidence
  DatazagEvidence: IndicatorEvidence
  IndicatorExpiry: IndicatorValidUntil
  ResolutionCount: QueryCount
  ObservedBy: SeenBy
entityMappings:
  - entityType: DNS
    fieldMappings:
      - identifier: DomainName
        columnName: IndicatorDomain
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIp
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: SourceHost
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: AccountUpn
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: 1d
    matchingMethod: Selected
    groupByEntities:
      - DNS
suppressionEnabled: false
suppressionDuration: 1h
version: 1.0.0
kind: Scheduled

Stages and Predicates

Parameters

let datazag_identity = "identity--55a5a448-c6f1-5128-bc71-4d85b719131e";
let ioc_lookback = 14d;
let dns_lookback = 1h;
let confidence_floor = 85;

let DatazagDomains is inlined into the numbered stages below.

Let Binding: DnsActivity used in Stages 1, 14

let DnsActivity = _Im_Dns
    | where TimeGenerated >= ago(dns_lookback)
    | where isnotempty(DnsQuery)
    | extend IndicatorDomain = tolower(trim_end(@"\.", DnsQuery))
    | project
        DnsTime         = TimeGenerated,
        IndicatorDomain,
        DnsQuery,
        SrcIpAddr       = column_ifexists("SrcIpAddr", ""),
        SrcHostname     = column_ifexists("SrcHostname", ""),
        SrcUsername     = column_ifexists("SrcUsername", ""),
        SrcUsernameType = column_ifexists("SrcUsernameType", ""),
        EventVendor     = column_ifexists("EventVendor", ""),
        EventProduct    = column_ifexists("EventProduct", "");

Stage 1: source

let DnsActivity

Stage 2: source

ThreatIntelIndicators

Stage 3: where

where TimeGenerated >= ago(1209600s)

Stage 4: where

where Data.created_by_ref == "identity--55a5a448-c6f1-5128-bc71-4d85b719131e"

Stage 5: where

where ObservableKey == "domain-name:value"

Stage 6: where

where isnotempty(ObservableValue)

Stage 7: summarize

summarize by Id

Stage 8: where

where IsDeleted == false

Stage 9: where

where Data.revoked != true

Stage 10: where

where (isempty(ValidUntil) or ValidUntil > now())

Stage 11: where

where Confidence >= 85

Stage 12: extend

extend IndicatorDomain

Stage 13: project

project Confidence, DatazagAlertRef, IndicatorDomain, IndicatorEvidence, IndicatorRecordId, IndicatorStixId, IndicatorValidUntil

Stage 14: join

join kind=innerunique (DnsActivity) on IndicatorDomain

Stage 15: summarize

summarize FirstSeen, LastSeen, QueryCount, SeenBy, SourceHosts, SourceIps, Upns by IndicatorDomain, IndicatorStixId, IndicatorRecordId, DatazagAlertRef, Confidence, IndicatorEvidence, IndicatorValidUntil

Stage 16: extend

extend AccountUpn, SourceHost, SourceIp

Stage 17: sort

sort by LastSeen

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
Confidencesummarize
DatazagAlertRefsummarize
FirstSeensummarize
IndicatorDomainsummarize
IndicatorEvidencesummarize
IndicatorRecordIdsummarize
IndicatorStixIdsummarize
IndicatorValidUntilsummarize
LastSeensummarize
QueryCountsummarize
SeenBysummarize
SourceHostssummarize
SourceIpssummarize
Upnssummarize
AccountUpnextend
SourceHostextend
SourceIpextend

Detect Malicious Usage of Recovery Tools to Delete Backup Files

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

This analytic rule detects usage of recovery tools vssadmin, wbadmin, wmic and bcedit to delete backup files or change recovery configuration. Adversaries may use these tools to delete shadow copies and backup files to prevent recovery of files. https://attack.mitre.org/techniques/T1490/

MITRE ATT&CK Coverage

TacticTechniques
Impact

Telemetry Coverage

Rule Body

id: 259de2c1-c546-4c6d-a17c-df639722f4d7
name: Detect Malicious Usage of Recovery Tools to Delete Backup Files
description: |
  This analytic rule detects usage of recovery tools vssadmin, wbadmin, wmic and bcedit to delete backup files or change recovery configuration. Adversaries may use these tools to delete shadow copies and backup files to prevent recovery of files.
  https://attack.mitre.org/techniques/T1490/
severity: High
status: Available 
tags:
  - Schema: _ASim_ProcessEvent
    SchemaVersion: 0.1.4
requiredDataConnectors:
  - connectorId: CrowdStrikeFalconEndpointProtection
    dataTypes: 
      - CommonSecurityLog
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - SecurityAlert
  - connectorId: SentinelOne
    dataTypes:
      - SentinelOne_CL
  - connectorId: VMwareCarbonBlack
    dataTypes:
      - CarbonBlackEvents_CL
  - connectorId: CiscoSecureEndpoint
    dataTypes:
      - CiscoSecureEndpoint_CL
  - connectorId: TrendMicroApexOne
    dataTypes:
      - TMApexOneEvent
  - connectorId: TrendMicroApexOneAma
    dataTypes:
      - TMApexOneEvent
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Impact
relevantTechniques:
  - T1490
query: |
  _ASim_ProcessEvent
  | where TargetProcessFilename has_any ('vssadmin.exe', 'wbadmin.exe', 'wmic.exe')
  | where CommandLine has_all ('delete', 'shadow')
  | union isfuzzy=True 
      (_ASim_ProcessEvent
      | where TargetProcessFilename =~ 'bcedit.exe'
      | where CommandLine has_all ('/set', 'recoveryenabled no')
      )
  | project
      TimeGenerated,
      DvcHostname,
      DvcIpAddr,
      DvcDomain,
      TargetUsername,
      TargetUsernameType,
      TargetProcessName,
      TargetProcessId,
      CommandLine
  | extend Username = iff(tostring(TargetUsernameType) == 'Windows', tostring(split(TargetUsername, '\\')[1]), TargetUsername)
  | extend NTDomain = iff(tostring(TargetUsernameType) == 'Windows', tostring(split(TargetUsername, '\\')[0]), TargetUsername)
  | extend Username = iff(tostring(TargetUsernameType) == 'UPN', tostring(split(TargetUsername, '@')[0]), Username)
  | extend UPNSuffix = iff(tostring(TargetUsernameType) == 'UPN', tostring(split(TargetUsername, '@')[1]), '')
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: DvcHostname
      - identifier: DnsDomain
        columnName: DvcDomain
      - identifier: NTDomain
        columnName: NTDomain
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: DvcIpAddr
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: Username
      - identifier: UPNSuffix
        columnName: UPNSuffix
      - identifier: NTDomain
        columnName: NTDomain
  - entityType: Process
    fieldMappings:
      - identifier: ProcessId
        columnName: TargetProcessId
      - identifier: CommandLine
        columnName: CommandLine
eventGroupingSettings:
  aggregationKind: AlertPerResult
alertDetailsOverride:
  alertDisplayNameFormat: "Tool {{TargetProcessName}} used to delete backup files on {{DvcHostname}} by {{TargetUsername}}"
  alertDescriptionFormat: "A system tool {{TargetProcessName}} ProcessId: ({{TargetProcessId}}) with {{CommandLine}} used to delete backup files."
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

_ASim_ProcessEvent

Stage 2: where

| where TargetProcessFilename has_any ('vssadmin.exe', 'wbadmin.exe', 'wmic.exe')

Stage 3: where

| where CommandLine has_all ('delete', 'shadow')

Stage 4: union

union of 1 branches

Stage 5: source

_ASim_ProcessEvent

Stage 6: where

| where TargetProcessFilename =~ 'bcedit.exe'

Stage 7: where

| where CommandLine has_all ('/set', 'recoveryenabled no')

Stage 8: project

project CommandLine, DvcDomain, DvcHostname, DvcIpAddr, TargetProcessId, TargetProcessName, TargetUsername, TargetUsernameType, TimeGenerated

Stage 9: extend (4 consecutive steps)

extend NTDomain, UPNSuffix, Username
Username =
ifTargetUsernameType == "Windows"tostring(split(TargetUsername, '\\')[1])
elseTargetUsername

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
CommandLinematch
  • /set transforms: term
  • delete transforms: term
  • recoveryenabled no transforms: term
  • shadow transforms: term
field:"CommandLine" kind:match
TargetProcessFilenameeq
  • bcedit.exe
field:"TargetProcessFilename" kind:eq value:"bcedit.exe"
TargetProcessFilenamematch
  • vssadmin.exe transforms: term
  • wbadmin.exe transforms: term
  • wmic.exe transforms: term
field:"TargetProcessFilename" kind:match

Output Fields

These fields are emitted when the rule matches.

FieldSource
CommandLineproject
DvcDomainproject
DvcHostnameproject
DvcIpAddrproject
TargetProcessIdproject
TargetProcessNameproject
TargetUsernameproject
TargetUsernameTypeproject
TimeGeneratedproject
Usernameextend
NTDomainextend
UPNSuffixextend

Dynatrace - Problem detection

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

'Detect application & infrastructure problems in your environment'

MITRE ATT&CK Coverage

Rule Body

id: 415978ff-074e-4203-824a-b06153d77bf7
name: Dynatrace - Problem detection
description: |
  'Detect application & infrastructure problems in your environment'
severity: Informational
status: Available
requiredDataConnectors:
  - connectorId: DynatraceProblems
    dataTypes:
      - DynatraceProblems
  - connectorId: DynatraceProblemsCCPDefinition
    dataTypes:
      - DynatraceProblemsV2_CL
  - connectorId: DTProblemsV3CCPDefinition
    dataTypes:
      - DynatraceProblemsV3_CL
tactics:
  - DefenseEvasion
  - Execution
  - Impact
  - InitialAccess
  - LateralMovement
  - Persistence
  - PrivilegeEscalation
relevantTechniques:
  - T1140
  - T1059
  - T1565
  - T1659
  - T1210
  - T1554
  - T1548
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
  aggregationKind: AlertPerResult
query: |
  DynatraceProblems
  | summarize  arg_max(StartTime, *) by ProblemId
alertDetailsOverride:
  alertDisplayNameFormat: 'Dynatrace problem detected - {{DisplayId}} : {{Title}}'
  alertDescriptionFormat: |
    A application and/or infrastructure problem has been detected in your environment
  alertSeverityColumnName: Severity
customDetails:
  ProblemIdentifier: ProblemId
  DisplayIdentifier: DisplayId
  ImpactLevel: ImpactLevel
  SeverityLevel: SeverityLevel
incidentConfiguration:
  createIncident: false
  groupingConfiguration:
    enabled: false
    reopenClosedIncident: false
    lookbackDuration: PT5H
    matchingMethod: AllEntities
version: 1.0.4
kind: Scheduled

Stages and Predicates

Stage 1: source

DynatraceProblems

Stage 2: summarize

| summarize  arg_max(StartTime, *) by ProblemId

Output Fields

These fields are emitted when the rule matches.

FieldSource
ProblemIdsummarize

Google DNS - IP check activity

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

'Detects requests to ip lookup resources.'

MITRE ATT&CK Coverage

TacticTechniques
Command & Control

Rule Body

id: 35221a58-cacb-4174-9bb4-ee777784fbce
name: Google DNS - IP check activity
description: |
  'Detects requests to ip lookup resources.'
severity: Medium
requiredDataConnectors:
  - connectorId: GCPDNSDataConnector
    dataTypes:
      - GCPCloudDNS
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
relevantTechniques:
  - T1095
query: |
  let ip_check = dynamic(['whatismyipaddress.com', 'ip2location.com', 'ipaddress.my', 'whatismyip.com', 'ipinfo.info', 'checkmyip.com', 'myip.com', 'checkmyip.org', 'canireachthe.net', 'ipv4.icanhazip.com', 'ip.anysrc.net', 'edns.ip-api.com', 'wtfismyip.com', 'checkip.dyndns.org', 'api.2ip.ua', 'icanhazip.com', 'api.ipify.org', 'ip-api.com', 'checkip.amazonaws.com', 'ipecho.net', 'ipinfo.io', 'ipv4bot.whatismyipaddress.com', 'freegeoip.app', 'checkip.azurewebsites.net']);
  GCPCloudDNS
  | where Query in~ (ip_check)
  | extend DNSCustomEntity = Query, IPCustomEntity = SrcIpAddr
entityMappings:
  - entityType: DNS
    fieldMappings:
      - identifier: DomainName
        columnName: DNSCustomEntity
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPCustomEntity
version: 1.0.0
kind: Scheduled

Stages and Predicates

Let Binding: ip_check used in Stage 2

let ip_check = dynamic(['whatismyipaddress.com', 'ip2location.com', 'ipaddress.my', 'whatismyip.com', 'ipinfo.info', 'checkmyip.com', 'myip.com', 'checkmyip.org', 'canireachthe.net', 'ipv4.icanhazip.com', 'ip.anysrc.net', 'edns.ip-api.com', 'wtfismyip.com', 'checkip.dyndns.org', 'api.2ip.ua', 'icanhazip.com', 'api.ipify.org', 'ip-api.com', 'checkip.amazonaws.com', 'ipecho.net', 'ipinfo.io', 'ipv4bot.whatismyipaddress.com', 'freegeoip.app', 'checkip.azurewebsites.net']);

Stage 1: source

GCPCloudDNS

Stage 2: where

| where Query in~ (ip_check)

Stage 3: extend

| extend DNSCustomEntity = Query, IPCustomEntity = SrcIpAddr

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Queryin
  • api.2ip.ua
  • api.ipify.org
  • canireachthe.net
  • checkip.amazonaws.com
  • checkip.azurewebsites.net
  • checkip.dyndns.org
  • checkmyip.com
  • checkmyip.org
  • edns.ip-api.com
  • freegeoip.app
  • icanhazip.com
  • ip-api.com
  • ip.anysrc.net
  • ip2location.com
  • ipaddress.my
  • ipecho.net
  • ipinfo.info
  • ipinfo.io
  • ipv4.icanhazip.com
  • ipv4bot.whatismyipaddress.com
  • myip.com
  • whatismyip.com
  • whatismyipaddress.com
  • wtfismyip.com
field:"Query" kind:in

Output Fields

These fields are emitted when the rule matches.

FieldSource
DNSCustomEntityextend
IPCustomEntityextend

GTI - Insider Threat Alert Detected

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

Triggers an incident when a GTI Relevance System Alert of type insider_threat is ingested. Insider Threat alerts indicate that GTI has identified intelligence suggesting malicious or negligent activity by individuals with legitimate access to your organisation - such as employee credential sales, internal data exposure, or rogue employee activity observed in underground sources. Each unique Alert ID is grouped into a single incident.

MITRE ATT&CK Coverage

Rule Body

id: e5f6a7b8-c9d0-1234-efab-345678901234
name: GTI - Insider Threat Alert Detected
description: |
  Triggers an incident when a GTI Relevance System Alert of type insider_threat is ingested.
  Insider Threat alerts indicate that GTI has identified intelligence suggesting malicious or negligent
  activity by individuals with legitimate access to your organisation - such as employee credential
  sales, internal data exposure, or rogue employee activity observed in underground sources.
  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:
  - PrivilegeEscalation
  - Exfiltration
  - CredentialAccess
  - Impact
relevantTechniques:
  - T1068
  - T1078
  - T1567
  - T1552
  - T1485
query: |
  GTIRelevanceSystemAlerts
  | where DetailType == "insider_threat"
  | project
      TimeGenerated,
      AlertId,
      AlertName           = DisplayName,
      AlertState          = State,
      InsiderSeverity     = InsiderThreatSeverity,
      DiscoveryDocuments  = InsiderThreatDiscoveryDocumentIds,
      PriorityLevel,
      PriorityReasoning,
      SeverityLevel,
      SeverityConfidence,
      SeverityReasoning,
      RelevanceLevel,
      RelevanceConfidence,
      RelevanceReasoning,
      FindingCount,
      AiSummary,
      CommonThemes        = RelevanceEvidenceCommonThemes,
      DistinctThemes      = RelevanceEvidenceDistinctThemes,
      AuditCreateTime,
      AuditUpdateTime
alertDetailsOverride:
  alertDisplayNameFormat: 'GTI Insider Threat: {{AlertName}}'
  alertDescriptionFormat: |
    Insider Severity: {{InsiderSeverity}} | Priority: {{PriorityLevel}}

    Summary: {{AiSummary}}
customDetails:
  AlertId: AlertId
  AlertState: AlertState
  InsiderSeverity: InsiderSeverity
  PriorityLevel: PriorityLevel
  SeverityLevel: SeverityLevel
  RelevanceLevel: RelevanceLevel
  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 DetailType == "insider_threat"

Stage 3: project

| project
    TimeGenerated,
    AlertId,
    AlertName           = DisplayName,
    AlertState          = State,
    InsiderSeverity     = InsiderThreatSeverity,
    DiscoveryDocuments  = InsiderThreatDiscoveryDocumentIds,
    PriorityLevel,
    PriorityReasoning,
    SeverityLevel,
    SeverityConfidence,
    SeverityReasoning,
    RelevanceLevel,
    RelevanceConfidence,
    RelevanceReasoning,
    FindingCount,
    AiSummary,
    CommonThemes        = RelevanceEvidenceCommonThemes,
    DistinctThemes      = RelevanceEvidenceDistinctThemes,
    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
DiscoveryDocumentsproject
DistinctThemesproject
FindingCountproject
InsiderSeverityproject
PriorityLevelproject
PriorityReasoningproject
RelevanceConfidenceproject
RelevanceLevelproject
RelevanceReasoningproject
SeverityConfidenceproject
SeverityLevelproject
SeverityReasoningproject
TimeGeneratedproject

GWorkspace - API Access Granted

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

Triggers when API Access has been granted to a new client.

MITRE ATT&CK Coverage

TacticTechniques
Lateral Movement

Telemetry Coverage

Rule Body

id: c45a9804-5da8-11ec-bf63-0242ac130002
name: GWorkspace - API Access Granted
description: |
  'Triggers when API Access has been granted to a new client.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: GoogleWorkspaceReportsAPI
    dataTypes:
      - GWorkspaceActivityReports
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - DefenseEvasion
  - LateralMovement
relevantTechniques:
  - T1550
query: |
  GWorkspaceActivityReports
  | where EventMessage has "AUTHORIZE_API_CLIENT_ACCESS"
  | extend AccountCustomEntity = ActorEmail
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountCustomEntity
version: 1.0.1
kind: Scheduled

Stages and Predicates

Stage 1: source

GWorkspaceActivityReports

Stage 2: where

| where EventMessage has "AUTHORIZE_API_CLIENT_ACCESS"

Stage 3: extend

| extend AccountCustomEntity = ActorEmail

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
EventMessagematch
  • AUTHORIZE_API_CLIENT_ACCESS transforms: term
field:"EventMessage" kind:match value:"AUTHORIZE_API_CLIENT_ACCESS"

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountCustomEntityextend

GWorkspace - Possible brute force attack

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

Detects possible brute force attack.

MITRE ATT&CK Coverage

TacticTechniques
Credential Access

Telemetry Coverage

Rule Body

id: 8f6cd9a4-5e57-11ec-bf63-0242ac130002
name: GWorkspace - Possible brute force attack
description: |
  'Detects possible brute force attack.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: GoogleWorkspaceReportsAPI
    dataTypes:
      - GWorkspaceActivityReports
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CredentialAccess
relevantTechniques:
  - T1110
query: |
  let threshold = 5;
  GWorkspaceActivityReports
  | where EventMessage has "login_failure"
  | summarize login_attempts = count() by ActorEmail, bin(TimeGenerated, 5m)
  | where login_attempts > threshold
  | extend AccountCustomEntity = ActorEmail
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountCustomEntity
version: 1.0.1
kind: Scheduled

Stages and Predicates

Parameters

let threshold = 5;

Stage 1: source

GWorkspaceActivityReports

Stage 2: where

| where EventMessage has "login_failure"

Stage 3: summarize

| summarize login_attempts = count() by ActorEmail, bin(TimeGenerated, 5m)

Stage 4: where

| where login_attempts > threshold

Stage 5: extend

| extend AccountCustomEntity = ActorEmail

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
ActorEmailsummarize
login_attemptssummarize
AccountCustomEntityextend

Hunt for privilege escalation paths with high ACLs

#
Group by
$left.OutgoingNodeId, $right.SourceNodeId, SourceNodeId, SourceNodeLabel, SourceNodeName, TargetNodeLabel
Author
Robbe Van den Daele
Source
github.com/HybridBrothers/Hunting-Queries-Detection-Rules

When an adversary establishes data collection of an Active Directory domain, they regularly search for interesting accounts with privilege escalation paths using the genericWrite and genericAll ACL permissions on objects. When using BloodHound, it is very easy to get a visual overview of these paths in an Active Directory domain. This query tries to establish the same using Defender XDR Exposure Management.

MITRE ATT&CK Coverage

TacticTechniques
Initial Access
Persistence
Privilege Escalation
Stealth

References

Telemetry Coverage

Rule Body

let high_permissions = dynamic(["genericWrite", "genericAll"]);
let edge_labels = dynamic(["member of", "has permissions to", "can authenticate to", "can authenticate as", "has credentials of", "can impersonate as"]);
// Get users and groups with high ACL permissions on other objects
let HighPermissionLinks = (ExposureGraphEdges
    // Get edges related to roles
    | where EdgeLabel == "has role on"
    // Get edges containing high permission ACLs
    | extend Permissions = todynamic(EdgeProperties).rawData.acl.controlTypes
    | where Permissions has_any (high_permissions)
    // Exclude Domain and Enterprise Administrators as source node
    | where not(SourceNodeLabel == "group" and SourceNodeName in ("Domain Admins", "Enterprise Admins"))
    // Exclude Built-in administrator account
    | where not(SourceNodeLabel == "user" and SourceNodeName == "Administrator")
    | summarize TargetNodes = make_set(TargetNodeName), TargetNodeCount = count() by SourceNodeName, SourceNodeLabel, tostring(Permissions), TargetNodeLabel, SourceNodeId
);
let HighPermissionNodes = toscalar(
    HighPermissionLinks
    | summarize SourceNodes = make_set(SourceNodeName)
);
// Get edges for links to the high ACL permissions
ExposureGraphEdges
| where TargetNodeName in (HighPermissionNodes)
| make-graph SourceNodeId --> TargetNodeId with ExposureGraphNodes on NodeId
// Get between one and three relations
| graph-match (SourceNode)-[anyEdge*1..3]->(TargetNode)
    project IncomingNodeName = SourceNode.NodeName, 
    IncomingNodeLabel = SourceNode.NodeLabel,
    Edges = anyEdge.EdgeLabel, 
    OutgoingNodeName = TargetNode.NodeName,
    OutgoingNodeId = TargetNode.NodeId
// Filter for interesting edges
| where Edges has_any (edge_labels)
// Join the high permission ACLs
| join kind=inner HighPermissionLinks on $left.OutgoingNodeId == $right.SourceNodeId
// Exclude Domain and Enterprise Administrators as source node
| where not(IncomingNodeLabel == "group" and IncomingNodeName in ("Domain Admins", "Enterprise Admins"))
// Exclude Built-in administrator account
| where not(IncomingNodeLabel == "user" and IncomingNodeName == "Administrator")
| distinct IncomingNodeName, IncomingNodeLabel, tostring(Edges), OutgoingNodeName, OutgoingNodeLabel = SourceNodeLabel, tostring(Permissions), TargetNodeLabel, tostring(TargetNodes), TargetNodeCount

Stages and Predicates

Parameters

let high_permissions = dynamic(["genericWrite", "genericAll"]);
let edge_labels = dynamic(["member of", "has permissions to", "can authenticate to", "can authenticate as", "has credentials of", "can impersonate as"]);

Let Binding: HighPermissionLinks used in Stages 1, 7

let HighPermissionLinks = (ExposureGraphEdges
    | where EdgeLabel == "has role on"
    | extend Permissions = todynamic(EdgeProperties).rawData.acl.controlTypes
    | where Permissions has_any (high_permissions)
    | where not(SourceNodeLabel == "group" and SourceNodeName in ("Domain Admins", "Enterprise Admins"))
    | where not(SourceNodeLabel == "user" and SourceNodeName == "Administrator")
    | summarize TargetNodes = make_set(TargetNodeName), TargetNodeCount = count() by SourceNodeName, SourceNodeLabel, tostring(Permissions), TargetNodeLabel, SourceNodeId
);

Let Binding: HighPermissionNodes used in Stage 3

let HighPermissionNodes = toscalar(
    HighPermissionLinks
    | summarize SourceNodes = make_set(SourceNodeName)
);

Stage 1: source

let HighPermissionLinks

Stage 2: source

ExposureGraphEdges

Stage 3: where

where TargetNodeName == "HighPermissionNodes"

Stage 4: macro

macro

Stage 5: macro

macro

Stage 6: where

where (Edges has "member of" or Edges has "has permissions to" or Edges has "can authenticate to" or Edges has "can authenticate as" or Edges has "has credentials of" or Edges has "can impersonate as")

Stage 7: join

join kind=inner (HighPermissionLinks) on $left.OutgoingNodeId, $right.SourceNodeId

Stage 8: where

where not ((IncomingNodeLabel == "group" and IncomingNodeName in ("Domain Admins", "Enterprise Admins")))

Stage 9: where

where not ((IncomingNodeLabel == "user" and IncomingNodeName == "Administrator"))

Stage 10: distinct

distinct IncomingNodeLabel, IncomingNodeName, OutgoingNodeName, TargetNodeCount, TargetNodeLabel

Stage 11: summarize aggregation inside the join branch

summarize by SourceNodeName, SourceNodeLabel, TargetNodeLabel, SourceNodeId

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
EdgeLabeleq
  • has role on
field:"EdgeLabel" kind:eq value:"has role on"
Edgesmatch
  • can authenticate as transforms: term
  • can authenticate to transforms: term
  • can impersonate as transforms: term
  • has credentials of transforms: term
  • has permissions to transforms: term
  • member of transforms: term
field:"Edges" kind:match
Permissionsmatch
  • genericAll transforms: term
  • genericWrite transforms: term
field:"Permissions" kind:match
TargetNodeNamein
  • HighPermissionNodes
field:"TargetNodeName" kind:in value:"HighPermissionNodes"

Exclusions

The rule actively suppresses these predicates.

Output Fields

These fields are emitted when the rule matches.

FieldSource
SourceNodeIdsummarize
SourceNodeLabelsummarize
SourceNodeNamesummarize
TargetNodeLabelsummarize

Multiple failed attempts of NetBackup login

#

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

This rule generates an incident when there are more than 5 failed login attemts for a given host in the last 15 minutes.

MITRE ATT&CK Coverage

Rule Body

id: d39f0c47-2e85-49b9-a686-388c2eb7062c
name: Multiple failed attempts of NetBackup login
description: This rule generates an incident when there are more than 5 failed login attemts for a given host in the last 15 minutes.
severity: Medium
status: Available
requiredDataConnectors: []
queryFrequency: 15m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
tactics:
    - CredentialAccess
    - Discovery
relevantTechniques:
    - T1110
    - T1212
query: "NetBackupAlerts_CL\r\n| where operation_s contains \"LOGIN\" and Message\
    \ contains \"authentication failed\" \r\n| extend userName =  split(userName_s,\
    \ \"@\")[0]\r\n| extend host = split(userName_s, \"@\")[1] \r\n| summarize Total=count()\
    \ by tostring(host)\r\n| where Total >= 5"
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: host
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: false
    reopenClosedIncident: false
    lookbackDuration: 5h
    matchingMethod: AllEntities
suppressionDuration: PT5H
suppressionEnabled: false
eventGroupingSettings:
  aggregationKind: SingleAlert
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

NetBackupAlerts_CL

Stage 2: where

| where operation_s contains "LOGIN" and Message contains "authentication failed"

Stage 3: extend

| extend userName =  split(userName_s, "@")[0]

Stage 4: extend

| extend host = split(userName_s, "@")[1]

Stage 5: summarize

| summarize Total=count() by tostring(host)

Stage 6: where

| where Total >= 5

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
Totalsummarize

Pathlock TDnR - Generic Table Content Changes

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

Detects generic change documents for SAP table content, forwarded by Pathlock Threat Detection and Response. Direct table data modifications may bypass normal application validation and audit trails, and could indicate data manipulation, configuration tampering, or fraud concealment.

MITRE ATT&CK Coverage

TacticTechniques
Defense Impairment
Impact

Rule Body

id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c67
name: Pathlock TDnR - Generic Table Content Changes
kind: Scheduled
description: >-
  Detects generic change documents for SAP table content, forwarded by Pathlock Threat Detection
  and Response. Direct table data modifications may bypass normal application validation and audit
  trails, and could indicate data manipulation, configuration tampering, or fraud concealment.
severity: High
status: Available
requiredDataConnectors:
  - connectorId: Pathlock_TDnR
    dataTypes:
      - Pathlock_TDnR_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - DefenseEvasion
  - Impact
relevantTechniques:
  - T1565
  - T1562
query: |
  Pathlock_TDnR_CL
  | where DataSource == "TABLE_CHANGES"
  | 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 == "TABLE_CHANGES"

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 - Global System Change Setting Events

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

Detects changes to global SAP system change settings, forwarded by Pathlock Threat Detection and Response. System change option modifications (e.g. enabling software changes in production) bypass change management processes and may allow unauthorized code or configuration changes to be applied directly to production systems.

MITRE ATT&CK Coverage

TacticTechniques
Persistence
Defense Impairment

Rule Body

id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c65
name: Pathlock TDnR - Global System Change Setting Events
kind: Scheduled
description: >-
  Detects changes to global SAP system change settings, forwarded by Pathlock Threat Detection
  and Response. System change option modifications (e.g. enabling software changes in production)
  bypass change management processes and may allow unauthorized code or configuration changes
  to be applied directly to production systems.
severity: High
status: Available
requiredDataConnectors:
  - connectorId: Pathlock_TDnR
    dataTypes:
      - Pathlock_TDnR_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - DefenseEvasion
  - Persistence
relevantTechniques:
  - T1562
  - T1098
query: |
  Pathlock_TDnR_CL
  | where DataSource == "SYSTEM_CHANGELOG"
  | 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 == "SYSTEM_CHANGELOG"

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

ProofpointPOD - High risk message not discarded

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

'Detects when email with high risk score was not rejected or discarded by filters.'

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Rule Body

id: c7cd6073-6d2c-4284-a5c8-da27605bdfde
name: ProofpointPOD - High risk message not discarded
description: |
  'Detects when email with high risk score was not rejected or discarded by filters.'
severity: Low
status: Available 
requiredDataConnectors:
  - connectorId: ProofpointPOD
    dataTypes:
      - ProofpointPOD_message_CL
queryFrequency: 10m
queryPeriod: 10m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1566
query: |
  let lbtime = 10m;
  ProofpointPOD
  | where TimeGenerated > ago(lbtime)
  | where EventType == 'message'
  | where NetworkDirection == 'inbound'
  | where FilterDisposition !in ('reject', 'discard')
  | where FilterModulesSpamScoresOverall == '100'
  | project SrcUserUpn, DstUserUpn
  | extend AccountCustomEntity = SrcUserUpn
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: AccountCustomEntity
version: 1.0.1
kind: Scheduled

Stages and Predicates

Parameters

let lbtime = 10m;

Stage 1: source

ProofpointPOD

Stage 2: where

| where TimeGenerated > ago(lbtime)

Stage 3: where

| where EventType == 'message'

Stage 4: where

| where NetworkDirection == 'inbound'

Stage 5: where

| where FilterDisposition !in ('reject', 'discard')

Stage 6: where

| where FilterModulesSpamScoresOverall == '100'

Stage 7: project

| project SrcUserUpn, DstUserUpn

Stage 8: extend

| extend AccountCustomEntity = SrcUserUpn

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
DstUserUpnproject
SrcUserUpnproject
AccountCustomEntityextend

PRT Credential Stealing

#
Group by
$left.InitiatingProcessParentFileName, $left.InitiatingProcessParentId, $right.FileName, $right.ProcessId, DeviceId
Author
FalconForce
Source
github.com/FalconForceTeam/FalconFriday

This query detects when BrowserCore.exe is accessed by a suspicious process. The BrowserCore.exe binary is responsible for allowing browser add-ons to use Single Sign On via Azure AD. This rule detects when an uncommon process interacts with the BrowserCore.exe process.

Known False Positives

  • None expected.

MITRE ATT&CK Coverage

References

Telemetry Coverage

Rule Body

let timeframe = 2*1h;
let default_global_prevalence = 0;
let lolbins = dynamic(["at.exe", "atbroker.exe", "bash.exe", "bitsadmin.exe", "certreq.exe", "certutil.exe", "cmd.exe", "cmdkey.exe", "cmstp.exe", "control.exe", "csc.exe", "cscript.exe", "desktopimgdownldr.exe", "dfsvc.exe", "diantz.exe", "diskshadow.exe", "dnscmd.exe", "esentutl.exe", "eventvwr.exe", "expand.exe", "extexport.exe", "extrac32.exe", "findstr.exe", "forfiles.exe", "ftp.exe", "gfxdownloadwrapper.exe", "gpscript.exe", "hh.exe", "ie4uinit.exe", "ieexec.exe", "ilasm.exe", "infdefaultinstall.exe", "installutil.exe", "jsc.exe", "makecab.exe", "mavinject.exe", "microsoft.workflow.compiler.exe", "mmc.exe", "mpcmdrun.exe", "msbuild.exe", "msconfig.exe", "msdt.exe", "mshta.exe", "msiexec.exe", "netsh.exe", "odbcconf.exe", "pcalua.exe", "pcwrun.exe", "pktmon.exe", "presentationhost.exe", "print.exe", "psr.exe", "rasautou.exe", "reg.exe", "regasm.exe", "regedit.exe", "regini.exe", "register-cimprovider.exe", "regsvcs.exe", "regsvr32.exe", "replace.exe", "rpcping.exe", "rundll32.exe", "runonce.exe", "runscripthelper.exe", "sc.exe", "schtasks.exe", "scriptrunner.exe", "syncappvpublishingserver.exe", "ttdinject.exe", "tttracer.exe", "vbc.exe", "verclsid.exe", "wab.exe", "wmic.exe", "wscript.exe", "wsreset.exe", "xwizard.exe", "agentexecutor.exe", "appvlp.exe", "bginfo.exe", "cdb.exe", "csi.exe", "devtoolslauncher.exe", "dnx.exe", "dotnet.exe", "dxcap.exe", "excel.exe", "mftrace.exe", "msdeploy.exe", "msxsl.exe", "ntdsutil.exe", "powerpnt.exe", "rcsi.exe", "sqldumper.exe", "sqlps.exe", "sqltoolsps.exe", "squirrel.exe", "te.exe", "tracker.exe", "vsjitdebugger.exe", "winword.exe", "wsl.exe", "powershell.exe", "pwsh.exe"]);
// Adding services.exe and svchost.exe to the list of LOLBINs since these can be abused to start cmd.exe and talk to browsercore.
let extendedLolbins = array_concat(lolbins, dynamic(["services.exe", "svchost.exe"]));
let trustedProcess = dynamic(["chrome.exe", "teams.exe"]);
let trustedProcessHashes = materialize(DeviceProcessEvents | where ActionType =~ "ProcessCreated" |  where FileName in~ (trustedProcess) | distinct SHA1);
let suspiciousHashes = materialize(trustedProcessHashes | extend SHA1=tolower(SHA1) | invoke FileProfile(SHA1, 1000) | where not(ProfileAvailability =~ "Error") | where coalesce(GlobalPrevalence,default_global_prevalence) < 200 or isempty(GlobalPrevalence));
let allBrowserCores = DeviceProcessEvents
| where ingestion_time() >= ago(timeframe)
| where ActionType =~ "ProcessCreated"
| where ProcessVersionInfoOriginalFileName =~ "BrowserCore.exe";
allBrowserCores
// FileProfile is case-sensitive and works on lower-case hashes.
| extend SHA1=tolower(SHA1)
// We need the grandparent SHA1, since the normal execution hierarchy is chrome->cmd->browsercore.exe (or similar with Teams).
| join kind = leftouter (DeviceProcessEvents | where ActionType =~ "ProcessCreated" | where FileName in~ ((allBrowserCores | project InitiatingProcessParentFileName))) on $left.InitiatingProcessParentId == $right.ProcessId, DeviceId, $left.InitiatingProcessParentFileName == $right.FileName
// Filter grandparent.
| extend GrandParentFileSHA1 = SHA11 // Renaming SHA1 to GrandParentFileSHA1 for future readability.
| where GrandParentFileSHA1 in~ (suspiciousHashes) or InitiatingProcessParentFileName !in~ (trustedProcess)
// Filter parent.
| where InitiatingProcessSHA1 in~ (suspiciousHashes) or InitiatingProcessFileName !in~ (trustedProcess)
// Removing empty GrandParentFileSHA1 hashes since sometimes the process start event is not logged and hence GrandParentFileSHA1 is empty.
// This contaminates the results. A future improvement would be to check if the trusted process we're looking
// for is logged as parent (instead of as child).
| where not(isempty(GrandParentFileSHA1))
| invoke FileProfile(GrandParentFileSHA1, 1000)
| where not(ProfileAvailability =~ "Error")
| where coalesce(GlobalPrevalence,default_global_prevalence) < 200 or isempty(GlobalPrevalence) or InitiatingProcessParentFileName in~ (extendedLolbins)
// Begin environment-specific filter.
// End environment-specific filter.

Stages and Predicates

Parameters

let timeframe = 2*1h;
let default_global_prevalence = 0;
let extendedLolbins = array_concat(lolbins, dynamic(["services.exe", "svchost.exe"]));
let trustedProcess = dynamic(["chrome.exe", "teams.exe"]);

let allBrowserCores is inlined into the numbered stages below.

Let Binding: lolbins

let lolbins = dynamic(["at.exe", "atbroker.exe", "bash.exe", "bitsadmin.exe", "certreq.exe", "certutil.exe", "cmd.exe", "cmdkey.exe", "cmstp.exe", "control.exe", "csc.exe", "cscript.exe", "desktopimgdownldr.exe", "dfsvc.exe", "diantz.exe", "diskshadow.exe", "dnscmd.exe", "esentutl.exe", "eventvwr.exe", "expand.exe", "extexport.exe", "extrac32.exe", "findstr.exe", "forfiles.exe", "ftp.exe", "gfxdownloadwrapper.exe", "gpscript.exe", "hh.exe", "ie4uinit.exe", "ieexec.exe", "ilasm.exe", "infdefaultinstall.exe", "installutil.exe", "jsc.exe", "makecab.exe", "mavinject.exe", "microsoft.workflow.compiler.exe", "mmc.exe", "mpcmdrun.exe", "msbuild.exe", "msconfig.exe", "msdt.exe", "mshta.exe", "msiexec.exe", "netsh.exe", "odbcconf.exe", "pcalua.exe", "pcwrun.exe", "pktmon.exe", "presentationhost.exe", "print.exe", "psr.exe", "rasautou.exe", "reg.exe", "regasm.exe", "regedit.exe", "regini.exe", "register-cimprovider.exe", "regsvcs.exe", "regsvr32.exe", "replace.exe", "rpcping.exe", "rundll32.exe", "runonce.exe", "runscripthelper.exe", "sc.exe", "schtasks.exe", "scriptrunner.exe", "syncappvpublishingserver.exe", "ttdinject.exe", "tttracer.exe", "vbc.exe", "verclsid.exe", "wab.exe", "wmic.exe", "wscript.exe", "wsreset.exe", "xwizard.exe", "agentexecutor.exe", "appvlp.exe", "bginfo.exe", "cdb.exe", "csi.exe", "devtoolslauncher.exe", "dnx.exe", "dotnet.exe", "dxcap.exe", "excel.exe", "mftrace.exe", "msdeploy.exe", "msxsl.exe", "ntdsutil.exe", "powerpnt.exe", "rcsi.exe", "sqldumper.exe", "sqlps.exe", "sqltoolsps.exe", "squirrel.exe", "te.exe", "tracker.exe", "vsjitdebugger.exe", "winword.exe", "wsl.exe", "powershell.exe", "pwsh.exe"]);

Let Binding: trustedProcessHashes used in Stage 1

let trustedProcessHashes = materialize(DeviceProcessEvents | where ActionType =~ "ProcessCreated" |  where FileName in~ (trustedProcess) | distinct SHA1);

Let Binding: suspiciousHashes used in Stages 2, 11, 12

let suspiciousHashes = materialize(trustedProcessHashes | extend SHA1=tolower(SHA1) | invoke FileProfile(SHA1, 1000) | where not(ProfileAvailability =~ "Error") | where coalesce(GlobalPrevalence,default_global_prevalence) < 200 or isempty(GlobalPrevalence));

Stage 1: source

let trustedProcessHashes

Stage 2: source

let suspiciousHashes

Stage 3: source

let allBrowserCores

Stage 4: source

DeviceProcessEvents

Stage 5: where

where ...

Stage 6: where

where ActionType =~ "ProcessCreated"

Stage 7: where

where ProcessVersionInfoOriginalFileName =~ "BrowserCore.exe"

Stage 8: extend

extend SHA1

Stage 9: join

join kind=leftouter (DeviceProcessEvents) on $left.InitiatingProcessParentId, $right.ProcessId, DeviceId, $left.InitiatingProcessParentFileName, $right.FileName

Stage 10: extend

extend GrandParentFileSHA1

Stage 11: where

where (not (InitiatingProcessParentFileName in~ ("chrome.exe", "teams.exe")) or GrandParentFileSHA1 =~ "suspiciousHashes")

Stage 12: where

where (not (InitiatingProcessFileName in~ ("chrome.exe", "teams.exe")) or InitiatingProcessSHA1 =~ "suspiciousHashes")

Stage 13: where

where not (isempty(GrandParentFileSHA1))

Stage 14: invoke

invoke

Stage 15: where

where not (ProfileAvailability =~ "Error")

Stage 16: where

where (isempty(GlobalPrevalence) or GlobalPrevalence < 200 or InitiatingProcessParentFileName =~ "extendedLolbins")

Indicators

These rows show field, operator, and value matches.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
GrandParentFileSHA1is_null(no value, null check)excludes:GrandParentFileSHA1
ProfileAvailabilityeqErrorexcludes:ProfileAvailability field:"ProfileAvailability" value:"Error"

Output Fields

These fields are emitted when the rule matches.

FieldSource
SHA1extend
GrandParentFileSHA1extend

Sentinel One - Agent uninstalled from multiple hosts

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

'Detects when agent was uninstalled from multiple hosts.'

MITRE ATT&CK Coverage

TacticTechniques
Stealth

Rule Body

id: 4ad87e4a-d045-4c6b-9652-c9de27fcb442
name: Sentinel One - Agent uninstalled from multiple hosts
description: |
  'Detects when agent was uninstalled from multiple hosts.'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: SentinelOne
    dataTypes:
      - SentinelOne
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - DefenseEvasion
relevantTechniques:
  - T1070
query: | 
  SentinelOne
  | where ActivityType == 51
  | summarize count() by DataComputerName, bin(TimeGenerated, 30m)
  | where count_ > 1
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: DataComputerName
version: 1.0.2
kind: Scheduled

Stages and Predicates

Stage 1: source

SentinelOne

Stage 2: where

| where ActivityType == 51

Stage 3: summarize

| summarize count() by DataComputerName, bin(TimeGenerated, 30m)

Stage 4: where

| where count_ > 1

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
DataComputerNamesummarize

Sentinel One - Blacklist hash deleted

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

'Detects when blacklist hash was deleted.'

MITRE ATT&CK Coverage

TacticTechniques
Stealth

Rule Body

id: de339761-2298-4b37-8f1b-80ebd4f0b5f6
name: Sentinel One - Blacklist hash deleted
description: |
  'Detects when blacklist hash was deleted.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: SentinelOne
    dataTypes:
      - SentinelOne
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - DefenseEvasion
relevantTechniques:
  - T1070
query: | 
  SentinelOne
  | where ActivityType == 3020
  | project EventCreationTime, SrcUserName, Hash=EventSubStatus
  | extend AccountCustomEntity = SrcUserName, HashCustomEntity = Hash, HashAlgorithmCustomEntity = "SHA1"
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountCustomEntity
  - entityType: FileHash
    fieldMappings:
      - identifier: Value
        columnName: HashCustomEntity
      - identifier: Algorithm
        columnName: HashAlgorithmCustomEntity
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

SentinelOne

Stage 2: where

| where ActivityType == 3020

Stage 3: project

| project EventCreationTime, SrcUserName, Hash=EventSubStatus

Stage 4: extend

| extend AccountCustomEntity = SrcUserName, HashCustomEntity = Hash, HashAlgorithmCustomEntity = "SHA1"

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
EventCreationTimeproject
Hashproject
SrcUserNameproject
AccountCustomEntityextend
HashAlgorithmCustomEntityextend
HashCustomEntityextend

Subtenant Deleted

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

Detects when a subtenant is deleted from Veeam Backup & Replication. This might indicate unauthorized removal of critical components.

Rule Body

id: 1c23715c-3a28-4b98-b135-fece6e7dcb8b
name: Subtenant Deleted
description: Detects when a subtenant is deleted from Veeam Backup & Replication.
  This might indicate unauthorized removal of critical components.
severity: High
status: Available
requiredDataConnectors:
- connectorId: Syslog
  dataTypes:
  - Syslog
- connectorId: SyslogAma
  dataTypes:
  - Syslog
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
  aggregationKind: AlertPerResult
tactics: []
relevantTechniques: []
query: "Veeam_GetSecurityEvents\n| where instanceId == 25210\n| project\n    Date\
  \ = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),\n    DataSource = original_host,\n\
  \    EventId = instanceId,\n    UserName = user,\n  MessageDetails = Description,\n\
  \    Severity = SeverityDescription"
version: 1.0.1
kind: Scheduled
customDetails:
  Date: Date
  VbrHostName: DataSource
  EventId: EventId
  MessageDetails: MessageDetails
  Severity: Severity

Stages and Predicates

Stage 1: source

Veeam_GetSecurityEvents

Stage 2: where

| where instanceId == 25210

Stage 3: project

| project
    Date = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),
    DataSource = original_host,
    EventId = instanceId,
    UserName = user,
  MessageDetails = Description,
    Severity = SeverityDescription

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
DataSourceproject
Dateproject
EventIdproject
MessageDetailsproject
Severityproject
UserNameproject

TIE DCSync

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

'Searches for DCSync attacks.'

MITRE ATT&CK Coverage

TacticTechniques
Credential Access

Rule Body

id: 19d1f964-ddcf-437b-92ce-b9c1c14d24f1
name: TIE DCSync
description: |
  'Searches for DCSync attacks.'
severity: High
requiredDataConnectors:
  - connectorId: TenableIE
    dataTypes:
      - Tenable_IE_CL
queryFrequency: 2h
queryPeriod: 2h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CredentialAccess
relevantTechniques:
  - T1003.006
query: |
  // For the query to work properly, make sure you have imported the afad_parser.yaml parser into the workspace
  // Retrieve the parser here: https://aka.ms/sentinel-TenableApp-afad-parser
  // Then, create the Kusto Function with alias afad_parser
  afad_parser
    | where MessageType == 2 and Codename == "DCSync"
    | extend HostName = tostring(split(Host, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(Host, '.'), 1, -1), '.'))
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: DnsDomain
version: 1.0.1
kind: Scheduled

Stages and Predicates

Stage 1: source

afad_parser

Stage 2: where

| where MessageType == 2 and Codename == "DCSync"

Stage 3: extend

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

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Codenameeq
  • DCSync corpus 3 (kusto 3)
field:"Codename" kind:eq value:"DCSync"
MessageTypeeq
  • 2 corpus 21 (kusto 21)
field:"MessageType" kind:eq value:"2"

Output Fields

These fields are emitted when the rule matches.

FieldSource
DnsDomainextend
HostNameextend

Whisper Security - C2 Communication Detection

#
Status
available
Severity
high
Time window
1h
Group by
$left.matchIndicator, $right.indicator
Source
github.com/Azure/Azure-Sentinel

Detects command-and-control communication by joining CommonSecurityLog with Whisper threat intelligence data. Fires when network traffic matches a known C2 indicator with a threat score above 60.

MITRE ATT&CK Coverage

TacticTechniques
Command & Control

Rule Body

id: 00682c4c-b8b5-5ef7-af33-50891b271b7b
name: Whisper Security - C2 Communication Detection
description: |
  Detects command-and-control communication by joining CommonSecurityLog with Whisper threat intelligence data. Fires when network traffic matches a known C2 indicator with a threat score above 60.
severity: High
status: Available
requiredDataConnectors:
- connectorId: WhisperSecurityConnector
  dataTypes:
  - WhisperThreatIntel_CL
queryFrequency: 5m
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- CommandAndControl
relevantTechniques:
- T1071
query: |
  // MITRE ATT&CK: T1071 - Application Layer Protocol
  // Tactic: Command and Control
  // Detects C2 communication by correlating network logs with Whisper threat intel
  let timeRange = 1h;
  let threatThreshold = 60;
  let c2Indicators = WhisperThreatIntel_CL
      | where TimeGenerated > ago(timeRange)
      | where isC2 == true
      | where threatScore > threatThreshold
      | project indicator, threatScore, threatLevel, feedNames, explanation, lastSeen;
  let CommonSecurityLogSafe = union isfuzzy=true CommonSecurityLog, (datatable(TimeGenerated:datetime, DestinationIP:string, DestinationHostName:string, SourceIP:string, DeviceProduct:string, DeviceAction:string)[]);
  CommonSecurityLogSafe
      | where TimeGenerated > ago(timeRange)
      | where isnotempty(DestinationIP) or isnotempty(DestinationHostName)
      | extend matchIndicator = coalesce(DestinationHostName, DestinationIP)
      | join kind=inner (c2Indicators) on $left.matchIndicator == $right.indicator
      | extend SourceIP = SourceIP, DestinationIP = DestinationIP, DnsDomain = DestinationHostName
      | project TimeGenerated, SourceIP, DestinationIP, DnsDomain, DeviceProduct, DeviceAction, threatScore, threatLevel, feedNames, explanation
entityMappings:
- entityType: IP
  fieldMappings:
  - identifier: Address
    columnName: SourceIP
- entityType: IP
  fieldMappings:
  - identifier: Address
    columnName: DestinationIP
- entityType: DNS
  fieldMappings:
  - identifier: DomainName
    columnName: DnsDomain
customDetails:
  ThreatScore: threatScore
  ThreatLevel: threatLevel
  FeedNames: feedNames
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: PT5H
    matchingMethod: AllEntities
version: 1.0.0
kind: Scheduled

Stages and Predicates

Parameters

let timeRange = 1h;
let threatThreshold = 60;

Let Binding: c2Indicators

let c2Indicators = WhisperThreatIntel_CL
    | where TimeGenerated > ago(timeRange)
    | where isC2 == true
    | where threatScore > threatThreshold
    | project indicator, threatScore, threatLevel, feedNames, explanation, lastSeen;

Union isfuzzy=true (2 Sources)

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

Leg 1: CommonSecurityLog

Leg 2: datatable(TimeGenerated:datetime,

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

Applied to the Combined Result

| where TimeGenerated > ago(timeRange) | where isnotempty(DestinationIP) or isnotempty(DestinationHostName) | extend matchIndicator = coalesce(DestinationHostName, DestinationIP) | join kind=inner (c2Indicators) on $left.matchIndicator == $right.indicator | extend SourceIP = SourceIP, DestinationIP = DestinationIP, DnsDomain = DestinationHostName | project TimeGenerated, SourceIP, DestinationIP, DnsDomain, DeviceProduct, DeviceAction, threatScore, threatLevel, feedNames, explanation

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
DestinationIPproject
DeviceActionproject
DeviceProductproject
DnsDomainproject
SourceIPproject
TimeGeneratedproject
explanationproject
feedNamesproject
threatLevelproject
threatScoreproject

Back to all Kusto rules