Detection rules › Kusto

Kusto rules: 27

[Deprecated] Explicit MFA Deny

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

User explicitly denies MFA push, indicating that login was not expected and the account's password may be compromised. This rule is deprecated as of July-2024. Alternative rule with similar logic and contex from more data source is available at https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Microsoft Entra ID/Analytic Rules/MFARejectedbyUser.yaml

MITRE ATT&CK Coverage

TacticTechniques
Credential Access

Telemetry Coverage

Rule Body

id: a22740ec-fc1e-4c91-8de6-c29c6450ad00
name: '[Deprecated] Explicit MFA Deny'
description: |
  'User explicitly denies MFA push, indicating that login was not expected and the account's password may be compromised.
  This rule is deprecated as of July-2024. Alternative rule with similar logic and contex from more data source 
  is available at https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Microsoft%20Entra%20ID/Analytic%20Rules/MFARejectedbyUser.yaml'
severity: Medium
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - SigninLogs
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AADNonInteractiveUserSignInLogs
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - DeviceInfo
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - CredentialAccess
relevantTechniques:
  - T1110
query: |
  let aadFunc = (tableName: string) {
      table(tableName)
      | where ResultType == 500121
      | where Status has "MFA Denied; user declined the authentication" or Status has "MFA denied; Phone App Reported Fraud"
      | extend Type = Type, PublicIP = IPAddress
      | extend
          Name = tostring(split(UserPrincipalName, '@', 0)[0]),
          UPNSuffix = tostring(split(UserPrincipalName, '@', 1)[0])
  };
  let aadSignin = aadFunc("SigninLogs");
  let dvcInfo = DeviceInfo
      | extend SensorHealthState = column_ifexists("SensorHealthState", "")
      | where OnboardingStatus == "Onboarded" and SensorHealthState == "Active"
      | project PublicIP, AadDeviceId;
  let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");
  union isfuzzy=true aadSignin, aadNonInt
  | join kind=leftouter dvcInfo on PublicIP
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: UserPrincipalName
      - identifier: Name
        columnName: Name
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: PublicIP
  - entityType: AzureResource
    fieldMappings:
      - identifier: ResourceId
        columnName: ResourceId
  - entityType: CloudApplication
    fieldMappings:
      - identifier: Name
        columnName: AppDisplayName
      - identifier: AppId
        columnName: AppId
version: 1.0.7
kind: Scheduled

Stages and Predicates

Parameters

let aadSignin = aadFunc("SigninLogs");
let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");

Let Binding: aadFunc

let aadFunc = (tableName: string) {
    table(tableName)
    | where ResultType == 500121
    | where Status has "MFA Denied; user declined the authentication" or Status has "MFA denied; Phone App Reported Fraud"
    | extend Type = Type, PublicIP = IPAddress
    | extend
        Name = tostring(split(UserPrincipalName, '@', 0)[0]),
        UPNSuffix = tostring(split(UserPrincipalName, '@', 1)[0])
};

Let Binding: dvcInfo

let dvcInfo = DeviceInfo
    | extend SensorHealthState = column_ifexists("SensorHealthState", "")
    | where OnboardingStatus == "Onboarded" and SensorHealthState == "Active"
    | project PublicIP, AadDeviceId;

Union isfuzzy=true (2 Sources)

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

Leg 1: aadSignin

Leg 2: aadNonInt

Applied to the Combined Result

| join kind=leftouter dvcInfo on PublicIP

Indicators

These rows show field, operator, and value matches.

Account Elevated to New Role

#
Severity
medium
Time window
14d
Group by
ElevatedRole, Id, TargetUserPrincipalName
Source
github.com/Azure/Azure-Sentinel

Detects an account that is elevated to a new role where that account has not had that role in the last 14 days. Role elevations are a key mechanism for gaining permissions, monitoring which users have which roles, and for anomalies in those roles is useful for finding suspicious activity. Ref: https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-privileged-accounts#changes-to-privileged-accounts

MITRE ATT&CK Coverage

TacticTechniques
Persistence

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: c1c66f0b-5531-4a3e-a619-9d2f770ef730
name: Account Elevated to New Role
description: |
  'Detects an account that is elevated to a new role where that account has not had that role in the last 14 days.
    Role elevations are a key mechanism for gaining permissions, monitoring which users have which roles, and for anomalies in those roles is useful for finding suspicious activity.
    Ref: https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-privileged-accounts#changes-to-privileged-accounts'
severity: Medium
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AuditLogs
queryFrequency: 1d
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Persistence
relevantTechniques:
  - T1078.004
tags:
  - AADSecOpsGuide
query: |
    let auditList =
    AuditLogs
    | where TimeGenerated >= ago(14d)
    | where OperationName =~ "Add member to role completed (PIM activation)"
    | where Result =~ "success"
    | extend TargetUserPrincipalName = tostring(TargetResources[2].userPrincipalName)
    | extend displayName = tostring(TargetResources[0].displayName)
    | extend displayName2 = tostring(TargetResources[3].displayName)
    | extend ElevatedRole = iif(displayName =~ "Member", displayName2, displayName)
    ;
    let lookbackList = auditList
    | where TimeGenerated between(ago(14d)..ago(1d))
    ;
    let recentList = auditList
    | where TimeGenerated > ago(1d)
    ;
    let newlyElevated = recentList
    | join kind = leftanti lookbackList on ElevatedRole, TargetUserPrincipalName
    ;
    newlyElevated | project Id, AdditionalDetails
    | mv-expand bagexpansion=array AdditionalDetails
    | evaluate bag_unpack(AdditionalDetails)
    | extend key = column_ifexists("key", ''), value = column_ifexists("value", '')
    | evaluate pivot(key, make_set(value))
    | extend ipaddr = todynamic(column_ifexists("ipaddr", ""))
    | mv-expand ipaddr
    | project Id, InitiatingIPAddress = tostring(ipaddr)
    | join kind=rightouter newlyElevated on Id
    | 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 = iff(isnotempty(tostring(InitiatedBy.user.ipAddress)), tostring(InitiatedBy.user.ipAddress), InitiatingIPAddress)
    | extend ElevatedBy = iff(isnotempty(InitiatingUserPrincipalName), InitiatingUserPrincipalName, InitiatingAppName)
    | extend ElevatedUser = TargetUserPrincipalName
    | extend InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1])
    | extend TargetAccountName = tostring(split(TargetUserPrincipalName, "@")[0]), TargetAccountUPNSuffix = tostring(split(TargetUserPrincipalName, "@")[1])
    | project-reorder ElevatedUser, ElevatedRole, ResultReason, ElevatedBy, InitiatingUserPrincipalName, InitiatingAadUserId, InitiatingIPAddress, TargetUserPrincipalName

entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: InitiatingUserPrincipalName
      - identifier: Name
        columnName: InitiatingAccountName
      - identifier: UPNSuffix
        columnName: InitiatingAccountUPNSuffix
  - entityType: Account
    fieldMappings:
      - identifier: AadUserId
        columnName: InitiatingAadUserId
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: TargetUserPrincipalName
      - identifier: Name
        columnName: TargetAccountName
      - identifier: UPNSuffix
        columnName: TargetAccountUPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: InitiatingIPAddress
version: 1.1.2
kind: Scheduled

Stages and Predicates

let auditList, let recentList and let newlyElevated are inlined into the numbered stages below.

Let Binding: lookbackList used in Stage 7

let lookbackList = auditList
| where TimeGenerated between(ago(14d)..ago(1d));

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

Stage 1: source

AuditLogs

Stage 2: where

| where TimeGenerated >= ago(14d)

Stage 3: where

| where OperationName =~ "Add member to role completed (PIM activation)"

Stage 4: where

| where Result =~ "success"

Stage 5: extend (4 consecutive steps)

| extend TargetUserPrincipalName = tostring(TargetResources[2].userPrincipalName)
| extend displayName = tostring(TargetResources[0].displayName)
| extend displayName2 = tostring(TargetResources[3].displayName)
| extend ElevatedRole = iif(displayName =~ "Member", displayName2, displayName)

Stage 6: where

| where TimeGenerated > ago(1d)

Stage 7: join (negated)

| join kind = leftanti lookbackList on ElevatedRole, TargetUserPrincipalName

Stage 8: project

newlyElevated
| project Id, AdditionalDetails

Stage 9: mv-expand

| mv-expand bagexpansion=array AdditionalDetails

Stage 10: evaluate

| evaluate bag_unpack(AdditionalDetails)

Stage 11: extend

| extend key = column_ifexists("key", ''), value = column_ifexists("value", '')

Stage 12: evaluate

| evaluate pivot(key, make_set(value))

Stage 13: extend

| extend ipaddr = todynamic(column_ifexists("ipaddr", ""))

Stage 14: mv-expand

| mv-expand ipaddr

Stage 15: project

| project Id, InitiatingIPAddress = tostring(ipaddr)

Stage 16: join

| join kind=rightouter newlyElevated on Id

Stage 17: extend (9 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 = iff(isnotempty(tostring(InitiatedBy.user.ipAddress)), tostring(InitiatedBy.user.ipAddress), InitiatingIPAddress)
| extend ElevatedBy = iff(isnotempty(InitiatingUserPrincipalName), InitiatingUserPrincipalName, InitiatingAppName)
| extend ElevatedUser = TargetUserPrincipalName
| extend InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1])
| extend TargetAccountName = tostring(split(TargetUserPrincipalName, "@")[0]), TargetAccountUPNSuffix = tostring(split(TargetUserPrincipalName, "@")[1])

Stage 18: project-reorder

| project-reorder ElevatedUser, ElevatedRole, ResultReason, ElevatedBy, InitiatingUserPrincipalName, InitiatingAadUserId, InitiatingIPAddress, TargetUserPrincipalName

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
OperationNameeq
  • Add member to role completed (PIM activation)
field:"OperationName" kind:eq value:"Add member to role completed (PIM activation)"
Resulteq
  • success corpus 23 (kusto 23)
field:"Result" kind:eq value:"success"

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
OperationNameeqAdd member to role completed (PIM activation)excludes:OperationName field:"OperationName" value:"Add member to role completed (PIM activation)"
Resulteqsuccessexcludes:Result field:"Result" value:"success"

Output Fields

These fields are emitted when the rule matches.

FieldSource
Idproject
InitiatingIPAddressextend
InitiatingAppNameextend
InitiatingAppServicePrincipalIdextend
InitiatingUserPrincipalNameextend
InitiatingAadUserIdextend
ElevatedByextend
ElevatedUserextend
InitiatingAccountNameextend
InitiatingAccountUPNSuffixextend
TargetAccountNameextend
TargetAccountUPNSuffixextend

API - BOLA

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

'42Crunch API protection against BOLA'

MITRE ATT&CK Coverage

TacticTechniques
Exfiltration

Rule Body

id: 1b047dc3-a879-4f99-949b-d1dc867efc83
name: API - BOLA
description: |
  '42Crunch API protection against BOLA'
severity: Medium
requiredDataConnectors:
  - connectorId: 42CrunchAPIProtection
    dataTypes:
      - FortyTwoCrunchAPIProtection
  - connectorId: FortyTwoCrunchAPIProtection
    dataTypes:
      - FortyTwoCrunchAPIProtection
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
  aggregationKind: SingleAlert
status: Available
tactics:
  - Exfiltration
relevantTechniques:
  - T1020
query: |
  let loginRec = FortyTwoCrunchAPIProtection
  | where TimeGenerated >= ago(5m) 
  | project-away NonBlockingMode, SourcePort, DestinationPort, Query, ApiId, RequestHeader, ResponseHeader, Errors, EventType, Uuid
  | where UriPath has "/api/login" and Status == 200;
  let ipAddress = toscalar(loginRec
  | top 1 by Timestamp desc
  | summarize by SourceIp);
  let listRec = FortyTwoCrunchAPIProtection
  | where TimeGenerated >= ago(5m) 
  | project-away NonBlockingMode, SourcePort, DestinationPort, Query, ApiId, RequestHeader, ResponseHeader, Errors, EventType, Uuid
  | where UriPath has "/api/accounts/list" and Status == 200;
  let listCnt = (toscalar(listRec | count));
  let accountRec = FortyTwoCrunchAPIProtection
  | where TimeGenerated >= ago(5m) 
  | project-away NonBlockingMode, SourcePort, DestinationPort, Query, ApiId, RequestHeader, ResponseHeader, Errors, EventType, Uuid
  | where SourceIp == ipAddress and UriPath matches regex "/api/accounts/\\d{6}$";
  let accountCnt = (toscalar(accountRec | count));
  let recCount = iff((toscalar(listRec | count) == 0) and (accountCnt > 5), accountCnt, 0);
  accountRec | take recCount
customDetails:
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIp
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: Hostname
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: InstanceName
version: 3.0.1
kind: Scheduled

Stages and Predicates

let accountRec is inlined into the numbered stages below.

Let Binding: loginRec

