Detection rules › Kusto
Kusto rules: 0n
Account created or deleted by non-approved user
#Identifies accounts that were created or deleted by a defined list of non-approved user principal names. Add to this list before running the query for accurate results. Ref : https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-user-accounts
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Azure | Add user |
| Azure | Delete user |
Rules Detecting the Same Action
These rules filter on the same operation.
- [Entra ID] Authentication Method Changed for Privileged Account (Kusto)
- Account Created and Deleted in Short Timeframe (Kusto)
- Account Created And Deleted Within A Close Time Frame (Sigma)
- Account created from non-approved sources (Kusto)
- Addition of a Temporary Access Pass to a Privileged Account (Kusto)
- Authentication Method Changed for Privileged Account (Kusto)
- Authentication Methods Changed for Privileged Account (Kusto)
- Azure AD External Guest User Invited (Splunk)
Rule Body
id: 6d63efa6-7c25-4bd4-a486-aa6bf50fde8a
name: Account created or deleted by non-approved user
description: |
'Identifies accounts that were created or deleted by a defined list of non-approved user principal names. Add to this list before running the query for accurate results.
Ref : https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-user-accounts'
severity: Medium
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- AuditLogs
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- InitialAccess
relevantTechniques:
- T1078.004
tags:
- AADSecOpsGuide
query: |
// Add non-approved user principal names or apps to the list below to search for their account creation/deletion activity
// ex: dynamic(["UPN1", "upn123"])
let nonapproved_users = dynamic([]);
let nonapproved_apps = dynamic([]);
AuditLogs
| where OperationName =~ "Add user" or OperationName =~ "Delete user"
| where Result =~ "success"
| extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
| extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
| extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend InitiatingIpAddress = tostring(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))
| where InitiatingUserPrincipalName has_any (nonapproved_users) or InitiatingAppName has_any (nonapproved_apps)
| extend InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: InitiatingAppName
- identifier: AadUserId
columnName: InitiatingAppServicePrincipalId
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: InitiatingUserPrincipalName
- identifier: Name
columnName: InitiatingAccountName
- identifier: UPNSuffix
columnName: InitiatingAccountUPNSuffix
- entityType: Account
fieldMappings:
- identifier: AadUserId
columnName: InitiatingAadUserId
- entityType: IP
fieldMappings:
- identifier: Address
columnName: InitiatingIpAddress
version: 1.1.1
kind: Scheduled
Stages and Predicates
Parameters
let nonapproved_users = dynamic([]);
let nonapproved_apps = dynamic([]);
Stage 1: source
AuditLogs
Stage 2: where
| where OperationName =~ "Add user" or OperationName =~ "Delete user"
Stage 3: where
| where Result =~ "success"
Stage 4: extend (5 consecutive steps)
| extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
| extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
| extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend InitiatingIpAddress = tostring(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))
Stage 5: where
| where InitiatingUserPrincipalName has_any (nonapproved_users) or InitiatingAppName has_any (nonapproved_apps)
Stage 6: extend
| extend InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1])
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
InitiatingAppName | match |
| field:"InitiatingAppName" kind:match value:"[]" |
InitiatingUserPrincipalName | match |
| field:"InitiatingUserPrincipalName" kind:match value:"[]" |
OperationName | eq |
| field:"OperationName" kind:eq |
Result | eq |
| field:"Result" kind:eq value:"success" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
InitiatingAppName | extend |
InitiatingAppServicePrincipalId | extend |
InitiatingUserPrincipalName | extend |
InitiatingAadUserId | extend |
InitiatingIpAddress | extend |
InitiatingAccountName | extend |
InitiatingAccountUPNSuffix | extend |
BloodHound Attack Path Finding - Logic App Contributor Role on Tier Zero Logic App
#This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.
BloodHound Attack Path Finding - Logic App Contributor Role on Tier Zero Logic App
Rule Body
id: 25ffa891-4832-485d-9461-f8c90df1d3ff
name: BloodHound Attack Path Finding - Logic App Contributor Role on Tier Zero Logic
App
description: BloodHound Attack Path Finding - Logic App Contributor Role on Tier Zero
Logic App
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: BloodHoundEnterprise
dataTypes:
- BHEAttackPathsData_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: BHEAttackPathsData_CL | where Finding == "AzureT0LogicAppContributor"
entityMappings:
- entityType: URL
fieldMappings:
- identifier: Url
columnName: domain_name
customDetails:
Description: ShortDescription
Remediation: ShortRemediation
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
BHEAttackPathsData_CL
Stage 2: where
| where Finding == "AzureT0LogicAppContributor"
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Finding | eq |
| field:"Finding" kind:eq value:"AzureT0LogicAppContributor" |
Bulk Changes to Privileged Account Permissions
#Identifies when changes to multiple users permissions are changed at once. Investigate immediately if not a planned change. This setting could enable an attacker access to Azure subscriptions in your environment. Ref : https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-privileged-identity-management
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Privilege Escalation |
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: 218f60de-c269-457a-b882-9966632b9dc6
name: Bulk Changes to Privileged Account Permissions
description: |
'Identifies when changes to multiple users permissions are changed at once. Investigate immediately if not a planned change. This setting could enable an attacker access to Azure subscriptions in your environment.
Ref : https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-privileged-identity-management'
severity: High
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- AuditLogs
queryFrequency: 2h
queryPeriod: 2h
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- PrivilegeEscalation
relevantTechniques:
- T1078.004
tags:
- AADSecOpsGuide
query: |
let AdminRecords = AuditLogs
| where Category =~ "RoleManagement"
| where ActivityDisplayName has_any ("Add eligible member to role", "Add member to role")
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type =~ "User"
| extend TargetUserPrincipalName = tostring(TargetResource.userPrincipalName),
props = TargetResource.modifiedProperties
)
| mv-apply Property = props on
(
where Property.displayName =~ "Role.DisplayName"
| extend RoleName = trim('"',tostring(Property.newValue))
)
| where RoleName contains "Admin";
AdminRecords
| summarize dcount(TargetUserPrincipalName) by bin(TimeGenerated, 1h)
| where dcount_TargetUserPrincipalName > 9
| join kind=rightsemi (
AdminRecords
| extend TimeWindow = bin(TimeGenerated, 1h)
) on $left.TimeGenerated == $right.TimeWindow
| extend InitiatedByUser = iff(isnotempty(InitiatedBy.user.userPrincipalName), tostring(InitiatedBy.user.userPrincipalName), "")
| extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
| extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
| extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend InitiatingIpAddress = tostring(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))
| extend TargetName = tostring(split(TargetUserPrincipalName,'@',0)[0]), TargetUPNSuffix = tostring(split(TargetUserPrincipalName,'@',1)[0])
| extend InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: InitiatingAppName
- identifier: AadUserId
columnName: InitiatingAppServicePrincipalId
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: TargetUserPrincipalName
- identifier: Name
columnName: TargetName
- identifier: UPNSuffix
columnName: TargetUPNSuffix
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: InitiatingUserPrincipalName
- identifier: Name
columnName: InitiatingAccountName
- identifier: UPNSuffix
columnName: InitiatingAccountUPNSuffix
- entityType: Account
fieldMappings:
- identifier: AadUserId
columnName: InitiatingAadUserId
- entityType: IP
fieldMappings:
- identifier: Address
columnName: InitiatingIpAddress
customDetails:
InitiatedByUser: InitiatedByUser
TargetUser: TargetUserPrincipalName
version: 1.0.7
kind: Scheduled
Stages and Predicates
let AdminRecords is inlined into the numbered stages below.
Stage 0: let
let AdminRecords = AuditLogs <inlined as stages below>;
Stages 1 to 6 define let AdminRecords as the rule's main pipeline source. Stages 7 to 10 use it.
Stage 1: source
AuditLogs
Stage 2: where
| where Category =~ "RoleManagement"
Stage 3: where
| where ActivityDisplayName has_any ("Add eligible member to role", "Add member to role")
Stage 4: kusto:mv-apply
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type =~ "User"
| extend TargetUserPrincipalName = tostring(TargetResource.userPrincipalName),
props = TargetResource.modifiedProperties
)
Stage 5: kusto:mv-apply
| mv-apply Property = props on
(
where Property.displayName =~ "Role.DisplayName"
| extend RoleName = trim('"',tostring(Property.newValue))
)
Stage 6: where
| where RoleName contains "Admin"
Stage 7: summarize
AdminRecords
| summarize dcount(TargetUserPrincipalName) by bin(TimeGenerated, 1h)
Stage 8: where
| where dcount_TargetUserPrincipalName > 9
Stage 9: join
| join kind=rightsemi (
AdminRecords
| extend TimeWindow = bin(TimeGenerated, 1h)
) on $left.TimeGenerated == $right.TimeWindow
Stage 10: extend (8 consecutive steps)
extend InitiatedByUser, InitiatingAadUserId, InitiatingAccountName, InitiatingAccountUPNSuffix, InitiatingAppName, InitiatingAppServicePrincipalId, InitiatingIpAddress, InitiatingUserPrincipalName, TargetName, TargetUPNSuffix
InitiatedByUser =isnotempty(InitiatedBy.user.userPrincipalName)tostring(InitiatedBy.user.userPrincipalName)""Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ActivityDisplayName | match |
| field:"azure_ad::activity_display_name" kind:match |
Category | eq |
| field:"Category" kind:eq value:"RoleManagement" |
Property.displayName | eq |
| field:"Property.displayName" kind:eq value:"Role.DisplayName" |
RoleName | contains |
| field:"RoleName" kind:contains value:"Admin" |
TargetResource.type | eq |
| field:"TargetResource.type" kind:eq value:"User" |
dcount_TargetUserPrincipalName | gt |
| field:"dcount_TargetUserPrincipalName" kind:gt value:"9" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
InitiatedByUser | extend |
InitiatingAppName | extend |
InitiatingAppServicePrincipalId | extend |
InitiatingUserPrincipalName | extend |
InitiatingAadUserId | extend |
InitiatingIpAddress | extend |
TargetName | extend |
TargetUPNSuffix | extend |
InitiatingAccountName | extend |
InitiatingAccountUPNSuffix | extend |
Cisco ASA - average attack detection rate increase
#'This will help you determine if Cisco ASA devices are under heavier attack than normal over the last hour versus the previous 6 hours based on DeviceEventClassID 733100 References: https://www.cisco.com/c/en/us/td/docs/security/asa/syslog/b_syslog/syslogs9.html Details on how to further troubleshoot/investigate: https://www.cisco.com/c/en/us/support/docs/security/asa-5500-x-series-next-generation-firewalls/113685-asa-threat-detection.html'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Discovery | |
| Impact |
Rule Body
id: 79f29feb-6a9d-4cdf-baaa-2daf480a5da1
name: Cisco ASA - average attack detection rate increase
description: |
'This will help you determine if Cisco ASA devices are under heavier attack than normal over the last hour versus the previous 6 hours based on DeviceEventClassID 733100
References: https://www.cisco.com/c/en/us/td/docs/security/asa/syslog/b_syslog/syslogs9.html
Details on how to further troubleshoot/investigate: https://www.cisco.com/c/en/us/support/docs/security/asa-5500-x-series-next-generation-firewalls/113685-asa-threat-detection.html'
severity: Low
status: Available
requiredDataConnectors:
- connectorId: CiscoAsaAma
dataTypes:
- CommonSecurityLog
queryFrequency: 1h
queryPeriod: 6h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Discovery
- Impact
relevantTechniques:
- T1046
- T1498
query: |
let timeframe = 1h;
let last1h = CommonSecurityLog
| where TimeGenerated >= ago(timeframe)
| where isempty(CommunicationDirection)
| where DeviceEventClassID == "733100"
| extend SourceOfDropRateCount = tostring(split(tostring(split(Message, "]")[0]),"[ ")[1])
| extend splitMessage = split(Message, ".")
| extend DropRate = tostring(split(tostring(splitMessage[0]),"] ")[1])
| extend CurrentBurstRate = split(tostring(split(tostring(splitMessage[1])," ")[0]),"is ")
| extend CurrentBurstRatePerSec = toint(split(tostring(CurrentBurstRate[1])," ")[0])
| extend MaxConfiguredBurstRate = toint(CurrentBurstRate[2])
| extend CurrentAvgRate = split(tostring(split(tostring(splitMessage[1])," ")[1]),"is ")
| extend CurrentAvgRatePerSec = toint(split(tostring(CurrentAvgRate[1])," ")[0])
| extend MaxConfiguredAvgRate = toint(CurrentAvgRate[2])
| extend CumulativeTotal = toint(split(tostring(split(tostring(splitMessage[1])," ")[2]),"is ")[1])
| summarize last1hCumTotal = sum(CumulativeTotal), last1hAvgRatePerSec = avg(CurrentAvgRatePerSec), last1hAvgBurstRatePerSec = avg(CurrentBurstRatePerSec) by DeviceName, DeviceEventClassID, SourceIP, SourceOfDropRateCount, DropRate;
let prev6h = CommonSecurityLog
| where TimeGenerated between (ago(6h) .. ago(1h))
| where isempty(CommunicationDirection)
| where DeviceEventClassID == "733100"
| extend SourceOfDropRateCount = tostring(split(tostring(split(Message, "]")[0]),"[ ")[1])
| extend splitMessage = split(Message, ".")
| extend DropRate = tostring(split(tostring(splitMessage[0]),"] ")[1])
| extend CurrentBurstRate = split(tostring(split(tostring(splitMessage[1])," ")[0]),"is ")
| extend prevCurrentBurstRatePerSec = toint(split(tostring(CurrentBurstRate[1])," ")[0])
| extend prevMaxConfiguredBurstRate = toint(CurrentBurstRate[2])
| extend CurrentAvgRate = split(tostring(split(tostring(splitMessage[1])," ")[1]),"is ")
| extend prevCurrentAvgRatePerSec = toint(split(tostring(CurrentAvgRate[1])," ")[0])
| extend prevMaxConfiguredAvgRate = toint(CurrentAvgRate[2])
| extend prevCumulativeTotal = toint(split(tostring(split(tostring(splitMessage[1])," ")[2]),"is ")[1])
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), prev6hCumTotal = sum(prevCumulativeTotal), prev6hAvgRatePerSec = avg(prevCurrentAvgRatePerSec), prev6hAvgBurstRatePerSec = avg(prevCurrentBurstRatePerSec)
by DeviceName, DeviceEventClassID, SourceIP, SourceOfDropRateCount, DropRate;
last1h | join (
prev6h
) on DeviceName, DeviceEventClassID, SourceIP, SourceOfDropRateCount, DropRate
| project StartTimeUtc, EndTimeUtc, DeviceName, DeviceEventClassID, SourceIP, SourceOfDropRateCount, DropRate, last1hCumTotal, prev6hCumTotal, prev6hAvgCumTotal = prev6hCumTotal/6, last1hAvgRatePerSec, prev6hAvgRatePerSec, last1hAvgBurstRatePerSec, prev6hAvgBurstRatePerSec
// Select only events that indicate a doubling of the expected rate in the last hour over the previous 6 hours
| where last1hCumTotal > 2*prev6hAvgCumTotal or last1hAvgRatePerSec > 2*prev6hAvgRatePerSec or last1hAvgBurstRatePerSec > 2*prev6hAvgBurstRatePerSec
| extend HostName = tostring(split(DeviceName, ".")[0]), DomainIndex = toint(indexof(DeviceName, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(DeviceName, DomainIndex + 1), DeviceName)
entityMappings:
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: DeviceName
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: HostNameDomain
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIP
version: 1.0.3
kind: Scheduled
Stages and Predicates
Parameters
let timeframe = 1h;
let last1h is inlined into the numbered stages below.
Let Binding: prev6h
let prev6h = CommonSecurityLog
| where TimeGenerated between (ago(6h) .. ago(1h))
| where isempty(CommunicationDirection)
| where DeviceEventClassID == "733100"
| extend SourceOfDropRateCount = tostring(split(tostring(split(Message, "]")[0]),"[ ")[1])
| extend splitMessage = split(Message, ".")
| extend DropRate = tostring(split(tostring(splitMessage[0]),"] ")[1])
| extend CurrentBurstRate = split(tostring(split(tostring(splitMessage[1])," ")[0]),"is ")
| extend prevCurrentBurstRatePerSec = toint(split(tostring(CurrentBurstRate[1])," ")[0])
| extend prevMaxConfiguredBurstRate = toint(CurrentBurstRate[2])
| extend CurrentAvgRate = split(tostring(split(tostring(splitMessage[1])," ")[1]),"is ")
| extend prevCurrentAvgRatePerSec = toint(split(tostring(CurrentAvgRate[1])," ")[0])
| extend prevMaxConfiguredAvgRate = toint(CurrentAvgRate[2])
| extend prevCumulativeTotal = toint(split(tostring(split(tostring(splitMessage[1])," ")[2]),"is ")[1])
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), prev6hCumTotal = sum(prevCumulativeTotal), prev6hAvgRatePerSec = avg(prevCurrentAvgRatePerSec), prev6hAvgBurstRatePerSec = avg(prevCurrentBurstRatePerSec)
by DeviceName, DeviceEventClassID, SourceIP, SourceOfDropRateCount, DropRate;
Stages 1 to 6 define let last1h as the rule's main pipeline source. Stages 7 to 11 use it.
Stage 1: source
CommonSecurityLog
Stage 2: where
| where TimeGenerated >= ago(timeframe)
Stage 3: where
| where isempty(CommunicationDirection)
Stage 4: where
| where DeviceEventClassID == "733100"
Stage 5: extend (10 consecutive steps)
| extend SourceOfDropRateCount = tostring(split(tostring(split(Message, "]")[0]),"[ ")[1])
| extend splitMessage = split(Message, ".")
| extend DropRate = tostring(split(tostring(splitMessage[0]),"] ")[1])
| extend CurrentBurstRate = split(tostring(split(tostring(splitMessage[1])," ")[0]),"is ")
| extend CurrentBurstRatePerSec = toint(split(tostring(CurrentBurstRate[1])," ")[0])
| extend MaxConfiguredBurstRate = toint(CurrentBurstRate[2])
| extend CurrentAvgRate = split(tostring(split(tostring(splitMessage[1])," ")[1]),"is ")
| extend CurrentAvgRatePerSec = toint(split(tostring(CurrentAvgRate[1])," ")[0])
| extend MaxConfiguredAvgRate = toint(CurrentAvgRate[2])
| extend CumulativeTotal = toint(split(tostring(split(tostring(splitMessage[1])," ")[2]),"is ")[1])
Stage 6: summarize
| summarize last1hCumTotal = sum(CumulativeTotal), last1hAvgRatePerSec = avg(CurrentAvgRatePerSec), last1hAvgBurstRatePerSec = avg(CurrentBurstRatePerSec) by DeviceName, DeviceEventClassID, SourceIP, SourceOfDropRateCount, DropRate
Stage 7: join
last1h
| join (
prev6h
) on DeviceName, DeviceEventClassID, SourceIP, SourceOfDropRateCount, DropRate
Stage 8: project
project DeviceEventClassID, DeviceName, DropRate, EndTimeUtc, SourceIP, SourceOfDropRateCount, StartTimeUtc, last1hAvgBurstRatePerSec, last1hAvgRatePerSec, last1hCumTotal, prev6hAvgBurstRatePerSec, prev6hAvgCumTotal, prev6hAvgRatePerSec, prev6hCumTotal
Stage 9: where
where (last1hAvgBurstRatePerSec > (prev6hAvgBurstRatePerSec * 2) or last1hAvgRatePerSec > (prev6hAvgRatePerSec * 2) or last1hCumTotal > (prev6hAvgCumTotal * 2))
Stage 10: extend
extend DomainIndex, HostName
Stage 11: extend
extend HostNameDomain
HostNameDomain =DomainIndex != -1substring(DeviceName, (DomainIndex + 1))DeviceNameIndicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
CommunicationDirection | is_null | field:"CommunicationDirection" kind:is_null | |
DeviceEventClassID | eq |
| field:"DeviceEventClassID" kind:eq value:"733100" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DeviceEventClassID | project |
DeviceName | project |
DropRate | project |
EndTimeUtc | project |
SourceIP | project |
SourceOfDropRateCount | project |
StartTimeUtc | project |
last1hAvgBurstRatePerSec | project |
last1hAvgRatePerSec | project |
last1hCumTotal | project |
prev6hAvgBurstRatePerSec | project |
prev6hAvgCumTotal | project |
prev6hAvgRatePerSec | project |
prev6hCumTotal | project |
DomainIndex | extend |
HostName | extend |
HostNameDomain | extend |
CYFIRMA - Attack Surface - Weak Certificate Exposure - High Rule
#This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.
"This alert indicates that a weak or insecure SSL/TLS certificate has been detected on a public-facing asset monitored by Cyfirma. Such certificates do not meet modern encryption standards and are considered insecure, especially for handling sensitive transactions. This exposure increases the risk of man-in-the-middle attacks and loss of data integrity or confidentiality. Immediate remediation is advised by replacing weak certificates with strong, industry-compliant ones."
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Reconnaissance | |
| Resource Development | |
| Initial Access | |
| Defense Impairment | |
| Credential Access |
Rule Body
id: 3b5a1c0e-7f3a-4d47-8416-6c0b8b91e9ce
name: CYFIRMA - Attack Surface - Weak Certificate Exposure - High Rule
description: |
"This alert indicates that a weak or insecure SSL/TLS certificate has been detected on a public-facing asset monitored by Cyfirma.
Such certificates do not meet modern encryption standards and are considered insecure, especially for handling sensitive transactions.
This exposure increases the risk of man-in-the-middle attacks and loss of data integrity or confidentiality.
Immediate remediation is advised by replacing weak certificates with strong, industry-compliant ones."
version: 1.0.1
kind: Scheduled
severity: High
requiredDataConnectors:
- connectorId: CyfirmaAttackSurfaceAlertsConnector
dataTypes:
- CyfirmaASCertificatesAlerts_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- DefenseEvasion
- ResourceDevelopment
- Reconnaissance
- InitialAccess
- CredentialAccess
relevantTechniques:
- T1553
- T1588
- T1595
- T1190
- T1552
query: |
// High Severity Weak Certificate Exposure Detected
let timeFrame = 5m;
CyfirmaASCertificatesAlerts_CL
| where severity == 'Critical' and TimeGenerated between (ago(timeFrame) .. now())
| extend
Description=description,
FirstSeen=first_seen,
LastSeen=last_seen,
ValidFrom=valid_from,
ValidTo=valid_to,
RiskScore=risk_score,
Domain=sub_domain,
TopDomain=top_domain,
Protocols=protocols,
SelfSigned=self_signed,
AlertUID=alert_uid,
UID=uid,
CertificateData= cert_data,
CertificateHash=cert_hash,
IssuedBy=issued_by,
IssuedTo=issued_to,
ProviderName='CYFIRMA',
ProductName='DeCYFIR/DeTCT'
| project
TimeGenerated,
Description,
Domain,
TopDomain,
RiskScore,
FirstSeen,
LastSeen,
ValidFrom,
ValidTo,
Protocols,
SelfSigned,
CertificateData,
CertificateHash,
IssuedBy,
IssuedTo,
ProviderName,
ProductName
entityMappings:
- entityType: DNS
fieldMappings:
- identifier: DomainName
columnName: Domain
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: TopDomain
- identifier: DnsDomain
columnName: Domain
customDetails:
TimeGenerated: TimeGenerated
CertificateData: CertificateData
CertificateHash: CertificateHash
FirstSeen: FirstSeen
LastSeen: LastSeen
ValidFrom: ValidFrom
ValidTo: ValidTo
Protocols: Protocols
SelfSigned: SelfSigned
IssuedBy: IssuedBy
IssuedTo: IssuedTo
Description: Description
RiskScore: RiskScore
TopDomain: TopDomain
Domain: Domain
alertDetailsOverride:
alertDisplayNameFormat: CYFIRMA - High Severity Weak Certificate Exposure Detected for this Domain - {{Domain}}
alertDescriptionFormat: CYFIRMA - High Severity Weak Certificate Exposure Detected - {{Description}}
alertDynamicProperties:
- alertProperty: ProductName
value: ProductName
- alertProperty: ProviderName
value: ProviderName
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
eventGroupingSettings:
aggregationKind: AlertPerResult
Stages and Predicates
Parameters
let timeFrame = 5m;
Stage 1: source
CyfirmaASCertificatesAlerts_CL
Stage 2: where
| where severity == 'Critical' and TimeGenerated between (ago(timeFrame) .. now())
Stage 3: extend
| extend
Description=description,
FirstSeen=first_seen,
LastSeen=last_seen,
ValidFrom=valid_from,
ValidTo=valid_to,
RiskScore=risk_score,
Domain=sub_domain,
TopDomain=top_domain,
Protocols=protocols,
SelfSigned=self_signed,
AlertUID=alert_uid,
UID=uid,
CertificateData= cert_data,
CertificateHash=cert_hash,
IssuedBy=issued_by,
IssuedTo=issued_to,
ProviderName='CYFIRMA',
ProductName='DeCYFIR/DeTCT'
Stage 4: project
| project
TimeGenerated,
Description,
Domain,
TopDomain,
RiskScore,
FirstSeen,
LastSeen,
ValidFrom,
ValidTo,
Protocols,
SelfSigned,
CertificateData,
CertificateHash,
IssuedBy,
IssuedTo,
ProviderName,
ProductName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
severity | eq |
| field:"severity" kind:eq value:"Critical" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
CertificateData | project |
CertificateHash | project |
Description | project |
Domain | project |
FirstSeen | project |
IssuedBy | project |
IssuedTo | project |
LastSeen | project |
ProductName | project |
Protocols | project |
ProviderName | project |
RiskScore | project |
SelfSigned | project |
TimeGenerated | project |
TopDomain | project |
ValidFrom | project |
ValidTo | project |
Datazag - impersonation domain resolved in DNS
#Matches active Datazag impersonation indicators against DNS activity normalized by the ASIM Dns schema, so the rule works across any normalized DNS source without modification. A match means a host inside the estate resolved a domain Datazag scored as brand impersonation, platform impersonation or attacker infrastructure. Requires the ASIM DNS parsers.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Command & Control |
Telemetry Coverage
| Provider | Record / event type |
|---|---|
| Sysmon | Event ID 22: DNSEvent (DNS query) |
Rule Body
id: 8f3c1a4e-6b52-4d19-9c07-2a5e8d0f7b41
name: Datazag - impersonation domain resolved in DNS
description: |
Matches active Datazag impersonation indicators against DNS activity normalized by the ASIM
Dns schema, so the rule works across any normalized DNS source without modification. A match
means a host inside the estate resolved a domain Datazag scored as brand impersonation,
platform impersonation or attacker infrastructure. Requires the ASIM DNS parsers.
severity: High
status: Available
requiredDataConnectors:
- connectorId: ThreatIntelligenceTaxii
dataTypes:
- ThreatIntelIndicators
tactics:
- InitialAccess
- CommandAndControl
relevantTechniques:
- T1566
- T1071
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
query: |
let datazag_identity = "identity--55a5a448-c6f1-5128-bc71-4d85b719131e";
let ioc_lookback = 14d;
let dns_lookback = 1h;
let confidence_floor = 85;
let DatazagDomains =
ThreatIntelIndicators
| where TimeGenerated >= ago(ioc_lookback)
| where tostring(Data.created_by_ref) == datazag_identity
| where ObservableKey == "domain-name:value"
| where isnotempty(ObservableValue)
| summarize arg_max(TimeGenerated, *) by Id
| where IsDeleted == false
| where tostring(Data.revoked) != "true"
| where isempty(ValidUntil) or ValidUntil > now()
| where Confidence >= confidence_floor
| extend IndicatorDomain = tolower(trim_end(@"\.", ObservableValue))
| project
IndicatorDomain,
IndicatorStixId = tostring(Data.id),
IndicatorRecordId = Id,
DatazagAlertRef = tostring(Data.external_references[0].external_id),
Confidence,
IndicatorEvidence = tostring(Data.labels),
IndicatorValidUntil = ValidUntil;
let DnsActivity =
_Im_Dns
| where TimeGenerated >= ago(dns_lookback)
| where isnotempty(DnsQuery)
| extend IndicatorDomain = tolower(trim_end(@"\.", DnsQuery))
| project
DnsTime = TimeGenerated,
IndicatorDomain,
DnsQuery,
SrcIpAddr = column_ifexists("SrcIpAddr", ""),
SrcHostname = column_ifexists("SrcHostname", ""),
SrcUsername = column_ifexists("SrcUsername", ""),
SrcUsernameType = column_ifexists("SrcUsernameType", ""),
EventVendor = column_ifexists("EventVendor", ""),
EventProduct = column_ifexists("EventProduct", "");
DatazagDomains
| join kind=innerunique DnsActivity on IndicatorDomain
| summarize
FirstSeen = min(DnsTime),
LastSeen = max(DnsTime),
QueryCount = count(),
SourceIps = make_set(SrcIpAddr, 50),
SourceHosts = make_set(SrcHostname, 50),
Upns = make_set_if(SrcUsername, SrcUsernameType == "UPN", 50),
SeenBy = make_set(strcat(EventVendor, " ", EventProduct), 10)
by
IndicatorDomain, IndicatorStixId, IndicatorRecordId, DatazagAlertRef,
Confidence, IndicatorEvidence, IndicatorValidUntil
| extend
SourceIp = tostring(SourceIps[0]),
SourceHost = tostring(SourceHosts[0]),
AccountUpn = tostring(Upns[0])
| order by LastSeen desc
eventGroupingSettings:
aggregationKind: AlertPerResult
alertDetailsOverride:
alertDisplayNameFormat: 'Datazag: {{IndicatorDomain}} resolved from inside the estate'
alertDescriptionFormat: |
{{IndicatorDomain}} was resolved from inside the estate. Datazag scored this indicator
at confidence {{Confidence}}. Supporting evidence: {{IndicatorEvidence}}
customDetails:
DatazagAlertRef: DatazagAlertRef
DatazagIndicatorRef: IndicatorStixId
DatazagConfidence: Confidence
DatazagEvidence: IndicatorEvidence
IndicatorExpiry: IndicatorValidUntil
ResolutionCount: QueryCount
ObservedBy: SeenBy
entityMappings:
- entityType: DNS
fieldMappings:
- identifier: DomainName
columnName: IndicatorDomain
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIp
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: SourceHost
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: AccountUpn
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 1d
matchingMethod: Selected
groupByEntities:
- DNS
suppressionEnabled: false
suppressionDuration: 1h
version: 1.0.0
kind: Scheduled
Stages and Predicates
Parameters
let datazag_identity = "identity--55a5a448-c6f1-5128-bc71-4d85b719131e";
let ioc_lookback = 14d;
let dns_lookback = 1h;
let confidence_floor = 85;
let DatazagDomains is inlined into the numbered stages below.
Let Binding: DnsActivity
let DnsActivity = _Im_Dns
| where TimeGenerated >= ago(dns_lookback)
| where isnotempty(DnsQuery)
| extend IndicatorDomain = tolower(trim_end(@"\.", DnsQuery))
| project
DnsTime = TimeGenerated,
IndicatorDomain,
DnsQuery,
SrcIpAddr = column_ifexists("SrcIpAddr", ""),
SrcHostname = column_ifexists("SrcHostname", ""),
SrcUsername = column_ifexists("SrcUsername", ""),
SrcUsernameType = column_ifexists("SrcUsernameType", ""),
EventVendor = column_ifexists("EventVendor", ""),
EventProduct = column_ifexists("EventProduct", "");
Stage 1: source
let DnsActivity
Stage 2: source
ThreatIntelIndicators
Stage 3: where
where TimeGenerated >= ago(1209600s)
Stage 4: where
where Data.created_by_ref == "identity--55a5a448-c6f1-5128-bc71-4d85b719131e"
Stage 5: where
where ObservableKey == "domain-name:value"
Stage 6: where
where isnotempty(ObservableValue)
Stage 7: summarize
summarize by Id
Stage 8: where
where IsDeleted == false
Stage 9: where
where Data.revoked != true
Stage 10: where
where (isempty(ValidUntil) or ValidUntil > now())
Stage 11: where
where Confidence >= 85
Stage 12: extend
extend IndicatorDomain
Stage 13: project
project Confidence, DatazagAlertRef, IndicatorDomain, IndicatorEvidence, IndicatorRecordId, IndicatorStixId, IndicatorValidUntil
Stage 14: join
join kind=innerunique (DnsActivity) on IndicatorDomain
Stage 15: summarize
summarize FirstSeen, LastSeen, QueryCount, SeenBy, SourceHosts, SourceIps, Upns by IndicatorDomain, IndicatorStixId, IndicatorRecordId, DatazagAlertRef, Confidence, IndicatorEvidence, IndicatorValidUntil
Stage 16: extend
extend AccountUpn, SourceHost, SourceIp
Stage 17: sort
sort by LastSeen
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Confidence | ge |
| field:"Confidence" kind:ge value:"85" |
Data.created_by_ref | eq |
| field:"Data.created_by_ref" kind:eq value:"identity--55a5a448-c6f1-5128-bc71-4d85b719131e" |
Data.revoked | ne |
| field:"Data.revoked" kind:ne value:"true" |
DnsQuery | is_not_null | field:"DnsQuery" kind:is_not_null | |
IsDeleted | eq |
| field:"IsDeleted" kind:eq value:"false" |
ObservableKey | eq |
| field:"ObservableKey" kind:eq value:"domain-name:value" |
ObservableValue | is_not_null | field:"ObservableValue" kind:is_not_null | |
ValidUntil | is_null | field:"ValidUntil" kind:is_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Confidence | summarize |
DatazagAlertRef | summarize |
FirstSeen | summarize |
IndicatorDomain | summarize |
IndicatorEvidence | summarize |
IndicatorRecordId | summarize |
IndicatorStixId | summarize |
IndicatorValidUntil | summarize |
LastSeen | summarize |
QueryCount | summarize |
SeenBy | summarize |
SourceHosts | summarize |
SourceIps | summarize |
Upns | summarize |
AccountUpn | extend |
SourceHost | extend |
SourceIp | extend |
Detect Malicious Usage of Recovery Tools to Delete Backup Files
#This analytic rule detects usage of recovery tools vssadmin, wbadmin, wmic and bcedit to delete backup files or change recovery configuration. Adversaries may use these tools to delete shadow copies and backup files to prevent recovery of files. https://attack.mitre.org/techniques/T1490/
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Impact |
Telemetry Coverage
Rule Body
id: 259de2c1-c546-4c6d-a17c-df639722f4d7
name: Detect Malicious Usage of Recovery Tools to Delete Backup Files
description: |
This analytic rule detects usage of recovery tools vssadmin, wbadmin, wmic and bcedit to delete backup files or change recovery configuration. Adversaries may use these tools to delete shadow copies and backup files to prevent recovery of files.
https://attack.mitre.org/techniques/T1490/
severity: High
status: Available
tags:
- Schema: _ASim_ProcessEvent
SchemaVersion: 0.1.4
requiredDataConnectors:
- connectorId: CrowdStrikeFalconEndpointProtection
dataTypes:
- CommonSecurityLog
- connectorId: MicrosoftThreatProtection
dataTypes:
- SecurityAlert
- connectorId: SentinelOne
dataTypes:
- SentinelOne_CL
- connectorId: VMwareCarbonBlack
dataTypes:
- CarbonBlackEvents_CL
- connectorId: CiscoSecureEndpoint
dataTypes:
- CiscoSecureEndpoint_CL
- connectorId: TrendMicroApexOne
dataTypes:
- TMApexOneEvent
- connectorId: TrendMicroApexOneAma
dataTypes:
- TMApexOneEvent
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Impact
relevantTechniques:
- T1490
query: |
_ASim_ProcessEvent
| where TargetProcessFilename has_any ('vssadmin.exe', 'wbadmin.exe', 'wmic.exe')
| where CommandLine has_all ('delete', 'shadow')
| union isfuzzy=True
(_ASim_ProcessEvent
| where TargetProcessFilename =~ 'bcedit.exe'
| where CommandLine has_all ('/set', 'recoveryenabled no')
)
| project
TimeGenerated,
DvcHostname,
DvcIpAddr,
DvcDomain,
TargetUsername,
TargetUsernameType,
TargetProcessName,
TargetProcessId,
CommandLine
| extend Username = iff(tostring(TargetUsernameType) == 'Windows', tostring(split(TargetUsername, '\\')[1]), TargetUsername)
| extend NTDomain = iff(tostring(TargetUsernameType) == 'Windows', tostring(split(TargetUsername, '\\')[0]), TargetUsername)
| extend Username = iff(tostring(TargetUsernameType) == 'UPN', tostring(split(TargetUsername, '@')[0]), Username)
| extend UPNSuffix = iff(tostring(TargetUsernameType) == 'UPN', tostring(split(TargetUsername, '@')[1]), '')
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: DvcHostname
- identifier: DnsDomain
columnName: DvcDomain
- identifier: NTDomain
columnName: NTDomain
- entityType: IP
fieldMappings:
- identifier: Address
columnName: DvcIpAddr
- entityType: Account
fieldMappings:
- identifier: Name
columnName: Username
- identifier: UPNSuffix
columnName: UPNSuffix
- identifier: NTDomain
columnName: NTDomain
- entityType: Process
fieldMappings:
- identifier: ProcessId
columnName: TargetProcessId
- identifier: CommandLine
columnName: CommandLine
eventGroupingSettings:
aggregationKind: AlertPerResult
alertDetailsOverride:
alertDisplayNameFormat: "Tool {{TargetProcessName}} used to delete backup files on {{DvcHostname}} by {{TargetUsername}}"
alertDescriptionFormat: "A system tool {{TargetProcessName}} ProcessId: ({{TargetProcessId}}) with {{CommandLine}} used to delete backup files."
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
_ASim_ProcessEvent
Stage 2: where
| where TargetProcessFilename has_any ('vssadmin.exe', 'wbadmin.exe', 'wmic.exe')
Stage 3: where
| where CommandLine has_all ('delete', 'shadow')
Stage 4: union
union of 1 branches
Stage 5: source
_ASim_ProcessEvent
Stage 6: where
| where TargetProcessFilename =~ 'bcedit.exe'
Stage 7: where
| where CommandLine has_all ('/set', 'recoveryenabled no')
Stage 8: project
project CommandLine, DvcDomain, DvcHostname, DvcIpAddr, TargetProcessId, TargetProcessName, TargetUsername, TargetUsernameType, TimeGenerated
Stage 9: extend (4 consecutive steps)
extend NTDomain, UPNSuffix, Username
Username =TargetUsernameType == "Windows"tostring(split(TargetUsername, '\\')[1])TargetUsernameIndicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
CommandLine | match |
| field:"CommandLine" kind:match |
TargetProcessFilename | eq |
| field:"TargetProcessFilename" kind:eq value:"bcedit.exe" |
TargetProcessFilename | match |
| field:"TargetProcessFilename" kind:match |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
CommandLine | project |
DvcDomain | project |
DvcHostname | project |
DvcIpAddr | project |
TargetProcessId | project |
TargetProcessName | project |
TargetUsername | project |
TargetUsernameType | project |
TimeGenerated | project |
Username | extend |
NTDomain | extend |
UPNSuffix | extend |
Dynatrace - Problem detection
#'Detect application & infrastructure problems in your environment'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Execution | |
| Persistence | |
| Privilege Escalation | |
| Stealth | |
| Lateral Movement | |
| Impact |
Rule Body
id: 415978ff-074e-4203-824a-b06153d77bf7
name: Dynatrace - Problem detection
description: |
'Detect application & infrastructure problems in your environment'
severity: Informational
status: Available
requiredDataConnectors:
- connectorId: DynatraceProblems
dataTypes:
- DynatraceProblems
- connectorId: DynatraceProblemsCCPDefinition
dataTypes:
- DynatraceProblemsV2_CL
- connectorId: DTProblemsV3CCPDefinition
dataTypes:
- DynatraceProblemsV3_CL
tactics:
- DefenseEvasion
- Execution
- Impact
- InitialAccess
- LateralMovement
- Persistence
- PrivilegeEscalation
relevantTechniques:
- T1140
- T1059
- T1565
- T1659
- T1210
- T1554
- T1548
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
aggregationKind: AlertPerResult
query: |
DynatraceProblems
| summarize arg_max(StartTime, *) by ProblemId
alertDetailsOverride:
alertDisplayNameFormat: 'Dynatrace problem detected - {{DisplayId}} : {{Title}}'
alertDescriptionFormat: |
A application and/or infrastructure problem has been detected in your environment
alertSeverityColumnName: Severity
customDetails:
ProblemIdentifier: ProblemId
DisplayIdentifier: DisplayId
ImpactLevel: ImpactLevel
SeverityLevel: SeverityLevel
incidentConfiguration:
createIncident: false
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
version: 1.0.4
kind: Scheduled
Stages and Predicates
Stage 1: source
DynatraceProblems
Stage 2: summarize
| summarize arg_max(StartTime, *) by ProblemId
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ProblemId | summarize |
Google DNS - IP check activity
#'Detects requests to ip lookup resources.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control |
Rule Body
id: 35221a58-cacb-4174-9bb4-ee777784fbce
name: Google DNS - IP check activity
description: |
'Detects requests to ip lookup resources.'
severity: Medium
requiredDataConnectors:
- connectorId: GCPDNSDataConnector
dataTypes:
- GCPCloudDNS
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- CommandAndControl
relevantTechniques:
- T1095
query: |
let ip_check = dynamic(['whatismyipaddress.com', 'ip2location.com', 'ipaddress.my', 'whatismyip.com', 'ipinfo.info', 'checkmyip.com', 'myip.com', 'checkmyip.org', 'canireachthe.net', 'ipv4.icanhazip.com', 'ip.anysrc.net', 'edns.ip-api.com', 'wtfismyip.com', 'checkip.dyndns.org', 'api.2ip.ua', 'icanhazip.com', 'api.ipify.org', 'ip-api.com', 'checkip.amazonaws.com', 'ipecho.net', 'ipinfo.io', 'ipv4bot.whatismyipaddress.com', 'freegeoip.app', 'checkip.azurewebsites.net']);
GCPCloudDNS
| where Query in~ (ip_check)
| extend DNSCustomEntity = Query, IPCustomEntity = SrcIpAddr
entityMappings:
- entityType: DNS
fieldMappings:
- identifier: DomainName
columnName: DNSCustomEntity
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPCustomEntity
version: 1.0.0
kind: Scheduled
Stages and Predicates
Let Binding: ip_check
let ip_check = dynamic(['whatismyipaddress.com', 'ip2location.com', 'ipaddress.my', 'whatismyip.com', 'ipinfo.info', 'checkmyip.com', 'myip.com', 'checkmyip.org', 'canireachthe.net', 'ipv4.icanhazip.com', 'ip.anysrc.net', 'edns.ip-api.com', 'wtfismyip.com', 'checkip.dyndns.org', 'api.2ip.ua', 'icanhazip.com', 'api.ipify.org', 'ip-api.com', 'checkip.amazonaws.com', 'ipecho.net', 'ipinfo.io', 'ipv4bot.whatismyipaddress.com', 'freegeoip.app', 'checkip.azurewebsites.net']);
Stage 1: source
GCPCloudDNS
Stage 2: where
| where Query in~ (ip_check)
Stage 3: extend
| extend DNSCustomEntity = Query, IPCustomEntity = SrcIpAddr
Indicators
These rows show field, operator, and value matches.
| 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 |
GTI - Insider Threat Alert Detected
#Triggers an incident when a GTI Relevance System Alert of type insider_threat is ingested. Insider Threat alerts indicate that GTI has identified intelligence suggesting malicious or negligent activity by individuals with legitimate access to your organisation - such as employee credential sales, internal data exposure, or rogue employee activity observed in underground sources. Each unique Alert ID is grouped into a single incident.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Privilege Escalation | |
| Credential Access | |
| Exfiltration | |
| Impact |
Rule Body
id: e5f6a7b8-c9d0-1234-efab-345678901234
name: GTI - Insider Threat Alert Detected
description: |
Triggers an incident when a GTI Relevance System Alert of type insider_threat is ingested.
Insider Threat alerts indicate that GTI has identified intelligence suggesting malicious or negligent
activity by individuals with legitimate access to your organisation - such as employee credential
sales, internal data exposure, or rogue employee activity observed in underground sources.
Each unique Alert ID is grouped into a single incident.
severity: High
status: Available
requiredDataConnectors:
- connectorId: GoogleThreatIntelligenceRelevanceSystemAlertsAPI
dataTypes:
- RelevanceSystemAlerts_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
- PrivilegeEscalation
- Exfiltration
- CredentialAccess
- Impact
relevantTechniques:
- T1068
- T1078
- T1567
- T1552
- T1485
query: |
GTIRelevanceSystemAlerts
| where DetailType == "insider_threat"
| project
TimeGenerated,
AlertId,
AlertName = DisplayName,
AlertState = State,
InsiderSeverity = InsiderThreatSeverity,
DiscoveryDocuments = InsiderThreatDiscoveryDocumentIds,
PriorityLevel,
PriorityReasoning,
SeverityLevel,
SeverityConfidence,
SeverityReasoning,
RelevanceLevel,
RelevanceConfidence,
RelevanceReasoning,
FindingCount,
AiSummary,
CommonThemes = RelevanceEvidenceCommonThemes,
DistinctThemes = RelevanceEvidenceDistinctThemes,
AuditCreateTime,
AuditUpdateTime
alertDetailsOverride:
alertDisplayNameFormat: 'GTI Insider Threat: {{AlertName}}'
alertDescriptionFormat: |
Insider Severity: {{InsiderSeverity}} | Priority: {{PriorityLevel}}
Summary: {{AiSummary}}
customDetails:
AlertId: AlertId
AlertState: AlertState
InsiderSeverity: InsiderSeverity
PriorityLevel: PriorityLevel
SeverityLevel: SeverityLevel
RelevanceLevel: RelevanceLevel
FindingCount: FindingCount
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: P5D
matchingMethod: Selected
groupByCustomDetails:
- AlertId
eventGroupingSettings:
aggregationKind: AlertPerResult
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
GTIRelevanceSystemAlerts
Stage 2: where
| where DetailType == "insider_threat"
Stage 3: project
| project
TimeGenerated,
AlertId,
AlertName = DisplayName,
AlertState = State,
InsiderSeverity = InsiderThreatSeverity,
DiscoveryDocuments = InsiderThreatDiscoveryDocumentIds,
PriorityLevel,
PriorityReasoning,
SeverityLevel,
SeverityConfidence,
SeverityReasoning,
RelevanceLevel,
RelevanceConfidence,
RelevanceReasoning,
FindingCount,
AiSummary,
CommonThemes = RelevanceEvidenceCommonThemes,
DistinctThemes = RelevanceEvidenceDistinctThemes,
AuditCreateTime,
AuditUpdateTime
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DetailType | eq |
| field:"DetailType" kind:eq value:"insider_threat" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AiSummary | project |
AlertId | project |
AlertName | project |
AlertState | project |
AuditCreateTime | project |
AuditUpdateTime | project |
CommonThemes | project |
DiscoveryDocuments | project |
DistinctThemes | project |
FindingCount | project |
InsiderSeverity | project |
PriorityLevel | project |
PriorityReasoning | project |
RelevanceConfidence | project |
RelevanceLevel | project |
RelevanceReasoning | project |
SeverityConfidence | project |
SeverityLevel | project |
SeverityReasoning | project |
TimeGenerated | project |
GWorkspace - API Access Granted
#Triggers when API Access has been granted to a new client.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Lateral Movement |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Google Workspace | AUTHORIZE_API_CLIENT_ACCESS: API Client Access Authorize |
Rule Body
id: c45a9804-5da8-11ec-bf63-0242ac130002
name: GWorkspace - API Access Granted
description: |
'Triggers when API Access has been granted to a new client.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: GoogleWorkspaceReportsAPI
dataTypes:
- GWorkspaceActivityReports
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
- LateralMovement
relevantTechniques:
- T1550
query: |
GWorkspaceActivityReports
| where EventMessage has "AUTHORIZE_API_CLIENT_ACCESS"
| extend AccountCustomEntity = ActorEmail
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountCustomEntity
version: 1.0.1
kind: Scheduled
Stages and Predicates
Stage 1: source
GWorkspaceActivityReports
Stage 2: where
| where EventMessage has "AUTHORIZE_API_CLIENT_ACCESS"
Stage 3: extend
| extend AccountCustomEntity = ActorEmail
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventMessage | match |
| field:"EventMessage" kind:match value:"AUTHORIZE_API_CLIENT_ACCESS" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountCustomEntity | extend |
GWorkspace - Possible brute force attack
#Detects possible brute force attack.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Google Workspace | login_failure: Login Failure |
| Google Workspace | login_failure: SAML Login Failure |
Rule Body
id: 8f6cd9a4-5e57-11ec-bf63-0242ac130002
name: GWorkspace - Possible brute force attack
description: |
'Detects possible brute force attack.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: GoogleWorkspaceReportsAPI
dataTypes:
- GWorkspaceActivityReports
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
relevantTechniques:
- T1110
query: |
let threshold = 5;
GWorkspaceActivityReports
| where EventMessage has "login_failure"
| summarize login_attempts = count() by ActorEmail, bin(TimeGenerated, 5m)
| where login_attempts > threshold
| extend AccountCustomEntity = ActorEmail
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountCustomEntity
version: 1.0.1
kind: Scheduled
Stages and Predicates
Parameters
let threshold = 5;
Stage 1: source
GWorkspaceActivityReports
Stage 2: where
| where EventMessage has "login_failure"
Stage 3: summarize
| summarize login_attempts = count() by ActorEmail, bin(TimeGenerated, 5m)
Stage 4: where
| where login_attempts > threshold
Stage 5: extend
| extend AccountCustomEntity = ActorEmail
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventMessage | match |
| field:"EventMessage" kind:match value:"login_failure" |
login_attempts | gt |
| field:"login_attempts" kind:gt value:"5" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActorEmail | summarize |
login_attempts | summarize |
AccountCustomEntity | extend |
Hunt for privilege escalation paths with high ACLs
#When an adversary establishes data collection of an Active Directory domain, they regularly search for interesting accounts with privilege escalation paths using the genericWrite and genericAll ACL permissions on objects. When using BloodHound, it is very easy to get a visual overview of these paths in an Active Directory domain. This query tries to establish the same using Defender XDR Exposure Management.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Persistence | |
| Privilege Escalation | |
| Stealth |
References
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Microsoft Defender for Endpoint | ExposureGraphEdges action any: Exposure graph edges |
Rule Body
let high_permissions = dynamic(["genericWrite", "genericAll"]);
let edge_labels = dynamic(["member of", "has permissions to", "can authenticate to", "can authenticate as", "has credentials of", "can impersonate as"]);
// Get users and groups with high ACL permissions on other objects
let HighPermissionLinks = (ExposureGraphEdges
// Get edges related to roles
| where EdgeLabel == "has role on"
// Get edges containing high permission ACLs
| extend Permissions = todynamic(EdgeProperties).rawData.acl.controlTypes
| where Permissions has_any (high_permissions)
// Exclude Domain and Enterprise Administrators as source node
| where not(SourceNodeLabel == "group" and SourceNodeName in ("Domain Admins", "Enterprise Admins"))
// Exclude Built-in administrator account
| where not(SourceNodeLabel == "user" and SourceNodeName == "Administrator")
| summarize TargetNodes = make_set(TargetNodeName), TargetNodeCount = count() by SourceNodeName, SourceNodeLabel, tostring(Permissions), TargetNodeLabel, SourceNodeId
);
let HighPermissionNodes = toscalar(
HighPermissionLinks
| summarize SourceNodes = make_set(SourceNodeName)
);
// Get edges for links to the high ACL permissions
ExposureGraphEdges
| where TargetNodeName in (HighPermissionNodes)
| make-graph SourceNodeId --> TargetNodeId with ExposureGraphNodes on NodeId
// Get between one and three relations
| graph-match (SourceNode)-[anyEdge*1..3]->(TargetNode)
project IncomingNodeName = SourceNode.NodeName,
IncomingNodeLabel = SourceNode.NodeLabel,
Edges = anyEdge.EdgeLabel,
OutgoingNodeName = TargetNode.NodeName,
OutgoingNodeId = TargetNode.NodeId
// Filter for interesting edges
| where Edges has_any (edge_labels)
// Join the high permission ACLs
| join kind=inner HighPermissionLinks on $left.OutgoingNodeId == $right.SourceNodeId
// Exclude Domain and Enterprise Administrators as source node
| where not(IncomingNodeLabel == "group" and IncomingNodeName in ("Domain Admins", "Enterprise Admins"))
// Exclude Built-in administrator account
| where not(IncomingNodeLabel == "user" and IncomingNodeName == "Administrator")
| distinct IncomingNodeName, IncomingNodeLabel, tostring(Edges), OutgoingNodeName, OutgoingNodeLabel = SourceNodeLabel, tostring(Permissions), TargetNodeLabel, tostring(TargetNodes), TargetNodeCount
Stages and Predicates
Parameters
let high_permissions = dynamic(["genericWrite", "genericAll"]);
let edge_labels = dynamic(["member of", "has permissions to", "can authenticate to", "can authenticate as", "has credentials of", "can impersonate as"]);
Let Binding: HighPermissionLinks
let HighPermissionLinks = (ExposureGraphEdges
| where EdgeLabel == "has role on"
| extend Permissions = todynamic(EdgeProperties).rawData.acl.controlTypes
| where Permissions has_any (high_permissions)
| where not(SourceNodeLabel == "group" and SourceNodeName in ("Domain Admins", "Enterprise Admins"))
| where not(SourceNodeLabel == "user" and SourceNodeName == "Administrator")
| summarize TargetNodes = make_set(TargetNodeName), TargetNodeCount = count() by SourceNodeName, SourceNodeLabel, tostring(Permissions), TargetNodeLabel, SourceNodeId
);
Let Binding: HighPermissionNodes
let HighPermissionNodes = toscalar(
HighPermissionLinks
| summarize SourceNodes = make_set(SourceNodeName)
);
Stage 1: source
let HighPermissionLinks
Stage 2: source
ExposureGraphEdges
Stage 3: where
where TargetNodeName == "HighPermissionNodes"
Stage 4: macro
macro
Stage 5: macro
macro
Stage 6: where
where (Edges has "member of" or Edges has "has permissions to" or Edges has "can authenticate to" or Edges has "can authenticate as" or Edges has "has credentials of" or Edges has "can impersonate as")
Stage 7: join
join kind=inner (HighPermissionLinks) on $left.OutgoingNodeId, $right.SourceNodeId
Stage 8: where
where not ((IncomingNodeLabel == "group" and IncomingNodeName in ("Domain Admins", "Enterprise Admins")))
Stage 9: where
where not ((IncomingNodeLabel == "user" and IncomingNodeName == "Administrator"))
Stage 10: distinct
distinct IncomingNodeLabel, IncomingNodeName, OutgoingNodeName, TargetNodeCount, TargetNodeLabel
Stage 11: summarize aggregation inside the join branch
summarize by SourceNodeName, SourceNodeLabel, TargetNodeLabel, SourceNodeId
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EdgeLabel | eq |
| field:"EdgeLabel" kind:eq value:"has role on" |
Edges | match |
| field:"Edges" kind:match |
Permissions | match |
| field:"Permissions" kind:match |
TargetNodeName | in |
| field:"TargetNodeName" kind:in value:"HighPermissionNodes" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
SourceNodeLabel | eq | group | excludes:SourceNodeLabel field:"SourceNodeLabel" value:"group" |
SourceNodeName | in | Domain Admins, Enterprise Admins | excludes:SourceNodeName field:"SourceNodeName" value:"Domain Admins" field:"SourceNodeName" value:"Enterprise Admins" |
SourceNodeLabel | eq | user | excludes:SourceNodeLabel field:"SourceNodeLabel" value:"user" |
SourceNodeName | eq | Administrator | excludes:SourceNodeName field:"SourceNodeName" value:"Administrator" |
IncomingNodeLabel | eq | group | excludes:IncomingNodeLabel field:"IncomingNodeLabel" value:"group" |
IncomingNodeName | in | Domain Admins, Enterprise Admins | excludes:IncomingNodeName field:"IncomingNodeName" value:"Domain Admins" field:"IncomingNodeName" value:"Enterprise Admins" |
IncomingNodeLabel | eq | user | excludes:IncomingNodeLabel field:"IncomingNodeLabel" value:"user" |
IncomingNodeName | eq | Administrator | excludes:IncomingNodeName field:"IncomingNodeName" value:"Administrator" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
SourceNodeId | summarize |
SourceNodeLabel | summarize |
SourceNodeName | summarize |
TargetNodeLabel | summarize |
Multiple failed attempts of NetBackup login
#This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.
This rule generates an incident when there are more than 5 failed login attemts for a given host in the last 15 minutes.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
Rule Body
id: d39f0c47-2e85-49b9-a686-388c2eb7062c
name: Multiple failed attempts of NetBackup login
description: This rule generates an incident when there are more than 5 failed login attemts for a given host in the last 15 minutes.
severity: Medium
status: Available
requiredDataConnectors: []
queryFrequency: 15m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
- Discovery
relevantTechniques:
- T1110
- T1212
query: "NetBackupAlerts_CL\r\n| where operation_s contains \"LOGIN\" and Message\
\ contains \"authentication failed\" \r\n| extend userName = split(userName_s,\
\ \"@\")[0]\r\n| extend host = split(userName_s, \"@\")[1] \r\n| summarize Total=count()\
\ by tostring(host)\r\n| where Total >= 5"
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: host
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: 5h
matchingMethod: AllEntities
suppressionDuration: PT5H
suppressionEnabled: false
eventGroupingSettings:
aggregationKind: SingleAlert
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
NetBackupAlerts_CL
Stage 2: where
| where operation_s contains "LOGIN" and Message contains "authentication failed"
Stage 3: extend
| extend userName = split(userName_s, "@")[0]
Stage 4: extend
| extend host = split(userName_s, "@")[1]
Stage 5: summarize
| summarize Total=count() by tostring(host)
Stage 6: where
| where Total >= 5
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Message | contains |
| field:"Message" kind:contains value:"authentication failed" |
Total | ge |
| field:"Total" kind:ge value:"5" |
operation_s | contains |
| field:"operation_s" kind:contains value:"LOGIN" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Total | summarize |
Pathlock TDnR - Generic Table Content Changes
#Detects generic change documents for SAP table content, forwarded by Pathlock Threat Detection and Response. Direct table data modifications may bypass normal application validation and audit trails, and could indicate data manipulation, configuration tampering, or fraud concealment.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Defense Impairment | |
| Impact |
Rule Body
id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c67
name: Pathlock TDnR - Generic Table Content Changes
kind: Scheduled
description: >-
Detects generic change documents for SAP table content, forwarded by Pathlock Threat Detection
and Response. Direct table data modifications may bypass normal application validation and audit
trails, and could indicate data manipulation, configuration tampering, or fraud concealment.
severity: High
status: Available
requiredDataConnectors:
- connectorId: Pathlock_TDnR
dataTypes:
- Pathlock_TDnR_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
- Impact
relevantTechniques:
- T1565
- T1562
query: |
Pathlock_TDnR_CL
| where DataSource == "TABLE_CHANGES"
| project TimeGenerated, Sysid, DataSource, Eventid, Instance, Hostname, Bname,
Tcode, Report, Area, Subid, SrcIp, DestIp, AffectedUser, LogLine,
MsgType, MsgId, MsgNo, MessageV1, MessageV2, MessageV3, MessageV4, CentralTs
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: Bname
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: Hostname
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIp
eventGroupingSettings:
aggregationKind: SingleAlert
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 5h
matchingMethod: AnyAlert
groupByEntities: []
groupByAlertDetails: []
groupByCustomDetails: []
suppressionDuration: 5h
suppressionEnabled: false
version: 1.0.0
Stages and Predicates
Stage 1: source
Pathlock_TDnR_CL
Stage 2: where
| where DataSource == "TABLE_CHANGES"
Stage 3: project
| project TimeGenerated, Sysid, DataSource, Eventid, Instance, Hostname, Bname,
Tcode, Report, Area, Subid, SrcIp, DestIp, AffectedUser, LogLine,
MsgType, MsgId, MsgNo, MessageV1, MessageV2, MessageV3, MessageV4, CentralTs
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DataSource | eq |
| field:"DataSource" kind:eq value:"TABLE_CHANGES" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AffectedUser | project |
Area | project |
Bname | project |
CentralTs | project |
DataSource | project |
DestIp | project |
Eventid | project |
Hostname | project |
Instance | project |
LogLine | project |
MessageV1 | project |
MessageV2 | project |
MessageV3 | project |
MessageV4 | project |
MsgId | project |
MsgNo | project |
MsgType | project |
Report | project |
SrcIp | project |
Subid | project |
Sysid | project |
Tcode | project |
TimeGenerated | project |
Pathlock TDnR - Global System Change Setting Events
#Detects changes to global SAP system change settings, forwarded by Pathlock Threat Detection and Response. System change option modifications (e.g. enabling software changes in production) bypass change management processes and may allow unauthorized code or configuration changes to be applied directly to production systems.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| Defense Impairment |
Rule Body
id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c65
name: Pathlock TDnR - Global System Change Setting Events
kind: Scheduled
description: >-
Detects changes to global SAP system change settings, forwarded by Pathlock Threat Detection
and Response. System change option modifications (e.g. enabling software changes in production)
bypass change management processes and may allow unauthorized code or configuration changes
to be applied directly to production systems.
severity: High
status: Available
requiredDataConnectors:
- connectorId: Pathlock_TDnR
dataTypes:
- Pathlock_TDnR_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
- Persistence
relevantTechniques:
- T1562
- T1098
query: |
Pathlock_TDnR_CL
| where DataSource == "SYSTEM_CHANGELOG"
| project TimeGenerated, Sysid, DataSource, Eventid, Instance, Hostname, Bname,
Tcode, Report, Area, Subid, SrcIp, DestIp, AffectedUser, LogLine,
MsgType, MsgId, MsgNo, MessageV1, MessageV2, MessageV3, MessageV4, CentralTs
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: Bname
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: Hostname
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIp
eventGroupingSettings:
aggregationKind: SingleAlert
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 5h
matchingMethod: AnyAlert
groupByEntities: []
groupByAlertDetails: []
groupByCustomDetails: []
suppressionDuration: 5h
suppressionEnabled: false
version: 1.0.0
Stages and Predicates
Stage 1: source
Pathlock_TDnR_CL
Stage 2: where
| where DataSource == "SYSTEM_CHANGELOG"
Stage 3: project
| project TimeGenerated, Sysid, DataSource, Eventid, Instance, Hostname, Bname,
Tcode, Report, Area, Subid, SrcIp, DestIp, AffectedUser, LogLine,
MsgType, MsgId, MsgNo, MessageV1, MessageV2, MessageV3, MessageV4, CentralTs
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DataSource | eq |
| field:"DataSource" kind:eq value:"SYSTEM_CHANGELOG" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AffectedUser | project |
Area | project |
Bname | project |
CentralTs | project |
DataSource | project |
DestIp | project |
Eventid | project |
Hostname | project |
Instance | project |
LogLine | project |
MessageV1 | project |
MessageV2 | project |
MessageV3 | project |
MessageV4 | project |
MsgId | project |
MsgNo | project |
MsgType | project |
Report | project |
SrcIp | project |
Subid | project |
Sysid | project |
Tcode | project |
TimeGenerated | project |
ProofpointPOD - High risk message not discarded
#'Detects when email with high risk score was not rejected or discarded by filters.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: c7cd6073-6d2c-4284-a5c8-da27605bdfde
name: ProofpointPOD - High risk message not discarded
description: |
'Detects when email with high risk score was not rejected or discarded by filters.'
severity: Low
status: Available
requiredDataConnectors:
- connectorId: ProofpointPOD
dataTypes:
- ProofpointPOD_message_CL
queryFrequency: 10m
queryPeriod: 10m
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1566
query: |
let lbtime = 10m;
ProofpointPOD
| where TimeGenerated > ago(lbtime)
| where EventType == 'message'
| where NetworkDirection == 'inbound'
| where FilterDisposition !in ('reject', 'discard')
| where FilterModulesSpamScoresOverall == '100'
| project SrcUserUpn, DstUserUpn
| extend AccountCustomEntity = SrcUserUpn
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: AccountCustomEntity
version: 1.0.1
kind: Scheduled
Stages and Predicates
Parameters
let lbtime = 10m;
Stage 1: source
ProofpointPOD
Stage 2: where
| where TimeGenerated > ago(lbtime)
Stage 3: where
| where EventType == 'message'
Stage 4: where
| where NetworkDirection == 'inbound'
Stage 5: where
| where FilterDisposition !in ('reject', 'discard')
Stage 6: where
| where FilterModulesSpamScoresOverall == '100'
Stage 7: project
| project SrcUserUpn, DstUserUpn
Stage 8: extend
| extend AccountCustomEntity = SrcUserUpn
Indicators
These rows show field, operator, and value matches.
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
FilterDisposition | in | discard, reject | excludes:FilterDisposition field:"FilterDisposition" value:"discard" field:"FilterDisposition" value:"reject" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DstUserUpn | project |
SrcUserUpn | project |
AccountCustomEntity | extend |
PRT Credential Stealing
#This query detects when BrowserCore.exe is accessed by a suspicious process. The BrowserCore.exe binary is responsible for allowing browser add-ons to use Single Sign On via Azure AD. This rule detects when an uncommon process interacts with the BrowserCore.exe process.
Known False Positives
- None expected.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Privilege Escalation | |
| Credential Access |
References
Telemetry Coverage
Rule Body
let timeframe = 2*1h;
let default_global_prevalence = 0;
let lolbins = dynamic(["at.exe", "atbroker.exe", "bash.exe", "bitsadmin.exe", "certreq.exe", "certutil.exe", "cmd.exe", "cmdkey.exe", "cmstp.exe", "control.exe", "csc.exe", "cscript.exe", "desktopimgdownldr.exe", "dfsvc.exe", "diantz.exe", "diskshadow.exe", "dnscmd.exe", "esentutl.exe", "eventvwr.exe", "expand.exe", "extexport.exe", "extrac32.exe", "findstr.exe", "forfiles.exe", "ftp.exe", "gfxdownloadwrapper.exe", "gpscript.exe", "hh.exe", "ie4uinit.exe", "ieexec.exe", "ilasm.exe", "infdefaultinstall.exe", "installutil.exe", "jsc.exe", "makecab.exe", "mavinject.exe", "microsoft.workflow.compiler.exe", "mmc.exe", "mpcmdrun.exe", "msbuild.exe", "msconfig.exe", "msdt.exe", "mshta.exe", "msiexec.exe", "netsh.exe", "odbcconf.exe", "pcalua.exe", "pcwrun.exe", "pktmon.exe", "presentationhost.exe", "print.exe", "psr.exe", "rasautou.exe", "reg.exe", "regasm.exe", "regedit.exe", "regini.exe", "register-cimprovider.exe", "regsvcs.exe", "regsvr32.exe", "replace.exe", "rpcping.exe", "rundll32.exe", "runonce.exe", "runscripthelper.exe", "sc.exe", "schtasks.exe", "scriptrunner.exe", "syncappvpublishingserver.exe", "ttdinject.exe", "tttracer.exe", "vbc.exe", "verclsid.exe", "wab.exe", "wmic.exe", "wscript.exe", "wsreset.exe", "xwizard.exe", "agentexecutor.exe", "appvlp.exe", "bginfo.exe", "cdb.exe", "csi.exe", "devtoolslauncher.exe", "dnx.exe", "dotnet.exe", "dxcap.exe", "excel.exe", "mftrace.exe", "msdeploy.exe", "msxsl.exe", "ntdsutil.exe", "powerpnt.exe", "rcsi.exe", "sqldumper.exe", "sqlps.exe", "sqltoolsps.exe", "squirrel.exe", "te.exe", "tracker.exe", "vsjitdebugger.exe", "winword.exe", "wsl.exe", "powershell.exe", "pwsh.exe"]);
// Adding services.exe and svchost.exe to the list of LOLBINs since these can be abused to start cmd.exe and talk to browsercore.
let extendedLolbins = array_concat(lolbins, dynamic(["services.exe", "svchost.exe"]));
let trustedProcess = dynamic(["chrome.exe", "teams.exe"]);
let trustedProcessHashes = materialize(DeviceProcessEvents | where ActionType =~ "ProcessCreated" | where FileName in~ (trustedProcess) | distinct SHA1);
let suspiciousHashes = materialize(trustedProcessHashes | extend SHA1=tolower(SHA1) | invoke FileProfile(SHA1, 1000) | where not(ProfileAvailability =~ "Error") | where coalesce(GlobalPrevalence,default_global_prevalence) < 200 or isempty(GlobalPrevalence));
let allBrowserCores = DeviceProcessEvents
| where ingestion_time() >= ago(timeframe)
| where ActionType =~ "ProcessCreated"
| where ProcessVersionInfoOriginalFileName =~ "BrowserCore.exe";
allBrowserCores
// FileProfile is case-sensitive and works on lower-case hashes.
| extend SHA1=tolower(SHA1)
// We need the grandparent SHA1, since the normal execution hierarchy is chrome->cmd->browsercore.exe (or similar with Teams).
| join kind = leftouter (DeviceProcessEvents | where ActionType =~ "ProcessCreated" | where FileName in~ ((allBrowserCores | project InitiatingProcessParentFileName))) on $left.InitiatingProcessParentId == $right.ProcessId, DeviceId, $left.InitiatingProcessParentFileName == $right.FileName
// Filter grandparent.
| extend GrandParentFileSHA1 = SHA11 // Renaming SHA1 to GrandParentFileSHA1 for future readability.
| where GrandParentFileSHA1 in~ (suspiciousHashes) or InitiatingProcessParentFileName !in~ (trustedProcess)
// Filter parent.
| where InitiatingProcessSHA1 in~ (suspiciousHashes) or InitiatingProcessFileName !in~ (trustedProcess)
// Removing empty GrandParentFileSHA1 hashes since sometimes the process start event is not logged and hence GrandParentFileSHA1 is empty.
// This contaminates the results. A future improvement would be to check if the trusted process we're looking
// for is logged as parent (instead of as child).
| where not(isempty(GrandParentFileSHA1))
| invoke FileProfile(GrandParentFileSHA1, 1000)
| where not(ProfileAvailability =~ "Error")
| where coalesce(GlobalPrevalence,default_global_prevalence) < 200 or isempty(GlobalPrevalence) or InitiatingProcessParentFileName in~ (extendedLolbins)
// Begin environment-specific filter.
// End environment-specific filter.
Stages and Predicates
Parameters
let timeframe = 2*1h;
let default_global_prevalence = 0;
let extendedLolbins = array_concat(lolbins, dynamic(["services.exe", "svchost.exe"]));
let trustedProcess = dynamic(["chrome.exe", "teams.exe"]);
let allBrowserCores is inlined into the numbered stages below.
Let Binding: lolbins
let lolbins = dynamic(["at.exe", "atbroker.exe", "bash.exe", "bitsadmin.exe", "certreq.exe", "certutil.exe", "cmd.exe", "cmdkey.exe", "cmstp.exe", "control.exe", "csc.exe", "cscript.exe", "desktopimgdownldr.exe", "dfsvc.exe", "diantz.exe", "diskshadow.exe", "dnscmd.exe", "esentutl.exe", "eventvwr.exe", "expand.exe", "extexport.exe", "extrac32.exe", "findstr.exe", "forfiles.exe", "ftp.exe", "gfxdownloadwrapper.exe", "gpscript.exe", "hh.exe", "ie4uinit.exe", "ieexec.exe", "ilasm.exe", "infdefaultinstall.exe", "installutil.exe", "jsc.exe", "makecab.exe", "mavinject.exe", "microsoft.workflow.compiler.exe", "mmc.exe", "mpcmdrun.exe", "msbuild.exe", "msconfig.exe", "msdt.exe", "mshta.exe", "msiexec.exe", "netsh.exe", "odbcconf.exe", "pcalua.exe", "pcwrun.exe", "pktmon.exe", "presentationhost.exe", "print.exe", "psr.exe", "rasautou.exe", "reg.exe", "regasm.exe", "regedit.exe", "regini.exe", "register-cimprovider.exe", "regsvcs.exe", "regsvr32.exe", "replace.exe", "rpcping.exe", "rundll32.exe", "runonce.exe", "runscripthelper.exe", "sc.exe", "schtasks.exe", "scriptrunner.exe", "syncappvpublishingserver.exe", "ttdinject.exe", "tttracer.exe", "vbc.exe", "verclsid.exe", "wab.exe", "wmic.exe", "wscript.exe", "wsreset.exe", "xwizard.exe", "agentexecutor.exe", "appvlp.exe", "bginfo.exe", "cdb.exe", "csi.exe", "devtoolslauncher.exe", "dnx.exe", "dotnet.exe", "dxcap.exe", "excel.exe", "mftrace.exe", "msdeploy.exe", "msxsl.exe", "ntdsutil.exe", "powerpnt.exe", "rcsi.exe", "sqldumper.exe", "sqlps.exe", "sqltoolsps.exe", "squirrel.exe", "te.exe", "tracker.exe", "vsjitdebugger.exe", "winword.exe", "wsl.exe", "powershell.exe", "pwsh.exe"]);
Let Binding: trustedProcessHashes
let trustedProcessHashes = materialize(DeviceProcessEvents | where ActionType =~ "ProcessCreated" | where FileName in~ (trustedProcess) | distinct SHA1);
Let Binding: suspiciousHashes
let suspiciousHashes = materialize(trustedProcessHashes | extend SHA1=tolower(SHA1) | invoke FileProfile(SHA1, 1000) | where not(ProfileAvailability =~ "Error") | where coalesce(GlobalPrevalence,default_global_prevalence) < 200 or isempty(GlobalPrevalence));
Stage 1: source
let trustedProcessHashes
Stage 2: source
let suspiciousHashes
Stage 3: source
let allBrowserCores
Stage 4: source
DeviceProcessEvents
Stage 5: where
where ...
Stage 6: where
where ActionType =~ "ProcessCreated"
Stage 7: where
where ProcessVersionInfoOriginalFileName =~ "BrowserCore.exe"
Stage 8: extend
extend SHA1
Stage 9: join
join kind=leftouter (DeviceProcessEvents) on $left.InitiatingProcessParentId, $right.ProcessId, DeviceId, $left.InitiatingProcessParentFileName, $right.FileName
Stage 10: extend
extend GrandParentFileSHA1
Stage 11: where
where (not (InitiatingProcessParentFileName in~ ("chrome.exe", "teams.exe")) or GrandParentFileSHA1 =~ "suspiciousHashes")
Stage 12: where
where (not (InitiatingProcessFileName in~ ("chrome.exe", "teams.exe")) or InitiatingProcessSHA1 =~ "suspiciousHashes")
Stage 13: where
where not (isempty(GrandParentFileSHA1))
Stage 14: invoke
invoke
Stage 15: where
where not (ProfileAvailability =~ "Error")
Stage 16: where
where (isempty(GlobalPrevalence) or GlobalPrevalence < 200 or InitiatingProcessParentFileName =~ "extendedLolbins")
Indicators
These rows show field, operator, and value matches.
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
GrandParentFileSHA1 | is_null | excludes:GrandParentFileSHA1 | |
ProfileAvailability | eq | Error | excludes:ProfileAvailability field:"ProfileAvailability" value:"Error" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
SHA1 | extend |
GrandParentFileSHA1 | extend |
Sentinel One - Agent uninstalled from multiple hosts
#'Detects when agent was uninstalled from multiple hosts.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Stealth |
Rule Body
id: 4ad87e4a-d045-4c6b-9652-c9de27fcb442
name: Sentinel One - Agent uninstalled from multiple hosts
description: |
'Detects when agent was uninstalled from multiple hosts.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: SentinelOne
dataTypes:
- SentinelOne
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
relevantTechniques:
- T1070
query: |
SentinelOne
| where ActivityType == 51
| summarize count() by DataComputerName, bin(TimeGenerated, 30m)
| where count_ > 1
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: DataComputerName
version: 1.0.2
kind: Scheduled
Stages and Predicates
Stage 1: source
SentinelOne
Stage 2: where
| where ActivityType == 51
Stage 3: summarize
| summarize count() by DataComputerName, bin(TimeGenerated, 30m)
Stage 4: where
| where count_ > 1
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ActivityType | eq |
| field:"ActivityType" kind:eq value:"51" |
count_ | gt |
| field:"count_" kind:gt value:"1" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DataComputerName | summarize |
Sentinel One - Blacklist hash deleted
#'Detects when blacklist hash was deleted.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Stealth |
Rule Body
id: de339761-2298-4b37-8f1b-80ebd4f0b5f6
name: Sentinel One - Blacklist hash deleted
description: |
'Detects when blacklist hash was deleted.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: SentinelOne
dataTypes:
- SentinelOne
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
relevantTechniques:
- T1070
query: |
SentinelOne
| where ActivityType == 3020
| project EventCreationTime, SrcUserName, Hash=EventSubStatus
| extend AccountCustomEntity = SrcUserName, HashCustomEntity = Hash, HashAlgorithmCustomEntity = "SHA1"
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountCustomEntity
- entityType: FileHash
fieldMappings:
- identifier: Value
columnName: HashCustomEntity
- identifier: Algorithm
columnName: HashAlgorithmCustomEntity
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
SentinelOne
Stage 2: where
| where ActivityType == 3020
Stage 3: project
| project EventCreationTime, SrcUserName, Hash=EventSubStatus
Stage 4: extend
| extend AccountCustomEntity = SrcUserName, HashCustomEntity = Hash, HashAlgorithmCustomEntity = "SHA1"
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ActivityType | eq |
| field:"ActivityType" kind:eq value:"3020" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
EventCreationTime | project |
Hash | project |
SrcUserName | project |
AccountCustomEntity | extend |
HashAlgorithmCustomEntity | extend |
HashCustomEntity | extend |
Subtenant Deleted
#Detects when a subtenant is deleted from Veeam Backup & Replication. This might indicate unauthorized removal of critical components.
Rule Body
id: 1c23715c-3a28-4b98-b135-fece6e7dcb8b
name: Subtenant Deleted
description: Detects when a subtenant is deleted from Veeam Backup & Replication.
This might indicate unauthorized removal of critical components.
severity: High
status: Available
requiredDataConnectors:
- connectorId: Syslog
dataTypes:
- Syslog
- connectorId: SyslogAma
dataTypes:
- Syslog
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
aggregationKind: AlertPerResult
tactics: []
relevantTechniques: []
query: "Veeam_GetSecurityEvents\n| where instanceId == 25210\n| project\n Date\
\ = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),\n DataSource = original_host,\n\
\ EventId = instanceId,\n UserName = user,\n MessageDetails = Description,\n\
\ Severity = SeverityDescription"
version: 1.0.1
kind: Scheduled
customDetails:
Date: Date
VbrHostName: DataSource
EventId: EventId
MessageDetails: MessageDetails
Severity: Severity
Stages and Predicates
Stage 1: source
Veeam_GetSecurityEvents
Stage 2: where
| where instanceId == 25210
Stage 3: project
| project
Date = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),
DataSource = original_host,
EventId = instanceId,
UserName = user,
MessageDetails = Description,
Severity = SeverityDescription
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
instanceId | eq |
| field:"instanceId" kind:eq value:"25210" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DataSource | project |
Date | project |
EventId | project |
MessageDetails | project |
Severity | project |
UserName | project |
TIE DCSync
#'Searches for DCSync attacks.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
Rule Body
id: 19d1f964-ddcf-437b-92ce-b9c1c14d24f1
name: TIE DCSync
description: |
'Searches for DCSync attacks.'
severity: High
requiredDataConnectors:
- connectorId: TenableIE
dataTypes:
- Tenable_IE_CL
queryFrequency: 2h
queryPeriod: 2h
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
relevantTechniques:
- T1003.006
query: |
// For the query to work properly, make sure you have imported the afad_parser.yaml parser into the workspace
// Retrieve the parser here: https://aka.ms/sentinel-TenableApp-afad-parser
// Then, create the Kusto Function with alias afad_parser
afad_parser
| where MessageType == 2 and Codename == "DCSync"
| extend HostName = tostring(split(Host, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(Host, '.'), 1, -1), '.'))
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: DnsDomain
version: 1.0.1
kind: Scheduled
Stages and Predicates
Stage 1: source
afad_parser
Stage 2: where
| where MessageType == 2 and Codename == "DCSync"
Stage 3: extend
| extend HostName = tostring(split(Host, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(Host, '.'), 1, -1), '.'))
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Codename | eq |
| field:"Codename" kind:eq value:"DCSync" |
MessageType | eq |
| field:"MessageType" kind:eq value:"2" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DnsDomain | extend |
HostName | extend |
Whisper Security - C2 Communication Detection
#Detects command-and-control communication by joining CommonSecurityLog with Whisper threat intelligence data. Fires when network traffic matches a known C2 indicator with a threat score above 60.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control |
Rule Body
id: 00682c4c-b8b5-5ef7-af33-50891b271b7b
name: Whisper Security - C2 Communication Detection
description: |
Detects command-and-control communication by joining CommonSecurityLog with Whisper threat intelligence data. Fires when network traffic matches a known C2 indicator with a threat score above 60.
severity: High
status: Available
requiredDataConnectors:
- connectorId: WhisperSecurityConnector
dataTypes:
- WhisperThreatIntel_CL
queryFrequency: 5m
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- CommandAndControl
relevantTechniques:
- T1071
query: |
// MITRE ATT&CK: T1071 - Application Layer Protocol
// Tactic: Command and Control
// Detects C2 communication by correlating network logs with Whisper threat intel
let timeRange = 1h;
let threatThreshold = 60;
let c2Indicators = WhisperThreatIntel_CL
| where TimeGenerated > ago(timeRange)
| where isC2 == true
| where threatScore > threatThreshold
| project indicator, threatScore, threatLevel, feedNames, explanation, lastSeen;
let CommonSecurityLogSafe = union isfuzzy=true CommonSecurityLog, (datatable(TimeGenerated:datetime, DestinationIP:string, DestinationHostName:string, SourceIP:string, DeviceProduct:string, DeviceAction:string)[]);
CommonSecurityLogSafe
| where TimeGenerated > ago(timeRange)
| where isnotempty(DestinationIP) or isnotempty(DestinationHostName)
| extend matchIndicator = coalesce(DestinationHostName, DestinationIP)
| join kind=inner (c2Indicators) on $left.matchIndicator == $right.indicator
| extend SourceIP = SourceIP, DestinationIP = DestinationIP, DnsDomain = DestinationHostName
| project TimeGenerated, SourceIP, DestinationIP, DnsDomain, DeviceProduct, DeviceAction, threatScore, threatLevel, feedNames, explanation
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIP
- entityType: IP
fieldMappings:
- identifier: Address
columnName: DestinationIP
- entityType: DNS
fieldMappings:
- identifier: DomainName
columnName: DnsDomain
customDetails:
ThreatScore: threatScore
ThreatLevel: threatLevel
FeedNames: feedNames
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
version: 1.0.0
kind: Scheduled
Stages and Predicates
Parameters
let timeRange = 1h;
let threatThreshold = 60;
Let Binding: c2Indicators
let c2Indicators = WhisperThreatIntel_CL
| where TimeGenerated > ago(timeRange)
| where isC2 == true
| where threatScore > threatThreshold
| project indicator, threatScore, threatLevel, feedNames, explanation, lastSeen;
Union isfuzzy=true (2 Sources)
Each leg below queries one source; the rule matches if any leg does. Sources: CommonSecurityLog, datatable(TimeGenerated:datetime,
Leg 1: CommonSecurityLog
Leg 2: datatable(TimeGenerated:datetime,
(datatable(TimeGenerated:datetime, DestinationIP:string, DestinationHostName:string, SourceIP:string, DeviceProduct:string, DeviceAction:string)[])
Applied to the Combined Result
| where TimeGenerated > ago(timeRange) | where isnotempty(DestinationIP) or isnotempty(DestinationHostName) | extend matchIndicator = coalesce(DestinationHostName, DestinationIP) | join kind=inner (c2Indicators) on $left.matchIndicator == $right.indicator | extend SourceIP = SourceIP, DestinationIP = DestinationIP, DnsDomain = DestinationHostName | project TimeGenerated, SourceIP, DestinationIP, DnsDomain, DeviceProduct, DeviceAction, threatScore, threatLevel, feedNames, explanation
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DestinationHostName | is_not_null | field:"DestinationHostName" kind:is_not_null | |
DestinationIP | is_not_null | field:"DestinationIP" kind:is_not_null | |
isC2 | eq |
| field:"isC2" kind:eq value:"true" |
threatScore | gt |
| field:"threatScore" kind:gt value:"60" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DestinationIP | project |
DeviceAction | project |
DeviceProduct | project |
DnsDomain | project |
SourceIP | project |
TimeGenerated | project |
explanation | project |
feedNames | project |
threatLevel | project |
threatScore | project |