Detection rules › Kusto
Kusto rules: 1m
BloodHound Attack Path Finding - Get Secrets on Tier Zero Key Vault
#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 - Get Secrets on Tier Zero Key Vault
Rule Body
id: cd3e607c-8ab4-4799-a27e-7ff63ae401af
name: BloodHound Attack Path Finding - Get Secrets on Tier Zero Key Vault
description: BloodHound Attack Path Finding - Get Secrets on Tier Zero Key Vault
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 == "AzureT0GetSecrets"
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 == "AzureT0GetSecrets"
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Finding | eq |
| field:"Finding" kind:eq value:"AzureT0GetSecrets" |
Cisco Cloud Security - Connection to Unpopular Website Detected
#'Detects first connection to an unpopular website (possible malicious payload delivery).'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control | |
| Exfiltration |
Rule Body
id: 75297f62-10a8-4fc1-9b2a-12f25c6f05a7
name: Cisco Cloud Security - Connection to Unpopular Website Detected
description: |
'Detects first connection to an unpopular website (possible malicious payload delivery).'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: CiscoUmbrellaDataConnector
dataTypes:
- Cisco_Umbrella_proxy_CL
queryFrequency: 1d
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- CommandAndControl
- Exfiltration
relevantTechniques:
- T1071.001
- T1041
query: |
let top_million_list = Cisco_Umbrella
| where EventType == "proxylogs"
| extend Hostname = parse_url(UrlOriginal)["Host"]
| summarize count() by tostring(Hostname)
| top 1000000 by count_
| summarize make_list(Hostname);
Cisco_Umbrella
| where EventType == "proxylogs"
| extend Hostname = parse_url(UrlOriginal)["Host"]
| where Hostname !in (top_million_list)
| extend Message = "Connect to unpopular website (possible malicious payload delivery)"
| project TimeGenerated, Message, SrcIpAddr, DstIpAddr, UrlOriginal
entityMappings:
- entityType: URL
fieldMappings:
- identifier: Url
columnName: UrlOriginal
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIpAddr
version: 1.1.3
kind: Scheduled
Stages and Predicates
Let Binding: top_million_list
let top_million_list = Cisco_Umbrella
| where EventType == "proxylogs"
| extend Hostname = parse_url(UrlOriginal)["Host"]
| summarize count() by tostring(Hostname)
| top 1000000 by count_
| summarize make_list(Hostname);
Stage 1: source
Cisco_Umbrella
Stage 2: where
| where EventType == "proxylogs"
Stage 3: extend
| extend Hostname = parse_url(UrlOriginal)["Host"]
Stage 4: where
| where Hostname !in (top_million_list)
Stage 5: extend
| extend Message = "Connect to unpopular website (possible malicious payload delivery)"
Stage 6: project
| project TimeGenerated, Message, SrcIpAddr, DstIpAddr, UrlOriginal
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventType | eq |
| field:"EventType" kind:eq value:"proxylogs" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
Hostname | eq | top_million_list | excludes:Hostname field:"Hostname" value:"top_million_list" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DstIpAddr | project |
Message | project |
SrcIpAddr | project |
TimeGenerated | project |
UrlOriginal | project |
Component Object Model Hijacking - Vault7 trick
#This detection looks for the very specific value of "Attribute" in the "ShellFolder" CLSID of a COM object. This value (0xf090013d) seems to only link back to this specific persistence method. The blog post linked here (https://www.ired.team/offensive-security/code-execution/forcing-iexplore.exe-to-load-a-malicious-dll-via-com-abuse) provides more background on the meaning of this value.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| Privilege Escalation |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Microsoft Defender for Endpoint | DeviceRegistryEvents action RegistryValueSet: Registry value set |
Rule Body
id: 1aaff41f-4e18-45b1-bb34-de6eb4943cf2
name: Component Object Model Hijacking - Vault7 trick
description: |
This detection looks for the very specific value of "Attribute" in the "ShellFolder" CLSID of a COM object. This value (0xf090013d) seems to only link back to this specific persistence method.
The blog post linked here (https://www.ired.team/offensive-security/code-execution/forcing-iexplore.exe-to-load-a-malicious-dll-via-com-abuse) provides more background on the meaning of this value.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: MicrosoftThreatProtection
dataTypes:
- DeviceRegistryEvents
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
- PrivilegeEscalation
relevantTechniques:
- T1546.015
query: |
DeviceRegistryEvents
| where RegistryKey has "ShellFolder" and ActionType =~ "RegistryValueSet" and RegistryValueName =~ "Attributes"
// toint automatically converts base10 and base16 strings to int toint("0xFF") == toint("255") == int(0xFF) == int(255).
// We are using >= to make sure that if someone adds an additional flag to this field, it doesn't bypass this hunt.
// Removing any flag will bypass this hunt, but more research is needed to understand which of the flag values are relevant.
| where toint(RegistryValueData) >= int(0xf090013d)
//UPDATE: Flag details are here: https://medium.com/falconforce/introducing-falcon-friday-1f972d65ce1b?source=friends_link&sk=e48d9cb974e216cc4b9b61945a3b177d
entityMappings:
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: DeviceName
- entityType: Account
fieldMappings:
- identifier: Sid
columnName: AccountSid
- identifier: Name
columnName: AccountName
- identifier: NTDomain
columnName: AccountDomain
- entityType: Process
fieldMappings:
- identifier: CommandLine
columnName: ProcessCommandLine
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
DeviceRegistryEvents
Stage 2: where
| where RegistryKey has "ShellFolder" and ActionType =~ "RegistryValueSet" and RegistryValueName =~ "Attributes"
Stage 3: where
| where toint(RegistryValueData) >= int(0xf090013d)
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ActionType | eq |
| field:"EventType" kind:eq value:"RegistryValueSet" |
RegistryKey | match |
| field:"TargetObject" kind:match value:"ShellFolder" |
RegistryValueData | ge |
| field:"Details" kind:ge value:"4035969341" |
RegistryValueName | eq |
| field:"RegistryValueName" kind:eq value:"Attributes" |
CTE - Stale Device (No Heartbeat in 30 Days)
#A device is still marked active in the Devices table but has not sent a heartbeat in 30+ days. Emitted once per device by the daily tenant health-check job (api/src/functions/tenantHealthChecks.js); clears automatically if the device checks in again, so it can re-fire after a later gap.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Defense Impairment |
Rule Body
id: a6d51bc1-bf51-47d1-9885-b81762cd6306
name: CTE - Stale Device (No Heartbeat in 30 Days)
description: |
A device is still marked active in the Devices table but has not sent a heartbeat in 30+ days. Emitted once per device by the daily tenant health-check job (api/src/functions/tenantHealthChecks.js); clears automatically if the device checks in again, so it can re-fire after a later gap.
severity: Medium
requiredDataConnectors:
- connectorId: cybereinforce_cte
dataTypes:
- CybereinforceCTE_CL
queryFrequency: 24h
queryPeriod: 2d
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
relevantTechniques:
- T1562
query: |
CybereinforceCTE_CL
| where EventType == "device.stale_heartbeat"
| extend d = parse_json(Details)
| extend
DeviceId = tostring(d.deviceId),
HostName = iff(isnotempty(tostring(d.hostname)), tostring(d.hostname), strcat("cte-device-", substring(EntityId, 0, 8))),
LastSeen = tostring(d.lastSeen)
| project TimeGenerated, TenantId, DeviceId, HostName, LastSeen, Severity, Category, Source
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: HostName
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
CybereinforceCTE_CL
Stage 2: where
| where EventType == "device.stale_heartbeat"
Stage 3: extend
| extend d = parse_json(Details)
Stage 4: extend
| extend
DeviceId = tostring(d.deviceId),
HostName = iff(isnotempty(tostring(d.hostname)), tostring(d.hostname), strcat("cte-device-", substring(EntityId, 0, 8))),
LastSeen = tostring(d.lastSeen)
HostName =isnotempty(tostring(d.hostname))tostring(d.hostname)strcat("cte-device-", substring(EntityId, 0, 8))Stage 5: project
| project TimeGenerated, TenantId, DeviceId, HostName, LastSeen, Severity, Category, Source
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventType | eq |
| field:"EventType" kind:eq value:"device.stale_heartbeat" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Category | project |
DeviceId | project |
HostName | project |
LastSeen | project |
Severity | project |
Source | project |
TenantId | project |
TimeGenerated | project |
Cyren High-Risk URL Indicators
#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.
'Detects high-risk URL indicators (risk score >= 80) from Cyren malware URL threat intelligence feeds in the last 24 hours. These URLs are associated with malware distribution, phishing campaigns, or other malicious content hosting.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: 6e8f9c4b-2a3b-5c6d-0e1f-d2e3f4a5b6c7
name: Cyren High-Risk URL Indicators
description: |
'Detects high-risk URL indicators (risk score >= 80) from Cyren malware URL threat intelligence feeds in the last 24 hours.
These URLs are associated with malware distribution, phishing campaigns, or other malicious content hosting.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: CyrenThreatIntel
dataTypes:
- Cyren_Indicators_CL
queryFrequency: 1h
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
- Execution
relevantTechniques:
- T1566
- T1189
query: |
Cyren_Indicators_CL
| where TimeGenerated > ago(1d)
| where isnotempty(url_s)
| extend Risk = toint(risk_d)
| where Risk >= 80
| summarize
DetectionCount = count(),
MaxRisk = max(Risk),
Categories = make_set(category_s)
by URL = url_s, Domain = domain_s, Source = source_s
| where DetectionCount >= 1
| extend
MaliciousURL = URL,
ThreatCategories = strcat_array(Categories, ", ")
suppressionDuration: 1h
suppressionEnabled: false
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 1d
matchingMethod: Selected
groupByEntities:
- URL
eventGroupingSettings:
aggregationKind: AlertPerResult
customDetails:
RiskScore: MaxRisk
DetectionCount: DetectionCount
Categories: ThreatCategories
Domain: Domain
Source: Source
entityMappings:
- entityType: URL
fieldMappings:
- identifier: Url
columnName: MaliciousURL
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
Cyren_Indicators_CL
Stage 2: where
| where TimeGenerated > ago(1d)
Stage 3: where
| where isnotempty(url_s)
Stage 4: extend
| extend Risk = toint(risk_d)
Stage 5: where
| where Risk >= 80
Stage 6: summarize
| summarize
DetectionCount = count(),
MaxRisk = max(Risk),
Categories = make_set(category_s)
by URL = url_s, Domain = domain_s, Source = source_s
Stage 7: where
| where DetectionCount >= 1
Stage 8: extend
| extend
MaliciousURL = URL,
ThreatCategories = strcat_array(Categories, ", ")
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DetectionCount | ge |
| field:"DetectionCount" kind:ge value:"1" |
Risk | ge |
| field:"Risk" kind:ge value:"80" |
url_s | is_not_null | field:"url_s" kind:is_not_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Categories | summarize |
DetectionCount | summarize |
Domain | summarize |
MaxRisk | summarize |
Source | summarize |
URL | summarize |
MaliciousURL | extend |
ThreatCategories | extend |
LaZagne Credential Theft
#LaZagne is a popular open-source tool used to recover passwords stored on a local computer. It has been used in ransomware attacks to steal credentials and escalate privileges. This query looks for the execution of LaZagne.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Microsoft Defender for Endpoint | DeviceProcessEvents action any: Process activity |
Rule Body
id: 7d0d3050-8dac-4b83-bfae-902f7dc0c21c
name: LaZagne Credential Theft
description: |
LaZagne is a popular open-source tool used to recover passwords stored on a local computer. It has been used in ransomware attacks to steal credentials and escalate privileges. This query looks for the execution of LaZagne.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: MicrosoftThreatProtection
dataTypes:
- DeviceProcessEvents
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
relevantTechniques:
- T1003
query: |
DeviceProcessEvents
| where FileName =~ 'reg.exe'
| where ProcessCommandLine has_all('save','hklm','sam')
| project DeviceId, DeviceName, TimeGenerated, InitiatingProcessId, InitiatingProcessFileName, ProcessId, FileName, ProcessCommandLine
| extend HostName = iff(DeviceName has '.', substring(DeviceName, 0, indexof(DeviceName, '.')), DeviceName)
| extend DnsDomain = iff(DeviceName has '.', substring(DeviceName, indexof(DeviceName, '.') + 1), "")
entityMappings:
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: DeviceName
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: DnsDomain
- entityType: Process
fieldMappings:
- identifier: ProcessId
columnName: ProcessId
- identifier: CommandLine
columnName: ProcessCommandLine
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
DeviceProcessEvents
Stage 2: where
| where FileName =~ 'reg.exe'
Stage 3: where
| where ProcessCommandLine has_all('save','hklm','sam')
Stage 4: project
| project DeviceId, DeviceName, TimeGenerated, InitiatingProcessId, InitiatingProcessFileName, ProcessId, FileName, ProcessCommandLine
Stage 5: extend
| extend HostName = iff(DeviceName has '.', substring(DeviceName, 0, indexof(DeviceName, '.')), DeviceName)
HostName =DeviceName has "."substring(DeviceName, 0, indexof(DeviceName, '.'))DeviceNameStage 6: extend
| extend DnsDomain = iff(DeviceName has '.', substring(DeviceName, indexof(DeviceName, '.') + 1), "")
DnsDomain =DeviceName has "."substring(DeviceName, (indexof(DeviceName, '.') + 1))""Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
FileName | eq |
| field:"process_basename" kind:eq value:"reg.exe" |
ProcessCommandLine | match |
| field:"CommandLine" kind:match |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DeviceId | project |
DeviceName | project |
FileName | project |
InitiatingProcessFileName | project |
InitiatingProcessId | project |
ProcessCommandLine | project |
ProcessId | project |
TimeGenerated | project |
HostName | extend |
DnsDomain | extend |
Lookout - New Threat events found.
#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.
'Created to detect new Threat events from the data which is recently synced by Lookout Solution.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Discovery |
Rule Body
id: 7593cc60-e294-402d-9202-279fb3c7d55f
name: Lookout - New Threat events found.
description: |
'Created to detect new Threat events from the data which is recently synced by Lookout Solution.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: LookoutAPI
dataTypes:
- Lookout_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Discovery
relevantTechniques:
- T1057
query: |
Lookout_CL
| where details_action_s == 'DETECTED' and type_s == 'THREAT'
| extend DetailsPackageName = details_packageName_s
| extend TargetPlatform = target_platform_s
| extend TargetOsVersion = target_osVersion_s
| extend Type = type_s
| extend Severity = details_severity_s
| extend Classifications = details_classifications_s
| extend Platform = target_platform_s
entityMappings:
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: DetailsPackageName
- identifier: OSFamily
columnName: TargetPlatform
- identifier: OSVersion
columnName: TargetOsVersion
customDetails:
Type: Type
Severity: Severity
Classification: Classifications
Platform: Platform
version: 1.0.1
kind: Scheduled
Stages and Predicates
Stage 1: source
Lookout_CL
Stage 2: where
| where details_action_s == 'DETECTED' and type_s == 'THREAT'
Stage 3: extend (7 consecutive steps)
| extend DetailsPackageName = details_packageName_s
| extend TargetPlatform = target_platform_s
| extend TargetOsVersion = target_osVersion_s
| extend Type = type_s
| extend Severity = details_severity_s
| extend Classifications = details_classifications_s
| extend Platform = target_platform_s
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
details_action_s | eq |
| field:"details_action_s" kind:eq value:"DETECTED" |
type_s | eq |
| field:"type_s" kind:eq value:"THREAT" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DetailsPackageName | extend |
TargetPlatform | extend |
TargetOsVersion | extend |
Type | extend |
Severity | extend |
Classifications | extend |
Platform | extend |
New country signIn with correct password
#Identifies an interrupted sign-in session from a country the user has not sign-in before in the last 7 days, where the password was correct. Although the session is interrupted by other controls such as multi factor authentication or conditional access policies, the user credentials should be reset due to logs indicating a correct password was observed during sign-in.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Credential Access |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| 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: 7808c05a-3afd-4d13-998a-a59e2297693f
name: New country signIn with correct password
description: |
'Identifies an interrupted sign-in session from a country the user has not sign-in before in the last 7 days, where the password was correct. Although the session is interrupted by other controls such as multi factor authentication or conditional access policies, the user credentials should be reset due to logs indicating a correct password was observed during sign-in.'
severity: Medium
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- SigninLogs
queryFrequency: 1d
queryPeriod: 7d
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
- CredentialAccess
relevantTechniques:
- T1078
- T1110
query: |
// Creating a list of successful sign-in by users in the last 7 days.
let KnownUserCountry = (
SigninLogs
| where TimeGenerated between (ago(7d) .. ago(1d) )
| where ResultType == 0
| summarize KnownCountry = make_set(Location,1048576) by UserPrincipalName
);
// Identify sign-ins that are no successful but have the auth details indicating a correct password.
SigninLogs
| where TimeGenerated >= ago(1d)
| where ResultType != 0
| extend ParseAuth = parse_json(AuthenticationDetails)
| extend AuthMethod = tostring(ParseAuth.[0].authenticationMethod),
PasswordResult = tostring(ParseAuth.[0].authenticationStepResultDetail),
AuthSucceeded = tostring(ParseAuth.[0].succeeded)
| where PasswordResult == "Correct Password" or AuthSucceeded == "true"
| where AuthMethod == "Password"
| extend failureReason = tostring(Status.failureReason)
| summarize NewCountry = make_set(Location,1048576), LastObservedTime = max(TimeGenerated), AppName = make_set(AppDisplayName,1048576) by UserPrincipalName, PasswordResult, AuthSucceeded, failureReason
// Combining both tables by user
| join kind=inner KnownUserCountry on UserPrincipalName
// Compare both arrays and identify if the country has been observed in the past.
| extend CountryDiff = set_difference(NewCountry,KnownCountry)
| extend CountryDiffCount = array_length(CountryDiff)
// Count the new column to only alert if there is a difference between both arrays
| where CountryDiffCount != 0
| extend NewCountryEvent = CountryDiff
// Getting UserName and Domain
| extend Name = split(UserPrincipalName,"@",0),
Domain = split(UserPrincipalName,"@",1)
| mv-expand Name,Domain
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: UserPrincipalName
- identifier: Name
columnName: Name
- identifier: NTDomain
columnName: Domain
customDetails:
LastObservedTime: LastObservedTime
AppName: AppName
NewCountryEvent: NewCountryEvent
PasswordResult: PasswordResult
AuthSucceeded: AuthSucceeded
failureReason: failureReason
eventGroupingSettings:
aggregationKind: SingleAlert
version: 1.0.2
kind: Scheduled
metadata:
source:
kind: Community
author:
name: Juanse
support:
tier: Community
categories:
domains: [ "Identity", "Security - Threat Protection" ]
Stages and Predicates
Let Binding: KnownUserCountry
let KnownUserCountry = (
SigninLogs
| where TimeGenerated between (ago(7d) .. ago(1d) )
| where ResultType == 0
| summarize KnownCountry = make_set(Location,1048576) by UserPrincipalName
);
Stage 1: source
SigninLogs
Stage 2: where
| where TimeGenerated >= ago(1d)
Stage 3: where
| where ResultType != 0
Stage 4: extend
| extend ParseAuth = parse_json(AuthenticationDetails)
Stage 5: extend
| extend AuthMethod = tostring(ParseAuth.[0].authenticationMethod),
PasswordResult = tostring(ParseAuth.[0].authenticationStepResultDetail),
AuthSucceeded = tostring(ParseAuth.[0].succeeded)
Stage 6: where
| where PasswordResult == "Correct Password" or AuthSucceeded == "true"
Stage 7: where
| where AuthMethod == "Password"
Stage 8: extend
| extend failureReason = tostring(Status.failureReason)
Stage 9: summarize
| summarize NewCountry = make_set(Location,1048576), LastObservedTime = max(TimeGenerated), AppName = make_set(AppDisplayName,1048576) by UserPrincipalName, PasswordResult, AuthSucceeded, failureReason
Stage 10: join
| join kind=inner KnownUserCountry on UserPrincipalName
Stage 11: extend
extend CountryDiff
Stage 12: extend
extend CountryDiffCount
Stage 13: where
where CountryDiffCount != 0
Stage 14: extend
extend NewCountryEvent
Stage 15: extend
extend Domain, Name
Stage 16: mv-expand
mv-expand Name
Indicators
These rows show field, operator, and value matches.
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AppName | summarize |
AuthSucceeded | summarize |
LastObservedTime | summarize |
NewCountry | summarize |
PasswordResult | summarize |
UserPrincipalName | summarize |
failureReason | summarize |
CountryDiff | extend |
CountryDiffCount | extend |
NewCountryEvent | extend |
Domain | extend |
Name | extend |
New Device/Location sign-in along with critical operation
#This query identifies users seen login from a new geo location/country and a new device, then correlates that sign-in with successful risky operations such as policy changes, MFA changes, API token actions etc. This can be an indication of an attacker gaining access to a user's credentials and then performing critical operations, which typically requires admin privileges. By detecting such patterns, organizations can quickly respond to potential security incidents and mitigate risks associated with unauthorized access and privilege escalation.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Persistence | |
| Privilege Escalation |
Telemetry Coverage
Rules Detecting the Same Action
These rules filter on the same operation.
- Attempt to Create Okta API Token (Elastic)
- Attempt to Deactivate an Okta Policy (Elastic)
- Attempt to Deactivate an Okta Policy Rule (Elastic)
- Attempt to Delete an Okta Policy (Elastic)
- Attempt to Delete an Okta Policy Rule (Elastic)
- Attempt to Modify an Okta Network Zone (Elastic)
- Attempt to Modify an Okta Policy (Elastic)
- Attempt to Modify an Okta Policy Rule (Elastic)
Rule Body
id: 41e843a8-92e7-444d-8d72-638f1145d1e1
name: New Device/Location sign-in along with critical operation
description: |
This query identifies users seen login from a new geo location/country and a new device, then correlates that sign-in with successful risky operations such as policy changes, MFA changes, API token actions etc. This can be an indication of an attacker gaining access to a user's credentials and then performing critical operations, which typically requires admin privileges. By detecting such patterns, organizations can quickly respond to potential security incidents and mitigate risks associated with unauthorized access and privilege escalation.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: OktaSSO
dataTypes:
- Okta_CL
- connectorId: OktaSSOv2
dataTypes:
- OktaSSO
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
- Persistence
- PrivilegeEscalation
relevantTechniques:
- T1078
- T1556
- T1098
query: |
let timeframe = 1h;
let RiskyOperations = dynamic(["policy.rule.update","policy.rule.create","policy.rule.delete", "policy.rule.deactivate", "policy.lifecycle.update", "policy.rule.modify", "policy.lifecycle.create", "policy.lifecycle.delete", "policy.lifecycle.deactivate", "policy.lifecycle.modify", "network_zone.rule.disabled", "system.api_token.create", "system.api_token.revoke", "application.policy.sign_on.update", "application.policy.sign_on.rule.delete","user.mfa.factor.deactivate", "user.mfa.factor.reset_all", "system.mfa.factor.deactivate", "user.mfa.attempt_bypass"]);
let UserLoginNewCountryDevice = OktaSSO
| where eventType_s == "user.session.start"
| where outcome_result_s == "SUCCESS"
| extend debugContext_debugData_logOnlySecurityData_s = column_ifexists('debugContext_debugData_logOnlySecurityData_s', '{}')
| extend behaviors = parse_json(tostring(parse_json(debugContext_debugData_logOnlySecurityData_s).behaviors))
| where tostring(behaviors.["New Country"]) == "POSITIVE"
| where tostring(behaviors.["New Geo-Location"]) == "POSITIVE"
| where tostring(behaviors.["New Device"]) == "POSITIVE"
| summarize by timekey = bin(TimeGenerated, timeframe), actor_alternateId_s, actor_displayName_s, client_userAgent_os_s, client_userAgent_browser_s, client_device_s, client_userAgent_rawUserAgent_s, client_ipAddress_s, authenticationContext_externalSessionId_s, client_geographicalContext_country_s, client_geographicalContext_city_s, displayMessage_s, outcome_result_s, outcome_reason_s, column_ifexists('debugContext_debugData_logOnlySecurityData_s', ""), column_ifexists('debugContext_debugData_threatSuspected_s', ""), client_geographicalContext_geolocation_lat_d, client_geographicalContext_geolocation_lon_d
| extend Location = strcat(client_geographicalContext_city_s, "-", client_geographicalContext_country_s);
let RiskyOperationsObserved = OktaSSO
| where eventType_s in (RiskyOperations)
| where outcome_result_s == "SUCCESS"
| summarize by timekey = bin(TimeGenerated, timeframe), actor_alternateId_s, actor_displayName_s, client_userAgent_os_s, client_userAgent_browser_s, client_device_s, client_userAgent_rawUserAgent_s, client_ipAddress_s, authenticationContext_externalSessionId_s, client_geographicalContext_country_s, client_geographicalContext_city_s, displayMessage_s, outcome_result_s, outcome_reason_s, column_ifexists('debugContext_debugData_logOnlySecurityData_s', ""), column_ifexists('debugContext_debugData_threatSuspected_s', ""), client_geographicalContext_geolocation_lat_d, client_geographicalContext_geolocation_lon_d;
UserLoginNewCountryDevice
| join kind=inner (RiskyOperationsObserved) on timekey, actor_displayName_s, client_ipAddress_s
| project TimeGenerated = timekey, actor_alternateId_s, actor_displayName_s, client_userAgent_os_s, client_userAgent_browser_s, client_device_s, client_userAgent_rawUserAgent_s, client_ipAddress_s, authenticationContext_externalSessionId_s, client_geographicalContext_country_s, client_geographicalContext_city_s, displayMessage_s, outcome_result_s, outcome_reason_s, column_ifexists('debugContext_debugData_logOnlySecurityData_s', ""), column_ifexists('debugContext_debugData_threatSuspected_s', ""), client_geographicalContext_geolocation_lat_d, client_geographicalContext_geolocation_lon_d, Location
| extend AccountName = tostring(split(actor_alternateId_s, "@")[0]), AccountUPNSuffix = tostring(split(actor_alternateId_s, "@")[1])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: AccountUPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: client_ipAddress_s
customDetails:
ActorDisplayName: actor_displayName_s
ActorDevice: client_device_s
ActorOS: client_userAgent_os_s
ActorBrowser: client_userAgent_browser_s
UserAgent: client_userAgent_rawUserAgent_s
SessionId: authenticationContext_externalSessionId_s
Location: Location
alertDetailsOverride:
alertDisplayNameFormat: 'New Device/Location {{Location}} sign-in along with critical operation'
alertDescriptionFormat: |
This query identifies users seen login from new geo location/country {{Location}} as well as a new device and performing critical operations.
version: 1.1.3
kind: Scheduled
Stages and Predicates
Parameters
let timeframe = 1h;
let UserLoginNewCountryDevice is inlined into the numbered stages below.
Let Binding: RiskyOperations
let RiskyOperations = dynamic(["policy.rule.update","policy.rule.create","policy.rule.delete", "policy.rule.deactivate", "policy.lifecycle.update", "policy.rule.modify", "policy.lifecycle.create", "policy.lifecycle.delete", "policy.lifecycle.deactivate", "policy.lifecycle.modify", "network_zone.rule.disabled", "system.api_token.create", "system.api_token.revoke", "application.policy.sign_on.update", "application.policy.sign_on.rule.delete","user.mfa.factor.deactivate", "user.mfa.factor.reset_all", "system.mfa.factor.deactivate", "user.mfa.attempt_bypass"]);
Let Binding: RiskyOperationsObserved
let RiskyOperationsObserved = OktaSSO
| where eventType_s in (RiskyOperations)
| where outcome_result_s == "SUCCESS"
| summarize by timekey = bin(TimeGenerated, timeframe), actor_alternateId_s, actor_displayName_s, client_userAgent_os_s, client_userAgent_browser_s, client_device_s, client_userAgent_rawUserAgent_s, client_ipAddress_s, authenticationContext_externalSessionId_s, client_geographicalContext_country_s, client_geographicalContext_city_s, displayMessage_s, outcome_result_s, outcome_reason_s, column_ifexists('debugContext_debugData_logOnlySecurityData_s', ""), column_ifexists('debugContext_debugData_threatSuspected_s', ""), client_geographicalContext_geolocation_lat_d, client_geographicalContext_geolocation_lon_d;
Stages 1 to 10 define let UserLoginNewCountryDevice as the rule's main pipeline source. Stages 11 to 13 use it.
Stage 1: source
OktaSSO
Stage 2: where
| where eventType_s == "user.session.start"
Stage 3: where
| where outcome_result_s == "SUCCESS"
Stage 4: extend
| extend debugContext_debugData_logOnlySecurityData_s = column_ifexists('debugContext_debugData_logOnlySecurityData_s', '{}')
Stage 5: extend
| extend behaviors = parse_json(tostring(parse_json(debugContext_debugData_logOnlySecurityData_s).behaviors))
Stage 6: where
| where tostring(behaviors.["New Country"]) == "POSITIVE"
Stage 7: where
| where tostring(behaviors.["New Geo-Location"]) == "POSITIVE"
Stage 8: where
| where tostring(behaviors.["New Device"]) == "POSITIVE"
Stage 9: summarize
| summarize by timekey = bin(TimeGenerated, timeframe), actor_alternateId_s, actor_displayName_s, client_userAgent_os_s, client_userAgent_browser_s, client_device_s, client_userAgent_rawUserAgent_s, client_ipAddress_s, authenticationContext_externalSessionId_s, client_geographicalContext_country_s, client_geographicalContext_city_s, displayMessage_s, outcome_result_s, outcome_reason_s, column_ifexists('debugContext_debugData_logOnlySecurityData_s', ""), column_ifexists('debugContext_debugData_threatSuspected_s', ""), client_geographicalContext_geolocation_lat_d, client_geographicalContext_geolocation_lon_d
Stage 10: extend
| extend Location = strcat(client_geographicalContext_city_s, "-", client_geographicalContext_country_s)
Stage 11: join
UserLoginNewCountryDevice
| join kind=inner (RiskyOperationsObserved) on timekey, actor_displayName_s, client_ipAddress_s
Stage 12: project
project Location, TimeGenerated, actor_alternateId_s, actor_displayName_s, authenticationContext_externalSessionId_s, client_device_s, client_geographicalContext_city_s, client_geographicalContext_country_s, client_geographicalContext_geolocation_lat_d, client_geographicalContext_geolocation_lon_d, client_ipAddress_s, client_userAgent_browser_s, client_userAgent_os_s, client_userAgent_rawUserAgent_s, displayMessage_s, outcome_reason_s, outcome_result_s
Stage 13: extend
extend AccountName, AccountUPNSuffix
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
behaviors.New Country | eq |
| field:"behaviors.New Country" kind:eq value:"POSITIVE" |
behaviors.New Device | eq |
| field:"behaviors.New Device" kind:eq value:"POSITIVE" |
behaviors.New Geo-Location | eq |
| field:"behaviors.New Geo-Location" kind:eq value:"POSITIVE" |
eventType_s | eq |
| field:"okta::eventType" kind:eq value:"user.session.start" |
eventType_s | in |
| field:"eventType_s" kind:in |
outcome_result_s | eq |
| field:"okta::outcome.result" kind:eq |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Location | project |
TimeGenerated | project |
actor_alternateId_s | project |
actor_displayName_s | project |
authenticationContext_externalSessionId_s | project |
client_device_s | project |
client_geographicalContext_city_s | project |
client_geographicalContext_country_s | project |
client_geographicalContext_geolocation_lat_d | project |
client_geographicalContext_geolocation_lon_d | project |
client_ipAddress_s | project |
client_userAgent_browser_s | project |
client_userAgent_os_s | project |
client_userAgent_rawUserAgent_s | project |
displayMessage_s | project |
outcome_reason_s | project |
outcome_result_s | project |
AccountName | extend |
AccountUPNSuffix | extend |
PaloAlto - Put and post method request in high risk file type
#'Detects put and post method request in high risk file type.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: f12e9d10-51ca-11ec-bf63-0242ac130002
name: PaloAlto - Put and post method request in high risk file type
description: |
'Detects put and post method request in high risk file type.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: CefAma
dataTypes:
- CommonSecurityLog
queryFrequency: 10m
queryPeriod: 10m
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1190
- T1133
query: |
let HighRiskFileType = dynamic(['.exe', '.msi', '.msp', '.jar', '.bat', '.cmd', '.js', '.jse', 'ws', '.ps1', '.ps2', '.msh']);
PaloAltoCDLEvent
| where EventResourceId =~ 'THREAT'
| where EventResult =~ 'file'
| where HttpRequestMethod has_any ("POST", "PUT")
| where FileType in (HighRiskFileType)
| extend FileCustomEntity = SrcFileName
entityMappings:
- entityType: File
fieldMappings:
- identifier: Name
columnName: FileCustomEntity
version: 1.0.4
kind: Scheduled
Stages and Predicates
Parameters
let HighRiskFileType = dynamic(['.exe', '.msi', '.msp', '.jar', '.bat', '.cmd', '.js', '.jse', 'ws', '.ps1', '.ps2', '.msh']);
Stage 1: source
PaloAltoCDLEvent
Stage 2: where
| where EventResourceId =~ 'THREAT'
Stage 3: where
| where EventResult =~ 'file'
Stage 4: where
| where HttpRequestMethod has_any ("POST", "PUT")
Stage 5: where
| where FileType in (HighRiskFileType)
Stage 6: extend
| extend FileCustomEntity = SrcFileName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventResourceId | eq |
| field:"EventResourceId" kind:eq value:"THREAT" |
EventResult | eq |
| field:"EventResult" kind:eq value:"file" |
FileType | in |
| field:"FileType" kind:in |
HttpRequestMethod | match |
| field:"HttpRequestMethod" kind:match |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
FileCustomEntity | extend |
Potential Fodhelper UAC Bypass (ASIM Version)
#This detection looks for the steps required to conduct a UAC bypass using Fodhelper.exe. By default this detection looks for the setting of the required registry keys and the invoking of the process within 1 hour - this can be tweaked as required.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Privilege Escalation |
Telemetry Coverage
Rule Body
id: ac9e233e-44d4-45eb-b522-6e47445f6582
name: Potential Fodhelper UAC Bypass (ASIM Version)
description: |
'This detection looks for the steps required to conduct a UAC bypass using Fodhelper.exe. By default this detection looks for the setting of the required registry keys and the invoking of the process within 1 hour - this can be tweaked as required.'
severity: Medium
requiredDataConnectors: []
queryFrequency: 2h
queryPeriod: 2h
triggerOperator: gt
triggerThreshold: 0
tactics:
- PrivilegeEscalation
relevantTechniques:
- T1548.002
query: |
imRegistry
| where EventType in ("RegistryValueSet", "RegistryKeyCreated")
| where RegistryKey has "Software\\Classes\\ms-settings\\shell\\open\\command"
| extend TimeKey = bin(TimeGenerated, 1h)
| join (imProcess
| where Process endswith "fodhelper.exe"
| where ParentProcessName endswith "cmd.exe" or ParentProcessName endswith "powershell.exe" or ParentProcessName endswith "powershell_ise.exe"
| extend TimeKey = bin(TimeGenerated, 1h)) on TimeKey, Dvc
entityMappings:
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: DvcHostname
- entityType: IP
fieldMappings:
- identifier: Address
columnName: DvcIpAddr
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: ActorUsername
version: 1.0.5
kind: Scheduled
metadata:
source:
kind: Community
author:
name: Pete Bryan
support:
tier: Community
categories:
domains: [ "Security - Others" ]
Stages and Predicates
Stage 1: source
imRegistry
Stage 2: where
| where EventType in ("RegistryValueSet", "RegistryKeyCreated")
Stage 3: where
| where RegistryKey has "Software\\Classes\\ms-settings\\shell\\open\\command"
Stage 4: extend
| extend TimeKey = bin(TimeGenerated, 1h)
Stage 5: join
| join (imProcess
| where Process endswith "fodhelper.exe"
| where ParentProcessName endswith "cmd.exe" or ParentProcessName endswith "powershell.exe" or ParentProcessName endswith "powershell_ise.exe"
| extend TimeKey = bin(TimeGenerated, 1h)) on TimeKey, Dvc
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventType | in |
| field:"EventType" kind:in |
ParentProcessName | ends_with |
| field:"ParentProcessName" kind:ends_with |
Process | ends_with |
| field:"Process" kind:ends_with value:"fodhelper.exe" |
RegistryKey | match |
| field:"TargetObject" kind:match value:"Software\Classes\ms-settings\shell\open\command" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
TimeKey | extend |
Snowflake - Unusual query
#'Detects unusual query.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Collection |
Rule Body
id: 1dd1d9e5-3ebf-43cb-be07-6082d5eabe79
name: Snowflake - Unusual query
description: |
'Detects unusual query.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: Snowflake
dataTypes:
- Snowflake
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Collection
relevantTechniques:
- T1119
query: |
Snowflake
| where QueryType =~ 'SELECT'
| where QueryExecutionStatus =~ 'SUCCESS'
| where QueryText startswith @'SELECT *'
| extend AccountCustomEntity = TargetUsername
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountCustomEntity
version: 1.0.1
kind: Scheduled
Stages and Predicates
Stage 1: source
Snowflake
Stage 2: where
| where QueryType =~ 'SELECT'
Stage 3: where
| where QueryExecutionStatus =~ 'SUCCESS'
Stage 4: where
| where QueryText startswith @'SELECT *'
Stage 5: extend
| extend AccountCustomEntity = TargetUsername
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
QueryExecutionStatus | eq |
| field:"QueryExecutionStatus" kind:eq value:"SUCCESS" |
QueryText | starts_with |
| field:"QueryText" kind:starts_with value:"SELECT *" |
QueryType | eq |
| field:"QueryType" kind:eq value:"SELECT" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountCustomEntity | extend |
Storage Settings Updated
#Detects when storage settings are updated in Veeam Backup & Replication. This might indicate configuration changes that require review.
Rule Body
id: be47e508-e35b-4c63-979b-a75b72102344
name: Storage Settings Updated
description: Detects when storage settings are updated in Veeam Backup & Replication.
This might indicate configuration changes that require review.
severity: Informational
status: Available
requiredDataConnectors:
- connectorId: Syslog
dataTypes:
- Syslog
- connectorId: SyslogAma
dataTypes:
- Syslog
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
aggregationKind: AlertPerResult
tactics: []
relevantTechniques: []
query: "Veeam_GetSecurityEvents\n| where instanceId == 41401\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 == 41401
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:"41401" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DataSource | project |
Date | project |
EventId | project |
MessageDetails | project |
Severity | project |
UserName | project |
TI Map URL entity to Web Session Events (ASIM Web Session schema)
#This rule identifies Web Sessions where the full requested URL matches a known malicious URL from Threat Intelligence sources. The rule uses the Advanced Security Information Model (ASIM) and supports any web session source compliant with ASIM.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control |
Rule Body
id: 3b4a8c72-5a2e-4f1e-b61a-9d8b2a6d7a21
name: TI Map URL entity to Web Session Events (ASIM Web Session schema)
description: |
This rule identifies Web Sessions where the full requested URL matches a known
malicious URL from Threat Intelligence sources. The rule uses the Advanced Security
Information Model (ASIM) and supports any web session source compliant with ASIM.
severity: Medium
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
kind: Scheduled
tactics:
- CommandAndControl
- InitialAccess
relevantTechniques:
- T1071
requiredDataConnectors:
- connectorId: SquidProxy
dataTypes:
- SquidProxy_CL
- connectorId: Zscaler
dataTypes:
- CommonSecurityLog
- connectorId: ThreatIntelligence
dataTypes:
- ThreatIntelIndicators
- connectorId: ThreatIntelligenceTaxii
dataTypes:
- ThreatIntelIndicators
- connectorId: MicrosoftDefenderThreatIntelligence
dataTypes:
- ThreatIntelIndicators
query: |
let HAS_ANY_MAX = 10000;
let dt_lookBack = 1h;
let ioc_lookBack = 14d;
// Extract URL-based Threat Intelligence indicators
let URL_TI =
ThreatIntelIndicators
| extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))
| where IndicatorType == "url"
| extend MaliciousUrl = tolower(ObservableValue)
| extend TrafficLightProtocolLevel = tostring(parse_json(AdditionalFields).TLPLevel)
| extend IndicatorId = tostring(split(Id, "--")[2])
| where TimeGenerated >= ago(ioc_lookBack)
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id, MaliciousUrl
| where IsActive and (ValidUntil > now() or isempty(ValidUntil));
// Build a dynamic list of malicious URLs
let URL_TI_list =
toscalar(
URL_TI
| summarize NIoCs = dcount(MaliciousUrl),
Urls = make_set(MaliciousUrl)
| project Urls = iff(NIoCs > HAS_ANY_MAX, dynamic([]), Urls)
);
// Match against ASIM Web Session events
URL_TI
| join kind=innerunique (
_Im_WebSession(starttime=ago(dt_lookBack), url_has_any = URL_TI_list)
| extend RequestedUrl = tolower(Url)
| where isnotempty(RequestedUrl)
| extend Event_TimeGenerated = TimeGenerated
) on $left.MaliciousUrl == $right.RequestedUrl
| where Event_TimeGenerated < ValidUntil
| summarize Event_TimeGenerated = arg_max(Event_TimeGenerated, *) by IndicatorId, RequestedUrl
| extend ParsedData = parse_json(Data)
| extend Description = tostring(ParsedData.description)
| extend ActivityGroupNames = extract(@"ActivityGroup:(\S+)", 1, tostring(ParsedData.labels))
| extend ThreatType = tostring(ParsedData.indicator_types[0])
| project
Event_TimeGenerated,
SrcIpAddr,
RequestedUrl,
IndicatorId,
ThreatType,
Confidence,
ValidUntil,
Description,
ActivityGroupNames
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIpAddr
- entityType: URL
fieldMappings:
- identifier: Url
columnName: RequestedUrl
customDetails:
EventTime: Event_TimeGenerated
IoCDescription: Description
ActivityGroupNames: ActivityGroupNames
IndicatorId: IndicatorId
ThreatType: ThreatType
IoCExpirationTime: ValidUntil
IoCConfidenceScore: Confidence
alertDetailsOverride:
alertDisplayNameFormat: A web request from {{SrcIpAddr}} to malicious URL matched an IoC
alertDescriptionFormat: A client with address {{SrcIpAddr}} requested the URL {{RequestedUrl}}, which is a known malicious URL associated with {{ThreatType}}. Review threat intelligence blade for further context.
version: 1.0.1
Stages and Predicates
Parameters
let HAS_ANY_MAX = 10000;
let dt_lookBack = 1h;
let ioc_lookBack = 14d;
let URL_TI is inlined into the numbered stages below.
Let Binding: URL_TI_list
let URL_TI_list = toscalar(
URL_TI
| summarize NIoCs = dcount(MaliciousUrl),
Urls = make_set(MaliciousUrl)
| project Urls = iff(NIoCs > HAS_ANY_MAX, dynamic([]), Urls)
);
Stages 1 to 7 define let URL_TI as the rule's main pipeline source. Stages 8 to 12 use it.
Stage 1: source
ThreatIntelIndicators
Stage 2: extend
| extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))
Stage 3: where
| where IndicatorType == "url"
Stage 4: extend (3 consecutive steps)
| extend MaliciousUrl = tolower(ObservableValue)
| extend TrafficLightProtocolLevel = tostring(parse_json(AdditionalFields).TLPLevel)
| extend IndicatorId = tostring(split(Id, "--")[2])
Stage 5: where
| where TimeGenerated >= ago(ioc_lookBack)
Stage 6: summarize
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id, MaliciousUrl
Stage 7: where
| where IsActive and (ValidUntil > now() or isempty(ValidUntil))
Stage 8: join
URL_TI
| join kind=innerunique (
_Im_WebSession(starttime=ago(dt_lookBack), url_has_any = URL_TI_list)
| extend RequestedUrl = tolower(Url)
| where isnotempty(RequestedUrl)
| extend Event_TimeGenerated = TimeGenerated
) on $left.MaliciousUrl == $right.RequestedUrl
Stage 9: where
where Event_TimeGenerated < ValidUntil
Stage 10: summarize
summarize Event_TimeGenerated by IndicatorId, RequestedUrl
Stage 11: extend (4 consecutive steps)
extend ActivityGroupNames, Description, ParsedData, ThreatType
Stage 12: project
project ActivityGroupNames, Confidence, Description, Event_TimeGenerated, IndicatorId, RequestedUrl, SrcIpAddr, ThreatType, ValidUntil
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
IndicatorType | eq |
| field:"IndicatorType" kind:eq value:"url" |
IsActive | eq |
| field:"IsActive" kind:eq value:"true" |
RequestedUrl | is_not_null | field:"RequestedUrl" kind:is_not_null | |
ValidUntil | is_null | field:"ValidUntil" kind:is_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActivityGroupNames | project |
Confidence | project |
Description | project |
Event_TimeGenerated | project |
IndicatorId | project |
RequestedUrl | project |
SrcIpAddr | project |
ThreatType | project |
ValidUntil | project |
UniFi Site Manager: Device Offline
#Identifies when a UniFi device transitions to offline status, which may indicate a network outage, power loss, or hardware failure requiring attention.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Impact |
Rule Body
id: f3fa4f3f-c8db-ae35-ee06-04de2dfac511
name: "UniFi Site Manager: Device Offline"
description: |
Identifies when a UniFi device transitions to offline status, which may indicate a network outage, power loss, or hardware failure requiring attention.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: UniFiSiteManagerConnectorDefinition
dataTypes:
- Unifi_SiteManager_Devices_CL
queryFrequency: 15m
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Impact
relevantTechniques:
- T1489
subTechniques: []
query: |
// UniFi Device Offline Detection
Unifi_SiteManager_Devices_CL
| where TimeGenerated > ago(1h)
| summarize arg_max(TimeGenerated, *) by Id
| where Status == "offline"
| extend OfflineMinutes = datetime_diff('minute', now(), TimeGenerated)
| where OfflineMinutes >= 10
| extend
DeviceName = coalesce(Name, "Unnamed"),
DeviceId = Id,
Model = Model,
IPAddress = Ip,
MACAddress = Mac,
ProductLine = ProductLine,
FirmwareVersion = Version
| extend HostName = DeviceName
| project
TimeGenerated,
DeviceName,
DeviceId,
Model,
IPAddress,
MACAddress,
ProductLine,
Status = Status,
OfflineMinutes,
FirmwareVersion,
HostName
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: HostName
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPAddress
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: PT4H
matchingMethod: AllEntities
kind: Scheduled
version: 1.0.0
Stages and Predicates
Stage 1: source
Unifi_SiteManager_Devices_CL
Stage 2: where
| where TimeGenerated > ago(1h)
Stage 3: summarize
| summarize arg_max(TimeGenerated, *) by Id
Stage 4: where
| where Status == "offline"
Stage 5: extend
| extend OfflineMinutes = datetime_diff('minute', now(), TimeGenerated)
Stage 6: where
| where OfflineMinutes >= 10
Stage 7: extend
| extend
DeviceName = coalesce(Name, "Unnamed"),
DeviceId = Id,
Model = Model,
IPAddress = Ip,
MACAddress = Mac,
ProductLine = ProductLine,
FirmwareVersion = Version
Stage 8: extend
| extend HostName = DeviceName
Stage 9: project
| project
TimeGenerated,
DeviceName,
DeviceId,
Model,
IPAddress,
MACAddress,
ProductLine,
Status = Status,
OfflineMinutes,
FirmwareVersion,
HostName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
OfflineMinutes | ge |
| field:"OfflineMinutes" kind:ge value:"10" |
Status | eq |
| field:"Status" kind:eq value:"offline" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DeviceId | project |
DeviceName | project |
FirmwareVersion | project |
HostName | project |
IPAddress | project |
MACAddress | project |
Model | project |
OfflineMinutes | project |
ProductLine | project |
Status | project |
TimeGenerated | project |
Varonis - Medium severity alerts detected
#'Identifies medium-severity alerts generated by Varonis that indicate suspicious activity requiring review and investigation to assess potential security impact.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Collection | |
| Exfiltration |
Rule Body
id: c4e8b16f-3a92-4d75-8e21-6f9c0b5d2a84
name: Varonis - Medium severity alerts detected
description: |
'Identifies medium-severity alerts generated by Varonis that indicate suspicious activity requiring review and investigation to assess potential security impact.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: VaronisSaaSAlertsPush
dataTypes:
- VaronisAlertsV2_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
- Collection
- Exfiltration
relevantTechniques:
- T1530
- T1213
- T1567
query: |
VaronisAlertsV2_CL
| where AlertSeverity_s == "Medium"
eventGroupingSettings:
aggregationKind: AlertPerResult
alertDetailsOverride:
alertDisplayNameFormat: 'Varonis alert: {{ThreatDetectionPolicyName_s}} ({{AlertSeverity_s}})'
alertDescriptionFormat: 'Varonis detected activity for policy {{ThreatDetectionPolicyName_s}} in category {{AlertCategory_s}}. Users: {{UserNames_s}}.'
customDetails:
VaronisAlertGuid: AlertId_g
AlertCategory: AlertCategory_s
MitreTactic: MitreTacticName_s
Status: Status_s
EventsCount: EventsCount_d
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: UserNames_s
- identifier: Name
columnName: UserSamAccountNames_s
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: DeviceNames_s
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
kind: Scheduled
version: 1.0.1
Stages and Predicates
Stage 1: source
VaronisAlertsV2_CL
Stage 2: where
| where AlertSeverity_s == "Medium"
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AlertSeverity_s | eq |
| field:"AlertSeverity_s" kind:eq value:"Medium" |
Zscaler - Unexpected event count of rejects by policy
#'Detects unexpected event count of rejects by policy.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: 593e3e2a-43ce-11ec-81d3-0242ac130003
name: Zscaler - Unexpected event count of rejects by policy
description: |
'Detects unexpected event count of rejects by policy.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: CustomLogsAma
datatypes:
- ZPA_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1078
- T1133
query: |
let threshold = 1000;
ZPAEvent
| where EventResult has "REJECTED_BY_POLICY"
| summarize rejected = count() by EventResult, DstUserName
| where rejected > threshold
| extend AccountCustomEntity = DstUserName
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountCustomEntity
version: 1.0.2
kind: Scheduled
Stages and Predicates
Parameters
let threshold = 1000;
Stage 1: source
ZPAEvent
Stage 2: where
| where EventResult has "REJECTED_BY_POLICY"
Stage 3: summarize
| summarize rejected = count() by EventResult, DstUserName
Stage 4: where
| where rejected > threshold
Stage 5: extend
| extend AccountCustomEntity = DstUserName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventResult | match |
| field:"EventResult" kind:match value:"REJECTED_BY_POLICY" |
rejected | gt |
| field:"rejected" kind:gt value:"1000" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DstUserName | summarize |
EventResult | summarize |
rejected | summarize |
AccountCustomEntity | extend |