let loginRec = FortyTwoCrunchAPIProtection
| where TimeGenerated >= ago(5m) 
| project-away NonBlockingMode, SourcePort, DestinationPort, Query, ApiId, RequestHeader, ResponseHeader, Errors, EventType, Uuid
| where UriPath has "/api/login" and Status == 200;

Let Binding: ipAddress used in Stage 4

let ipAddress = toscalar(loginRec
| top 1 by Timestamp desc
| summarize by SourceIp);

Let Binding: listRec

let listRec = FortyTwoCrunchAPIProtection
| where TimeGenerated >= ago(5m) 
| project-away NonBlockingMode, SourcePort, DestinationPort, Query, ApiId, RequestHeader, ResponseHeader, Errors, EventType, Uuid
| where UriPath has "/api/accounts/list" and Status == 200;

Let Binding: listCnt

let listCnt = (toscalar(listRec | count));

Let Binding: accountCnt

let accountCnt = (toscalar(accountRec | count));

Let Binding: recCount used in Stage 5

let recCount = iff((toscalar(listRec | count) == 0) and (accountCnt > 5), accountCnt, 0);

Stages 1 to 4 define let accountRec as the rule's main pipeline source. Stage 5 uses it.

Stage 1: source

FortyTwoCrunchAPIProtection

Stage 2: where

| where TimeGenerated >= ago(5m)

Stage 3: project-away

| project-away NonBlockingMode, SourcePort, DestinationPort, Query, ApiId, RequestHeader, ResponseHeader, Errors, EventType, Uuid

Stage 4: where

| where SourceIp == ipAddress and UriPath matches regex "/api/accounts/\\d{6}$"

Stage 5: take

accountRec
| take recCount

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
UriPathregex_match
  • /api/accounts/\d{6}$
field:"UriPath" kind:regex_match value:"/api/accounts/\d{6}$"

Application Gateway WAF - SQLi Detection

#
Severity
high
Time window
6h
Group by
SQLI_Score, TotalInboundScore, Uri, action_s, clientIp_s, transactionId_g
Author
shabaz-github
Source
github.com/Azure/Azure-Sentinel

'Identifies a match for SQL Injection attack in the Application gateway WAF logs. The Threshold value in the query can be changed as per your infrastructure's requirement. References: https://owasp.org/Top10/A03_2021-Injection/'

MITRE ATT&CK Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: 68c0b6bb-6bd9-4ef4-9011-08998c8ef90f
name: Application Gateway WAF - SQLi Detection  
description: | 
  'Identifies a match for SQL Injection attack in the Application gateway WAF logs. The Threshold value in the query can be changed as per your infrastructure's requirement.
   References: https://owasp.org/Top10/A03_2021-Injection/'
severity: High 
requiredDataConnectors: 
  - connectorId: WAF
    dataTypes: 
      - AzureDiagnostics
queryFrequency: 6h 
queryPeriod: 6h 
triggerOperator: gt 
triggerThreshold: 0 
tactics: 
  - DefenseEvasion
  - Execution
  - InitialAccess
  - PrivilegeEscalation 
relevantTechniques:
  - T1211
  - T1059
  - T1190
  - T0890
tags:
  - SQL Injection
query:  |  
  let Threshold = 3;
  AzureDiagnostics
  | where Category == "ApplicationGatewayFirewallLog"
  | where action_s == "Matched"
  | project transactionId_g, hostname_s, requestUri_s, TimeGenerated, clientIp_s, Message, details_message_s, details_data_s
  | join kind = inner(
  AzureDiagnostics
  | where Category == "ApplicationGatewayFirewallLog"
  | where action_s == "Blocked"
  | parse Message with MessageText 'Total Inbound Score: ' TotalInboundScore ' - SQLI=' SQLI_Score ',XSS=' XSS_Score ',RFI=' RFI_Score ',LFI=' LFI_Score ',RCE=' RCE_Score ',PHPI=' PHPI_Score ',HTTP=' HTTP_Score ',SESS=' SESS_Score '): ' Blocked_Reason '; individual paranoia level scores:' Paranoia_Score
  | where Blocked_Reason contains "SQL Injection Attack" and toint(SQLI_Score) >=10 and toint(TotalInboundScore) >= 15) on transactionId_g
  | extend Uri = strcat(hostname_s,requestUri_s)
  | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), TransactionID = make_set(transactionId_g), Message = make_set(Message), Detail_Message = make_set(details_message_s), Detail_Data = make_set(details_data_s), Total_TransactionId = dcount(transactionId_g) by clientIp_s, Uri, action_s, SQLI_Score, TotalInboundScore
  | where Total_TransactionId >= Threshold
  
# The Threshold value above can be changed as per your infrastructure's requirement 
entityMappings:
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: Uri
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: clientIp_s 
version: 1.0.1
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: shabaz-github
    support:
        tier: Community
    categories:
        domains: [ "Security - Threat Protection", "Platform" ]

Stages and Predicates

Parameters

let Threshold = 3;

Stage 1: source

AzureDiagnostics

Stage 2: where

| where Category == "ApplicationGatewayFirewallLog"

Stage 3: where

| where action_s == "Matched"

Stage 4: project

| project transactionId_g, hostname_s, requestUri_s, TimeGenerated, clientIp_s, Message, details_message_s, details_data_s

Stage 5: join

| join kind = inner(
AzureDiagnostics
| where Category == "ApplicationGatewayFirewallLog"
| where action_s == "Blocked"
| parse Message with MessageText 'Total Inbound Score: ' TotalInboundScore ' - SQLI=' SQLI_Score ',XSS=' XSS_Score ',RFI=' RFI_Score ',LFI=' LFI_Score ',RCE=' RCE_Score ',PHPI=' PHPI_Score ',HTTP=' HTTP_Score ',SESS=' SESS_Score '): ' Blocked_Reason '; individual paranoia level scores:' Paranoia_Score
| where Blocked_Reason contains "SQL Injection Attack" and toint(SQLI_Score) >=10 and toint(TotalInboundScore) >= 15) on transactionId_g

Stage 6: extend

extend Uri

Stage 7: summarize

summarize Detail_Data, Detail_Message, EndTime, Message, StartTime, Total_TransactionId, TransactionID by clientIp_s, Uri, action_s, SQLI_Score, TotalInboundScore

Stage 8: where

where Total_TransactionId >= 3

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
Detail_Datasummarize
Detail_Messagesummarize
EndTimesummarize
Messagesummarize
SQLI_Scoresummarize
StartTimesummarize
TotalInboundScoresummarize
Total_TransactionIdsummarize
TransactionIDsummarize
Urisummarize
action_ssummarize
clientIp_ssummarize

BloodHound Attack Path Finding - Computers Vulnerable to Coercion-Based NTLM Relay to SMB Attack

#

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 - Computers Vulnerable to Coercion-Based NTLM Relay to SMB Attack

Rule Body

id: e9ef6adb-6fa3-49ff-b105-c4cbd4f37808
name: BloodHound Attack Path Finding - Computers Vulnerable to Coercion-Based NTLM
  Relay to SMB Attack
description: BloodHound Attack Path Finding - Computers Vulnerable to Coercion-Based
  NTLM Relay to SMB Attack
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: BloodHoundEnterprise
    dataTypes:
      - BHEAttackPathsData_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: "BHEAttackPathsData_CL \n| where Finding == \"LargeDefaultGroupsCoerceAndRelayNTLMToSMB\""
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 == "LargeDefaultGroupsCoerceAndRelayNTLMToSMB"

Indicators

These rows show field, operator, and value matches.

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

BloodHound Attack Path Finding - Kerberos Delegation on Tier Zero Objects

#

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 - Kerberos Delegation on Tier Zero Objects

Rule Body

id: 2f770ce2-97cb-4afc-8dce-28114c3ad66e
name: BloodHound Attack Path Finding - Kerberos Delegation on Tier Zero Objects
description: BloodHound Attack Path Finding - Kerberos Delegation on Tier Zero Objects
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: BloodHoundEnterprise
    dataTypes:
      - BHEAttackPathsData_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: "BHEAttackPathsData_CL \n| where Finding == \"T0MarkSensitive\""
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 == "T0MarkSensitive"

Indicators

These rows show field, operator, and value matches.

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

Cisco SDWAN - Maleware Events

#
Status
available
Severity
high
Time window
3h
Group by
NetflowFwSrcAddrIpv4, SourceIP
Source
github.com/Azure/Azure-Sentinel

'This analytic rule will monitor Malware Events in Syslog and Netflow Data'

MITRE ATT&CK Coverage

TacticTechniques
Resource Development

Rule Body

id: cb14defd-3415-4420-a2e4-2dd0f3e07a86
name: Cisco SDWAN - Maleware Events
description: |
  'This analytic rule will monitor Malware Events in Syslog and Netflow Data'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: CiscoSDWAN
    dataTypes:
      - CiscoSyslogUTD
  - connectorId: CiscoSDWAN
    dataTypes:
      - CiscoSDWANNetflow
queryFrequency: 3h
queryPeriod: 3h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - ResourceDevelopment
relevantTechniques:
  - T1587.001
query: |
  CiscoSyslogUTD
  | where isnotempty(Malware) and Malware != "None"
  | distinct Malware, SourceIP
  | join kind=inner (CiscoSDWANNetflow
  | where isnotempty(NetflowUsername)
  | summarize arg_max(TimeStamp, NetflowUsername) by NetflowFwSrcAddrIpv4
  | distinct 
      ["Username"] = NetflowUsername,
      ["SourceIP"] = NetflowFwSrcAddrIpv4) on SourceIP
  | project Malware, SourceIP, Username
incidentConfiguration:
  createIncident: true
eventGroupingSettings:
    aggregationKind: AlertPerResult
customDetails:
  "mal_malware": "Malware"
  "mal_src_ip": "SourceIP"
  "mal_username": "Username"
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIP
  - entityType: Malware
    fieldMappings:
      - identifier: Name
        columnName: Malware
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: Username
version: 1.0.1
kind: Scheduled

Stages and Predicates

Stage 1: source

CiscoSyslogUTD

Stage 2: where

| where isnotempty(Malware) and Malware != "None"

Stage 3: distinct

| distinct Malware, SourceIP

Stage 4: join

| join kind=inner (CiscoSDWANNetflow
| where isnotempty(NetflowUsername)
| summarize arg_max(TimeStamp, NetflowUsername) by NetflowFwSrcAddrIpv4
| distinct 
    ["Username"] = NetflowUsername,
    ["SourceIP"] = NetflowFwSrcAddrIpv4) on SourceIP

Stage 5: project

project Malware, SourceIP, Username

Stage 6: summarize aggregation inside the join branch

summarize by NetflowFwSrcAddrIpv4

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Malwareis_not_null
  • (no value, null check)
field:"Malware" kind:is_not_null
Malwarene
  • None
field:"Malware" kind:ne value:"None"
NetflowUsernameis_not_null
  • (no value, null check)
field:"NetflowUsername" kind:is_not_null

Output Fields

These fields are emitted when the rule matches.

FieldSource
NetflowFwSrcAddrIpv4summarize

COM Event System Loading New DLL

#
Severity
medium
Time window
1d
Group by
$left.Image, $right.ParentImage
Author
Shain
Source
github.com/Azure/Azure-Sentinel

This query uses Sysmon Image Load (Event ID 7) and Process Create (Event ID 1) data to look for COM Event System being used to load a newly seen DLL.

MITRE ATT&CK Coverage

TacticTechniques
Privilege Escalation

Telemetry Coverage

Rule Body

id: 02f6c2e5-219d-4426-a0bf-ad67abc63d53
name: COM Event System Loading New DLL
description: |
  'This query uses Sysmon Image Load (Event ID 7) and Process Create (Event ID 1) data to look for COM Event System being used to load a newly seen DLL.'
severity: Medium
requiredDataConnectors:
  - connectorId: SecurityEvents
    dataTypes:
      - SecurityEvents
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - PrivilegeEscalation
relevantTechniques:
  - T1543
