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.

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

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.

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

Cisco Cloud Security - Connection to Unpopular Website Detected

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

'Detects first connection to an unpopular website (possible malicious payload delivery).'

MITRE ATT&CK Coverage

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 used in Stage 4

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.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
Hostnameeqtop_million_listexcludes:Hostname field:"Hostname" value:"top_million_list"

Output Fields

These fields are emitted when the rule matches.

FieldSource
DstIpAddrproject
Messageproject
SrcIpAddrproject
TimeGeneratedproject
UrlOriginalproject

Component Object Model Hijacking - Vault7 trick

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

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

Telemetry Coverage

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.

CTE - Stale Device (No Heartbeat in 30 Days)

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

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

TacticTechniques
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 =
ifisnotempty(tostring(d.hostname))tostring(d.hostname)
elsestrcat("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.

FieldKindValuesSearch
EventTypeeq
  • device.stale_heartbeat
field:"EventType" kind:eq value:"device.stale_heartbeat"

Output Fields

These fields are emitted when the rule matches.

FieldSource
Categoryproject
DeviceIdproject
HostNameproject
LastSeenproject
Severityproject
Sourceproject
TenantIdproject
TimeGeneratedproject

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.

Status
available
Severity
high
Time window
1d
Group by
Domain, Source, URL
Source
github.com/Azure/Azure-Sentinel

'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

TacticTechniques
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.

FieldKindValuesSearch
DetectionCountge
  • 1 transforms: number
field:"DetectionCount" kind:ge value:"1"
Riskge
  • 80 transforms: number
field:"Risk" kind:ge value:"80"
url_sis_not_null
  • (no value, null check)
field:"url_s" kind:is_not_null

Output Fields

These fields are emitted when the rule matches.

FieldSource
Categoriessummarize
DetectionCountsummarize
Domainsummarize
MaxRisksummarize
Sourcesummarize
URLsummarize
MaliciousURLextend
ThreatCategoriesextend

LaZagne Credential Theft

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

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

TacticTechniques
Credential Access

Telemetry Coverage

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 =
ifDeviceName has "."substring(DeviceName, 0, indexof(DeviceName, '.'))
elseDeviceName

Stage 6: extend

| extend DnsDomain = iff(DeviceName has '.', substring(DeviceName, indexof(DeviceName, '.') + 1), "")
DnsDomain =
ifDeviceName has "."substring(DeviceName, (indexof(DeviceName, '.') + 1))
else""

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
FileNameeq
  • reg.exe corpus 28 (elastic 18, splunk 9, kusto 1)
field:"process_basename" kind:eq value:"reg.exe"
ProcessCommandLinematch
  • hklm transforms: term
  • sam transforms: term
  • save transforms: term
field:"CommandLine" kind:match

Output Fields

These fields are emitted when the rule matches.

FieldSource
DeviceIdproject
DeviceNameproject
FileNameproject
InitiatingProcessFileNameproject
InitiatingProcessIdproject
ProcessCommandLineproject
ProcessIdproject
TimeGeneratedproject
HostNameextend
DnsDomainextend

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.

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

'Created to detect new Threat events from the data which is recently synced by Lookout Solution.'

MITRE ATT&CK Coverage

TacticTechniques
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.

Output Fields

These fields are emitted when the rule matches.

FieldSource
DetailsPackageNameextend
TargetPlatformextend
TargetOsVersionextend
Typeextend
Severityextend
Classificationsextend
Platformextend

New country signIn with correct password

#
Severity
medium
Time window
7d
Group by
AuthSucceeded, PasswordResult, UserPrincipalName, failureReason
Author
Juanse
Source
github.com/Azure/Azure-Sentinel

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

TacticTechniques
Initial Access
Credential Access

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

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 used in Stage 10

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.

FieldSource
AppNamesummarize
AuthSucceededsummarize
LastObservedTimesummarize
NewCountrysummarize
PasswordResultsummarize
UserPrincipalNamesummarize
failureReasonsummarize
CountryDiffextend
CountryDiffCountextend
NewCountryEventextend
Domainextend
Nameextend

New Device/Location sign-in along with critical operation

#
Status
available
Severity
medium
Time window
1h
Group by
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, timekey
Source
github.com/Azure/Azure-Sentinel

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

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

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 used in Stage 11

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.

FieldKindValuesSearch
behaviors.New Countryeq
  • POSITIVE transforms: tostring, kusto_dynamic_path
field:"behaviors.New Country" kind:eq value:"POSITIVE"
behaviors.New Deviceeq
  • POSITIVE transforms: tostring, kusto_dynamic_path
field:"behaviors.New Device" kind:eq value:"POSITIVE"
behaviors.New Geo-Locationeq
  • POSITIVE transforms: tostring, kusto_dynamic_path
field:"behaviors.New Geo-Location" kind:eq value:"POSITIVE"
eventType_seq
  • user.session.start
field:"okta::eventType" kind:eq value:"user.session.start"
eventType_sin
  • application.policy.sign_on.rule.delete
  • application.policy.sign_on.update
  • network_zone.rule.disabled
  • policy.lifecycle.create
  • policy.lifecycle.deactivate
  • policy.lifecycle.delete
  • policy.lifecycle.modify
  • policy.lifecycle.update
  • policy.rule.create
  • policy.rule.deactivate
  • policy.rule.delete
  • policy.rule.modify
  • policy.rule.update
  • system.api_token.create
  • system.api_token.revoke
  • system.mfa.factor.deactivate
  • user.mfa.attempt_bypass
  • user.mfa.factor.deactivate
  • user.mfa.factor.reset_all
field:"eventType_s" kind:in
outcome_result_seq
  • SUCCESS corpus 7 (elastic 4, kusto 3)
  • SUCCESS
field:"okta::outcome.result" kind:eq

Output Fields

These fields are emitted when the rule matches.

FieldSource
Locationproject
TimeGeneratedproject
actor_alternateId_sproject
actor_displayName_sproject
authenticationContext_externalSessionId_sproject
client_device_sproject
client_geographicalContext_city_sproject
client_geographicalContext_country_sproject
client_geographicalContext_geolocation_lat_dproject
client_geographicalContext_geolocation_lon_dproject
client_ipAddress_sproject
client_userAgent_browser_sproject
client_userAgent_os_sproject
client_userAgent_rawUserAgent_sproject
displayMessage_sproject
outcome_reason_sproject
outcome_result_sproject
AccountNameextend
AccountUPNSuffixextend

PaloAlto - Put and post method request in high risk file type

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

'Detects put and post method request in high risk file type.'

MITRE ATT&CK Coverage

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.

Output Fields

These fields are emitted when the rule matches.

FieldSource
FileCustomEntityextend

Potential Fodhelper UAC Bypass (ASIM Version)

#
Severity
medium
Time window
2h
Group by
Dvc, TimeKey
Author
Pete Bryan
Source
github.com/Azure/Azure-Sentinel

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

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.

FieldKindValuesSearch
EventTypein
  • RegistryKeyCreated corpus 5 (kusto 5)
  • RegistryValueSet corpus 5 (kusto 5)
field:"EventType" kind:in
ParentProcessNameends_with
  • cmd.exe
  • powershell.exe
  • powershell_ise.exe
field:"ParentProcessName" kind:ends_with
Processends_with
  • fodhelper.exe
field:"Process" kind:ends_with value:"fodhelper.exe"
RegistryKeymatch
  • Software\Classes\ms-settings\shell\open\command transforms: term corpus 2 (kusto 2)
field:"TargetObject" kind:match value:"Software\Classes\ms-settings\shell\open\command"

Output Fields

These fields are emitted when the rule matches.

FieldSource
TimeKeyextend

Snowflake - Unusual query

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

'Detects unusual query.'

MITRE ATT&CK Coverage

TacticTechniques
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.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountCustomEntityextend

Storage Settings Updated

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

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.

Output Fields

These fields are emitted when the rule matches.

FieldSource
DataSourceproject
Dateproject
EventIdproject
MessageDetailsproject
Severityproject
UserNameproject

TI Map URL entity to Web Session Events (ASIM Web Session schema)

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

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

TacticTechniques
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 used in Stage 8

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.

Output Fields

These fields are emitted when the rule matches.

FieldSource
ActivityGroupNamesproject
Confidenceproject
Descriptionproject
Event_TimeGeneratedproject
IndicatorIdproject
RequestedUrlproject
SrcIpAddrproject
ThreatTypeproject
ValidUntilproject

UniFi Site Manager: Device Offline

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

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

TacticTechniques
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.

Output Fields

These fields are emitted when the rule matches.

FieldSource
DeviceIdproject
DeviceNameproject
FirmwareVersionproject
HostNameproject
IPAddressproject
MACAddressproject
Modelproject
OfflineMinutesproject
ProductLineproject
Statusproject
TimeGeneratedproject

Varonis - Medium severity alerts detected

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

'Identifies medium-severity alerts generated by Varonis that indicate suspicious activity requiring review and investigation to assess potential security impact.'

MITRE ATT&CK Coverage

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.

Zscaler - Unexpected event count of rejects by policy

#
Status
available
Severity
high
Time window
1h
Group by
DstUserName, EventResult
Source
github.com/Azure/Azure-Sentinel

'Detects unexpected event count of rejects by policy.'

MITRE ATT&CK Coverage

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.

FieldKindValuesSearch
EventResultmatch
  • REJECTED_BY_POLICY transforms: term
field:"EventResult" kind:match value:"REJECTED_BY_POLICY"
rejectedgt
  • 1000 transforms: number
field:"rejected" kind:gt value:"1000"

Output Fields

These fields are emitted when the rule matches.

FieldSource
DstUserNamesummarize
EventResultsummarize
rejectedsummarize
AccountCustomEntityextend

Back to all Kusto rules