Detection rules › Kusto
Kusto rules: 27
[Deprecated] Explicit MFA Deny
#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
| Tactic | Techniques |
|---|---|
| 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.
| Field | Kind | Values | Search |
|---|---|---|---|
OnboardingStatus | eq |
| field:"OnboardingStatus" kind:eq value:"Onboarded" |
SensorHealthState | eq |
| field:"SensorHealthState" kind:eq value:"Active" |
Account Elevated to New Role
#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
| Tactic | Techniques |
|---|---|
| Persistence |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Azure | Add member to role completed (PIM activation) |
Rules Detecting the Same Action
These rules filter on the same operation.
- [Entra ID] Application Assigned Administrator Permissions Immediately After Obtaining Role Management Permissions (Kusto)
- [Entra ID] Mass Privileged Role Change Activity Detected (Kusto)
- [Entra ID] Privilege Elevation Request Denied (Kusto)
- [Entra ID] Privileged Role Assigned to a New User (Kusto)
- [Entra ID] Privileged Role Assigned to User (Kusto)
- Admin promotion after Role Management Application Permission Grant (Kusto)
- App Assigned To Azure RBAC/Microsoft Entra Role (Sigma)
- Azure AD Application Administrator Role Assigned (Splunk)
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
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.
| Field | Kind | Values | Search |
|---|---|---|---|
OperationName | eq |
| field:"OperationName" kind:eq value:"Add member to role completed (PIM activation)" |
Result | eq |
| field:"Result" kind:eq value:"success" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
OperationName | eq | Add member to role completed (PIM activation) | excludes:OperationName field:"OperationName" value:"Add member to role completed (PIM activation)" |
Result | eq | success | excludes:Result field:"Result" value:"success" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Id | project |
InitiatingIPAddress | extend |
InitiatingAppName | extend |
InitiatingAppServicePrincipalId | extend |
InitiatingUserPrincipalName | extend |
InitiatingAadUserId | extend |
ElevatedBy | extend |
ElevatedUser | extend |
InitiatingAccountName | extend |
InitiatingAccountUPNSuffix | extend |
TargetAccountName | extend |
TargetAccountUPNSuffix | extend |
API - BOLA
#'42Crunch API protection against BOLA'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| 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
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
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.
| Field | Kind | Values | Search |
|---|---|---|---|
UriPath | regex_match |
| field:"UriPath" kind:regex_match value:"/api/accounts/\d{6}$" |
Application Gateway WAF - SQLi Detection
#'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
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Execution | |
| Stealth | |
| Privilege Escalation |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
Blocked_Reason | contains |
| field:"Blocked_Reason" kind:contains value:"SQL Injection Attack" |
Category | eq |
| field:"Category" kind:eq value:"ApplicationGatewayFirewallLog" |
SQLI_Score | ge |
| field:"SQLI_Score" kind:ge value:"10" |
TotalInboundScore | ge |
| field:"TotalInboundScore" kind:ge value:"15" |
Total_TransactionId | ge |
| field:"Total_TransactionId" kind:ge value:"3" |
action_s | eq |
| field:"action_s" kind:eq |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Detail_Data | summarize |
Detail_Message | summarize |
EndTime | summarize |
Message | summarize |
SQLI_Score | summarize |
StartTime | summarize |
TotalInboundScore | summarize |
Total_TransactionId | summarize |
TransactionID | summarize |
Uri | summarize |
action_s | summarize |
clientIp_s | summarize |
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.
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.
| Field | Kind | Values | Search |
|---|---|---|---|
Finding | eq |
| 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.
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.
| Field | Kind | Values | Search |
|---|---|---|---|
Finding | eq |
| field:"Finding" kind:eq value:"T0MarkSensitive" |
Cisco SDWAN - Maleware Events
#'This analytic rule will monitor Malware Events in Syslog and Netflow Data'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| 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.
| Field | Kind | Values | Search |
|---|---|---|---|
Malware | is_not_null | field:"Malware" kind:is_not_null | |
Malware | ne |
| field:"Malware" kind:ne value:"None" |
NetflowUsername | is_not_null | field:"NetflowUsername" kind:is_not_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
NetflowFwSrcAddrIpv4 | summarize |
COM Event System Loading New DLL
#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
| Tactic | Techniques |
|---|---|
| Privilege Escalation |
Telemetry Coverage
| Provider | Record / event type |
|---|---|
| Sysmon | Event ID 1: Process creation |
| Sysmon | Event ID 7: Image loaded |
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
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.
| Field | Kind | Values | Search |
|---|---|---|---|
EventID | eq |
| field:"EventID" kind:eq |
Image | ends_with |
| field:"Image" kind:ends_with value:"\svchost.exe" |
Images.@Name | eq |
| field:"Images.@Name" kind:eq value:"Image" |
LoadedItems.@Name | eq |
| field:"LoadedItems.@Name" kind:eq value:"ImageLoaded" |
ParentImage | ends_with |
| field:"ParentImage" kind:ends_with value:"\svchost.exe" |
Source | eq |
| field:"Source" kind:eq value:"Microsoft-Windows-Sysmon" |
delta | le |
| field:"delta" kind:le value:"5s" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
DLL | eq | known_dlls | excludes:DLL field:"DLL" value:"known_dlls" |
CyberArkEPM - Uncommon Windows process started from System folder
#'Detects when uncommon windows proccess is started from System folder.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| 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
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.
| Field | Kind | Values | Search |
|---|---|---|---|
ActingProcessName | match |
| field:"ParentImage" kind:match |
EventSubType | ne |
| field:"EventSubType" kind:ne value:"AttackAttempt" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
ActingProcessFileInternalName | eq | sys_proc | excludes:ActingProcessFileInternalName field:"ActingProcessFileInternalName" value:"sys_proc" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountCustomEntity | extend |
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.
"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
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Execution | |
| Privilege Escalation | |
| Defense Impairment | |
| Credential Access | |
| Lateral Movement |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
confidence_score | ge |
| field:"confidence_score" kind:ge value:"80" |
vulnerability_category | eq |
| field:"vulnerability_category" kind:eq value:"ATTACK_SURFACE_VULNERABILITY" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
NetworkIPs | project |
ProductName | project |
ProviderName | project |
TimeGenerated | project |
UID | project |
attack_complexity | project |
attack_vector | project |
confidence_score | project |
confidentiality_impact | project |
created | project |
cvss_score | project |
cvss_vector | project |
cvss_version | project |
description | project |
exploitability_score | project |
impact_score | project |
integrity_impact | project |
modified | project |
name | project |
privileges_required | project |
products | project |
scope | project |
servers | project |
technologies | project |
user_interaction | project |
vendors | project |
vulnerability_category | project |
vulnerability_type | project |
Cynerio - Suspicious Connection to External Address
#Suspicious Connection to External Address
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Lateral Movement |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
module_s | eq |
| field:"module_s" kind:eq value:"IDS" |
title_s | eq |
| field:"title_s" kind:eq value:"Suspicious Connection" |
Detect Msiexec executing DLL network connections
#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
| Tactic | Techniques |
|---|---|
| Stealth |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
InitiatingProcessCommandLine1 | match |
| field:"InitiatingProcessCommandLine1" kind:match |
InitiatingProcessFileName | eq |
| field:"InitiatingProcessFileName" kind:eq value:"msiexec.exe" |
InitiatingProcessParentFileName | eq |
| field:"parent_process_basename" kind:eq value:"msiexec.exe" |
Dragos Notifications
#'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
#'Detects requests to ip lookup resources.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| 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.
| Field | Kind | Values | Search |
|---|---|---|---|
Query | in |
| field:"Query" kind:in |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DNSCustomEntity | extend |
IPCustomEntity | extend |
Guardian- Content Safety Toxicity Policy Violation Detection.
#'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.
| Field | Kind | Values | Search |
|---|---|---|---|
PolicyViolatedControlFeature | eq |
| field:"PolicyViolatedControlFeature" kind:eq value:"Toxicity" |
Severity | eq |
| field:"Severity" kind:eq value:"Low" |
High-Risk Cross-Cloud User Impersonation
#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
| Tactic | Techniques |
|---|---|
| Privilege Escalation |
Telemetry Coverage
Rules Detecting the Same Action
These rules filter on the same operation.
- AWS IAM Backdoor Users Keys (Sigma)
- AWS IAM Create User via Assumed Role on EC2 Instance (Elastic)
- AWS IAM Credentials Added to a Bedrock API Key Phantom User (Elastic)
- AWS IAM Failure Group Deletion (Splunk)
- AWS IAM Group Creation (Elastic)
- AWS IAM Group Deletion (Elastic)
- AWS IAM S3Browser User or AccessKey Creation (Sigma)
- AWS IAM Sensitive Operations via Lambda Execution Role (Elastic)
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 =ResultType == 0"Successful Signin""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.
| Field | Kind | Values | Search |
|---|---|---|---|
AppDisplayName | in |
| field:"Application" kind:in |
ErrorMessage | is_null | field:"ErrorMessage" kind:is_null | |
EventName | in |
| field:"EventName" kind:in |
EventSource | in |
| field:"EventSource" kind:in |
RiskLevelAggregated | eq |
| field:"RiskLevelAggregated" kind:eq value:"high" |
RiskLevelDuringSignIn | eq |
| field:"RiskLevelDuringSignIn" kind:eq value:"high" |
timedef | ge |
| field:"timedef" kind:ge value:"0" |
timedef | le |
| field:"timedef" kind:le value:"8" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
IPAddress | summarize |
RiskEventTypes | summarize |
RiskEventTypes_V2 | summarize |
UserPrincipalName | summarize |
signInTime | summarize |
timedef | extend |
Jamf Protect - Unified Logs
#'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.
| Field | Kind | Values | Search |
|---|---|---|---|
EventSeverity | is_not_null | field:"EventSeverity" kind:is_not_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Host_IPs | extend |
RunningRAT request parameters
#'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
| Tactic | Techniques |
|---|---|
| Command & Control | |
| Exfiltration |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
RequestMethod | eq |
| field:"RequestMethod" kind:eq value:"GET" |
RequestURL | match |
| field:"RequestURL" kind:match |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DestinationDnsDomain | project |
DestinationIP | project |
DeviceAction | project |
DeviceProduct | project |
DeviceVendor | project |
RequestClientApplication | project |
RequestURL | project |
SourceHostName | project |
SourceIP | project |
TimeGenerated | project |
ShieldBreak: Defender AV Privilege Escalation
#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
| Tactic | Techniques |
|---|---|
| Privilege Escalation | No specific technique |
References
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Microsoft Defender for Endpoint | DeviceProcessEvents action any: Process activity |
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
#Identifies a match across various data feeds for domains related to an actor tracked by Microsoft as Star Blizzard.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| 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.
| Field | Kind | Values | Search |
|---|---|---|---|
Category | eq |
| field:"Category" kind:eq value:"AzureFirewallApplicationRule" |
DNSName | in |
| field:"DNSName" kind:in value:"DomainNames" |
DestinationHost | is_not_null | field:"DestinationHost" kind:is_not_null | |
DestinationHost | match |
| field:"DestinationHost" kind:match value:"DomainNames" |
EmailDirection | eq |
| field:"EmailDirection" kind:eq value:"Inbound" |
RemoteUrl | match |
| field:"DestinationHostname" kind:match value:"DomainNames" |
ResourceType | eq |
| field:"ResourceType" kind:eq value:"AZUREFIREWALLS" |
Url | match |
| field:"Url" kind:match value:"DomainNames" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Account | extend |
Computer | extend |
IPAddress | extend |
DNSName | extend |
AccountName | extend |
AccountUPNSuffix | extend |
DomainIndex | extend |
HostName | extend |
HostNameDomain | extend |
Suspicious Process Injection from Office application
#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
| Tactic | Techniques |
|---|---|
| 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.
| Field | Kind | Values | Search |
|---|---|---|---|
ActionType | in |
| field:"ActionType" kind:in |
InitiatingProcessFileName | in |
| field:"InitiatingProcessFileName" kind:in |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
InitiatingProcessCommandLine | contains | /dde | excludes:InitiatingProcessCommandLine field:"InitiatingProcessCommandLine" value:"/dde" |
InitiatingProcessCommandLine | match | .docx, dotx, .xlsx, .xltx, .pptx | excludes:InitiatingProcessCommandLine |
Tailscale: Split-DNS configuration modified
#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
| Tactic | Techniques |
|---|---|
| Persistence | |
| Defense Impairment | |
| Credential Access | |
| Command & Control |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
Action | eq |
| field:"Action" kind:eq value:"UPDATE" |
Target.property | eq |
| field:"Target.property" kind:eq value:"DNS_SPLIT_DNS" |
Target.type | eq |
| field:"Target.type" kind:eq value:"TAILNET" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActorLogin | project |
AddedDomains | project |
New | project |
Old | project |
Origin | project |
RemovedDomains | project |
TimeGenerated | project |
Threat Essentials - Multiple admin membership removals from newly created admin.
#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
| Tactic | Techniques |
|---|---|
| Impact |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Azure | any: Entra ID audit event (any operation) |
Rules Detecting the Same Action
These rules filter on the same operation.
- [Entra ID] Application Assigned Administrator Permissions Immediately After Obtaining Role Management Permissions (Kusto)
- [Entra ID] Mass Privileged Role Change Activity Detected (Kusto)
- [Entra ID] Privilege Elevation Request Denied (Kusto)
- [Entra ID] Privileged Role Assigned to a New User (Kusto)
- [Entra ID] Privileged Role Assigned to User (Kusto)
- Account Elevated to New Role (Kusto)
- Admin promotion after Role Management Application Permission Grant (Kusto)
- App Assigned To Azure RBAC/Microsoft Entra Role (Sigma)
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
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.
| Field | Kind | Values | Search |
|---|---|---|---|
AADOperationType | in |
| field:"AADOperationType" kind:in |
ActivityDisplayName | match |
| field:"azure_ad::activity_display_name" kind:match |
Category | eq |
| field:"Category" kind:eq value:"RoleManagement" |
Initiator | ne |
| field:"Initiator" kind:ne |
NoofAdminsRemoved | gt |
| field:"NoofAdminsRemoved" kind:gt value:"1" |
Result | eq |
| field:"Result" kind:eq value:"success" |
RoleName | eq |
| field:"RoleName" kind:eq value:"Global Administrator" |
displayName_ | eq |
| field:"displayName_" kind:eq value:"Role.DisplayName" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AddedGlobalAdminTime | project |
Initiator | project |
NoofAdminsRemoved | project |
RemovedGlobalAdminTime | project |
Target | project |
TargetAdmins | project |
Name | extend |
UPNSuffix | extend |
ThreatConnect TI map Email entity to OfficeActivity
#'Identifies a match in OfficeActivity table from any Email IOC from ThreatConnect TI'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| 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.
| Field | Kind | Values | Search |
|---|---|---|---|
Active | eq |
| field:"Active" kind:eq value:"true" |
EmailSenderAddress | is_not_null | field:"EmailSenderAddress" kind:is_not_null | |
SourceSystem | starts_with |
| field:"SourceSystem" kind:starts_with value:"ThreatConnect-" |
UserId | is_not_null | field:"UserId" kind:is_not_null | |
UserId | regex_match |
| field:"UserId" kind:regex_match value:"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+$" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActivityGroupNames | project |
ClientIP | project |
ConfidenceScore | project |
Description | project |
EmailRecipient | project |
EmailSenderName | project |
EmailSourceDomain | project |
EmailSourceIpAddress | project |
EmailSubject | project |
ExpirationDateTime | project |
FileHashType | project |
FileHashValue | project |
IndicatorId | project |
OfficeActivity_TimeGenerated | project |
OfficeWorkload | project |
Operation | project |
Parameters | project |
RecordType | project |
ThreatType | project |
Url | project |
UserId | project |
UserType | project |
Name | extend |
UPNSuffix | extend |
timestamp | extend |
Uniqkey - Event ingestion stopped
#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
| Tactic | Techniques |
|---|---|
| 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
#'Identifies informational alerts generated by Varonis that provide contextual signals supporting correlation, enrichment, and overall security visibility.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Collection | |
| Exfiltration |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
AlertSeverity_s | eq |
| field:"AlertSeverity_s" kind:eq value:"Informational" |
VMware ESXi - Dormant VM started
#'Detects when dormant VM was started.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| 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
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 =rec_p_on > 0rec_p_on[0]""Stage 10: extend
extend HostName, NTDomain
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
SyslogMessage | match |
| field:"SyslogMessage" kind:match |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
rec_p_on | summarize |
hostname | extend |
HostName | extend |
NTDomain | extend |
WAN Accelerator Settings Updated
#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.
| Field | Kind | Values | Search |
|---|---|---|---|
instanceId | eq |
| field:"instanceId" kind:eq value:"27200" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DataSource | project |
Date | project |
EventId | project |
MessageDetails | project |
Severity | project |
UserName | project |