query: |
  let lookback_start = 7d;
  let lookback_end = 1d;
  let timedelta = 5s;
  // Get a list of previously seen DLLs being loaded
  let known_dlls = (Event
  | where TimeGenerated between(ago(lookback_start)..ago(lookback_end))
  | where EventID == 7
  | extend EvData = parse_xml(EventData)
  | extend EventDetail = EvData.DataItem.EventData.Data
  | extend LoadedItems = parse_json(tostring(parse_json(tostring(EvData.DataItem)).EventData)).["Data"]
  | mv-expand LoadedItems
  | where tostring(LoadedItems.["@Name"]) =~ "ImageLoaded"
  | extend DLL = tostring(LoadedItems.["#text"])
  | summarize by DLL);
  // Get Image Load events related to svchost.exe
  Event
  | where Source =~ "Microsoft-Windows-Sysmon"
  // Image Load Event in Sysmon
  | where EventID == 7
  | extend EvData = parse_xml(EventData)
  | extend EventDetail = EvData.DataItem.EventData.Data
  | extend Images = parse_json(tostring(parse_json(tostring(EvData.DataItem)).EventData)).["Data"]
  | mv-expand Images
  // Parse out executing process
  | where tostring(Images.["@Name"]) =~ "Image"
  | extend Image = tostring(Images.["#text"])
  | where Image endswith "\\svchost.exe"
  // Parse out loaded DLLs
  | extend LoadedItems = parse_json(tostring(parse_json(tostring(EvData.DataItem)).EventData)).["Data"]
  | mv-expand LoadedItems
  | where tostring(LoadedItems.["@Name"]) =~ "ImageLoaded"
  | extend DLL = tostring(LoadedItems.["#text"])
  | extend Image = tostring(Image)
  | extend ImageLoadTime = TimeGenerated
  // Join with processes with a command line related to COM Event System
  | join kind = inner(Event
  | where Source =~ "Microsoft-Windows-Sysmon"
  // Sysmon process execution events
  | where EventID == 1
  | extend RenderedDescription = tostring(split(RenderedDescription, ":")[0])
  | extend EventData = parse_xml(EventData).DataItem.EventData.Data
  | mv-expand bagexpansion=array EventData
  | evaluate bag_unpack(EventData)
  | extend Key = tostring(column_ifexists('@Name', "")), Value = column_ifexists('#text', "")
  | evaluate pivot(Key, any(Value), TimeGenerated, Source, EventLog, Computer, EventLevel, EventLevelName, EventID, UserName, RenderedDescription, MG, ManagementGroupName, Type, _ResourceId)
  | extend ParentImage = tostring(column_ifexists("ParentImage", "NotAvailable"))
  // Command line related to COM Event System
  | where ParentImage endswith "\\svchost.exe"
  //| where ParentCommandLine has_all (" -k LocalService"," -p"," -s EventSystem")
  | extend ProcessExecutionTime = TimeGenerated) on $left.Image == $right.ParentImage
  // Check timespan between DLL load and process creation
  | extend delta =  ProcessExecutionTime - ImageLoadTime
  | where ImageLoadTime <= ProcessExecutionTime and delta <= timedelta
  // Filter to only newly seen DLLs
  | where DLL !in (known_dlls)
  | extend ParentCommandLine = tostring(column_ifexists("ParentCommandLine", "NotAvailable"))
  | project-reorder ImageLoadTime, ProcessExecutionTime , Image, ParentCommandLine, DLL
  | extend Hashes = tostring(column_ifexists("Hashes", "NotAvailable, NotAvailable"))
  | extend Hashes = split(Hashes, ",")
  | mv-apply Hashes on (summarize FileHashes = make_bag(pack(tostring(split(Hashes, "=")[0]), tostring(split(Hashes, "=")[1]))))
  | extend SHA1 = tostring(FileHashes.SHA1)
  | extend HashAlgo = "SHA1"
  | extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
  | extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
  | extend Name = tostring(split(UserName, "\\")[1]), NTDomain = tostring(split(UserName, "\\")[0])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: UserName
      - identifier: Name
        columnName: Name
      - identifier: NTDomain
        columnName: NTDomain
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: Computer
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: HostNameDomain
  - entityType: FileHash
    fieldMappings:
      - identifier: Value
        columnName: SHA1
      - identifier: Algorithm
        columnName: HashAlgo
version: 1.0.4
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: Shain
    support:
        tier: Community
    categories:
        domains: [ "Security - Others" ]

Stages and Predicates

Parameters

let lookback_start = 7d;
let lookback_end = 1d;
let timedelta = 5s;

Let Binding: known_dlls used in Stage 16

let known_dlls = (Event
| where TimeGenerated between(ago(lookback_start)..ago(lookback_end))
| where EventID == 7
| extend EvData = parse_xml(EventData)
| extend EventDetail = EvData.DataItem.EventData.Data
| extend LoadedItems = parse_json(tostring(parse_json(tostring(EvData.DataItem)).EventData)).["Data"]
| mv-expand LoadedItems
| where tostring(LoadedItems.["@Name"]) =~ "ImageLoaded"
| extend DLL = tostring(LoadedItems.["#text"])
| summarize by DLL);

Stage 1: source

Event

Stage 2: where

where Source =~ "Microsoft-Windows-Sysmon"

Stage 3: where

where EventID == 7

Stage 4: extend (3 consecutive steps)

extend EvData, EventDetail, Images

Stage 5: mv-expand

mv-expand Images

Stage 6: where

where Images.@Name =~ "Image"

Stage 7: extend

extend Image

Stage 8: where

where Image endswith @"\svchost.exe"

Stage 9: extend

extend LoadedItems

Stage 10: mv-expand

mv-expand LoadedItems

Stage 11: where

where LoadedItems.@Name =~ "ImageLoaded"

Stage 12: extend (3 consecutive steps)

extend DLL, Image, ImageLoadTime

Stage 13: join

join kind=inner (Event) on $left.Image, $right.ParentImage

Stage 14: extend

extend delta

Stage 15: where

where ImageLoadTime <= ProcessExecutionTime and delta <= "5s"

Stage 16: where

where not (DLL == "known_dlls")

Stage 17: extend

extend ParentCommandLine

Stage 18: project-reorder

project-reorder

Stage 19: extend

extend Hashes

Stage 20: extend

extend Hashes

Stage 21: kusto:mv-apply

kusto:mv-apply

Stage 22: extend (5 consecutive steps)

extend DomainIndex, HashAlgo, HostName, HostNameDomain, NTDomain, Name, SHA1

Stage 23: summarize aggregation inside the mv-apply branch

summarize

Indicators

These rows show field, operator, and value matches.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
DLLeqknown_dllsexcludes:DLL field:"DLL" value:"known_dlls"

CyberArkEPM - Uncommon Windows process started from System folder

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

'Detects when uncommon windows proccess is started from System folder.'

MITRE ATT&CK Coverage

TacticTechniques
Execution
Stealth

Rule Body

id: 16b940d2-aaf8-4eaa-a5e1-05df5f5c3d43
name: CyberArkEPM - Uncommon Windows process started from System folder
description: |
  'Detects when uncommon windows proccess is started from System folder.'
severity: Medium
requiredDataConnectors:
  - connectorId: CyberArkEPM
    dataTypes:
      - CyberArkEPM
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Execution
  - DefenseEvasion
relevantTechniques:
  - T1204
  - T1036
query: | 
  let lb_period = 14d;
  let q_time = 1h;
  let sys_proc = CyberArkEPM
  | where TimeGenerated between (ago(lb_period) .. ago(q_time))
  | where EventSubType != 'AttackAttempt'
  | where ActingProcessName has @'\'
  | where ActingProcessName has_any ('System32', 'SysWOW64')
  | summarize makeset(ActingProcessFileInternalName);
  CyberArkEPM
  | where TimeGenerated > ago(q_time)
  | where EventSubType != 'AttackAttempt'
  | where ActingProcessName has @'\'
  | where ActingProcessName has_any ('System32', 'SysWOW64')
  | where ActingProcessFileInternalName !in (sys_proc)
  | extend AccountCustomEntity = ActorUsername
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountCustomEntity
version: 1.0.0
kind: Scheduled

Stages and Predicates

Parameters

let lb_period = 14d;
let q_time = 1h;

Let Binding: sys_proc used in Stage 6

let sys_proc = CyberArkEPM
| where TimeGenerated between (ago(lb_period) .. ago(q_time))
| where EventSubType != 'AttackAttempt'
| where ActingProcessName has @'\'
| where ActingProcessName has_any ('System32', 'SysWOW64')
| summarize makeset(ActingProcessFileInternalName);

Stage 1: source

CyberArkEPM

Stage 2: where

| where TimeGenerated > ago(q_time)

Stage 3: where

| where EventSubType != 'AttackAttempt'

Stage 4: where

| where ActingProcessName has @'\'

Stage 5: where

| where ActingProcessName has_any ('System32', 'SysWOW64')

Stage 6: where

| where ActingProcessFileInternalName !in (sys_proc)

Stage 7: extend

| extend AccountCustomEntity = ActorUsername

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
ActingProcessNamematch
  • SysWOW64 transforms: term
  • System32 transforms: term
  • \ transforms: term
field:"ParentImage" kind:match
EventSubTypene
  • AttackAttempt
field:"EventSubType" kind:ne value:"AttackAttempt"

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
ActingProcessFileInternalNameeqsys_procexcludes:ActingProcessFileInternalName field:"ActingProcessFileInternalName" value:"sys_proc"

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountCustomEntityextend

CYFIRMA - High Severity Attack Surface based Vulnerabilities Rule Alert

#

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
Group by
name
Source
github.com/Azure/Azure-Sentinel

"This rule detects high severity attack surface-based vulnerabilities from CYFIRMA's vulnerability intelligence data. It identifies vulnerabilities with a confidence score of 80 or higher, excluding those categorized as 'ASSET_VULNERABILITY', and generates alerts for assets that may be at risk."

MITRE ATT&CK Coverage

Rule Body

id: 6cc62c46-dd44-46d7-8681-8422f780eabd
name: CYFIRMA - High Severity Attack Surface based Vulnerabilities Rule Alert
description: |
  "This rule detects high severity attack surface-based vulnerabilities from CYFIRMA's vulnerability intelligence data. 
  It identifies vulnerabilities with a confidence score of 80 or higher, excluding those categorized as 'ASSET_VULNERABILITY', and generates alerts for assets that may be at risk."
version: 1.0.1
kind: Scheduled
severity: High
enabled: false
requiredDataConnectors:
  - connectorId: CyfirmaVulnerabilitiesIntelDC
    dataTypes:
      - CyfirmaVulnerabilities_CL
query: |
  // High severity - Attack Surface based Vulnerabilities
  let timeFrame= 5m;
  CyfirmaVulnerabilities_CL
  | 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 
      attack_complexity         = tostring(props.attack_complexity),
      cvss_score                = toreal(props.cvss_score),
      integrity_impact          = tostring(props.integrity_impact),
      impact_score              = tostring(props.impact_score),
      attack_vector             = tostring(props.attack_vector),
      privileges_required       = tostring(props.privileges_required),
      cvss_version              = tostring(props.cvss_version),
      user_interaction          = tostring(props.user_interaction),
      cvss_vector               = tostring(props.cvss_vector),
      scope                     = tostring(props.scope),
      confidentiality_impact    = tostring(props.confidentiality_impact),
      exploitability_score      = toreal(props.exploitability_score),
      products                  = tostring(props.products),
      technologies              = tostring(props.technologies),
      vendors                   = tostring(props.vendors),
      confidence_score          = toint(confidence),
      servers                   = tostring(props.servers),
      vulnerability_type        = tostring(props.vulnerability_type),
      vulnerability_category        = tostring(props.vulnerability_category),
      NetworkIPs                = tostring(props.ips),
      ProviderName              ='CYFIRMA',
      ProductName               ='DeCYFIR/DeTCT'
  | summarize arg_max(
                  integrity_impact,
                  TimeGenerated, 
                  id,
                  description,
                  confidence_score,
                  created,
                  modified,
                  attack_complexity,
                  cvss_score,
                  impact_score,
                  attack_vector,
                  privileges_required,
                  cvss_version,
                  user_interaction,
                  cvss_vector,
                  scope,
                  confidentiality_impact,
                  exploitability_score,
                  products,
                  technologies,
                  vendors,
                  ProviderName,
                  ProductName,
                  servers,
                  NetworkIPs,
                  vulnerability_type,
                  vulnerability_category
              )
      by name
  | where confidence_score >= 80 and vulnerability_category == 'ATTACK_SURFACE_VULNERABILITY' and TimeGenerated between (ago(timeFrame) .. now())
  | project 
      TimeGenerated,
      name,
      confidence_score,
      integrity_impact,
      attack_complexity,
      cvss_score,
      impact_score,
      attack_vector,
      UID = id,
      description,
      created,
      modified,
      privileges_required,
      cvss_version,
      user_interaction,
      cvss_vector,
      scope,
      confidentiality_impact,
      exploitability_score,
      products,
      technologies,
      vendors,
      ProviderName,
      ProductName,
      servers,
      NetworkIPs,
      vulnerability_type,
      vulnerability_category
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: GreaterThan
triggerThreshold: 0
suppressionDuration: 5m
suppressionEnabled: false
tactics:
  - Execution
  - LateralMovement
  - PrivilegeEscalation
  - InitialAccess
  - CredentialAccess
  - DefenseEvasion
relevantTechniques:
  - T1059
  - T1203
  - T1210
  - T1068
  - T1190
  - T1133
  - T1003
  - T1553
  - T1548.002
  - T1021.002
alertDetailsOverride:
  alertDisplayNameFormat: "CYFIRMA - High Severity Attack Surface based Vulnerability Identified - {{name}} "
  alertDescriptionFormat: "{{description}} "
  alertDynamicProperties:
    - alertProperty: ProductName
      value: ProductName
    - alertProperty: ProviderName
      value: ProviderName
