Detection rules › Kusto
Kusto rules: 26
ApexOne - Multiple deny or terminate actions on single IP
#'Detects multiple deny or terminate actions on single IP.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: cd94e078-67b7-11ec-90d6-0242ac120003
name: ApexOne - Multiple deny or terminate actions on single IP
description: |
'Detects multiple deny or terminate actions on single IP.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: CefAma
dataTypes:
- CommonSecurityLog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1190
query: |
let threshold = 5;
TMApexOneEvent
| where EventMessage has "Behavior Monitoring"
| where DvcAction == 2 or DvcAction == 3
| summarize count() by SrcIpAddr, bin(TimeGenerated, 10m)
| where count_ > threshold
| extend IPCustomEntity = SrcIpAddr
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPCustomEntity
version: 1.0.3
kind: Scheduled
Stages and Predicates
Parameters
let threshold = 5;
Stage 1: source
TMApexOneEvent
Stage 2: where
| where EventMessage has "Behavior Monitoring"
Stage 3: where
| where DvcAction == 2 or DvcAction == 3
Stage 4: summarize
| summarize count() by SrcIpAddr, bin(TimeGenerated, 10m)
Stage 5: where
| where count_ > threshold
Stage 6: extend
| extend IPCustomEntity = SrcIpAddr
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DvcAction | eq |
| field:"DvcAction" kind:eq |
EventMessage | match |
| field:"EventMessage" kind:match value:"Behavior Monitoring" |
count_ | gt |
| field:"count_" kind:gt value:"5" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
SrcIpAddr | summarize |
IPCustomEntity | extend |
Application ID URI Changed
#Detects changes to an Application ID URI. Monitor these changes to make sure that they were authorized. Ref: https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-applications#appid-uri-added-modified-or-removed
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| 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] Application Granted Administrative Permission to Assign Microsoft Entra ID Roles (Kusto)
- Added Credentials to Existing Application (Sigma)
- Added Owner To Application (Sigma)
- Admin promotion after Role Management Application Permission Grant (Kusto)
- App Granted Microsoft Permissions (Sigma)
- App Granted Privileged Delegated Or App Permissions (Sigma)
- Application AppID Uri Configuration Changes (Sigma)
Rule Body
id: 9fb2ee72-959f-4c2b-bc38-483affc539e4
name: Application ID URI Changed
description: |
'Detects changes to an Application ID URI.
Monitor these changes to make sure that they were authorized.
Ref: https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-applications#appid-uri-added-modified-or-removed'
severity: Medium
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- AuditLogs
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
- PrivilegeEscalation
relevantTechniques:
- T1078.004
tags:
- AADSecOpsGuide
query: |
AuditLogs
| where Category == "ApplicationManagement"
| where OperationName has_any ("Update Application", "Update Service principal")
| where TargetResources has "AppIdentifierUri"
| extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
| extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
| extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend InitiatingIPAddress = tostring(InitiatedBy.user.ipAddress)
| extend mod_props = TargetResources[0].modifiedProperties
| extend TargetAppName = tostring(TargetResources[0].displayName)
| mv-expand mod_props
| where mod_props.displayName has "AppIdentifierUri"
| extend OldURI = tostring(mod_props.oldValue)
| extend NewURI = tostring(mod_props.newValue)
| extend UpdatedBy = iif(isnotempty(InitiatingAppName), InitiatingAppName, InitiatingUserPrincipalName)
| extend InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1])
| project-reorder TimeGenerated, InitiatingAppName, InitiatingAppServicePrincipalId, InitiatingAadUserId, InitiatingUserPrincipalName, InitiatingIPAddress
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: InitiatingUserPrincipalName
- identifier: Name
columnName: InitiatingAccountName
- identifier: UPNSuffix
columnName: InitiatingAccountUPNSuffix
- entityType: Account
fieldMappings:
- identifier: AadUserId
columnName: InitiatingAadUserId
- entityType: URL
fieldMappings:
- identifier: Url
columnName: OldURI
- entityType: URL
fieldMappings:
- identifier: Url
columnName: NewURI
- entityType: IP
fieldMappings:
- identifier: Address
columnName: InitiatingIPAddress
version: 1.1.1
kind: Scheduled
metadata:
source:
kind: Community
author:
name: Microsoft Security Research
support:
tier: Community
categories:
domains: [ "Security - Others" ]
Stages and Predicates
Stage 1: source
AuditLogs
Stage 2: where
| where Category == "ApplicationManagement"
Stage 3: where
| where OperationName has_any ("Update Application", "Update Service principal")
Stage 4: where
| where TargetResources has "AppIdentifierUri"
Stage 5: extend (7 consecutive steps)
| extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
| extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
| extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend InitiatingIPAddress = tostring(InitiatedBy.user.ipAddress)
| extend mod_props = TargetResources[0].modifiedProperties
| extend TargetAppName = tostring(TargetResources[0].displayName)
Stage 6: mv-expand
| mv-expand mod_props
Stage 7: where
| where mod_props.displayName has "AppIdentifierUri"
Stage 8: extend (4 consecutive steps)
| extend OldURI = tostring(mod_props.oldValue)
| extend NewURI = tostring(mod_props.newValue)
| extend UpdatedBy = iif(isnotempty(InitiatingAppName), InitiatingAppName, InitiatingUserPrincipalName)
| extend InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1])
Stage 9: project-reorder
| project-reorder TimeGenerated, InitiatingAppName, InitiatingAppServicePrincipalId, InitiatingAadUserId, InitiatingUserPrincipalName, InitiatingIPAddress
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Category | eq |
| field:"Category" kind:eq value:"ApplicationManagement" |
OperationName | match |
| field:"OperationName" kind:match |
TargetResources | match |
| field:"azure_ad::target_resources" kind:match value:"AppIdentifierUri" |
mod_props.displayName | match |
| field:"mod_props.displayName" kind:match value:"AppIdentifierUri" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
InitiatingAppName | extend |
InitiatingAppServicePrincipalId | extend |
InitiatingUserPrincipalName | extend |
InitiatingAadUserId | extend |
InitiatingIPAddress | extend |
mod_props | extend |
TargetAppName | extend |
OldURI | extend |
NewURI | extend |
UpdatedBy | extend |
InitiatingAccountName | extend |
InitiatingAccountUPNSuffix | extend |
AV detections related to Dev-0530 actors
#This query looks for Microsoft Defender AV detections related to Dev-0530 actors. In Microsoft Sentinel the SecurityAlerts table includes only the Device Name of the affected device, this query joins the DeviceInfo table to clearly connect other information such as Device group, ip, logged on users etc. This would allow the Microsoft Sentinel analyst to have more context related to the alert, if available.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Impact |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Microsoft Defender for Endpoint | DeviceInfo action any: Device information |
Rule Body
id: 5f171045-88ab-4634-baae-a7b6509f483b
name: AV detections related to Dev-0530 actors
description: |
'This query looks for Microsoft Defender AV detections related to Dev-0530 actors.
In Microsoft Sentinel the SecurityAlerts table includes only the Device Name of the affected device, this query joins the DeviceInfo table to clearly connect other information such as Device group, ip, logged on users etc. This would allow the Microsoft Sentinel analyst to have more context related to the alert, if available.'
severity: High
requiredDataConnectors:
- connectorId: MicrosoftThreatProtection
dataTypes:
- SecurityAlert
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Impact
relevantTechniques:
- T1486
tags:
- Dev-0530 actors
query: |
let Dev0530_threats = dynamic(["Trojan:Win32/SiennaPurple.A", "Ransom:Win32/SiennaBlue.A", "Ransom:Win32/SiennaBlue.B"]);
SecurityAlert
| where ProviderName == "MDATP"
| extend ThreatName = tostring(parse_json(ExtendedProperties).ThreatName)
| extend ThreatFamilyName = tostring(parse_json(ExtendedProperties).ThreatFamilyName)
| where ThreatName in~ (Dev0530_threats) or ThreatFamilyName in~ (Dev0530_threats)
| extend CompromisedEntity = tolower(CompromisedEntity)
| join kind=inner (DeviceInfo
| extend DeviceName = tolower(DeviceName)
) on $left.CompromisedEntity == $right.DeviceName
| summarize by bin(TimeGenerated, 1d), DisplayName, ThreatName, ThreatFamilyName, PublicIP, AlertSeverity, Description, tostring(LoggedOnUsers), DeviceId, TenantId, CompromisedEntity, tostring(LoggedOnUsers), ProductName, Entities
| extend HostName = tostring(split(CompromisedEntity, ".")[0]), DomainIndex = toint(indexof(CompromisedEntity, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(CompromisedEntity, DomainIndex + 1), CompromisedEntity)
| project-away DomainIndex
entityMappings:
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: CompromisedEntity
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: HostNameDomain
- entityType: IP
fieldMappings:
- identifier: Address
columnName: PublicIP
version: 1.0.5
kind: Scheduled
metadata:
source:
kind: Community
author:
name: Microsoft Security Research
support:
tier: Community
categories:
domains: [ "Security - Others" ]
Stages and Predicates
Parameters
let Dev0530_threats = dynamic(["Trojan:Win32/SiennaPurple.A", "Ransom:Win32/SiennaBlue.A", "Ransom:Win32/SiennaBlue.B"]);
Stage 1: source
SecurityAlert
Stage 2: where
| where ProviderName == "MDATP"
Stage 3: extend
| extend ThreatName = tostring(parse_json(ExtendedProperties).ThreatName)
Stage 4: extend
| extend ThreatFamilyName = tostring(parse_json(ExtendedProperties).ThreatFamilyName)
Stage 5: where
| where ThreatName in~ (Dev0530_threats) or ThreatFamilyName in~ (Dev0530_threats)
Stage 6: extend
| extend CompromisedEntity = tolower(CompromisedEntity)
Stage 7: join
| join kind=inner (DeviceInfo
| extend DeviceName = tolower(DeviceName)
) on $left.CompromisedEntity == $right.DeviceName
Stage 8: summarize
summarize by DisplayName, ThreatName, ThreatFamilyName, PublicIP, AlertSeverity, Description, DeviceId, TenantId, CompromisedEntity, ProductName, Entities
Stage 9: extend
extend DomainIndex, HostName
Stage 10: extend
extend HostNameDomain
HostNameDomain =DomainIndex != -1substring(CompromisedEntity, (DomainIndex + 1))CompromisedEntityStage 11: project-away
project-away DomainIndex
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ProviderName | eq |
| field:"ProviderName" kind:eq value:"MDATP" |
ThreatFamilyName | in |
| field:"ThreatFamilyName" kind:in |
ThreatName | in |
| field:"ThreatName" kind:in |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AlertSeverity | summarize |
CompromisedEntity | summarize |
Description | summarize |
DeviceId | summarize |
DisplayName | summarize |
Entities | summarize |
ProductName | summarize |
PublicIP | summarize |
TenantId | summarize |
ThreatFamilyName | summarize |
ThreatName | summarize |
HostName | extend |
HostNameDomain | extend |
AWSCloudTrail - S3 Object Exfiltration from Anonymous User
#Detects S3 GetObject activity performed by anonymous principals, which can indicate unauthorized object access and potential data exfiltration from public or misconfigured buckets.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Collection |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| AWS | CloudTrail event GetObject: Retrieves an object from a specified S3 bucket. |
Rules Detecting the Same Action
These rules filter on the same operation.
- AWS Exfiltration via Anomalous GetObject API Activity (Splunk)
- AWSCloudTrail - S3 bucket suspicious ransomware activity (Kusto)
- AWSCloudTrail - Successful brute force attack on S3 Bucket (Kusto)
- S3 Access Via VPC Endpoint From External IP (Panther)
- Suspicious access of BEC related documents in AWS S3 buckets (Kusto)
Rule Body
id: 15d3bf4e-8708-41c8-a836-8b0aa5be730e
name: AWSCloudTrail - S3 Object Exfiltration from Anonymous User
description: |
Detects S3 GetObject activity performed by anonymous principals, which can indicate unauthorized object access
and potential data exfiltration from public or misconfigured buckets.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: AWS
dataTypes:
- AWSCloudTrail
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
- Collection
relevantTechniques:
- T1530
query: |
AWSCloudTrail
| where ((UserIdentityAccountId =~ @'ANONYMOUS_PRINCIPAL' or UserIdentityPrincipalid == "Anonymous") and EventName == 'GetObject')
| extend
BucketName = parse_json(RequestParameters).bucketName,
ObjectKey = parse_json(RequestParameters).key
| extend UserName = substring(UserIdentityPrincipalid, indexof_regex(UserIdentityPrincipalid, ":") + 1)
| extend Name = split(UserName,'@')[0],UpnSuffix = split(UserName,'@')[1]
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: Name
- identifier: UPNSuffix
columnName: UpnSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIpAddress
customDetails:
BucketName: BucketName
ObjectKey: ObjectKey
alertDetailsOverride:
alertDisplayNameFormat: 'AWS S3 object access by anonymous principal in bucket {{BucketName}}'
alertDescriptionFormat: 'Detected anonymous GetObject activity for bucket {{BucketName}} and object {{ObjectKey}} from {{SourceIpAddress}}.'
version: 1.0.1
kind: Scheduled
Stages and Predicates
Stage 1: source
AWSCloudTrail
Stage 2: where
| where ((UserIdentityAccountId =~ @'ANONYMOUS_PRINCIPAL' or UserIdentityPrincipalid == "Anonymous") and EventName == 'GetObject')
Stage 3: extend (3 consecutive steps)
| extend
BucketName = parse_json(RequestParameters).bucketName,
ObjectKey = parse_json(RequestParameters).key
| extend UserName = substring(UserIdentityPrincipalid, indexof_regex(UserIdentityPrincipalid, ":") + 1)
| extend Name = split(UserName,'@')[0],UpnSuffix = split(UserName,'@')[1]
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventName | eq |
| field:"aws::eventName" kind:eq value:"GetObject" |
UserIdentityAccountId | eq |
| field:"UserIdentityAccountId" kind:eq value:"ANONYMOUS_PRINCIPAL" |
UserIdentityPrincipalid | eq |
| field:"aws::userIdentity.principalId" kind:eq value:"Anonymous" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
BucketName | extend |
ObjectKey | extend |
UserName | extend |
Name | extend |
UpnSuffix | extend |
BloodHound Attack Path Finding - Large Default Groups With Limited Ownership Privileges
#This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.
BloodHound Attack Path Finding - Large Default Groups With Limited Ownership Privileges
Rule Body
id: 12ecc3cf-a1db-4fa4-bcb2-605dd3a274e0
name: BloodHound Attack Path Finding - Large Default Groups With Limited Ownership
Privileges
description: BloodHound Attack Path Finding - Large Default Groups With Limited Ownership
Privileges
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: BloodHoundEnterprise
dataTypes:
- BHEAttackPathsData_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: BHEAttackPathsData_CL | where Finding == "LargeDefaultGroupsOwnsLimitedRights"
entityMappings:
- entityType: URL
fieldMappings:
- identifier: Url
columnName: domain_name
customDetails:
Description: ShortDescription
Remediation: ShortRemediation
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
BHEAttackPathsData_CL
Stage 2: where
| where Finding == "LargeDefaultGroupsOwnsLimitedRights"
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Finding | eq |
| field:"Finding" kind:eq value:"LargeDefaultGroupsOwnsLimitedRights" |
Brute force attack against user credentials
#'Identifies evidence of brute force activity against a user based on multiple authentication failures and at least one successful authentication within a given time window. This query limits IPAddresses to 100 and may not potentially cover all IPAddresses. The default failure threshold is 10, success threshold is 1, and the default time window is 20 minutes.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
Rule Body
id: 5a6ce089-e756-40fb-b022-c8e8864a973a
name: Brute force attack against user credentials
description: |
'Identifies evidence of brute force activity against a user based on multiple authentication failures and at least one successful authentication within a given time window. This query limits IPAddresses to 100 and may not potentially cover all IPAddresses.
The default failure threshold is 10, success threshold is 1, and the default time window is 20 minutes.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: SalesforceServiceCloudCCPDefinition
dataTypes:
- SalesforceServiceCloud
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
relevantTechniques:
- T1110
query: |
let failureCountThreshold = 10;
let successCountThreshold = 1;
let Failures =
SalesforceServiceCloud
| where EventType == "Login" and LoginStatus != "LOGIN_NO_ERROR"
| summarize
FailureStartTime = min(TimeGenerated),
FailureEndTime = max(TimeGenerated),
IpAddresses = make_set (ClientIp, 100),
FailureCount = count() by User, UserId, UserType;
SalesforceServiceCloud
| where EventType == "Login" and LoginStatus == "LOGIN_NO_ERROR"
| summarize
SuccessStartTime = min(TimeGenerated),
SuccessEndTime = max(TimeGenerated),
IpAddresses = make_set (ClientIp, 100),
SuccessCount = count() by User, UserId, UserType
| join kind=leftouter Failures on UserId
| where FailureCount >= failureCountThreshold and SuccessCount >= successCountThreshold
| where FailureEndTime < SuccessStartTime
| project User, EventStartTime = FailureStartTime, EventEndTime = SuccessEndTime, IpAddresses
customDetails:
EventStartTime: FailureStartTime
EventEndTime: SuccessEndTime
IPAddresses: IpAddresses
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: User
version: 1.0.4
kind: Scheduled
Stages and Predicates
Parameters
let failureCountThreshold = 10;
let successCountThreshold = 1;
Let Binding: Failures
let Failures = SalesforceServiceCloud
| where EventType == "Login" and LoginStatus != "LOGIN_NO_ERROR"
| summarize
FailureStartTime = min(TimeGenerated),
FailureEndTime = max(TimeGenerated),
IpAddresses = make_set (ClientIp, 100),
FailureCount = count() by User, UserId, UserType;
Stage 1: source
SalesforceServiceCloud
Stage 2: where
| where EventType == "Login" and LoginStatus == "LOGIN_NO_ERROR"
Stage 3: summarize
| summarize
SuccessStartTime = min(TimeGenerated),
SuccessEndTime = max(TimeGenerated),
IpAddresses = make_set (ClientIp, 100),
SuccessCount = count() by User, UserId, UserType
Stage 4: join
| join kind=leftouter Failures on UserId
Stage 5: where
where FailureCount >= 10 and SuccessCount >= 1
Stage 6: where
where FailureEndTime < SuccessStartTime
Stage 7: project
project EventEndTime, EventStartTime, IpAddresses, User
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventType | eq |
| field:"EventType" kind:eq value:"Login" |
FailureCount | ge |
| field:"FailureCount" kind:ge value:"10" |
LoginStatus | eq |
| field:"LoginStatus" kind:eq value:"LOGIN_NO_ERROR" |
LoginStatus | ne |
| field:"LoginStatus" kind:ne value:"LOGIN_NO_ERROR" |
SuccessCount | ge |
| field:"SuccessCount" kind:ge value:"1" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
EventEndTime | project |
EventStartTime | project |
IpAddresses | project |
User | project |
Cisco WSA - Unexpected uploads
#'Detects unexpected file uploads.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Exfiltration |
Rule Body
id: 32c460ad-2d40-43e9-8ead-5cdd1d7a3163
name: Cisco WSA - Unexpected uploads
description: |
'Detects unexpected file uploads.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: SyslogAma
datatypes:
- Syslog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Exfiltration
relevantTechniques:
- T1567
query: |
let f_cnt_upload_threshold = 5;
let f_upload_size_threshold = 5000000;
CiscoWSAEvent
| where HttpRequestMethod in~ ('POST', 'PUT')
| where isnotempty(AmpFileName)
| where UrlCategory in~ ('IW_fts', 'IW_osb')
| where DstBytes > f_upload_size_threshold
| summarize count() by AmpFileName, SrcUserName, bin(TimeGenerated, 10m)
| where count_ >= f_cnt_upload_threshold
| extend AccountCustomEntity = SrcUserName
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountCustomEntity
version: 1.0.3
kind: Scheduled
Stages and Predicates
Parameters
let f_cnt_upload_threshold = 5;
let f_upload_size_threshold = 5000000;
Stage 1: source
CiscoWSAEvent
Stage 2: where
| where HttpRequestMethod in~ ('POST', 'PUT')
Stage 3: where
| where isnotempty(AmpFileName)
Stage 4: where
| where UrlCategory in~ ('IW_fts', 'IW_osb')
Stage 5: where
| where DstBytes > f_upload_size_threshold
Stage 6: summarize
| summarize count() by AmpFileName, SrcUserName, bin(TimeGenerated, 10m)
Stage 7: where
| where count_ >= f_cnt_upload_threshold
Stage 8: extend
| extend AccountCustomEntity = SrcUserName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AmpFileName | is_not_null | field:"AmpFileName" kind:is_not_null | |
DstBytes | gt |
| field:"DstBytes" kind:gt value:"5000000" |
HttpRequestMethod | in |
| field:"HttpRequestMethod" kind:in |
UrlCategory | in |
| field:"UrlCategory" kind:in |
count_ | ge |
| field:"count_" kind:ge value:"5" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AmpFileName | summarize |
SrcUserName | summarize |
AccountCustomEntity | extend |
CYFIRMA - High severity Malicious Phishing Network Indicators - Monitor Recommended Rule
#This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.
"This analytics rule identifies network-based indicators such as URLs, IP addresses, and domains related to phishing campaigns, as reported by CYFIRMA threat intelligence. These indicators are flagged with a recommended action to block and are categorized under the 'Phishing' role. Such infrastructure is often used to deliver phishing emails, host fake login portals, or redirect victims to credential-harvesting pages. monitoring these indicators proactively helps prevent user compromise and data theft."
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Execution | |
| Credential Access | |
| Exfiltration |
Rule Body
id: 359e2afb-b6d4-45db-90aa-c89ce7234d72
name: CYFIRMA - High severity Malicious Phishing Network Indicators - Monitor Recommended Rule
description: |
"This analytics rule identifies network-based indicators such as URLs, IP addresses, and domains related to phishing campaigns, as reported by CYFIRMA threat intelligence.
These indicators are flagged with a recommended action to block and are categorized under the 'Phishing' role.
Such infrastructure is often used to deliver phishing emails, host fake login portals, or redirect victims to credential-harvesting pages.
monitoring these indicators proactively helps prevent user compromise and data theft."
version: 1.0.1
kind: Scheduled
severity: High
enabled: false
requiredDataConnectors:
- connectorId: CyfirmaCyberIntelligenceDC
dataTypes:
- CyfirmaIndicators_CL
query: |
//Phishing Network Indicators - Monitor Recommended
let timeFrame= 5m;
CyfirmaIndicators_CL
| where ConfidenceScore >= 80
and TimeGenerated between (ago(timeFrame) .. now())
and pattern !contains 'file:hashes' and RecommendedActions has 'Monitor' and Roles has 'Phishing'
| extend IPv4 = extract(@"ipv4-addr:value\s*=\s*'([^']+)'", 1, pattern)
| extend IPv6 = extract(@"ipv6-addr:value\s*=\s*'([^']+)'", 1, pattern)
| extend URL = extract(@"url:value\s*=\s*'([^']+)'", 1, pattern)
| extend Domain = extract(@"domain-name:value\s*=\s*'([^']+)'", 1, pattern)
| extend parsed = parse_json(extensions)
| extend extensionKeys = bag_keys(parsed)
| mv-expand extensionKeys
| extend extensionKeyStr = tostring(extensionKeys)
| extend ext = parsed[extensionKeyStr]
| extend props = ext.properties
| extend
extension_id = extensionKeyStr,
ASN_Owner = props.asn_owner,
ASN = props.asn,
ProviderName = 'CYFIRMA',
ProductName = 'DeCYFIR/DeTCT'
| project
IPv4,
IPv6,
URL,
Domain,
ThreatActors,
RecommendedActions,
Sources,
Roles,
Country,
IPAbuse,
name,
Description,
ConfidenceScore,
IndicatorID,
created,
modified,
valid_from,
Tags,
ThreatType,
TimeGenerated,
SecurityVendors,
ProductName,
ProviderName
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: GreaterThan
triggerThreshold: 0
suppressionDuration: 5m
suppressionEnabled: true
tactics:
- InitialAccess
- Execution
- CredentialAccess
- Exfiltration
relevantTechniques:
- T1566
- T1204
- T1556
- T1110
- T1041
- T1566.001
- T1566.002
- T1204.001
- T1556.002
- T1110.003
alertDetailsOverride:
alertDisplayNameFormat: "High-Confidence Malicious Phishing Network Indicators - Monitor Recommended - {{name}} "
alertDescriptionFormat: "{{Description}} - {{name}} "
alertDynamicProperties:
- alertProperty: ProductName
value: ProductName
- alertProperty: ProviderName
value: ProviderName
customDetails:
ThreatActors: ThreatActors
Sources: Sources
RecommendedActions: RecommendedActions
Roles: Roles
Country: Country
Description: Description
ConfidenceScore: ConfidenceScore
SecurityVendors: SecurityVendors
IndicatorID: IndicatorID
Created: created
Modified: modified
ValidFrom: valid_from
Tags: Tags
ThreatType: ThreatType
TimeGenerated: TimeGenerated
IPAbuse: IPAbuse
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPv4
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPv6
- entityType: DNS
fieldMappings:
- identifier: DomainName
columnName: Domain
- entityType: URL
fieldMappings:
- identifier: Url
columnName: URL
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
eventGroupingSettings:
aggregationKind: AlertPerResult
Stages and Predicates
Parameters
let timeFrame = 5m;
Stage 1: source
CyfirmaIndicators_CL
Stage 2: where
| where ConfidenceScore >= 80
and TimeGenerated between (ago(timeFrame) .. now())
and pattern !contains 'file:hashes' and RecommendedActions has 'Monitor' and Roles has 'Phishing'
Stage 3: extend (6 consecutive steps)
| extend IPv4 = extract(@"ipv4-addr:value\s*=\s*'([^']+)'", 1, pattern)
| extend IPv6 = extract(@"ipv6-addr:value\s*=\s*'([^']+)'", 1, pattern)
| extend URL = extract(@"url:value\s*=\s*'([^']+)'", 1, pattern)
| extend Domain = extract(@"domain-name:value\s*=\s*'([^']+)'", 1, pattern)
| extend parsed = parse_json(extensions)
| extend extensionKeys = bag_keys(parsed)
Stage 4: mv-expand
| mv-expand extensionKeys
Stage 5: extend (4 consecutive steps)
| extend extensionKeyStr = tostring(extensionKeys)
| extend ext = parsed[extensionKeyStr]
| extend props = ext.properties
| extend
extension_id = extensionKeyStr,
ASN_Owner = props.asn_owner,
ASN = props.asn,
ProviderName = 'CYFIRMA',
ProductName = 'DeCYFIR/DeTCT'
Stage 6: project
| project
IPv4,
IPv6,
URL,
Domain,
ThreatActors,
RecommendedActions,
Sources,
Roles,
Country,
IPAbuse,
name,
Description,
ConfidenceScore,
IndicatorID,
created,
modified,
valid_from,
Tags,
ThreatType,
TimeGenerated,
SecurityVendors,
ProductName,
ProviderName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ConfidenceScore | ge |
| field:"ConfidenceScore" kind:ge value:"80" |
RecommendedActions | match |
| field:"RecommendedActions" kind:match value:"Monitor" |
Roles | match |
| field:"Roles" kind:match value:"Phishing" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
pattern | contains | file:hashes | excludes:pattern field:"pattern" value:"file:hashes" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ConfidenceScore | project |
Country | project |
Description | project |
Domain | project |
IPAbuse | project |
IPv4 | project |
IPv6 | project |
IndicatorID | project |
ProductName | project |
ProviderName | project |
RecommendedActions | project |
Roles | project |
SecurityVendors | project |
Sources | project |
Tags | project |
ThreatActors | project |
ThreatType | project |
TimeGenerated | project |
URL | project |
created | project |
modified | project |
name | project |
valid_from | project |
CYFIRMA - Social and Public Exposure - Exposure of PII/CII in Public Domain Rule
#This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.
"This analytics rule detects high severity alerts from CYFIRMA indicating exposure of Personally Identifiable Information (PII) or Confidential Information (CII) in public or unsecured sources. Such leaks may include email addresses, credentials, phone numbers, or other sensitive personal or organizational data. These exposures can lead to identity theft, phishing, credential compromise, or regulatory non-compliance. Investigate promptly and initiate remediation steps including user notifications and credential resets."
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Credential Access | |
| Collection | |
| Exfiltration |
Rule Body
id: 52d71822-41e4-4c21-b36f-400294f2b43a
name: CYFIRMA - Social and Public Exposure - Exposure of PII/CII in Public Domain Rule
description: |
"This analytics rule detects high severity alerts from CYFIRMA indicating exposure of Personally Identifiable Information (PII) or Confidential Information (CII) in public or unsecured sources.
Such leaks may include email addresses, credentials, phone numbers, or other sensitive personal or organizational data.
These exposures can lead to identity theft, phishing, credential compromise, or regulatory non-compliance.
Investigate promptly and initiate remediation steps including user notifications and credential resets."
version: 1.0.1
kind: Scheduled
severity: High
requiredDataConnectors:
- connectorId: CyfirmaDigitalRiskAlertsConnector
dataTypes:
- CyfirmaSPEPIIAndCIIAlerts_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- InitialAccess
- Exfiltration
- Collection
- CredentialAccess
relevantTechniques:
- T1078
- T1003
- T1213
- T1537
query: |
// High severity - Social and Public Exposure - Exposure of PII/CII in Public Domain
let timeFrame = 5m;
CyfirmaSPEPIIAndCIIAlerts_CL
| where severity == 'Critical' and TimeGenerated between (ago(timeFrame) .. now())
| extend
Description=description,
FirstSeen=first_seen,
LastSeen=last_seen,
RiskScore=risk_score,
AlertUID=alert_uid,
UID=uid,
AssetType=asset_type,
AssetValue=signature,
Source=source,
Impact=impact,
Recommendation=recommendation,
PostedDate=posted_date,
ProviderName='CYFIRMA',
ProductName='DeCYFIR/DeTCT',
AlertTitle=Alert_title
| project
TimeGenerated,
Description,
RiskScore,
FirstSeen,
LastSeen,
AlertUID,
UID,
AssetType,
AssetValue,
Source,
Impact,
Recommendation,
PostedDate,
ProductName,
ProviderName,
AlertTitle
customDetails:
AssetType: AssetType
AssetValue: AssetValue
RiskScore: RiskScore
Source: Source
Impact: Impact
Description: Description
Recommendation: Recommendation
FirstSeen: FirstSeen
LastSeen: LastSeen
AlertUID: AlertUID
UID: UID
TimeGenerated: TimeGenerated
alertDetailsOverride:
alertDisplayNameFormat: "CYFIRMA - High Severity: Exposure of PII/CII in Public Domain - {{AlertTitle}} "
alertDescriptionFormat: "{{Description}} "
alertDynamicProperties:
- alertProperty: ProductName
value: ProductName
- alertProperty: ProviderName
value: ProviderName
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
eventGroupingSettings:
aggregationKind: AlertPerResult
Stages and Predicates
Parameters
let timeFrame = 5m;
Stage 1: source
CyfirmaSPEPIIAndCIIAlerts_CL
Stage 2: where
| where severity == 'Critical' and TimeGenerated between (ago(timeFrame) .. now())
Stage 3: extend
| extend
Description=description,
FirstSeen=first_seen,
LastSeen=last_seen,
RiskScore=risk_score,
AlertUID=alert_uid,
UID=uid,
AssetType=asset_type,
AssetValue=signature,
Source=source,
Impact=impact,
Recommendation=recommendation,
PostedDate=posted_date,
ProviderName='CYFIRMA',
ProductName='DeCYFIR/DeTCT',
AlertTitle=Alert_title
Stage 4: project
| project
TimeGenerated,
Description,
RiskScore,
FirstSeen,
LastSeen,
AlertUID,
UID,
AssetType,
AssetValue,
Source,
Impact,
Recommendation,
PostedDate,
ProductName,
ProviderName,
AlertTitle
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
severity | eq |
| field:"severity" kind:eq value:"Critical" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AlertTitle | project |
AlertUID | project |
AssetType | project |
AssetValue | project |
Description | project |
FirstSeen | project |
Impact | project |
LastSeen | project |
PostedDate | project |
ProductName | project |
ProviderName | project |
Recommendation | project |
RiskScore | project |
Source | project |
TimeGenerated | project |
UID | project |
GTI - High Relevance Alert Detected
#Triggers an incident when a GTI Relevance System Alert is assessed as RELEVANCE_LEVEL_HIGH with a HIGH confidence level. High relevance means Google Threat Intelligence has determined the threat directly or very closely matches your organisation profile - regardless of threat type. These are the most actionable alerts and warrant priority investigation. Each unique Alert ID is grouped into a single incident.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Privilege Escalation | |
| Credential Access | |
| Exfiltration | |
| Impact |
Rule Body
id: f6a7b8c9-d0e1-2345-fabc-456789012345
name: GTI - High Relevance Alert Detected
description: |
Triggers an incident when a GTI Relevance System Alert is assessed as RELEVANCE_LEVEL_HIGH
with a HIGH confidence level. High relevance means Google Threat Intelligence has determined
the threat directly or very closely matches your organisation profile - regardless of threat type.
These are the most actionable alerts and warrant priority investigation.
Each unique Alert ID is grouped into a single incident.
severity: High
status: Available
requiredDataConnectors:
- connectorId: GoogleThreatIntelligenceRelevanceSystemAlertsAPI
dataTypes:
- RelevanceSystemAlerts_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
- Exfiltration
- Impact
- CredentialAccess
- PrivilegeEscalation
relevantTechniques:
- T1566
- T1068
- T1078
- T1567
- T1486
- T1552
query: |
GTIRelevanceSystemAlerts
| where RelevanceLevel == "HIGH"
and IsRelevant == true
| extend
DetailSeverity = coalesce(DataLeakSeverity, InsiderThreatSeverity, InitialAccessBrokerSeverity, "")
| project
TimeGenerated,
AlertId,
AlertName = DisplayName,
AlertState = State,
DetailType,
DetailSeverity,
RelevanceLevel,
RelevanceConfidence,
RelevanceReasoning,
CommonThemes = RelevanceEvidenceCommonThemes,
DistinctThemes = RelevanceEvidenceDistinctThemes,
SeverityLevel,
SeverityConfidence,
SeverityReasoning,
PriorityLevel,
PriorityReasoning,
FindingCount,
AiSummary,
AuditCreateTime,
AuditUpdateTime
alertDetailsOverride:
alertDisplayNameFormat: 'GTI High Relevance Alert: {{AlertName}}'
alertDescriptionFormat: |
Relevance: {{RelevanceLevel}}
Why it matches your organisation:
{{RelevanceReasoning}}
Summary: {{AiSummary}}
customDetails:
AlertId: AlertId
AlertState: AlertState
DetailType: DetailType
RelevanceLevel: RelevanceLevel
RelevanceConfidence: RelevanceConfidence
PriorityLevel: PriorityLevel
SeverityLevel: SeverityLevel
FindingCount: FindingCount
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: P5D
matchingMethod: Selected
groupByCustomDetails:
- AlertId
eventGroupingSettings:
aggregationKind: AlertPerResult
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
GTIRelevanceSystemAlerts
Stage 2: where
| where RelevanceLevel == "HIGH"
and IsRelevant == true
Stage 3: extend
| extend
DetailSeverity = coalesce(DataLeakSeverity, InsiderThreatSeverity, InitialAccessBrokerSeverity, "")
Stage 4: project
| project
TimeGenerated,
AlertId,
AlertName = DisplayName,
AlertState = State,
DetailType,
DetailSeverity,
RelevanceLevel,
RelevanceConfidence,
RelevanceReasoning,
CommonThemes = RelevanceEvidenceCommonThemes,
DistinctThemes = RelevanceEvidenceDistinctThemes,
SeverityLevel,
SeverityConfidence,
SeverityReasoning,
PriorityLevel,
PriorityReasoning,
FindingCount,
AiSummary,
AuditCreateTime,
AuditUpdateTime
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
IsRelevant | eq |
| field:"IsRelevant" kind:eq value:"true" |
RelevanceLevel | eq |
| field:"RelevanceLevel" kind:eq value:"HIGH" |
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 |
DetailSeverity | project |
DetailType | project |
DistinctThemes | project |
FindingCount | project |
PriorityLevel | project |
PriorityReasoning | project |
RelevanceConfidence | project |
RelevanceLevel | project |
RelevanceReasoning | project |
SeverityConfidence | project |
SeverityLevel | project |
SeverityReasoning | project |
TimeGenerated | project |
Microsoft Entra ID UserAgent OS Missmatch
#This query extracts the operating system from the UserAgent header and compares this to the DeviceDetail information present in Microsoft Entra ID.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Stealth |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Azure | Success: Successful sign-in |
| Entra ID | SigninLogs sign-in event: Successful sign-in |
Rules Detecting the Same Action
These rules filter on the same operation.
- [Entra ID] Suspicious Continuous OAuth Token Usage (Kusto)
- Anomalous sign-in location by user account and authenticating application (Kusto)
- Anomalous Single Factor Signin (Kusto)
- Authentications of Privileged Accounts Outside of Expected Controls (Kusto)
- Azure Portal sign in from another Azure Tenant (Kusto)
- Azure Service Principal Sign-In Followed by Arc Cluster Credential Access (Elastic)
- Azure SignIn via Legacy Authentication Protocol (Panther)
- Cisco - firewall block but success logon to Microsoft Entra ID (Kusto)
Rule Body
id: 6a638d80-f6b2-473b-9087-3cac78a84b40
name: Microsoft Entra ID UserAgent OS Missmatch
description: |
This query extracts the operating system from the UserAgent header and compares this to the DeviceDetail information present in Microsoft Entra ID.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- SigninLogs
- connectorId: AzureActiveDirectory
dataTypes:
- AADNonInteractiveUserSignInLogs
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
relevantTechniques:
- T1036
query: |
let timeframe = 1d;
let ExtractOSFromUA=(ua:string) {
case(
ua has "Windows NT 6.0", "Windows Vista/Windows Server 2008",
ua has "Windows NT 6.1", "Windows 7/Windows Server 2008R2",
ua has "Windows NT 6.1", "Windows 7/Windows Server 2008",
ua has "Windows NT 6.2", "Windows 8/Windows Server 2012",
ua has "Windows NT 6.3", "Windows 8.1/Windows Server 2012R2",
ua has "Windows NT 10.0", "Windows 10",
ua has "Windows NT 11.0", "Windows 11",
ua has "Windows Phone", "WindowsPhone",
ua has "Android", "Android",
ua has "iPhone;", "IOS",
ua has "iPad;", "IOS",
ua has "Polycom/", "Polycom",
ua has "Darwin/", "MacOS",
ua has "Mac OS X", "MacOS",
ua has "macOS", "MacOS",
ua has "ubuntu", "Linux",
ua has "Linux", "Linux",
ua has "curl", "CLI",
ua has "python", "CLI",
"Unknown"
)
};
// Query to obtain 'simplified' user agents in a given timespan.
union withsource=tbl_name AADNonInteractiveUserSignInLogs, SigninLogs
| where TimeGenerated >= ago(timeframe)
| extend UserAgentOS=tolower(ExtractOSFromUA(UserAgent))
| where not(isempty(UserAgent))
| where not(isempty(AppId))
| where ResultType == 0
| extend DeviceOS=tolower(DeviceDetail_dynamic.operatingSystem)
| where not(isempty(DeviceOS))
| where not(UserAgentOS == "unknown")
// Look for matches both ways, since sometimes the browser OS is more specific and sometimes the DeviceOS is more specific.
| where not(UserAgentOS contains DeviceOS) and not(DeviceOS contains UserAgentOS)
| where not(DeviceOS == "ios" and UserAgentOS == "macos") // This can happen for 'request desktop site'
| where not(DeviceOS == "android" and UserAgentOS == "linux") // Android and Linux sometimes confused
| summarize count(), arg_min(TimeGenerated,*) by DeviceOS, UserAgentOS, UserPrincipalName
// Begin allow-list.
// End allow-list.
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: UserPrincipalName
version: 1.0.2
kind: Scheduled
Stages and Predicates
Parameters
let timeframe = 1d;
Let Binding: ExtractOSFromUA
let ExtractOSFromUA = (ua:string) {
case(
ua has "Windows NT 6.0", "Windows Vista/Windows Server 2008",
ua has "Windows NT 6.1", "Windows 7/Windows Server 2008R2",
ua has "Windows NT 6.1", "Windows 7/Windows Server 2008",
ua has "Windows NT 6.2", "Windows 8/Windows Server 2012",
ua has "Windows NT 6.3", "Windows 8.1/Windows Server 2012R2",
ua has "Windows NT 10.0", "Windows 10",
ua has "Windows NT 11.0", "Windows 11",
ua has "Windows Phone", "WindowsPhone",
ua has "Android", "Android",
ua has "iPhone;", "IOS",
ua has "iPad;", "IOS",
ua has "Polycom/", "Polycom",
ua has "Darwin/", "MacOS",
ua has "Mac OS X", "MacOS",
ua has "macOS", "MacOS",
ua has "ubuntu", "Linux",
ua has "Linux", "Linux",
ua has "curl", "CLI",
ua has "python", "CLI",
"Unknown"
)
};
Union withsource=tbl_name (2 Sources)
Each leg below queries one source; the rule matches if any leg does. Sources: AADNonInteractiveUserSignInLogs, SigninLogs
Leg 1: AADNonInteractiveUserSignInLogs
Leg 2: SigninLogs
Applied to the Combined Result
| where TimeGenerated >= ago(timeframe) | extend UserAgentOS=tolower(ExtractOSFromUA(UserAgent)) | where not(isempty(UserAgent)) | where not(isempty(AppId)) | where ResultType == 0 | extend DeviceOS=tolower(DeviceDetail_dynamic.operatingSystem) | where not(isempty(DeviceOS)) | where not(UserAgentOS == "unknown") | where not(UserAgentOS contains DeviceOS) and not(DeviceOS contains UserAgentOS) | where not(DeviceOS == "ios" and UserAgentOS == "macos") | where not(DeviceOS == "android" and UserAgentOS == "linux") | summarize count(), arg_min(TimeGenerated,*) by DeviceOS, UserAgentOS, UserPrincipalName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ResultType | eq |
| field:"ResultType" kind:eq value:"0" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
UserAgent | is_null | excludes:UserAgent | |
AppId | is_null | excludes:AppId | |
DeviceOS | is_null | excludes:DeviceOS | |
UserAgentOS | eq | unknown | excludes:UserAgentOS field:"UserAgentOS" value:"unknown" |
DeviceOS | cross_field_compare | UserAgentOS | excludes:DeviceOS field:"DeviceOS" value:"UserAgentOS" |
UserAgentOS | cross_field_compare | DeviceOS | excludes:UserAgentOS field:"UserAgentOS" value:"DeviceOS" |
DeviceOS | eq | ios | excludes:DeviceOS field:"DeviceOS" value:"ios" |
UserAgentOS | eq | macos | excludes:UserAgentOS field:"UserAgentOS" value:"macos" |
DeviceOS | eq | android | excludes:DeviceOS field:"DeviceOS" value:"android" |
UserAgentOS | eq | linux | excludes:UserAgentOS field:"UserAgentOS" value:"linux" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DeviceOS | summarize |
UserAgentOS | summarize |
UserPrincipalName | summarize |
Pathlock TDnR - Outbound SAP SMTP Email
#Detects outbound SMTP email activity from SAP systems, forwarded by Pathlock Threat Detection and Response. Anomalous outbound email patterns may indicate data exfiltration, unauthorized use of SAP email functions to send sensitive business data outside the organization.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Exfiltration |
Rule Body
id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c39
name: Pathlock TDnR - Outbound SAP SMTP Email
kind: Scheduled
description: >-
Detects outbound SMTP email activity from SAP systems, forwarded by Pathlock Threat Detection
and Response. Anomalous outbound email patterns may indicate data exfiltration, unauthorized
use of SAP email functions to send sensitive business data outside the organization.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: Pathlock_TDnR
dataTypes:
- Pathlock_TDnR_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Exfiltration
relevantTechniques:
- T1048
query: |
Pathlock_TDnR_CL
| where DataSource == "OUTBOUND_SMTP_MAIL"
| project TimeGenerated, Sysid, DataSource, Eventid, Instance, Hostname, Bname,
Tcode, Report, Area, Subid, SrcIp, DestIp, AffectedUser, LogLine,
MsgType, MsgId, MsgNo, MessageV1, MessageV2, MessageV3, MessageV4, CentralTs
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: Bname
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: Hostname
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIp
eventGroupingSettings:
aggregationKind: SingleAlert
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 5h
matchingMethod: AnyAlert
groupByEntities: []
groupByAlertDetails: []
groupByCustomDetails: []
suppressionDuration: 5h
suppressionEnabled: false
version: 1.0.0
Stages and Predicates
Stage 1: source
Pathlock_TDnR_CL
Stage 2: where
| where DataSource == "OUTBOUND_SMTP_MAIL"
Stage 3: project
| project TimeGenerated, Sysid, DataSource, Eventid, Instance, Hostname, Bname,
Tcode, Report, Area, Subid, SrcIp, DestIp, AffectedUser, LogLine,
MsgType, MsgId, MsgNo, MessageV1, MessageV2, MessageV3, MessageV4, CentralTs
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DataSource | eq |
| field:"DataSource" kind:eq value:"OUTBOUND_SMTP_MAIL" |
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 - User Authorization Buffer Manipulation
#Detects unauthorized manipulations of the SAP user authorization buffer (USRBF2), forwarded by Pathlock Threat Detection and Response. Direct manipulation of the authorization buffer bypasses the normal role/profile assignment process and can grant arbitrary privileges to any user without leaving a trace in standard authorization management logs.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Privilege Escalation | |
| Defense Impairment |
Rule Body
id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c72
name: Pathlock TDnR - User Authorization Buffer Manipulation
kind: Scheduled
description: >-
Detects unauthorized manipulations of the SAP user authorization buffer (USRBF2), forwarded by
Pathlock Threat Detection and Response. Direct manipulation of the authorization buffer bypasses
the normal role/profile assignment process and can grant arbitrary privileges to any user without
leaving a trace in standard authorization management logs.
severity: High
status: Available
requiredDataConnectors:
- connectorId: Pathlock_TDnR
dataTypes:
- Pathlock_TDnR_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- PrivilegeEscalation
- DefenseEvasion
relevantTechniques:
- T1548
- T1562
query: |
Pathlock_TDnR_CL
| where DataSource == "USER_AUTH_BUFFER"
| project TimeGenerated, Sysid, DataSource, Eventid, Instance, Hostname, Bname,
Tcode, Report, Area, Subid, SrcIp, DestIp, AffectedUser, LogLine,
MsgType, MsgId, MsgNo, MessageV1, MessageV2, MessageV3, MessageV4, CentralTs
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: Bname
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: Hostname
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIp
eventGroupingSettings:
aggregationKind: SingleAlert
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 5h
matchingMethod: AnyAlert
groupByEntities: []
groupByAlertDetails: []
groupByCustomDetails: []
suppressionDuration: 5h
suppressionEnabled: false
version: 1.0.0
Stages and Predicates
Stage 1: source
Pathlock_TDnR_CL
Stage 2: where
| where DataSource == "USER_AUTH_BUFFER"
Stage 3: project
| project TimeGenerated, Sysid, DataSource, Eventid, Instance, Hostname, Bname,
Tcode, Report, Area, Subid, SrcIp, DestIp, AffectedUser, LogLine,
MsgType, MsgId, MsgNo, MessageV1, MessageV2, MessageV3, MessageV4, CentralTs
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DataSource | eq |
| field:"DataSource" kind:eq value:"USER_AUTH_BUFFER" |
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 |
Power Automate - Unusual bulk deletion of flow resources
#Identifies bulk deletion of Power Automate flows that exceed a predefined threshold defined in the query and deviate from activity patterns observed in the last 14 days.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Defense Impairment | |
| Impact | |
| Impact |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Microsoft 365 | DeleteFlow |
Rule Body
id: 56cb646e-56a0-4f0e-8866-9bc1dd15da78
kind: Scheduled
name: Power Automate - Unusual bulk deletion of flow resources
description: Identifies bulk deletion of Power Automate flows that exceed a predefined
threshold defined in the query and deviate from activity patterns observed in the
last 14 days.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: PowerAutomate
dataTypes:
- PowerAutomateActivity
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Impact
- DefenseEvasion
relevantTechniques:
- T1485
- T0828
- T1562
query: |
// minThreshold: Minimum number of apps to be deleted to be considered an anomaly;
// This is to prevent one-off isolated delete flow to be considered outlier.
// The Min Threshold can be reduced or increased according to the traffic in the organization.
let minThreshold=10;
let interval = 1h;
let startTime = ago(14d);
let endTime = now();
let query_frequency = 1h;
let flow_deletion_events = PowerAutomateActivity
| where TimeGenerated >= startTime
| where EventOriginalType =~ "DeleteFlow"
| extend IngestionTimeGenerated = TimeGenerated;
flow_deletion_events
| make-series DeletedFlowCount=count() on IngestionTimeGenerated from startTime to endTime step interval by ActorName, UserUpn, ActorUserId
| extend(Anomalies, AnomalyScore, ExpectedUsage) = series_decompose_anomalies(DeletedFlowCount)
| mv-expand
DeletedFlowCount to typeof(double),
IngestionTimeGenerated to typeof(datetime),
Anomalies to typeof(double),
AnomalyScore to typeof(double),
ExpectedUsage to typeof(long)
| where IngestionTimeGenerated >= ago(query_frequency)
| where Anomalies != 0 and DeletedFlowCount >= minThreshold
| lookup (flow_deletion_events
| where IngestionTimeGenerated >= ago(query_frequency))
on ActorName, UserUpn, ActorUserId
| extend
AccountName = tostring(split(ActorName, "@")[0]),
UPNSuffix = tostring(split(ActorName, "@")[1]),
PowerAutomateAppId = 27592
| project
TimeGenerated,
ActorName,
DeletedFlowCount,
ExpectedUsage,
Anomalies,
AnomalyScore,
AccountName,
UPNSuffix,
PowerAutomateAppId,
UserUpn,
ActorUserId
eventGroupingSettings:
aggregationKind: SingleAlert
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: CloudApplication
fieldMappings:
- identifier: AppId
columnName: PowerAutomateAppId
alertDetailsOverride:
alertDisplayNameFormat: Power Automate - unusual bulk deletion of {{DeletedFlowCount}}
flows
alertDescriptionFormat: User {{ActorName}} deleted {{DeletedFlowCount}} flows in
the last hour, surpassing the bulk delete threshold. This is anomalous compared
to the past 14 days.
customDetails:
DeletedFlowCount: DeletedFlowCount
version: 3.2.0
Stages and Predicates
Parameters
let minThreshold = 10;
let interval = 1h;
let startTime = ago(14d);
let endTime = now();
let query_frequency = 1h;
let flow_deletion_events is inlined into the numbered stages below.
Stages 1 to 4 define let flow_deletion_events as the rule's main pipeline source. Stages 5 to 12 use it.
Stage 1: source
PowerAutomateActivity
Stage 2: where
| where TimeGenerated >= startTime
Stage 3: where
| where EventOriginalType =~ "DeleteFlow"
Stage 4: extend
| extend IngestionTimeGenerated = TimeGenerated
The stages below score time-series anomalies (make-series, series_decompose_anomalies).
Stage 5: summarize
flow_deletion_events
| make-series DeletedFlowCount=count() on IngestionTimeGenerated from startTime to endTime step interval by ActorName, UserUpn, ActorUserId
Stage 6: extend
| extend(Anomalies, AnomalyScore, ExpectedUsage) = series_decompose_anomalies(DeletedFlowCount)
Stage 7: mv-expand
| mv-expand
DeletedFlowCount to typeof(double),
IngestionTimeGenerated to typeof(datetime),
Anomalies to typeof(double),
AnomalyScore to typeof(double),
ExpectedUsage to typeof(long)
Stage 8: where
| where IngestionTimeGenerated >= ago(query_frequency)
Stage 9: where
| where Anomalies != 0 and DeletedFlowCount >= minThreshold
Stage 10: kusto:lookup
| lookup (flow_deletion_events
| where IngestionTimeGenerated >= ago(query_frequency))
on ActorName, UserUpn, ActorUserId
Stage 11: extend
extend AccountName, PowerAutomateAppId, UPNSuffix
Stage 12: project
project AccountName, ActorName, ActorUserId, Anomalies, AnomalyScore, DeletedFlowCount, ExpectedUsage, PowerAutomateAppId, TimeGenerated, UPNSuffix, UserUpn
Indicators
These rows show field, operator, and value matches.
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountName | project |
ActorName | project |
ActorUserId | project |
Anomalies | project |
AnomalyScore | project |
DeletedFlowCount | project |
ExpectedUsage | project |
PowerAutomateAppId | project |
TimeGenerated | project |
UPNSuffix | project |
UserUpn | project |
RecordedFuture Threat Hunting Hash All Actors
#Recorded Future Threat Hunting hash correlation for all actors.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Execution | |
| Persistence |
Telemetry Coverage
Rule Body
id: 6db6a8e6-2959-440b-ba57-a505875fcb37
name: RecordedFuture Threat Hunting Hash All Actors
description: |
'Recorded Future Threat Hunting hash correlation for all actors.'
severity: Medium
requiredDataConnectors:
- connectorId: ThreatIntelligenceUploadIndicatorsAPI
dataTypes:
- ThreatIntelIndicators
queryFrequency: 15m
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
- Execution
- Persistence
relevantTechniques:
- T1189
- T1059
- T1554
customDetails:
ActorInformation: RecordedFuturePortalLink
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 1h
matchingMethod: AllEntities
eventGroupingSettings:
aggregationKind: AlertPerResult
alertDetailsOverride:
alertDisplayNameFormat: '{{Description}}'
alertDescriptionFormat: '**{{Description}}**\n\nCorrelation found on {{Hash}} from the {{Type}} table.\n'
alertDynamicProperties:
- alertProperty: AlertLink
value: RecordedFuturePortalLink
query: |
let ioc_lookBack = 1d;
// The source table (imFileEvent) is a ASIM parser table, but can be replaced by any infrastructure table containing Hash data.
// The following workbook: Recorded Future - Hash Correlation will help researching available data and selecting tables and columns
imFileEvent
| where isnotempty(Hash)
| extend lowerHash=tolower(Hash)
| join kind=inner (
ThreatIntelIndicators
// Only look for IOCs
| where ObservableKey contains'file:hashes'
| where isnotempty(ObservableValue)
// Only look at Recorded Future Threat Hunt Indicators.
| where Data.description startswith "Recorded Future - Threat Hunt"
// Only work with the latest indicators
| where TimeGenerated >= ago(ioc_lookBack)
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id
| where IsActive == true and ValidUntil > now()
| extend lowerHash=tolower(ObservableValue)
) on lowerHash
// select column from the source table to match with Recorded Future ThreatIntelIndicator $left.Hash
| mv-expand Label=Data.labels
| extend RecordedFuturePortalLink = parse_json(tostring(Label)).RecordedFuturePortalLink
// Hash and HashType is defaulted to data from Recorded Future, it can be changed to data from the infrastructure table.
| project Hash=ObservableValue, HashType=extract("file:hashes.('[^']*')", 1, ObservableKey), Description=Data.description, Type, TimeGenerated, RecordedFuturePortalLink
entityMappings:
- entityType: FileHash
fieldMappings:
- identifier: Value
columnName: Hash
- identifier: Algorithm
columnName: HashType
version: 1.1.1
kind: Scheduled
Stages and Predicates
Parameters
let ioc_lookBack = 1d;
Stage 1: source
imFileEvent
Stage 2: where
| where isnotempty(Hash)
Stage 3: extend
| extend lowerHash=tolower(Hash)
Stage 4: join
| join kind=inner (
ThreatIntelIndicators
| where ObservableKey contains'file:hashes'
| where isnotempty(ObservableValue)
| where Data.description startswith "Recorded Future - Threat Hunt"
| where TimeGenerated >= ago(ioc_lookBack)
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id
| where IsActive == true and ValidUntil > now()
| extend lowerHash=tolower(ObservableValue)
) on lowerHash
Stage 5: mv-expand
mv-expand Label
Stage 6: extend
extend RecordedFuturePortalLink
Stage 7: project
project Description, Hash, HashType, RecordedFuturePortalLink, TimeGenerated, Type
Stage 8: summarize aggregation inside the join branch
summarize by Id
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Data.description | starts_with |
| field:"Data.description" kind:starts_with value:"Recorded Future - Threat Hunt" |
Hash | is_not_null | field:"Hash" kind:is_not_null | |
IsActive | eq |
| field:"IsActive" kind:eq value:"true" |
ObservableKey | contains |
| field:"ObservableKey" kind:contains value:"file:hashes" |
ObservableValue | is_not_null | field:"ObservableValue" kind:is_not_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Id | summarize |
Registry Run Keys - Suspicious Registry Run Keys
#Below query looks for suspicious additions to Run, RunOnce and several other registry keys. The query analyzes all values in the specified registry keys and finds anomalous ones based on commonality in the environment and excludes possible legitimate activities like software installations. The query might require tuning according to the environment.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence |
Telemetry Coverage
Rule Body
// Author: Cyb3rMonk(https://twitter.com/Cyb3rMonk, https://mergene.medium.com)
// Link to original post: https://mergene.medium.com/threat-hunting-with-data-science-registry-run-keys-9ae329d1ad85
// Description: This query looks for suspicious additions to Run, RunOnce and several other registry keys.
// The query analyzes all values in the specified registry keys and finds anomalous ones based on
// commonality in the environment and excludes possible legitimate activities like software installations.
// The query might requiure tuning according to the environment.
let dataset= materialize (
DeviceRegistryEvents
| where ActionType == "RegistryValueSet"
// registry keys to be monitored
| where RegistryKey has @"Microsoft\Windows\CurrentVersion\RunOnce"
or RegistryKey has @"Microsoft\Windows\CurrentVersion\RunOnceEx"
or RegistryKey has @"Microsoft\Windows\CurrentVersion\Run"
or RegistryKey has @"\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
or (RegistryKey has @"\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders")
or (RegistryKey has @"Software\Microsoft\Windows\CurrentVersion\Policies\Explorer" and RegistryValueName == "Run")
or (RegistryKey has @"\Session Manager" and RegistryValueName == "BootExecute")
or (RegistryKey has @"Microsoft\Windows NT\CurrentVersion\Winlogon" and RegistryValueName == "Userinit")
or (RegistryKey has @"Microsoft\Windows NT\CurrentVersion\Winlogon" and RegistryValueName == "Shell")
or (RegistryKey has @"Microsoft\Windows NT\CurrentVersion\Windows" and RegistryValueName == "load")
or RegistryKey has @"Microsoft\Windows NT\CurrentVersion\Winlogon\Notify"
or RegistryKey has @"\Software\Microsoft\Windows\CurrentVersion\RunServices"
or RegistryKey has @"\Software\Microsoft\Windows\CurrentVersion\RunServicesOnce"
or RegistryKey has @"\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer\Run"
// below is related to the persistence using CLSID (junction folders, etc.)
or (RegistryKey has @"SOFTWARE\Classes\CLSID" and RegistryKey endswith "InprocServer32" and isempty(RegistryValueName))
// exclude Config.Msi folder items
| where RegistryValueData !endswith @".rbf /restore"
// create NormalizedRegistryValueData field
| extend NormalizedRegistryValueData = replace(@'(C|D):\\Users\\.*?\\', @'C:\\Users\\userxx\\',RegistryValueData )
| extend NormalizedRegistryValueData = replace(@'\{.*\}', @'\{xxxxxxxxxx\}',NormalizedRegistryValueData ) //{fe07d7-d438-4dd9-bb0f-5721658f4f}
| extend NormalizedRegistryValueData = replace(@'\\[A-Za-z0-9-]+-[A-Za-z0-9]+\\', @'\\xxxxxxxxxx\\',NormalizedRegistryValueData ) //\fe07d7-d438-4dd9-bb0f-5721658f4f\
| extend NormalizedRegistryValueData = replace(@'\d+\.\d+\.\d+\.\d+', @'X.Y.Z.T',NormalizedRegistryValueData )
| extend NormalizedRegistryValueData = replace(@'-\d+\.\d+\.\d+', @'-X.Y.Z',NormalizedRegistryValueData )
| extend NormalizedRegistryValueData = replace(@'_\d+\.log', @'_XYZT.log',NormalizedRegistryValueData )
| extend NormalizedRegistryValueData = replace(@'--quiet|--passive', @'',NormalizedRegistryValueData )
| extend NormalizedRegistryValueData = replace(@'installSessionId\s[A-Za-z0-9-]+', @'installSessionId xxxxxx',NormalizedRegistryValueData )
| extend NormalizedRegistryValueData = replace(@'C:\\ProgramData\\.*?\\Microsoft\\Teams\\',@'C:\\ProgramData\\userxxx\\Microsoft\\Teams\\',NormalizedRegistryValueData)
);
dataset
| summarize dcount_device = dcount(DeviceId), total_count = count() by NormalizedRegistryValueData
| where dcount_device <=5 and total_count <20
| join kind=inner (dataset| where Timestamp > ago(1d)) on NormalizedRegistryValueData
| invoke FileProfile(InitiatingProcessSHA1,1000)
| where GlobalPrevalence <100
or isempty(GlobalPrevalence)
// inlcude processes that are involved in malicious attacks(e.g. office macro creating the registry key)
or InitiatingProcessFileName in~ ("powershell.exe","reg.exe", "regedit.exe", "cmd.exe","winword.exe","excel.exe","powerpnt.exe")
Stages and Predicates
let dataset is inlined into the numbered stages below.
Stage 1: source
let dataset
Stage 2: source
DeviceRegistryEvents
Stage 3: where
where ActionType == "RegistryValueSet"
Stage 4: where
where ((RegistryKey endswith "InprocServer32" and RegistryKey has @"SOFTWARE\Classes\CLSID" and isempty(RegistryValueName)) or (RegistryKey has @"Microsoft\Windows NT\CurrentVersion\Windows" and RegistryValueName == "load") or (RegistryKey has @"Microsoft\Windows NT\CurrentVersion\Winlogon" and RegistryValueName == "Shell") or (RegistryKey has @"Microsoft\Windows NT\CurrentVersion\Winlogon" and RegistryValueName == "Userinit") or (RegistryKey has @"Software\Microsoft\Windows\CurrentVersion\Policies\Explorer" and RegistryValueName == "Run") or (RegistryKey has @"\Session Manager" and RegistryValueName == "BootExecute") or RegistryKey has @"Microsoft\Windows NT\CurrentVersion\Winlogon\Notify" or RegistryKey has @"Microsoft\Windows\CurrentVersion\Run" or RegistryKey has @"Microsoft\Windows\CurrentVersion\RunOnce" or RegistryKey has @"Microsoft\Windows\CurrentVersion\RunOnceEx" or RegistryKey has @"\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders" or RegistryKey has @"\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders" or RegistryKey has @"\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer\Run" or RegistryKey has @"\Software\Microsoft\Windows\CurrentVersion\RunServices" or RegistryKey has @"\Software\Microsoft\Windows\CurrentVersion\RunServicesOnce")
Stage 5: where
where not (RegistryValueData endswith ".rbf /restore")
Stage 6: extend (9 consecutive steps)
extend NormalizedRegistryValueData
Stage 7: summarize
summarize dcount_device, total_count by NormalizedRegistryValueData
Stage 8: where
where dcount_device <= 5 and total_count < 20
Stage 9: join
join kind=inner (dataset) on NormalizedRegistryValueData
Stage 10: invoke
invoke
Stage 11: where
where (isempty(GlobalPrevalence) or GlobalPrevalence < 100 or InitiatingProcessFileName in~ ("cmd.exe", "excel.exe", "powerpnt.exe", "powershell.exe", "reg.exe", "regedit.exe", "winword.exe"))
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ActionType | eq |
| field:"ActionType" kind:eq value:"RegistryValueSet" |
GlobalPrevalence | is_null | field:"GlobalPrevalence" kind:is_null | |
GlobalPrevalence | lt |
| field:"GlobalPrevalence" kind:lt value:"100" |
InitiatingProcessFileName | in |
| field:"InitiatingProcessFileName" kind:in |
RegistryKey | ends_with |
| field:"RegistryKey" kind:ends_with value:"InprocServer32" |
RegistryKey | match |
| field:"RegistryKey" kind:match |
RegistryValueName | eq |
| field:"RegistryValueName" kind:eq |
RegistryValueName | is_null | field:"RegistryValueName" kind:is_null | |
dcount_device | le |
| field:"dcount_device" kind:le value:"5" |
total_count | lt |
| field:"total_count" kind:lt value:"20" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
RegistryValueData | ends_with | .rbf /restore | excludes:RegistryValueData field:"RegistryValueData" value:".rbf /restore" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
NormalizedRegistryValueData | summarize |
dcount_device | summarize |
total_count | summarize |
SAP BTP - User added to Cloud Identity Service privileged Administrators list
#Identifies when a user is granted privileged administrator permissions in SAP Cloud Identity Service. These permissions include managing Identity Providers, Service Providers, Users, Groups, and Access controls.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Privilege Escalation | |
| Persistence | |
| Lateral Movement |
Rule Body
id: 7d4e9f2a-8b1c-4a5d-9e3f-6c2b1a0d8e7f
kind: Scheduled
name: SAP BTP - User added to Cloud Identity Service privileged Administrators list
description: Identifies when a user is granted privileged administrator permissions
in SAP Cloud Identity Service. These permissions include managing Identity Providers,
Service Providers, Users, Groups, and Access controls.
severity: High
status: Available
requiredDataConnectors:
- connectorId: SAPBTPAuditEvents
dataTypes:
- SAPBTPAuditLog_CL
queryFrequency: 15m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
tactics:
- LateralMovement
- PrivilegeEscalation
relevantTechniques:
- T0859
- T1078
query: |
let monitored_permissions = dynamic(["ManageIdP", "ManageSP", "ManageUsers", "ReadUsers", "ManageAccess", "ManageGroups"]);
SAPBTPAuditLog_CL
| extend data_s = tostring(Message.data)
| extend action = extract(@"action=""([^""]+)""", 1, data_s),
state = extract(@"state=""([^""]+)""", 1, data_s),
changedAttribute = extract(@"changedAttribute=""([^""]+)""", 1, data_s),
newValue = extract(@"newValue=""([^""]+)""", 1, data_s),
targetUser = extract(@"userIdentifier=""([^""]+)""", 1, data_s),
callerMail = extract(@"callerMail=""([^""]+)""", 1, data_s),
ipAddress = extract(@"ipAddress=""([^""]+)""", 1, data_s)
| where action == "grantPermissions"
| where state == "successful"
| where changedAttribute == "authorizations"
| where isnotempty(newValue)
| mv-expand permission = monitored_permissions
| where newValue contains tostring(permission)
| summarize
GrantedPermissions = make_set(newValue, 10),
MatchedPermissions = make_set(tostring(permission), 10)
by UpdatedOn, UserName, callerMail, targetUser, Tenant, ipAddress
| project UpdatedOn, UserName, callerMail, targetUser, GrantedPermissions, MatchedPermissions, Tenant, ipAddress, CloudApp = "SAP Cloud Identity Service"
| extend AccountName = split(UserName, "@")[0], UPNSuffix = split(UserName, "@")[1]
eventGroupingSettings:
aggregationKind: SingleAlert
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: CloudApplication
fieldMappings:
- identifier: Name
columnName: CloudApp
- entityType: IP
fieldMappings:
- identifier: Address
columnName: ipAddress
version: 1.0.1
Stages and Predicates
Parameters
let monitored_permissions = dynamic(["ManageIdP", "ManageSP", "ManageUsers", "ReadUsers", "ManageAccess", "ManageGroups"]);
Stage 1: source
SAPBTPAuditLog_CL
Stage 2: extend
| extend data_s = tostring(Message.data)
Stage 3: extend
| extend action = extract(@"action=""([^""]+)""", 1, data_s),
state = extract(@"state=""([^""]+)""", 1, data_s),
changedAttribute = extract(@"changedAttribute=""([^""]+)""", 1, data_s),
newValue = extract(@"newValue=""([^""]+)""", 1, data_s),
targetUser = extract(@"userIdentifier=""([^""]+)""", 1, data_s),
callerMail = extract(@"callerMail=""([^""]+)""", 1, data_s),
ipAddress = extract(@"ipAddress=""([^""]+)""", 1, data_s)
Stage 4: where
| where action == "grantPermissions"
Stage 5: where
| where state == "successful"
Stage 6: where
| where changedAttribute == "authorizations"
Stage 7: where
| where isnotempty(newValue)
Stage 8: mv-expand
| mv-expand permission = monitored_permissions
Stage 9: where
| where newValue contains tostring(permission)
Stage 10: summarize
| summarize
GrantedPermissions = make_set(newValue, 10),
MatchedPermissions = make_set(tostring(permission), 10)
by UpdatedOn, UserName, callerMail, targetUser, Tenant, ipAddress
Stage 11: project
| project UpdatedOn, UserName, callerMail, targetUser, GrantedPermissions, MatchedPermissions, Tenant, ipAddress, CloudApp = "SAP Cloud Identity Service"
Stage 12: extend
| extend AccountName = split(UserName, "@")[0], UPNSuffix = split(UserName, "@")[1]
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
action | eq |
| field:"action" kind:eq value:"grantPermissions" |
changedAttribute | eq |
| field:"changedAttribute" kind:eq value:"authorizations" |
newValue | is_not_null | field:"azure_ad::modified_properties_new" kind:is_not_null | |
state | eq |
| field:"state" kind:eq value:"successful" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
CloudApp | project |
GrantedPermissions | project |
MatchedPermissions | project |
Tenant | project |
UpdatedOn | project |
UserName | project |
callerMail | project |
ipAddress | project |
targetUser | project |
AccountName | extend |
UPNSuffix | extend |
SAST Auth Finding Correlated with Brute Force
#Detects applications with open SAST authentication findings that are simultaneously experiencing brute force login attempts in Azure AD. Correlates StratoSecure SAST findings with SigninLogs failure spikes.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Credential Access |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Entra ID | SigninLogs sign-in event: Sign-in record (SigninLogs, any result) |
Rule Body
id: 4ae9f294-410d-4c11-9072-0d8fcf5162d8
name: SAST Auth Finding Correlated with Brute Force
description: |
Detects applications with open SAST authentication findings that are
simultaneously experiencing brute force login attempts in Azure AD.
Correlates StratoSecure SAST findings with SigninLogs failure spikes.
severity: High
status: Available
requiredDataConnectors:
- connectorId: StratoSecurePush
dataTypes:
- StratoSecure_Findings_CL
queryFrequency: 1h
queryPeriod: 75m
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
- InitialAccess
relevantTechniques:
- T1110
- T1078
query: |
let lookback = 1h;
let bruteForceThreshold = 20;
let AuthFindings = StratoSecure_Findings_CL
| where TimeGenerated >= ago(lookback)
| where FixStatus =~ "open"
| where Severity in ("Critical", "High")
| where RuleId has_any ("auth", "jwt", "login", "session", "token", "password", "credential")
| summarize FindingCount = count(), RuleIds = make_set(RuleId), Repos = make_set(Repository)
by ApplicationId, StraTenantId;
let BruteForce = SigninLogs
| where TimeGenerated >= ago(lookback)
| where ResultType != "0"
| summarize FailedAttempts = count(), DistinctUsers = dcount(UserPrincipalName),
IPList = make_set(IPAddress, 20), FirstAttempt = min(TimeGenerated), LastAttempt = max(TimeGenerated)
by AppId, AppDisplayName
| where FailedAttempts >= bruteForceThreshold;
AuthFindings
| join kind=inner BruteForce on $left.ApplicationId == $right.AppId
| project TimeGenerated = LastAttempt, ApplicationId, AppDisplayName, StraTenantId,
FindingCount, RuleIds, Repos, FailedAttempts, DistinctUsers, IPList
entityMappings:
- entityType: CloudApplication
fieldMappings:
- identifier: AppId
columnName: ApplicationId
version: 1.0.0
kind: Scheduled
Stages and Predicates
Parameters
let lookback = 1h;
let bruteForceThreshold = 20;
let AuthFindings is inlined into the numbered stages below.
Let Binding: BruteForce
let BruteForce = SigninLogs
| where TimeGenerated >= ago(lookback)
| where ResultType != "0"
| summarize FailedAttempts = count(), DistinctUsers = dcount(UserPrincipalName),
IPList = make_set(IPAddress, 20), FirstAttempt = min(TimeGenerated), LastAttempt = max(TimeGenerated)
by AppId, AppDisplayName
| where FailedAttempts >= bruteForceThreshold;
Stages 1 to 6 define let AuthFindings as the rule's main pipeline source. Stages 7 to 8 use it.
Stage 1: source
StratoSecure_Findings_CL
Stage 2: where
| where TimeGenerated >= ago(lookback)
Stage 3: where
| where FixStatus =~ "open"
Stage 4: where
| where Severity in ("Critical", "High")
Stage 5: where
| where RuleId has_any ("auth", "jwt", "login", "session", "token", "password", "credential")
Stage 6: summarize
| summarize FindingCount = count(), RuleIds = make_set(RuleId), Repos = make_set(Repository)
by ApplicationId, StraTenantId
Stage 7: join
AuthFindings
| join kind=inner BruteForce on $left.ApplicationId == $right.AppId
Stage 8: project
project AppDisplayName, ApplicationId, DistinctUsers, FailedAttempts, FindingCount, IPList, Repos, RuleIds, StraTenantId, TimeGenerated
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
FailedAttempts | ge |
| field:"FailedAttempts" kind:ge value:"20" |
FixStatus | eq |
| field:"FixStatus" kind:eq value:"open" |
ResultType | ne |
| field:"ResultType" kind:ne value:"0" |
RuleId | match |
| field:"RuleId" kind:match |
Severity | in |
| field:"Severity" kind:in |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AppDisplayName | project |
ApplicationId | project |
DistinctUsers | project |
FailedAttempts | project |
FindingCount | project |
IPList | project |
Repos | project |
RuleIds | project |
StraTenantId | project |
TimeGenerated | project |