customDetails:
  TimeGenerated: TimeGenerated
  CVE: name
  ConfidenceScore: confidence_score
  IntegrityImpact: integrity_impact
  AttackComplexity: attack_complexity
  CVSSScore: cvss_score
  ImpactScore: impact_score
  AttackVector: attack_vector
  ConfidentialImpact: confidentiality_impact
  PrivilegesRequired: privileges_required
  CVSSVersion: cvss_version
  UserInteraction: user_interaction
  CVSSVector: scope
  scope: exploitability_score
  ExploitabilityScore: modified
  Modified: products
  Products: technologies
  Vendors: vendors
  Technologies: technologies
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

CyfirmaVulnerabilities_CL

Stage 2: extend

| extend parsed = parse_json(extensions)

Stage 3: extend

| 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 
    attack_complexity         = tostring(props.attack_complexity),
    cvss_score                = toreal(props.cvss_score),
    integrity_impact          = tostring(props.integrity_impact),
    impact_score              = tostring(props.impact_score),
    attack_vector             = tostring(props.attack_vector),
    privileges_required       = tostring(props.privileges_required),
    cvss_version              = tostring(props.cvss_version),
    user_interaction          = tostring(props.user_interaction),
    cvss_vector               = tostring(props.cvss_vector),
    scope                     = tostring(props.scope),
    confidentiality_impact    = tostring(props.confidentiality_impact),
    exploitability_score      = toreal(props.exploitability_score),
    products                  = tostring(props.products),
    technologies              = tostring(props.technologies),
    vendors                   = tostring(props.vendors),
    confidence_score          = toint(confidence),
    servers                   = tostring(props.servers),
    vulnerability_type        = tostring(props.vulnerability_type),
    vulnerability_category        = tostring(props.vulnerability_category),
    NetworkIPs                = tostring(props.ips),
    ProviderName              ='CYFIRMA',
    ProductName               ='DeCYFIR/DeTCT'

Stage 6: summarize

| summarize arg_max(
                integrity_impact,
                TimeGenerated, 
                id,
                description,
                confidence_score,
                created,
                modified,
                attack_complexity,
                cvss_score,
                impact_score,
                attack_vector,
                privileges_required,
                cvss_version,
                user_interaction,
                cvss_vector,
                scope,
                confidentiality_impact,
                exploitability_score,
                products,
                technologies,
                vendors,
                ProviderName,
                ProductName,
                servers,
                NetworkIPs,
                vulnerability_type,
                vulnerability_category
            )
    by name

Stage 7: where

| where confidence_score >= 80 and vulnerability_category == 'ATTACK_SURFACE_VULNERABILITY' and TimeGenerated between (ago(timeFrame) .. now())

Stage 8: project

| project 
    TimeGenerated,
    name,
    confidence_score,
    integrity_impact,
    attack_complexity,
    cvss_score,
    impact_score,
    attack_vector,
    UID = id,
    description,
    created,
    modified,
    privileges_required,
    cvss_version,
    user_interaction,
    cvss_vector,
    scope,
    confidentiality_impact,
    exploitability_score,
    products,
    technologies,
    vendors,
    ProviderName,
    ProductName,
    servers,
    NetworkIPs,
    vulnerability_type,
    vulnerability_category

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
NetworkIPsproject
ProductNameproject
ProviderNameproject
TimeGeneratedproject
UIDproject
attack_complexityproject
attack_vectorproject
confidence_scoreproject
confidentiality_impactproject
createdproject
cvss_scoreproject
cvss_vectorproject
cvss_versionproject
descriptionproject
exploitability_scoreproject
impact_scoreproject
integrity_impactproject
modifiedproject
nameproject
privileges_requiredproject
productsproject
scopeproject
serversproject
technologiesproject
user_interactionproject
vendorsproject
vulnerability_categoryproject
vulnerability_typeproject

Cynerio - Suspicious Connection to External Address

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

Suspicious Connection to External Address

MITRE ATT&CK Coverage

Rule Body

id: c0756978-baa6-4239-9174-bac1b1ca1a6a
name: Cynerio - Suspicious Connection to External Address
description: Suspicious Connection to External Address
severity: High
requiredDataConnectors:
  - connectorId: CynerioSecurityEvents
    dataTypes:
      - CynerioEvent_CL
queryFrequency: 5h
queryPeriod: 5h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - LateralMovement
relevantTechniques:
  - T0866
query: |
  CynerioEvent_CL
  | where TimeGenerated > ago(24h)
  | where module_s == 'IDS'
  | where title_s == 'Suspicious Connection'
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: asset_ip_s
eventGroupingSettings:
  aggregationKind: SingleAlert
customDetails: 
  Hostname: host_s
  DstIP: dst_ip_s
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

CynerioEvent_CL

Stage 2: where

| where TimeGenerated > ago(24h)

Stage 3: where

| where module_s == 'IDS'

Stage 4: where

| where title_s == 'Suspicious Connection'

Indicators

These rows show field, operator, and value matches.

Detect Msiexec executing DLL network connections

#
Group by
$left.InitiatingProcessParentCreationTime, $left.InitiatingProcessParentId, $right.InitiatingProcessCreationTime, $right.InitiatingProcessId, DeviceId
Author
Robbe Van den Daele
Source
github.com/HybridBrothers/Hunting-Queries-Detection-Rules

Adversaries regularly use Msiexec (or other lolbins) to execute their malicious programs with. A common way to do this is more specifically using Msiexec to execute beacons encapsulated in DLL files. While this happens a lot in legitimate cases, a DLL file loaded via Msiexec starting network connections may indicate a beacon running. > [!WARNING] > You might need to add environment specific finetuning to this rule in order to reduce BP detections from legitimate processes.

MITRE ATT&CK Coverage

References

Telemetry Coverage

Rule Body

DeviceNetworkEvents
| where TimeGenerated > ago(1h)
| where InitiatingProcessParentFileName =~ "msiexec.exe"
| join kind=inner (
    DeviceProcessEvents
    | where Timestamp > ago(3d)
    | where InitiatingProcessFileName =~ "msiexec.exe"
) on DeviceId, 
    $left.InitiatingProcessParentId == $right.InitiatingProcessId,
    $left.InitiatingProcessParentCreationTime == $right.InitiatingProcessCreationTime
| where InitiatingProcessCommandLine1 has_any ("/y", "-y", "/z", "-z")

Stages and Predicates

Stage 1: source

DeviceNetworkEvents

Stage 2: where

| where TimeGenerated > ago(1h)

Stage 3: where

| where InitiatingProcessParentFileName =~ "msiexec.exe"

Stage 4: join

| join kind=inner (
    DeviceProcessEvents
    | where Timestamp > ago(3d)
    | where InitiatingProcessFileName =~ "msiexec.exe"
) on DeviceId, 
    $left.InitiatingProcessParentId == $right.InitiatingProcessId,
    $left.InitiatingProcessParentCreationTime == $right.InitiatingProcessCreationTime

Stage 5: where

where (InitiatingProcessCommandLine1 has "/y" or InitiatingProcessCommandLine1 has "-y" or InitiatingProcessCommandLine1 has "/z" or InitiatingProcessCommandLine1 has "-z")

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
InitiatingProcessCommandLine1match
  • -y transforms: term
  • -z transforms: term
  • /y transforms: term
  • /z transforms: term
field:"InitiatingProcessCommandLine1" kind:match
InitiatingProcessFileNameeq
  • msiexec.exe
field:"InitiatingProcessFileName" kind:eq value:"msiexec.exe"
InitiatingProcessParentFileNameeq
  • msiexec.exe corpus 18 (elastic 14, splunk 3, kusto 1)
field:"parent_process_basename" kind:eq value:"msiexec.exe"

Dragos Notifications

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

'Fires Microsoft Sentinel alerts for Dragos Notifcations.'

Rule Body

id: 9a74fe72-4c21-4ac5-80d9-37434e809721
name: Dragos Notifications
description: |
  'Fires Microsoft Sentinel alerts for Dragos Notifcations.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: DragosSitestoreCCP
    dataTypes: 
      - DragosAlerts_CL
  - connectorId: CefAma
    dataTypes:
      - CommonSecurityLog
queryFrequency: 10m
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: |
  DragosNotificationsToSentinel()
eventGroupingSettings:
  aggregationKind: AlertPerResult
entityMappings:
  - entityType: SentinelEntities
    fieldMappings:
      - identifier: Entities
        columnName: SentinelEntities
alertDetailsOverride:
  alertDisplayNameFormat: 'Dragos: {{summary}}'
  alertDescriptionFormat: '{{content}}'
  alertSeverityColumnName: MSSentinelSeverity
  alertTacticsColumnName: MitreTactics
  alertDynamicProperties:
    - alertProperty: Techniques
      value: MitreTechniques
    - alertProperty: ProductName
      value: AlertProductName
customDetails:
  DragosIdentifier: id
  DragosSeverity: severity
  DragosDetectionQuads: detectionQuads
  DragosCreatedAt: createdAt
  DragosFirstSeenAt: firstSeenAt
  DragosLastSeenAt: lastSeenAt
  DragosOccurredAt: occurredAt
  DragosState: state
  DragosSource: source
  DragosThreatInfo: threatInfo
  DragosIpAddresses: IpAddresses
  DragosMacAddresses: MacAddresses
  DragosConnectSrc: DragosConnectorSource
incidentConfiguration:
  createIncident: true
  # We handle de-duping via the raw queries, but still keep this
  # as a fall back
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: PT1H
    matchingMethod: Selected
    groupByCustomDetails:
      - DragosIdentifier
version: 1.0.1
kind: Scheduled

Stages and Predicates

Stage 1: source

DragosNotificationsToSentinel()

Google DNS - Request to dynamic DNS service

#
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: 09fc03e0-daec-4b22-8afa-4bba30d7e909
name: Google DNS - Request to dynamic DNS service
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 dyndns = dynamic(['noip.com', 'dynu.com', 'dyn.com', 'dynv6.com']);
  GCPCloudDNS
  | where Query in~ (dyndns)
  | 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

Parameters

let dyndns = dynamic(['noip.com', 'dynu.com', 'dyn.com', 'dynv6.com']);

Stage 1: source

GCPCloudDNS

Stage 2: where

| where Query in~ (dyndns)

Stage 3: extend

| extend DNSCustomEntity = Query, IPCustomEntity = SrcIpAddr

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Queryin
  • dyn.com
  • dynu.com
  • dynv6.com
  • noip.com
field:"Query" kind:in

Output Fields

These fields are emitted when the rule matches.

FieldSource
DNSCustomEntityextend
IPCustomEntityextend

Guardian- Content Safety Toxicity Policy Violation Detection.

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

'This alert creates an incident when Content Safety Toxicity Policy Violation detected from the Guardian'

Rule Body

id: 378e5160-70cf-4f1a-b3c8-3bc2f0c884ad
name: Guardian- Content Safety Toxicity Policy Violation Detection.
description: |
  'This alert creates an incident when Content Safety Toxicity Policy Violation detected from the Guardian'
severity: Low
status: Available
requiredDataConnectors:
  - connectorId: BoschAIShield
    dataTypes:
      - Guardian
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: |
  Guardian
  | where PolicyViolatedControlFeature =~ 'Toxicity'
  | where Severity =~ 'Low'
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: HostName
      - identifier: NTDomain
        columnName: NTDomain
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIP
eventGroupingSettings:
  aggregationKind: SingleAlert
alertDetailsOverride:
  alertDisplayNameFormat: Guardian- Content Safety Toxicity Policy Violation detection
  alertDescriptionFormat: |
    This query detects Content Safety Toxicity Policy Violation detected from the Guardian generated at {{TimeGenerated}}.\n\nPlease check the source for more information and investigate further
  alertTacticsColumnName: null
  alertSeverityColumnName: Severity
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

Guardian

Stage 2: where

| where PolicyViolatedControlFeature =~ 'Toxicity'

Stage 3: where

| where Severity =~ 'Low'

Indicators

These rows show field, operator, and value matches.

High-Risk Cross-Cloud User Impersonation

#
Severity
medium
Time window
1d
Group by
$left.IPAddress, $right.SourceIpAddress, IPAddress, RiskEventTypes, RiskEventTypes_V2, UserPrincipalName, signInTime
Source
github.com/Azure/Azure-Sentinel

This detection focuses on identifying high-risk cross-cloud activities and sign-in anomalies that may indicate potential security threats. The query starts by analyzing Microsoft Entra ID Signin Logs to pinpoint instances where specific applications, risk levels, and result types align. It then correlates this information with relevant AWS CloudTrail events to identify activities across Azure and AWS environments.

MITRE ATT&CK Coverage

Telemetry Coverage

PlatformRecord / event type
AWSCloudTrail event AddUserToGroup: Adds an IAM user to the specified IAM group, granting the user the permissions attached to that group.
AWSCloudTrail event CreateAccessKey: Creates a new AWS access key pair (access key ID and secret access key) for the specified IAM user.
AWSCloudTrail event CreateGroup: Creates a new IAM group to which IAM users can be added to share a common set of permissions.
AWSCloudTrail event CreateRole: Creates a new IAM role with a specified trust policy, defining which principals can assume the role.
AWSCloudTrail event CreateUser: Creates a new IAM user in the AWS account with a specified user name.
AWSCloudTrail event CreateVirtualMFADevice: Creates a new virtual MFA device and returns the QR code seed for enrollment.
AWSCloudTrail event DeleteGroup: Deletes an IAM group, which must not have any users or attached policies before deletion.
AWSCloudTrail event DeleteLoginProfile: Deletes the password-based login profile for an IAM user, preventing console sign-in.
AWSCloudTrail event ChangePassword: Changes the password of the IAM user who is calling this operation.
AWSCloudTrail event CreateServiceSpecificCredential: Generates a set of credentials consisting of a user name and password that can be used to access the service specified in the request.
AWSCloudTrail event DeleteAccessKey: Deletes the access key pair associated with the specified IAM user.
AWSCloudTrail event DeleteGroupPolicy: Deletes the specified inline policy that is embedded in the specified IAM group.
AWSCloudTrail event DeleteRole: Deletes the specified role.
AWSCloudTrail event DeleteServiceSpecificCredential: Deletes the specified service-specific credential.
AWSCloudTrail event DeleteUser: Deletes the specified IAM user.
AWSCloudTrail event RemoveUserFromGroup: Removes the specified user from the specified group.
AWSCloudTrail event UploadServerCertificate: Uploads a server certificate entity for the Amazon Web Services account.
AWSCloudTrail event CreateGroup: Creates a group within the specified identity store.
AWSCloudTrail event CreateUser: Creates a user within the specified identity store.
AWSCloudTrail event DeleteGroup: Delete a group within an identity store given GroupId.
AWSCloudTrail event DeleteUser: Deletes a user within an identity store given UserId.
AWSCloudTrail event CreateUser: Creates a user in a Simple AD or Microsoft AD directory.
AWSCloudTrail event DeleteUser: Deletes the specified user from a Simple AD or Microsoft AD directory.
AWSCloudTrail event CreateGroup: Creates a group that can be used in WorkMail by calling the RegisterToWorkMail operation.
AWSCloudTrail event CreateOrganization: Creates a new WorkMail organization.
AWSCloudTrail event CreateUser: Creates a user who can be used in WorkMail by calling the RegisterToWorkMail operation.
AWSCloudTrail event DeleteGroup: Deletes a group from WorkMail.
AWSCloudTrail event DeleteUser: Deletes a user from WorkMail and all subsequent systems.
AWSCloudTrail event RegisterToWorkMail: Registers an existing and disabled user, group, or resource for WorkMail use by associating a mailbox and calendaring capabilities.
AWSCloudTrail event ResetPassword: Allows the administrator to reset the password for a user.
Entra IDSigninLogs sign-in event: Sign-in with an Identity Protection risk assessment
Entra IDSigninLogs sign-in event: Sign-in naming a target application

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: f4a28082-2808-4783-9736-33c1ae117475
name: High-Risk Cross-Cloud User Impersonation 
description: |
    'This detection focuses on identifying high-risk cross-cloud activities and sign-in anomalies that may indicate potential security threats. The query starts by analyzing Microsoft Entra ID Signin Logs to pinpoint instances where specific applications, risk levels, and result types align. It then correlates this information with relevant AWS CloudTrail events to identify activities across Azure and AWS environments.'
severity: Medium
requiredDataConnectors:
  - connectorId: AWS
    dataTypes:
      - AWSCloudTrail
  - connectorId: AzureActiveDirectory
    dataTypes:
      - SigninLogs
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - PrivilegeEscalation
relevantTechniques:
  - T1134
  - T1078.002
  - T1078.004
query: |
  // Retrieve Azure AD SigninLogs within the last day
  SigninLogs 
  // Filter for specific AppDisplayNames, ResultType, and Risk Levels
  | where AppDisplayName in ("Azure Portal", "ADFS Trust", "Microsoft Azure PowerShell")
      and RiskLevelAggregated == "high"
      and RiskLevelDuringSignIn == "high"
  // Summarize AppDisplayNames by relevant attributes
  | extend Result = iff(ResultType == 0, "Successful Signin", "Failed Signin")
  | summarize make_set(AppDisplayName)
      by
      IPAddress,
      signInTime=TimeGenerated,
      UserPrincipalName,
      RiskEventTypes,
      RiskEventTypes_V2
  // Inner join with AWS CloudTrail events
  | join kind=inner (
      AWSCloudTrail
      | where isempty(ErrorMessage)
      | where EventSource in ("iam.amazonaws.com", "identitystore.amazonaws.com", "workmail.amazonaws.com", "workdocs.amazonaws.com")
      // List of AWS event names
      | where EventName in~ ("CreateRole", "DeleteRole", "CreateUser", "CreateAccessKey", "DeleteAccessKey", "CreateGroup", "AddUserToGroup", "ChangePassword", "DeleteGroup", "DeleteUser", "RemoveUserFromGroup", "CreateVirtualMFADevice", "DeleteLoginProfile", "CreateOrganization", "SetDefaultMailDomain", "SetMailUserDetails", "CreateMailUser", "ResetPassword", "RegisterToWorkMail", "DisableMailUsers", "EnableMailUsers", "DeleteServiceSpecificCredential", "CreateServiceSpecificCredential", "UpdateAccountEmailAddress", "DeleteGroupPolicy", "UploadServerCertificate")  
      // Summarize relevant attributes
      | summarize make_set(RequestParameters), make_set(ResponseElements)
          by
          SourceIpAddress,
          UserIdentityArn,
          UserIdentityType,
          EventName,
          EventTime=TimeGenerated,
          EventSource
      )
      on $left.IPAddress == $right.SourceIpAddress  
  // Calculate time difference in hours between AWS event and Azure sign-in
  | extend timedef = datetime_diff("hour", EventTime, signInTime)
  // Filter for time differences within a certain range
  | where timedef between (0 .. 8)
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIpAddress
customDetails:
   AwsUser: UserIdentityArn
   RiskEventTypes: RiskEventTypes
   AzureUser: UserPrincipalName
   AWSEventName: EventName
kind: Scheduled
version: 1.0.1

Stages and Predicates

Stage 1: source

SigninLogs

Stage 2: where

| where AppDisplayName in ("Azure Portal", "ADFS Trust", "Microsoft Azure PowerShell")
    and RiskLevelAggregated == "high"
    and RiskLevelDuringSignIn == "high"

Stage 3: extend

| extend Result = iff(ResultType == 0, "Successful Signin", "Failed Signin")
Result =
ifResultType == 0"Successful Signin"
else"Failed Signin"

Stage 4: summarize

| summarize make_set(AppDisplayName)
    by
    IPAddress,
    signInTime=TimeGenerated,
    UserPrincipalName,
    RiskEventTypes,
    RiskEventTypes_V2

Stage 5: join

| join kind=inner (
    AWSCloudTrail
    | where isempty(ErrorMessage)
    | where EventSource in ("iam.amazonaws.com", "identitystore.amazonaws.com", "workmail.amazonaws.com", "workdocs.amazonaws.com")
    | where EventName in~ ("CreateRole", "DeleteRole", "CreateUser", "CreateAccessKey", "DeleteAccessKey", "CreateGroup", "AddUserToGroup", "ChangePassword", "DeleteGroup", "DeleteUser", "RemoveUserFromGroup", "CreateVirtualMFADevice", "DeleteLoginProfile", "CreateOrganization", "SetDefaultMailDomain", "SetMailUserDetails", "CreateMailUser", "ResetPassword", "RegisterToWorkMail", "DisableMailUsers", "EnableMailUsers", "DeleteServiceSpecificCredential", "CreateServiceSpecificCredential", "UpdateAccountEmailAddress", "DeleteGroupPolicy", "UploadServerCertificate")  
    | summarize make_set(RequestParameters), make_set(ResponseElements)
        by
        SourceIpAddress,
        UserIdentityArn,
        UserIdentityType,
        EventName,
        EventTime=TimeGenerated,
        EventSource
    )
    on $left.IPAddress == $right.SourceIpAddress

Stage 6: extend

extend timedef

Stage 7: where

where timedef >= 0 and timedef <= 8

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
AppDisplayNamein
  • ADFS Trust
  • Azure Portal
  • Microsoft Azure PowerShell
field:"Application" kind:in
ErrorMessageis_null
  • (no value, null check)
field:"ErrorMessage" kind:is_null
EventNamein
  • AddUserToGroup
  • ChangePassword
  • CreateAccessKey
  • CreateGroup
  • CreateMailUser
  • CreateOrganization
  • CreateRole
  • CreateServiceSpecificCredential
  • CreateUser
  • CreateVirtualMFADevice
  • DeleteAccessKey
  • DeleteGroup
  • DeleteGroupPolicy
  • DeleteLoginProfile
  • DeleteRole
  • DeleteServiceSpecificCredential
  • DeleteUser
  • DisableMailUsers
  • EnableMailUsers
  • RegisterToWorkMail
  • RemoveUserFromGroup
  • ResetPassword
  • SetDefaultMailDomain
  • SetMailUserDetails
  • UpdateAccountEmailAddress
  • UploadServerCertificate
field:"EventName" kind:in
EventSourcein
  • iam.amazonaws.com
  • identitystore.amazonaws.com
  • workdocs.amazonaws.com
  • workmail.amazonaws.com
field:"EventSource" kind:in
RiskLevelAggregatedeq
  • high
field:"RiskLevelAggregated" kind:eq value:"high"
RiskLevelDuringSignIneq
  • high
field:"RiskLevelDuringSignIn" kind:eq value:"high"
timedefge
  • 0
field:"timedef" kind:ge value:"0"
timedefle
  • 8
field:"timedef" kind:le value:"8"

Output Fields

These fields are emitted when the rule matches.

FieldSource
IPAddresssummarize
RiskEventTypessummarize
RiskEventTypes_V2summarize
UserPrincipalNamesummarize
signInTimesummarize
timedefextend

Jamf Protect - Unified Logs

#
Status
available
Severity
informational
Source
github.com/Azure/Azure-Sentinel

'Creates an informational incident based on Jamf Protect Unified Log data in Microsoft Sentinel'

Rule Body

id: 9eb2f758-003b-4303-83c6-97aed4c03e41
name: Jamf Protect - Unified Logs
description: |
  'Creates an informational incident based on Jamf Protect Unified Log data in Microsoft Sentinel'
severity: Informational
status: Available
requiredDataConnectors:
  - connectorId: JamfProtect
    dataTypes: 
      - jamfprotect_CL
suppressionDuration: PT5H
eventGroupingSettings:
  aggregationKind: AlertPerResult
suppressionEnabled: false
tactics:
relevantTechniques:
query: |
  JamfProtectUnifiedLogs
  | where isnotempty(EventSeverity)
  | extend Host_IPs = tostring(parse_json(DvcIpAddr)[0])
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: false
    reopenClosedIncident: false
    lookbackDuration: PT5H
    matchingMethod: AllEntities
alertDetailsOverride:
  alertDisplayNameFormat: "{{EventDescription}} on {{DvcHostname}}"
  alertDescriptionFormat: "{{EventDescription}} has been captured in the unified logs"
  alertSeverityColumnName: EventSeverity
  alertDynamicProperties:
    - alertProperty: ProviderName
      value: EventVendor
    - alertProperty: ProductName
      value: EventProduct
customDetails:
  Protect_Event_Type: EventType
  Unified_Log: EventDescription
  Tags: Match_tags
  Event_Process: TargetProcessName
entityMappings:
- entityType: Host
  fieldMappings:
  - identifier: HostName
    columnName: DvcHostname
- entityType: IP
  fieldMappings:
  - identifier: Address
    columnName: Host_IPs
version: 1.0.3
kind: NRT

Stages and Predicates

Stage 1: source

JamfProtectUnifiedLogs

Stage 2: where

| where isnotempty(EventSeverity)

Stage 3: extend

| extend Host_IPs = tostring(parse_json(DvcIpAddr)[0])

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
EventSeverityis_not_null
  • (no value, null check)
field:"EventSeverity" kind:is_not_null

Output Fields

These fields are emitted when the rule matches.

FieldSource
Host_IPsextend

RunningRAT request parameters

#
Severity
high
Time window
1d
Author
Thomas McElroy
Source
github.com/Azure/Azure-Sentinel

'This detection will alert when RunningRAT URI parameters or paths are detect in an HTTP request. Id the device blocked this communication presence of this alert means the RunningRAT implant is likely still executing on the source host.'

MITRE ATT&CK Coverage

Rule Body

id: baedfdf4-7cc8-45a1-81a9-065821628b83
name: RunningRAT request parameters
description: |
  'This detection will alert when RunningRAT URI parameters or paths are detect in an HTTP request.
  Id the device blocked this communication presence of this alert means the RunningRAT implant is likely still executing on the source host.'
severity: High
requiredDataConnectors:
  - connectorId: Zscaler
    dataTypes:
      - CommonSecurityLog
  - connectorId: Fortinet
    dataTypes:
      - CommonSecurityLog
  - connectorId: CheckPoint
    dataTypes:
      - CommonSecurityLog
  - connectorId: PaloAltoNetworks
    dataTypes:
      - CommonSecurityLog
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Exfiltration
  - CommandAndControl
relevantTechniques:
  - T1041
  - T1071.001
tags:
  - POLONIUM
query: |
  let runningRAT_parameters = dynamic(['/ui/chk', 'mactok=', 'UsRnMe=', 'IlocalP=', 'kMnD=']);
  CommonSecurityLog
  | where RequestMethod == "GET"
  | project TimeGenerated, DeviceVendor, DeviceProduct, DeviceAction, DestinationDnsDomain, DestinationIP, RequestURL, SourceIP, SourceHostName, RequestClientApplication
  | where RequestURL has_any (runningRAT_parameters)
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIP
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: DestinationIP
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: SourceHostName
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: RequestURL
version: 1.0.2
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: Thomas McElroy
    support:
        tier: Community
    categories:
        domains: [ "Security - Others" ]

Stages and Predicates

Parameters

let runningRAT_parameters = dynamic(['/ui/chk', 'mactok=', 'UsRnMe=', 'IlocalP=', 'kMnD=']);

Stage 1: source

CommonSecurityLog

Stage 2: where

| where RequestMethod == "GET"

Stage 3: project

| project TimeGenerated, DeviceVendor, DeviceProduct, DeviceAction, DestinationDnsDomain, DestinationIP, RequestURL, SourceIP, SourceHostName, RequestClientApplication

Stage 4: where

| where RequestURL has_any (runningRAT_parameters)

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
RequestMethodeq
  • GET
field:"RequestMethod" kind:eq value:"GET"
RequestURLmatch
  • /ui/chk transforms: term
  • IlocalP= transforms: term
  • UsRnMe= transforms: term
  • kMnD= transforms: term
  • mactok= transforms: term
field:"RequestURL" kind:match

Output Fields

These fields are emitted when the rule matches.

FieldSource
DestinationDnsDomainproject
DestinationIPproject
DeviceActionproject
DeviceProductproject
DeviceVendorproject
RequestClientApplicationproject
RequestURLproject
SourceHostNameproject
SourceIPproject
TimeGeneratedproject

ShieldBreak: Defender AV Privilege Escalation

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

The query below detects processes created with System-level integrity by wermgr.exe. DLLs loaded during the exploitation is not recorded in MDE for some reason (filtering/sampling). That's why process creation chain is more reliable.

MITRE ATT&CK Coverage

TacticTechniques
Privilege EscalationNo specific technique

References

Telemetry Coverage

Rule Body

// Description: detects processes created with System-level integrity by wermgr.exe 
// Author: Cyb3rMonk(https://twitter.com/Cyb3rMonk, https://academy.bluraven.io)
//
// Query Parameters
DeviceProcessEvents
| where 
    (InitiatingProcessFileName =~ "wermgr.exe" and ProcessIntegrityLevel =~ "System") or 
    (InitiatingProcessParentFileName =~ "wermgr.exe" and ProcessIntegrityLevel =~ "System")

Stages and Predicates

Stage 1: source

DeviceProcessEvents

Stage 2: where

| where 
    (InitiatingProcessFileName =~ "wermgr.exe" and ProcessIntegrityLevel =~ "System") or 
    (InitiatingProcessParentFileName =~ "wermgr.exe" and ProcessIntegrityLevel =~ "System")

Indicators

These rows show field, operator, and value matches.

Star Blizzard C2 Domains August 2022

#
Severity
high
Time window
1d
Group by
NetworkMessageId
Author
Microsoft Security Research
Source
github.com/Azure/Azure-Sentinel

Identifies a match across various data feeds for domains related to an actor tracked by Microsoft as Star Blizzard.

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: 2149d9bb-8298-444c-8f99-f7bf0274dd05
name: Star Blizzard C2 Domains August 2022
description: |
  'Identifies a match across various data feeds for domains related to an actor tracked by Microsoft as Star Blizzard.'
severity: High
requiredDataConnectors:
  - connectorId: AzureMonitor(VMInsights)
    dataTypes:
      - VMConnection
  - connectorId: CiscoASA
    dataTypes:
      - CommonSecurityLog
  - connectorId: PaloAltoNetworks
    dataTypes:
      - CommonSecurityLog
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - DeviceNetworkEvents
      - EmailUrlInfo
      - EmailEvents
  - connectorId: AzureFirewall
    dataTypes:
      - AzureDiagnostics
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1566
tags:
  - Star Blizzard
  - Schema: ASIMDns
    SchemaVersion: 0.1.1
query: |
  let iocs = externaldata(DateAdded:string,IoC:string,Type:string) [@"https://raw.githubusercontent.com/Azure/Azure-Sentinel/master/Sample%20Data/Feeds/SEABORGIUMIOC.csv"] with (format="csv", ignoreFirstRecord=True);
  let DomainNames = (iocs | where Type =~ "domainname"| project IoC);
  (union isfuzzy=true
  (CommonSecurityLog
  | parse Message with * '(' DNSName ')' *
  | where DNSName in~ (DomainNames)
  | extend Account = SourceUserID, Computer = DeviceName, IPAddress =  DestinationIP
  ),
  (_Im_Dns (domain_has_any=DomainNames)
  | extend DNSName = DnsQuery
  | extend IPAddress =  SrcIpAddr, Computer = Dvc
  ),
  (_Im_WebSession (url_has_any=DomainNames)
  | extend DNSName = tostring(parse_url(Url)["Host"])
  | extend IPAddress =  SrcIpAddr, Computer = Dvc
  ),
  (VMConnection
  | parse RemoteDnsCanonicalNames with * '["' DNSName '"]' *
  | where DNSName  in~ (DomainNames)
  | extend IPAddress = RemoteIp
  ),
  (DeviceNetworkEvents
  | where RemoteUrl  has_any (DomainNames)
  | extend IPAddress = RemoteIP
  | extend Computer = DeviceName
  ),
  (EmailUrlInfo
  | where Url has_any (DomainNames)
  | join (EmailEvents
  | where EmailDirection == "Inbound" ) on NetworkMessageId
  | extend IPAddress = SenderIPv4
  | extend Account = RecipientEmailAddress
  ),
  (AzureDiagnostics
  | where ResourceType == "AZUREFIREWALLS"
  | where Category == "AzureFirewallApplicationRule"
  | parse msg_s with Protocol 'request from ' SourceHost ':' SourcePort 'to ' DestinationHost ':' DestinationPort '. Action:' Action
  | where isnotempty(DestinationHost)
  | where DestinationHost has_any (DomainNames)
  | extend DNSName = DestinationHost
  | extend IPAddress = SourceHost
  )
  )
  | extend AccountName = tostring(split(Account, "@")[0]), AccountUPNSuffix = tostring(split(Account, "@")[1])
  | extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
  | extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
  | extend AccountName = tostring(split(Account, "@")[0]), AccountUPNSuffix = tostring(split(Account, "@")[1])
  | extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
  | extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: Account
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: AccountUPNSuffix
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: Computer
      - identifier: HostName
        columnName: HostName
      - identifier: NTDomain
        columnName: HostNameDomain
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPAddress
version: 1.0.5
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: Microsoft Security Research
    support:
        tier: Community
    categories:
        domains: [ "Security - Threat Intelligence" ]

Stages and Predicates

Let Binding: iocs

let iocs = externaldata(DateAdded:string,IoC:string,Type:string) [@"https://raw.githubusercontent.com/Azure/Azure-Sentinel/master/Sample%20Data/Feeds/SEABORGIUMIOC.csv"] with (format="csv", ignoreFirstRecord=True);

Let Binding: DomainNames

let DomainNames = (iocs | where Type =~ "domainname"| project IoC);

Union isfuzzy=true (7 Sources)

Each leg below queries one source; the rule matches if any leg does. Sources: CommonSecurityLog, _Im_Dns, _Im_WebSession, VMConnection, DeviceNetworkEvents, EmailUrlInfo, AzureDiagnostics

Leg 1: CommonSecurityLog

CommonSecurityLog
| parse Message with * '(' DNSName ')' *
| where DNSName in~ (DomainNames)
| extend Account = SourceUserID, Computer = DeviceName, IPAddress =  DestinationIP

Leg 2: _Im_Dns

_Im_Dns (domain_has_any=DomainNames)
| extend DNSName = DnsQuery
| extend IPAddress =  SrcIpAddr, Computer = Dvc

Leg 3: _Im_WebSession

_Im_WebSession (url_has_any=DomainNames)
| extend DNSName = tostring(parse_url(Url)["Host"])
| extend IPAddress =  SrcIpAddr, Computer = Dvc

Leg 4: VMConnection

VMConnection
| parse RemoteDnsCanonicalNames with * '["' DNSName '"]' *
| where DNSName  in~ (DomainNames)
| extend IPAddress = RemoteIp

Leg 5: DeviceNetworkEvents

DeviceNetworkEvents
| where RemoteUrl  has_any (DomainNames)
| extend IPAddress = RemoteIP
| extend Computer = DeviceName

Leg 6: EmailUrlInfo

EmailUrlInfo
| where Url has_any (DomainNames)
| join (EmailEvents
| where EmailDirection == "Inbound" ) on NetworkMessageId
| extend IPAddress = SenderIPv4
| extend Account = RecipientEmailAddress

Leg 7: AzureDiagnostics

AzureDiagnostics
| where ResourceType == "AZUREFIREWALLS"
| where Category == "AzureFirewallApplicationRule"
| parse msg_s with Protocol 'request from ' SourceHost ':' SourcePort 'to ' DestinationHost ':' DestinationPort '. Action:' Action
| where isnotempty(DestinationHost)
| where DestinationHost has_any (DomainNames)
| extend DNSName = DestinationHost
| extend IPAddress = SourceHost

Applied to the Combined Result

| extend AccountName = tostring(split(Account, "@")[0]), AccountUPNSuffix = tostring(split(Account, "@")[1])
| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
| extend AccountName = tostring(split(Account, "@")[0]), AccountUPNSuffix = tostring(split(Account, "@")[1])
| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
Accountextend
Computerextend
IPAddressextend
DNSNameextend
AccountNameextend
AccountUPNSuffixextend
DomainIndexextend
HostNameextend
HostNameDomainextend

Suspicious Process Injection from Office application

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

This query detects process injections using CreateRemoteThread, QueueUserAPC or SetThread context APIs, originating from an Office process (only Word/Excel/PowerPoint)that might contains macros. Performing process injection from a macro is a common technique by attackers to escape out of the Office process into something longer running.

MITRE ATT&CK Coverage

TacticTechniques
Execution

Telemetry Coverage

Rule Body

id: a4d8e681-6f30-440a-a2f3-c312bc1389d0
name: Suspicious Process Injection from Office application
description: |
  This query detects process injections using CreateRemoteThread, QueueUserAPC or SetThread context APIs, originating from an Office process (only Word/Excel/PowerPoint)that might contains macros. Performing process injection from a macro is a common technique by attackers to escape out of the Office process into something longer running. 
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - DeviceEvents
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Execution
relevantTechniques:
  - T1204
query: |
  DeviceEvents 
  | where ActionType in ("CreateRemoteThreadApiCall", "QueueUserApcRemoteApiCall", "SetThreadContextRemoteApiCall")
  | where InitiatingProcessFileName in~ ("winword.exe", "excel.exe", "powerpnt.exe")
  | where InitiatingProcessCommandLine !contains "/dde"
  | where not(InitiatingProcessCommandLine has_any (".docx", "dotx",".xlsx", ".xltx", ".pptx")) // These files can't contain macros.
  // Enable the line below if the results are too noisy.
  //| where InitiatingProcessCommandLine has_any (".doc", ".wbk", ".docm", ".dot", ".dotm" ".xls", ".xlsm", ".xltm", ".xla",".xll", ".xlam", ".ppt", ".pptm", ".pot", ".potm", ".ppsm", ".sldm") 
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Sid
        columnName: InitiatingProcessAccountSid 
      - identifier: Name
        columnName: InitiatingProcessAccountName
      - identifier: NTDomain
        columnName: InitiatingProcessAccountDomain
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: DeviceName
  - entityType: Process
    fieldMappings:
      - identifier: CommandLine
        columnName: InitiatingProcessCommandLine
version: 1.0.1
kind: Scheduled

Stages and Predicates

Stage 1: source

DeviceEvents

Stage 2: where

| where ActionType in ("CreateRemoteThreadApiCall", "QueueUserApcRemoteApiCall", "SetThreadContextRemoteApiCall")

Stage 3: where

| where InitiatingProcessFileName in~ ("winword.exe", "excel.exe", "powerpnt.exe")

Stage 4: where

| where InitiatingProcessCommandLine !contains "/dde"

Stage 5: where

| where not(InitiatingProcessCommandLine has_any (".docx", "dotx",".xlsx", ".xltx", ".pptx"))

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
ActionTypein
  • CreateRemoteThreadApiCall
  • QueueUserApcRemoteApiCall
  • SetThreadContextRemoteApiCall
field:"ActionType" kind:in
InitiatingProcessFileNamein
  • excel.exe
  • powerpnt.exe
  • winword.exe
field:"InitiatingProcessFileName" kind:in

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
InitiatingProcessCommandLinecontains/ddeexcludes:InitiatingProcessCommandLine field:"InitiatingProcessCommandLine" value:"/dde"
InitiatingProcessCommandLinematch.docx, dotx, .xlsx, .xltx, .pptxexcludes:InitiatingProcessCommandLine

Tailscale: Split-DNS configuration modified

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

Identifies when the tailnet split-DNS configuration is modified. Split-DNS overrides per-domain resolution within the tailnet - an attacker adding a new domain mapping or changing the resolver IP can hijack DNS for that domain.

MITRE ATT&CK Coverage

Rule Body

id: b4c5d6e7-1234-5678-90ab-cdef12345010
name: "Tailscale: Split-DNS configuration modified"
description: |
  Identifies when the tailnet split-DNS configuration is modified. Split-DNS overrides per-domain resolution within the tailnet - an attacker adding a new domain mapping or changing the resolver IP can hijack DNS for that domain.
severity: High
status: Available
requiredDataConnectors:
  - connectorId: TailscaleCCF
    dataTypes:
      - Tailscale_Audit_CL
queryFrequency: 15m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - DefenseEvasion
  - CommandAndControl
relevantTechniques:
  - T1556
  - T1568
query: |
  Tailscale_Audit_CL
  | where Action == "UPDATE"
  | where tostring(Target.type) == "TAILNET"
  | where tostring(Target.property) == "DNS_SPLIT_DNS"
  | extend ActorLogin = tostring(Actor.loginName)
  | extend OldDomains = bag_keys(Old)
  | extend NewDomains = bag_keys(New)
  | extend AddedDomains = set_difference(NewDomains, OldDomains)
  | extend RemovedDomains = set_difference(OldDomains, NewDomains)
  | project TimeGenerated, ActorLogin, AddedDomains, RemovedDomains, Old, New, Origin
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: ActorLogin
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: P1D
    matchingMethod: AllEntities
kind: Scheduled
version: 1.0.0

Stages and Predicates

Stage 1: source

Tailscale_Audit_CL

Stage 2: where

| where Action == "UPDATE"

Stage 3: where

| where tostring(Target.type) == "TAILNET"

Stage 4: where

| where tostring(Target.property) == "DNS_SPLIT_DNS"

Stage 5: extend (5 consecutive steps)

| extend ActorLogin = tostring(Actor.loginName)
| extend OldDomains = bag_keys(Old)
| extend NewDomains = bag_keys(New)
| extend AddedDomains = set_difference(NewDomains, OldDomains)
| extend RemovedDomains = set_difference(OldDomains, NewDomains)

Stage 6: project

| project TimeGenerated, ActorLogin, AddedDomains, RemovedDomains, Old, New, Origin

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Actioneq
  • UPDATE
field:"Action" kind:eq value:"UPDATE"
Target.propertyeq
  • DNS_SPLIT_DNS transforms: tostring, kusto_dynamic_path
field:"Target.property" kind:eq value:"DNS_SPLIT_DNS"
Target.typeeq
  • TAILNET transforms: tostring, kusto_dynamic_path
field:"Target.type" kind:eq value:"TAILNET"

Output Fields

These fields are emitted when the rule matches.

FieldSource
ActorLoginproject
AddedDomainsproject
Newproject
Oldproject
Originproject
RemovedDomainsproject
TimeGeneratedproject

Threat Essentials - Multiple admin membership removals from newly created admin.

#
Status
available
Severity
medium
Time window
7d
Group by
$left.Target, $right.Initiator, Initiator, OperationName, Result, RoleName, Target
Source
github.com/Azure/Azure-Sentinel

This query detects when newly created Global admin removes multiple existing global admins which can be an attempt by adversaries to lock down organization and retain sole access. Investigate reasoning and intention of multiple membership removal by new Global admins and take necessary actions accordingly.

MITRE ATT&CK Coverage

TacticTechniques
Impact

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: 199978c5-cd6d-4194-b505-8ef5800739df
name: Threat Essentials - Multiple admin membership removals from newly created admin.
description: |
  'This query detects when newly created Global admin removes multiple existing global admins which can be an attempt by adversaries to lock down organization and retain sole access. 
   Investigate reasoning and intention of multiple membership removal by new Global admins and take necessary actions accordingly.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AuditLogs
queryFrequency: 1h
queryPeriod: 7d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Impact
relevantTechniques:
  - T1531
tags:
  - DEV-0537
query: |
  let lookback = 7d; 
  let timeframe = 1h; 
  let GlobalAdminsRemoved = AuditLogs 
  | where TimeGenerated > ago(timeframe) 
  | where Category =~ "RoleManagement" 
  | where AADOperationType in~ ("Unassign", "RemoveEligibleRole") 
  | where ActivityDisplayName has_any ("Remove member from role", "Remove eligible member from role") 
  | mv-expand TargetResources 
  | mv-expand TargetResources.modifiedProperties 
  | extend displayName_ = tostring(TargetResources_modifiedProperties.displayName) 
  | where displayName_ =~ "Role.DisplayName" 
  | extend RoleName = tostring(parse_json(tostring(TargetResources_modifiedProperties.oldValue))) 
  | where RoleName =~ "Global Administrator" // Add other Privileged role if applicable 
  | extend InitiatingApp = tostring(parse_json(tostring(InitiatedBy.app)).displayName) 
  | extend Initiator = iif(isnotempty(InitiatingApp), InitiatingApp, tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName)) 
  | where Initiator != "MS-PIM" and Initiator != "MS-PIM-Fairfax"  // Filtering PIM events 
  | extend Target = tostring(TargetResources.userPrincipalName) 
  | summarize RemovedGlobalAdminTime = max(TimeGenerated), TargetAdmins = make_set(Target) by OperationName,  RoleName, Initiator, Result; 
  let GlobalAdminsAdded = AuditLogs 
  | where TimeGenerated > ago(lookback) 
  | where Category =~ "RoleManagement" 
  | where AADOperationType in~ ("Assign", "AssignEligibleRole") 
  | where ActivityDisplayName has_any ("Add eligible member to role", "Add member to role") and Result =~ "success" 
  | mv-expand TargetResources 
  | mv-expand TargetResources.modifiedProperties 
  | extend displayName_ = tostring(TargetResources_modifiedProperties.displayName) 
  | where displayName_ =~ "Role.DisplayName" 
  | extend RoleName = tostring(parse_json(tostring(TargetResources_modifiedProperties.newValue))) 
  | where RoleName =~ "Global Administrator" // Add other Privileged role if applicable 
  | extend InitiatingApp = tostring(parse_json(tostring(InitiatedBy.app)).displayName) 
  | extend Initiator = iif(isnotempty(InitiatingApp), InitiatingApp, tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName)) 
  | where Initiator != "MS-PIM" and Initiator != "MS-PIM-Fairfax"  // Filtering PIM events 
  | extend Target = tostring(TargetResources.userPrincipalName) 
  | summarize AddedGlobalAdminTime = max(TimeGenerated) by OperationName,  RoleName, Target, Initiator, Result; 
  GlobalAdminsAdded 
  | join kind= inner GlobalAdminsRemoved on $left.Target == $right.Initiator 
  | where AddedGlobalAdminTime < RemovedGlobalAdminTime 
  | extend NoofAdminsRemoved = array_length(TargetAdmins) 
  | where NoofAdminsRemoved > 1
  | project AddedGlobalAdminTime, Initiator, Target, RemovedGlobalAdminTime, TargetAdmins, NoofAdminsRemoved
  | extend Name=split(Target, "@")[0], UPNSuffix=split(Target, "@")[1]
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: Name
      - identifier: UPNSuffix
        columnName: UPNSuffix
version: 1.0.2
kind: Scheduled

Stages and Predicates

Parameters

let lookback = 7d;
let timeframe = 1h;

let GlobalAdminsAdded is inlined into the numbered stages below.

Let Binding: GlobalAdminsRemoved used in Stage 17

let GlobalAdminsRemoved = AuditLogs 
| where TimeGenerated > ago(timeframe) 
| where Category =~ "RoleManagement" 
| where AADOperationType in~ ("Unassign", "RemoveEligibleRole") 
| where ActivityDisplayName has_any ("Remove member from role", "Remove eligible member from role") 
| mv-expand TargetResources 
| mv-expand TargetResources.modifiedProperties 
| extend displayName_ = tostring(TargetResources_modifiedProperties.displayName) 
| where displayName_ =~ "Role.DisplayName" 
| extend RoleName = tostring(parse_json(tostring(TargetResources_modifiedProperties.oldValue))) 
| where RoleName =~ "Global Administrator"
| extend InitiatingApp = tostring(parse_json(tostring(InitiatedBy.app)).displayName) 
| extend Initiator = iif(isnotempty(InitiatingApp), InitiatingApp, tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName)) 
| where Initiator != "MS-PIM" and Initiator != "MS-PIM-Fairfax"
| extend Target = tostring(TargetResources.userPrincipalName) 
| summarize RemovedGlobalAdminTime = max(TimeGenerated), TargetAdmins = make_set(Target) by OperationName,  RoleName, Initiator, Result;

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

Stage 1: source

AuditLogs

Stage 2: where

| where TimeGenerated > ago(lookback)

Stage 3: where

| where Category =~ "RoleManagement"

Stage 4: where

| where AADOperationType in~ ("Assign", "AssignEligibleRole")

Stage 5: where

| where ActivityDisplayName has_any ("Add eligible member to role", "Add member to role") and Result =~ "success"

Stage 6: mv-expand

| mv-expand TargetResources

Stage 7: mv-expand

| mv-expand TargetResources.modifiedProperties

Stage 8: extend

| extend displayName_ = tostring(TargetResources_modifiedProperties.displayName)

Stage 9: where

| where displayName_ =~ "Role.DisplayName"

Stage 10: extend

| extend RoleName = tostring(parse_json(tostring(TargetResources_modifiedProperties.newValue)))

Stage 11: where

| where RoleName =~ "Global Administrator"

Stage 12: extend

| extend InitiatingApp = tostring(parse_json(tostring(InitiatedBy.app)).displayName)

Stage 13: extend

| extend Initiator = iif(isnotempty(InitiatingApp), InitiatingApp, tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName))

Stage 14: where

| where Initiator != "MS-PIM" and Initiator != "MS-PIM-Fairfax"

Stage 15: extend

| extend Target = tostring(TargetResources.userPrincipalName)

Stage 16: summarize

| summarize AddedGlobalAdminTime = max(TimeGenerated) by OperationName,  RoleName, Target, Initiator, Result

Stage 17: join

GlobalAdminsAdded
| join kind= inner GlobalAdminsRemoved on $left.Target == $right.Initiator

Stage 18: where

where AddedGlobalAdminTime < RemovedGlobalAdminTime

Stage 19: extend

extend NoofAdminsRemoved

Stage 20: where

where NoofAdminsRemoved > 1

Stage 21: project

project AddedGlobalAdminTime, Initiator, NoofAdminsRemoved, RemovedGlobalAdminTime, Target, TargetAdmins

Stage 22: extend

extend Name, UPNSuffix

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
AADOperationTypein
  • Assign corpus 2 (kusto 2)
  • AssignEligibleRole corpus 2 (kusto 2)
  • RemoveEligibleRole
  • Unassign
field:"AADOperationType" kind:in
ActivityDisplayNamematch
  • Add eligible member to role transforms: term corpus 9 (kusto 9)
  • Add member to role transforms: term corpus 9 (kusto 9)
  • Remove eligible member from role transforms: term
  • Remove member from 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"
Initiatorne
  • MS-PIM
  • MS-PIM-Fairfax
field:"Initiator" kind:ne
NoofAdminsRemovedgt
  • 1 transforms: number
field:"NoofAdminsRemoved" kind:gt value:"1"
Resulteq
  • success corpus 23 (kusto 23)
field:"Result" kind:eq value:"success"
RoleNameeq
  • Global Administrator
field:"RoleName" kind:eq value:"Global Administrator"
displayName_eq
  • Role.DisplayName
field:"displayName_" kind:eq value:"Role.DisplayName"

Output Fields

These fields are emitted when the rule matches.

FieldSource
AddedGlobalAdminTimeproject
Initiatorproject
NoofAdminsRemovedproject
RemovedGlobalAdminTimeproject
Targetproject
TargetAdminsproject
Nameextend
UPNSuffixextend

ThreatConnect TI map Email entity to OfficeActivity

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

'Identifies a match in OfficeActivity table from any Email IOC from ThreatConnect TI'

MITRE ATT&CK Coverage

TacticTechniques
Command & Control

Rule Body

id: 4f7ade3e-7121-5274-83ea-d7ed22a01fea
name: ThreatConnect TI map Email entity to OfficeActivity
version: 1.2.5
kind: Scheduled
description: |
  'Identifies a match in OfficeActivity table from any Email IOC from ThreatConnect TI'
severity: Medium
requiredDataConnectors:
  - connectorId: Office365
    dataTypes:
      - OfficeActivity
  - connectorId: ThreatIntelligence
    dataTypes:
      - ThreatIntelligenceIndicator
  - connectorId: ThreatIntelligenceTaxii
    dataTypes:
      - ThreatIntelligenceIndicator
  - connectorId: MicrosoftDefenderThreatIntelligence
    dataTypes:
      - ThreatIntelligenceIndicator
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
relevantTechniques:
  - T1071
query: |
  let dt_lookBack = 1h;
  let ioc_lookBack = 14d;
  let emailregex = @'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$';
  ThreatIntelligenceIndicator
    | where TimeGenerated >= ago(ioc_lookBack)
    | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
    | where ExpirationDateTime > now() and Active == true
  // Filter out non ThreatConnect TI Sources
  | where SourceSystem startswith "ThreatConnect-"
  //Filtering the table for Email related IOCs
  | where isnotempty(EmailSenderAddress)
  // using innerunique to keep perf fast and result set low, we only need one match to indicate potential malicious activity that needs to be investigated
  | join kind=innerunique (
      OfficeActivity | where TimeGenerated >= ago(dt_lookBack) and isnotempty(UserId)
      | where UserId matches regex emailregex
      | extend OfficeActivity_TimeGenerated = TimeGenerated
  )
  on $left.EmailSenderAddress == $right.UserId
  | where OfficeActivity_TimeGenerated < ExpirationDateTime
  | summarize OfficeActivity_TimeGenerated = arg_max(OfficeActivity_TimeGenerated, *) by IndicatorId, UserId
  | project OfficeActivity_TimeGenerated, Description, ActivityGroupNames, IndicatorId, ThreatType, Url, ExpirationDateTime, ConfidenceScore,
  EmailSenderName, EmailRecipient, EmailSourceDomain, EmailSourceIpAddress, EmailSubject, FileHashValue, FileHashType, UserId, ClientIP, Operation, UserType, RecordType, OfficeWorkload, Parameters
  | extend Name = tostring(split(UserId, '@', 0)[0]), UPNSuffix = tostring(split(UserId, '@', 1)[0])
  | extend timestamp = OfficeActivity_TimeGenerated
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: Name
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: ClientIP
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: Url

Stages and Predicates

Parameters

let dt_lookBack = 1h;
let ioc_lookBack = 14d;
let emailregex = @'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$';

Stage 1: source

ThreatIntelligenceIndicator

Stage 2: where

| where TimeGenerated >= ago(ioc_lookBack)

Stage 3: summarize

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

Stage 4: where

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

Stage 5: where

| where SourceSystem startswith "ThreatConnect-"

Stage 6: where

| where isnotempty(EmailSenderAddress)

Stage 7: join

| join kind=innerunique (
    OfficeActivity | where TimeGenerated >= ago(dt_lookBack) and isnotempty(UserId)
    | where UserId matches regex emailregex
    | extend OfficeActivity_TimeGenerated = TimeGenerated
)
on $left.EmailSenderAddress == $right.UserId

Stage 8: where

where OfficeActivity_TimeGenerated < ExpirationDateTime

Stage 9: summarize

summarize OfficeActivity_TimeGenerated by IndicatorId, UserId

Stage 10: project

project ActivityGroupNames, ClientIP, ConfidenceScore, Description, EmailRecipient, EmailSenderName, EmailSourceDomain, EmailSourceIpAddress, EmailSubject, ExpirationDateTime, FileHashType, FileHashValue, IndicatorId, OfficeActivity_TimeGenerated, OfficeWorkload, Operation, Parameters, RecordType, ThreatType, Url, UserId, UserType

Stage 11: extend

extend Name, UPNSuffix

Stage 12: extend

extend timestamp

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
ActivityGroupNamesproject
ClientIPproject
ConfidenceScoreproject
Descriptionproject
EmailRecipientproject
EmailSenderNameproject
EmailSourceDomainproject
EmailSourceIpAddressproject
EmailSubjectproject
ExpirationDateTimeproject
FileHashTypeproject
FileHashValueproject
IndicatorIdproject
OfficeActivity_TimeGeneratedproject
OfficeWorkloadproject
Operationproject
Parametersproject
RecordTypeproject
ThreatTypeproject
Urlproject
UserIdproject
UserTypeproject
Nameextend
UPNSuffixextend
timestampextend

Uniqkey - Event ingestion stopped

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

Fires when the UniqkeyEvents_CL table has received no events for six consecutive hours. A silent feed can mean the data connector is failing, the API token has expired or been revoked, or the audit feed has been disabled on the Uniqkey side, and an attacker disabling logging looks identical to an operational outage, so the gap itself is treated as a security signal. Extend the query period if your organization is regularly quiet for longer stretches, for example over weekends.

MITRE ATT&CK Coverage

TacticTechniques
Defense Impairment

Rule Body

id: 97857198-99f2-48dd-b036-461d85a29570
name: Uniqkey - Event ingestion stopped
version: 1.0.0
kind: Scheduled
description: |-
  Fires when the UniqkeyEvents_CL table has received no events for six consecutive hours. A silent feed can mean the data connector is failing, the API token has expired or been revoked, or the audit feed has been disabled on the Uniqkey side, and an attacker disabling logging looks identical to an operational outage, so the gap itself is treated as a security signal. Extend the query period if your organization is regularly quiet for longer stretches, for example over weekends.
severity: Medium
requiredDataConnectors:
  - connectorId: UniqkeyEventsConnector
    dataTypes:
      - UniqkeyEvents_CL
queryFrequency: 1h
queryPeriod: 6h
triggerOperator: Equal
triggerThreshold: 0
tactics:
- DefenseEvasion
relevantTechniques:
- T1562
query: |-
  UniqkeyEvents_CL
  | where TimeGenerated > ago(6h)
  | take 1
suppressionEnabled: false
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: false
    reopenClosedIncident: false
    lookbackDuration: 5h
    matchingMethod: AllEntities
suppressionDuration: 5h
eventGroupingSettings:
  aggregationKind: SingleAlert

Stages and Predicates

Stage 1: source

UniqkeyEvents_CL

Stage 2: where

| where TimeGenerated > ago(6h)

Stage 3: take

| take 1

Varonis - Informational alerts detected

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

'Identifies informational alerts generated by Varonis that provide contextual signals supporting correlation, enrichment, and overall security visibility.'

MITRE ATT&CK Coverage

Rule Body

id: e5a7c93b-8d14-42f6-b7c0-3a9e1f6d5b28
name: Varonis - Informational alerts detected
description: |
  'Identifies informational alerts generated by Varonis that provide contextual signals supporting correlation, enrichment, and overall security visibility.'
severity: Informational
status: Available
requiredDataConnectors:
  - connectorId: VaronisSaaSAlertsPush
    dataTypes:
      - VaronisAlertsV2_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Collection
  - Exfiltration
relevantTechniques:
  - T1530
  - T1213
  - T1567
query: |
  VaronisAlertsV2_CL
  | where AlertSeverity_s == "Informational"
eventGroupingSettings:
  aggregationKind: AlertPerResult
alertDetailsOverride:
  alertDisplayNameFormat: 'Varonis alert: {{ThreatDetectionPolicyName_s}} ({{AlertSeverity_s}})'
  alertDescriptionFormat: 'Varonis detected activity for policy {{ThreatDetectionPolicyName_s}} in category {{AlertCategory_s}}. Users: {{UserNames_s}}.'
customDetails:
  VaronisAlertGuid: AlertId_g
  AlertCategory: AlertCategory_s
  MitreTactic: MitreTacticName_s
  Status: Status_s
  EventsCount: EventsCount_d
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: UserNames_s
      - identifier: Name
        columnName: UserSamAccountNames_s
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: DeviceNames_s
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: false
    reopenClosedIncident: false
    lookbackDuration: PT5H
    matchingMethod: AllEntities
kind: Scheduled
version: 1.0.1

Stages and Predicates

Stage 1: source

VaronisAlertsV2_CL

Stage 2: where

| where AlertSeverity_s == "Informational"

Indicators

These rows show field, operator, and value matches.

VMware ESXi - Dormant VM started

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

'Detects when dormant VM was started.'

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Rule Body

id: 4cdcd5d8-89df-4076-a917-bc50abb9f2ab
name: VMware ESXi - Dormant VM started
description: |
  'Detects when dormant VM was started.'
severity: Medium
requiredDataConnectors:
  - connectorId: SyslogAma
    datatypes:
      - Syslog
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1190
query: |
  let vm_p_off =
  VMwareESXi
  | where TimeGenerated > ago(14d)
  | where SyslogMessage has ('VmPoweredOffEvent')
  | extend DstHostname = extract(@'\[\d+\]\s+\[(.*?)\s+on', 1, SyslogMessage)
  | summarize LastPowerOffTime=max(TimeGenerated) by DstHostname
  | where datetime_diff('day',datetime(now),LastPowerOffTime) >= 20; 
  let vm_p_on =
  VMwareESXi
  | where TimeGenerated > ago(14d)
  | where SyslogMessage has ('VmPoweredOnEvent')
  | extend DstHostname = extract(@'\[\d+\]\s+\[(.*?)\s+on', 1, SyslogMessage)
  | summarize LastPowerOnTime=max(TimeGenerated) by DstHostname
  | where datetime_diff('day',datetime(now),LastPowerOnTime) >= 20;
  let off_vms =
  vm_p_on
  | join (vm_p_off) on DstHostname
  | where LastPowerOffTime > LastPowerOnTime
  | summarize p_off_vm = make_set(DstHostname,200)
  | extend k=1;
  VMwareESXi
  | where TimeGenerated between (ago(24h) .. datetime(now))
  | where SyslogMessage has ('VmPoweredOnEvent')
  | extend DstHostname = extract(@'\[\d+\]\s+\[(.*?)\s+on', 1, SyslogMessage)
  | extend k=1
  | join (off_vms) on k
  | where p_off_vm !has DstHostname
  | summarize rec_p_on = make_set(DstHostname,200)
  | extend hostname = iff(array_length(rec_p_on) > 0, rec_p_on[0], "")
  | extend NTDomain = tostring(split(hostname, '\\', 1)[0]), HostName = tostring(split(hostname, '.', 0)[0])
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: HostName
      - identifier: NTDomain
        columnName: NTDomain
version: 1.0.3
kind: Scheduled

Stages and Predicates

Let Binding: vm_p_off

let vm_p_off = VMwareESXi
| where TimeGenerated > ago(14d)
| where SyslogMessage has ('VmPoweredOffEvent')
| extend DstHostname = extract(@'\[\d+\]\s+\[(.*?)\s+on', 1, SyslogMessage)
| summarize LastPowerOffTime=max(TimeGenerated) by DstHostname
| where datetime_diff('day',datetime(now),LastPowerOffTime) >= 20;

Let Binding: vm_p_on

let vm_p_on = VMwareESXi
| where TimeGenerated > ago(14d)
| where SyslogMessage has ('VmPoweredOnEvent')
| extend DstHostname = extract(@'\[\d+\]\s+\[(.*?)\s+on', 1, SyslogMessage)
| summarize LastPowerOnTime=max(TimeGenerated) by DstHostname
| where datetime_diff('day',datetime(now),LastPowerOnTime) >= 20;

Let Binding: off_vms used in Stage 6

let off_vms = vm_p_on
| join (vm_p_off) on DstHostname
| where LastPowerOffTime > LastPowerOnTime
| summarize p_off_vm = make_set(DstHostname,200)
| extend k=1;

Stage 1: source

VMwareESXi

Stage 2: where

| where TimeGenerated between (ago(24h) .. datetime(now))

Stage 3: where

| where SyslogMessage has ('VmPoweredOnEvent')

Stage 4: extend

| extend DstHostname = extract(@'\[\d+\]\s+\[(.*?)\s+on', 1, SyslogMessage)

Stage 5: extend

| extend k=1

Stage 6: join

join (off_vms) on k

Stage 7: where

where p_off_vm =~ DstHostname

Stage 8: summarize

summarize rec_p_on

Stage 9: extend

extend hostname
hostname =
ifrec_p_on > 0rec_p_on[0]
else""

Stage 10: extend

extend HostName, NTDomain

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
SyslogMessagematch
  • VmPoweredOffEvent transforms: term
  • VmPoweredOnEvent transforms: term
field:"SyslogMessage" kind:match

Output Fields

These fields are emitted when the rule matches.

FieldSource
rec_p_onsummarize
hostnameextend
HostNameextend
NTDomainextend

WAN Accelerator Settings Updated

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

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

Rule Body

id: 2e4fe360-ce67-433b-930e-42f83058dfd0
name: WAN Accelerator Settings Updated
description: Detects when WAN accelerator settings are updated in Veeam Backup & Replication.
  This might indicate configuration changes that require review.
severity: Informational
status: Available
requiredDataConnectors:
- connectorId: Syslog
  dataTypes:
  - Syslog
- connectorId: SyslogAma
  dataTypes:
  - Syslog
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
  aggregationKind: AlertPerResult
tactics: []
relevantTechniques: []
query: "Veeam_GetSecurityEvents\n| where instanceId == 27200\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 == 27200

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

Back to all Kusto rules