Detection rules › Kusto

Kusto rules: 24

Anomaly Sign In Event from an IP

#
Severity
medium
Time window
1h
Group by
IPAddress
Author
Juanse
Source
github.com/Azure/Azure-Sentinel

Identifies sign-in anomalies from an IP in the last hour, targeting multiple users where the password is correct after multiple attempts

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Telemetry Coverage

Rule Body

id: 9c1e9381-79dd-4ddf-9570-b73a1dc59fe0
name: Anomaly Sign In Event from an IP
description: |
  'Identifies sign-in anomalies from an IP in the last hour, targeting multiple users where the password is correct after multiple attempts'
severity: Medium
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - SigninLogs 
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1078
query: |
  let LookBack = 1h;
  let Data = (
  SigninLogs
  | where TimeGenerated >= ago(LookBack)
  | where parse_json(NetworkLocationDetails)[0].networkType != "trustedNamedLocation" // Excludes known tagged networks
  // Counts the number of sign in events in the last hour every 15 minutes by IP
  | make-series EventCounts = count() on TimeGenerated from ago(LookBack) to now() step 15m by IPAddress 
  );
  let AnomalyAlert = (
  Data
  | extend (Anomalies, Score, Baseline) = series_decompose_anomalies(EventCounts,1.5,-1,'linefit')
  | mv-expand EventCounts,TimeGenerated,Anomalies to typeof(double),Baseline to typeof(long),Score to typeof(double)
  | where Anomalies > 0
  );
  AnomalyAlert
  | join kind = inner (SigninLogs
  | where TimeGenerated between (ago(LookBack) .. now())
  | where parse_json(NetworkLocationDetails)[0].networkType != "trustedNamedLocation"
  | extend PasswordResult = tostring(parse_json(AuthenticationDetails).authenticationStepResultDetail)
  | summarize UserCount = dcount(UserPrincipalName), UserList = make_set(UserPrincipalName), AppName = make_set(AppDisplayName), PasswordResult = make_list(PasswordResult) by IPAddress) on IPAddress
  | where PasswordResult has "Correct Password"
  | where UserCount > 1 // looks for events targeting more than one user.
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPAddress
customDetails:
  Score: Score
  Baseline: Baseline
  UserCount: UserCount
  AppName: AppName
  PasswordResult: PasswordResult
  UserList: UserList
version: 1.0.1
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: Juanse
    support:
        tier: Community
    categories:
        domains: [ "Identity" ]

Stages and Predicates

Parameters

let LookBack = 1h;

let Data and let AnomalyAlert are inlined into the numbered stages below.

Stages 1 to 7 define let AnomalyAlert as the rule's main pipeline source. Stages 8 to 10 use it.

Stage 1: source

SigninLogs

Stage 2: where

| where TimeGenerated >= ago(LookBack)

Stage 3: where

| where parse_json(NetworkLocationDetails)[0].networkType != "trustedNamedLocation"

The stages below score time-series anomalies (make-series, series_decompose_anomalies).

Stage 4: make-series

| make-series EventCounts = count() on TimeGenerated from ago(LookBack) to now() step 15m by IPAddress

Stage 5: extend

| extend (Anomalies, Score, Baseline) = series_decompose_anomalies(EventCounts,1.5,-1,'linefit')

Stage 6: mv-expand

| mv-expand EventCounts,TimeGenerated,Anomalies to typeof(double),Baseline to typeof(long),Score to typeof(double)

Stage 7: where

| where Anomalies > 0

Stage 8: join

AnomalyAlert
| join kind = inner (SigninLogs
| where TimeGenerated between (ago(LookBack) .. now())
| where parse_json(NetworkLocationDetails)[0].networkType != "trustedNamedLocation"
| extend PasswordResult = tostring(parse_json(AuthenticationDetails).authenticationStepResultDetail)
| summarize UserCount = dcount(UserPrincipalName), UserList = make_set(UserPrincipalName), AppName = make_set(AppDisplayName), PasswordResult = make_list(PasswordResult) by IPAddress) on IPAddress

Stage 9: where

where PasswordResult has "Correct Password"

Stage 10: where

where UserCount > 1

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Anomaliesgt
  • 0 transforms: number
field:"Anomalies" kind:gt value:"0"
NetworkLocationDetails[0].networkTypene
  • trustedNamedLocation transforms: kusto_accessor_expr:{"expression":"parse_json(NetworkLocationDetails)[0].networkType","field":"NetworkLocationDetails[0].networkType"}, kusto_dynamic_path
field:"NetworkLocationDetails[0].networkType" kind:ne value:"trustedNamedLocation"
PasswordResultmatch
  • Correct Password transforms: term
field:"PasswordResult" kind:match value:"Correct Password"
UserCountgt
  • 1 transforms: number
field:"UserCount" kind:gt value:"1"

Output Fields

These fields are emitted when the rule matches.

FieldSource
EventCountssummarize
IPAddresssummarize
Anomaliesextend
Baselineextend
Scoreextend

ApexOne - C&C callback events

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

'Detects C&C callback events.'

MITRE ATT&CK Coverage

TacticTechniques
Command & Control

Rule Body

id: 1a87cd10-67b7-11ec-90d6-0242ac120003
name: ApexOne - C&C callback events
description: |
  'Detects C&C callback events.'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: CefAma
    dataTypes:
      - CommonSecurityLog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
relevantTechniques:
  - T1071
query: |
  TMApexOneEvent
  | where EventMessage has "CnC Callback"
  | extend IPCustomEntity = SrcIpAddr, AccountCustomEntity = DstUserName
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPCustomEntity
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountCustomEntity
version: 1.0.3
kind: Scheduled

Stages and Predicates

Stage 1: source

TMApexOneEvent

Stage 2: where

| where EventMessage has "CnC Callback"

Stage 3: extend

| extend IPCustomEntity = SrcIpAddr, AccountCustomEntity = DstUserName

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
EventMessagematch
  • CnC Callback transforms: term
field:"EventMessage" kind:match value:"CnC Callback"

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountCustomEntityextend
IPCustomEntityextend

AWSCloudTrail - Privilege escalation via Lambda policy

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

Detects inline IAM policy updates that grant broad AWS Lambda permissions, including combinations that can link Lambda with other services such as DynamoDB. These permissions can provide execution control and support privilege escalation paths in the cloud environment.

MITRE ATT&CK Coverage

TacticTechniques
Privilege Escalation

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: 8e01c41d-bd4c-4bbe-aed5-18592735052d
name: AWSCloudTrail - Privilege escalation via Lambda policy
description: |
  Detects inline IAM policy updates that grant broad AWS Lambda permissions, including combinations that can
  link Lambda with other services such as DynamoDB. These permissions can provide execution control and support
  privilege escalation paths in the cloud environment.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: AWS
    dataTypes:
      - AWSCloudTrail
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - PrivilegeEscalation
relevantTechniques:
  - T1098.003
query: |
    AWSCloudTrail
      | where EventName in ("PutUserPolicy","PutRolePolicy","PutGroupPolicy") and isempty(ErrorCode) and isempty(ErrorMessage)
      | extend PolicyName = tostring(parse_json(RequestParameters).policyName)
      | extend Statement = parse_json(tostring((parse_json(RequestParameters).policyDocument))).Statement
      | mvexpand Statement
      | extend Action = parse_json(Statement).Action , Effect = tostring(parse_json(Statement).Effect), Resource = tostring(parse_json(Statement).Resource), Condition = tostring(parse_json(Statement).Condition)
      | extend Action = tostring(Action)
      | where Effect =~ "Allow" and ((((Action contains "iam:*" or Action contains "iam:PassRole") and Action contains "lambda:*") or ((Action contains "iam:*" or Action contains "iam:PassRole") and Action contains "lambda:CreateFunction" and Action contains "lambda:InvokeFunction") or ((Action contains "iam:*" or Action contains "iam:PassRole") and Action contains "lambda:Create*" and Action contains "lambda:Invoke*")) or ((Action contains "iam:*" or Action contains "iam:PassRole") and Action contains "lambda:*" and Action contains "dynamodb:*") or ((Action contains "iam:*" or Action contains "iam:PassRole") and Action contains "lambda:CreateFunction" and Action contains "lambda:CreateEventSourceMapping" and Action contains "dynamodb:PutItem" and Action contains "dynamodb:CreateTable") or ((Action contains "iam:*" or Action contains "iam:PassRole") and Action contains "lambda:Create*" and Action contains "dynamodb:Put*" and Action contains "dynamodb:Create*")) and Resource == "*" and Condition == ""
      | extend UserIdentityArn = iif(isempty(UserIdentityArn), tostring(parse_json(Resources)[0].ARN), UserIdentityArn)
      | extend UserName = tostring(split(UserIdentityArn, '/')[-1])
      | extend AccountName = case( UserIdentityPrincipalid == "Anonymous", "Anonymous", isempty(UserIdentityUserName), UserName, UserIdentityUserName)
      | extend AccountName = iif(AccountName contains "@", tostring(split(AccountName, '@', 0)[0]), AccountName),
        AccountUPNSuffix = iif(AccountName contains "@", tostring(split(AccountName, '@', 1)[0]), "")
      | distinct TimeGenerated, EventName, PolicyName, SourceIpAddress, RecipientAccountId, AccountName, AccountUPNSuffix, UserIdentityArn, UserIdentityUserName
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: AccountUPNSuffix
      - identifier: CloudAppAccountId
        columnName: RecipientAccountId
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIpAddress
customDetails:
  PolicyName: PolicyName
  EventName: EventName
  UserIdentityArn: UserIdentityArn
  RecipientAccountId: RecipientAccountId
alertDetailsOverride:
  alertDisplayNameFormat: 'AWS Lambda privilege escalation policy update by {{AccountName}}'
  alertDescriptionFormat: 'Detected {{EventName}} Event, updating inline Lambda escalation policy {{PolicyName}} in account {{RecipientAccountId}}.'
version: 1.0.2
kind: Scheduled

Stages and Predicates

Stage 1: source

AWSCloudTrail

Stage 2: where

| where EventName in ("PutUserPolicy","PutRolePolicy","PutGroupPolicy") and isempty(ErrorCode) and isempty(ErrorMessage)

Stage 3: extend

| extend PolicyName = tostring(parse_json(RequestParameters).policyName)

Stage 4: extend

| extend Statement = parse_json(tostring((parse_json(RequestParameters).policyDocument))).Statement

Stage 5: mv-expand

| mvexpand Statement

Stage 6: extend

| extend Action = parse_json(Statement).Action , Effect = tostring(parse_json(Statement).Effect), Resource = tostring(parse_json(Statement).Resource), Condition = tostring(parse_json(Statement).Condition)

Stage 7: extend

| extend Action = tostring(Action)

Stage 8: where

| where Effect =~ "Allow" and ((((Action contains "iam:*" or Action contains "iam:PassRole") and Action contains "lambda:*") or ((Action contains "iam:*" or Action contains "iam:PassRole") and Action contains "lambda:CreateFunction" and Action contains "lambda:InvokeFunction") or ((Action contains "iam:*" or Action contains "iam:PassRole") and Action contains "lambda:Create*" and Action contains "lambda:Invoke*")) or ((Action contains "iam:*" or Action contains "iam:PassRole") and Action contains "lambda:*" and Action contains "dynamodb:*") or ((Action contains "iam:*" or Action contains "iam:PassRole") and Action contains "lambda:CreateFunction" and Action contains "lambda:CreateEventSourceMapping" and Action contains "dynamodb:PutItem" and Action contains "dynamodb:CreateTable") or ((Action contains "iam:*" or Action contains "iam:PassRole") and Action contains "lambda:Create*" and Action contains "dynamodb:Put*" and Action contains "dynamodb:Create*")) and Resource == "*" and Condition == ""

Stage 9: extend (4 consecutive steps)

| extend UserIdentityArn = iif(isempty(UserIdentityArn), tostring(parse_json(Resources)[0].ARN), UserIdentityArn)
| extend UserName = tostring(split(UserIdentityArn, '/')[-1])
| extend AccountName = case( UserIdentityPrincipalid == "Anonymous", "Anonymous", isempty(UserIdentityUserName), UserName, UserIdentityUserName)
| extend AccountName = iif(AccountName contains "@", tostring(split(AccountName, '@', 0)[0]), AccountName),
    AccountUPNSuffix = iif(AccountName contains "@", tostring(split(AccountName, '@', 1)[0]), "")

Stage 10: distinct

| distinct TimeGenerated, EventName, PolicyName, SourceIpAddress, RecipientAccountId, AccountName, AccountUPNSuffix, UserIdentityArn, UserIdentityUserName

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Actioncontains
  • dynamodb:*
  • dynamodb:Create*
  • dynamodb:CreateTable
  • dynamodb:Put*
  • dynamodb:PutItem
  • iam:*
  • iam:PassRole
  • lambda:*
  • lambda:Create*
  • lambda:CreateEventSourceMapping
  • lambda:CreateFunction
  • lambda:Invoke*
  • lambda:InvokeFunction
field:"Action" kind:contains
Effecteq
  • Allow
field:"Effect" kind:eq value:"Allow"
ErrorCodeis_null
  • (no value, null check)
field:"aws::errorCode" kind:is_null
ErrorMessageis_null
  • (no value, null check)
field:"aws::errorMessage" kind:is_null
EventNamein
  • PutGroupPolicy corpus 12 (kusto 11, panther 1)
  • PutRolePolicy corpus 12 (kusto 11, panther 1)
  • PutUserPolicy corpus 12 (kusto 11, panther 1)
field:"aws::eventName" kind:in
Resourceeq
  • *
field:"Resource" kind:eq value:"*"

Output Fields

These fields are emitted when the rule matches.

FieldSource
PolicyNameextend
Statementextend
Actionextend
Conditionextend
Effectextend
Resourceextend
UserIdentityArnextend
UserNameextend
AccountNameextend
AccountUPNSuffixextend

Azure RBAC (Elevate Access)

#
Severity
high
Time window
2h
Source
github.com/Azure/Azure-Sentinel

Detects when a Global Administrator elevates access to all subscriptions and management groups in a tenant. When a Global Administrator elevates access they are assigned the User Access Administrator role at root scope. This Microsoft Sentinel Analytic Rule monitors who has elevated access in your tenant so that admins can take appropriate action. Learn more

MITRE ATT&CK Coverage

TacticTechniques
Privilege Escalation

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: 132fdff4-c044-4855-a390-c1b71e0f833b
name: Azure RBAC (Elevate Access)
kind: Scheduled
description: |
  'Detects when a Global Administrator elevates access to all subscriptions and management groups in a tenant. When a Global Administrator elevates access they are assigned the User Access Administrator role at root scope. This Microsoft Sentinel Analytic Rule monitors who has elevated access in your tenant so that admins can take appropriate action. [Learn more](https://learn.microsoft.com/en-us/azure/role-based-access-control/elevate-access-global-admin?tabs=azure-portal)'
severity: High
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AuditLogs
queryFrequency: 2h
queryPeriod: 2h
triggerOperator: GreaterThan
triggerThreshold: 0
tactics:
  - PrivilegeEscalation
relevantTechniques: 
  - T1078
enabled: true
query: |
  AuditLogs
  | where Category =~ "AzureRBACRoleManagementElevateAccess"
  | where ActivityDisplayName =~ "User has elevated their access to User Access Administrator for their Azure Resources"
  | extend Actor = tostring(InitiatedBy.user.userPrincipalName)
  | extend IPAddress = tostring(InitiatedBy.user.ipAddress) 
  | project
      TimeGenerated,
      Actor,
      OperationName,
      IPAddress,
      Result,
      LoggedByService
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: Actor
  - entityType: IP
    fieldMappings:
      - identifier: Address 
        columnName: IPAddress
suppressionDuration: PT5H
suppressionEnabled: false
alertRuleTemplateName: null
incidentConfiguration: 
  createIncident: true
  groupingConfiguration: 
    enabled: false
    reopenClosedIncident: false
    lookbackDuration: PT5H
    matchingMethod: AllEntities
    groupByEntities: []
    groupByAlertDetails: []
    groupByCustomDetails: []
eventGroupingSettings:
  aggregationKind: SingleAlert
version: 1.0.0 
 

Stages and Predicates

Stage 1: source

AuditLogs

Stage 2: where

| where Category =~ "AzureRBACRoleManagementElevateAccess"

Stage 3: where

| where ActivityDisplayName =~ "User has elevated their access to User Access Administrator for their Azure Resources"

Stage 4: extend

| extend Actor = tostring(InitiatedBy.user.userPrincipalName)

Stage 5: extend

| extend IPAddress = tostring(InitiatedBy.user.ipAddress)

Stage 6: project

| project
    TimeGenerated,
    Actor,
    OperationName,
    IPAddress,
    Result,
    LoggedByService

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
ActivityDisplayNameeq
  • User has elevated their access to User Access Administrator for their Azure Resources corpus 2 (elastic 1, kusto 1)
field:"azure_ad::activity_display_name" kind:eq value:"User has elevated their access to User Access Administrator for their Azure Resources"
Categoryeq
  • AzureRBACRoleManagementElevateAccess
field:"Category" kind:eq value:"AzureRBACRoleManagementElevateAccess"

Output Fields

These fields are emitted when the rule matches.

FieldSource
Actorproject
IPAddressproject
LoggedByServiceproject
OperationNameproject
Resultproject
TimeGeneratedproject

BloodHound Attack Path Finding - Tier Zero Service Principal Control via MS Graph App Role

#

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 - Tier Zero Service Principal Control via MS Graph App Role

Rule Body

id: d817dc53-1787-4b02-b8cf-d6254eec5ddb
name: BloodHound Attack Path Finding - Tier Zero Service Principal Control via MS
  Graph App Role
description: BloodHound Attack Path Finding - Tier Zero Service Principal Control
  via MS Graph App Role
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: BloodHoundEnterprise
    dataTypes:
      - BHEAttackPathsData_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: "BHEAttackPathsData_CL \n| where Finding == \"AzureT0MGAddSecret\""
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 == "AzureT0MGAddSecret"

Indicators

These rows show field, operator, and value matches.

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

Cisco SE - Multiple malware on host

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

'This rule triggers when multiple malware where detected on host.'

MITRE ATT&CK Coverage

Rule Body

id: b13489d7-feb1-4ad3-9a4c-09f6d64448fd
name: Cisco SE - Multiple malware on host
description: |
  'This rule triggers when multiple malware where detected on host.'
severity: High
requiredDataConnectors:
  - connectorId: CiscoSecureEndpoint
    dataTypes:
      - CiscoSecureEndpoint
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - InitialAccess
relevantTechniques:
  - T1190
  - T1133
query: |
  let threshold = 2;
  CiscoSecureEndpoint
  | where isnotempty(ThreatName)
  | summarize infected = makeset(ThreatName) by DstHostname, bin(TimeGenerated, 10m)
  | where array_length(infected) >= threshold
  | extend HostCustomEntity = DstHostname
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: HostCustomEntity
version: 1.0.0
kind: Scheduled

Stages and Predicates

Parameters

let threshold = 2;

Stage 1: source

CiscoSecureEndpoint

Stage 2: where

| where isnotempty(ThreatName)

Stage 3: summarize

| summarize infected = makeset(ThreatName) by DstHostname, bin(TimeGenerated, 10m)

Stage 4: where

| where array_length(infected) >= threshold

Stage 5: extend

| extend HostCustomEntity = DstHostname

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
ThreatNameis_not_null
  • (no value, null check)
field:"ThreatName" kind:is_not_null

Output Fields

These fields are emitted when the rule matches.

FieldSource
DstHostnamesummarize
infectedsummarize
HostCustomEntityextend

Contrast Probes

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

'Creates Incidents for Probed events sourced from the Contrast Protect agent.'

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Rule Body

id: 297596de-d9ae-4fb8-b6ff-00fc01c9462d
name: Contrast Probes
description: |
  'Creates Incidents for Probed events sourced from the Contrast Protect agent.'
severity: Informational
requiredDataConnectors:
  - connectorId: ContrastProtect
    dataTypes:
      - CommonSecurityLog
  - connectorId: ContrastProtectAma
    dataTypes:
      - CommonSecurityLog
  - connectorId: CefAma
    dataTypes:
      - CommonSecurityLog
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 10
status: Available
tactics:
  - InitialAccess
  - Exfiltration
relevantTechniques:
  - T1566
query: |
  let extract_data=(a:string, k:string) {
    parse_urlquery(replace(@';', @'&', a))["Query Parameters"][k]
  };

  CommonSecurityLog 
  | where DeviceVendor == "Contrast Security"
  | where AdditionalExtensions contains "PROBED" or AdditionalExtensions contains "INEFFECTIVE"
  | extend DeviceProduct
  | extend SourceIP
  | extend DeviceVersion
  | extend Activity
  | extend ApplicationProtocol
  | extend RequestURL
  | extend RequestMethod
  | extend Rule = extract_data(AdditionalExtensions, 'pri')
customDetails:
  Application: ApplicationProtocol
  Agent: DeviceProduct
  AgentVersion: DeviceVersion
  Attack: Activity
  Details: AdditionalExtensions
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIP
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: RequestURL
  - entityType: CloudApplication
    fieldMappings:
      - identifier: Name
        columnName: ApplicationProtocol
  - entityType: Malware
    fieldMappings:
      - identifier: Name
        columnName: Activity
      - identifier: Category
        columnName: Rule
version: 1.0.2
kind: Scheduled

Stages and Predicates

Let Binding: extract_data used in Stage 4

let extract_data = (a:string, k:string) {
  parse_urlquery(replace(@';', @'&', a))["Query Parameters"][k]
};

Stage 1: source

CommonSecurityLog

Stage 2: where

| where DeviceVendor == "Contrast Security"

Stage 3: where

| where AdditionalExtensions contains "PROBED" or AdditionalExtensions contains "INEFFECTIVE"

Stage 4: extend (8 consecutive steps)

| extend DeviceProduct
| extend SourceIP
| extend DeviceVersion
| extend Activity
| extend ApplicationProtocol
| extend RequestURL
| extend RequestMethod
| extend Rule = extract_data(AdditionalExtensions, 'pri')

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
DeviceProductextend
SourceIPextend
DeviceVersionextend
Activityextend
ApplicationProtocolextend
RequestURLextend
RequestMethodextend
Ruleextend

Cortex XDR Incident - Medium

#

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.

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

'A new incident was created in the Cortex XDR portal with a severity "Medium". Click on the events for incident details. '

Rule Body

id: 2b05823b-ee15-4b92-a642-b13170e37c35
name: Cortex XDR Incident - Medium
description: |
  'A new incident was created in the Cortex XDR portal with a severity "Medium". Click on the events for incident details. '
severity: Medium
requiredDataConnectors:
  - connectorId: CortexXDR
    dataTypes:
      - CortexXDR_Incidents_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
suppressionDuration: 5h
suppressionEnabled: false
tactics: []
relevantTechniques: []
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: 5h
    matchingMethod: AllEntities
eventGroupingSettings:
  aggregationKind: SingleAlert
alertDetailsOverride: null
customDetails: null
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: users_s
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: xdr_url_s
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: hosts_s
sentinelEntitiesMappings: null              
query: | 
  CortexXDR_Incidents_CL
  | where severity_s contains "medium"
  | where status_s !contains "resolved_auto"
  // the below line filters for incidents that has at least one user or one host in it. Comment the below line if you want to generated incidents that have 0 user or 0 host as well
  | where host_count_d > 0 or user_count_d > 0
  | project TimeGenerated, incident_id_s, status_s, severity_s, incident_sources_s, description_s, xdr_url_s, hosts_s, users_s, alert_categories_s, host_count_d, user_count_d, alert_count_d, creation_time_d, modification_time_d
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

CortexXDR_Incidents_CL

Stage 2: where

| where severity_s contains "medium"

Stage 3: where

| where status_s !contains "resolved_auto"

Stage 4: where

| where host_count_d > 0 or user_count_d > 0

Stage 5: project

| project TimeGenerated, incident_id_s, status_s, severity_s, incident_sources_s, description_s, xdr_url_s, hosts_s, users_s, alert_categories_s, host_count_d, user_count_d, alert_count_d, creation_time_d, modification_time_d

Indicators

These rows show field, operator, and value matches.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
status_scontainsresolved_autoexcludes:status_s field:"status_s" value:"resolved_auto"

Output Fields

These fields are emitted when the rule matches.

FieldSource
TimeGeneratedproject
alert_categories_sproject
alert_count_dproject
creation_time_dproject
description_sproject
host_count_dproject
hosts_sproject
incident_id_sproject
incident_sources_sproject
modification_time_dproject
severity_sproject
status_sproject
user_count_dproject
users_sproject
xdr_url_sproject

Excessive Blocked Traffic Events Generated by User

#
Status
available
Severity
medium
Time window
1h
Group by
ApplicationName, LocalHostIpAddr, LocalPortNumber, RemoteHostIpAddr, RemotePortNumber, RuleName, ServerName, TrafficDirection, UserName
Source
github.com/Azure/Azure-Sentinel

'Creates an incident when a Symantec Endpoint Proection agent detects excessive amounts of blocked traffic generated by a single user.'

MITRE ATT&CK Coverage

Rule Body

id: fa0ab69c-7124-4f62-acdd-61017cf6ce89
name: Excessive Blocked Traffic Events Generated by User
description: |
  'Creates an incident when a Symantec Endpoint Proection agent detects excessive amounts of blocked traffic generated by a single user.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: SyslogAma
    datatypes:
      - Syslog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Exfiltration
  - CommandAndControl
  - LateralMovement
relevantTechniques:
  - T1041
  - T1132
  - T1001
  - T1021
query: |
  let threshold = 15;
  let NoteableEvents = SymantecEndpointProtection
  | where LogType == "Agent Traffic Logs"
  | where Action =~ "Blocked"
  | summarize TotalBlockedEvents = count() by UserName
  | where TotalBlockedEvents > threshold;
  SymantecEndpointProtection
  | where LogType =~ "Agent Traffic Logs"
  | where Action =~ "Blocked"
  | join kind=inner (NoteableEvents) on UserName
  | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), Total = count() by UserName, RuleName, ServerName, LocalHostIpAddr, LocalPortNumber, TrafficDirection, RemoteHostIpAddr, RemotePortNumber, ApplicationName
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: UserName
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: LocalHostIpAddr
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: RemoteHostIpAddr
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: ServerName
version: 1.0.3
kind: Scheduled

Stages and Predicates

Parameters

let threshold = 15;

Let Binding: NoteableEvents used in Stage 4

let NoteableEvents = SymantecEndpointProtection
| where LogType == "Agent Traffic Logs"
| where Action =~ "Blocked"
| summarize TotalBlockedEvents = count() by UserName
| where TotalBlockedEvents > threshold;

Stage 1: source

SymantecEndpointProtection

Stage 2: where

| where LogType =~ "Agent Traffic Logs"

Stage 3: where

| where Action =~ "Blocked"

Stage 4: join

| join kind=inner (NoteableEvents) on UserName

Stage 5: summarize

summarize EndTimeUtc, StartTimeUtc, Total by UserName, RuleName, ServerName, LocalHostIpAddr, LocalPortNumber, TrafficDirection, RemoteHostIpAddr, RemotePortNumber, ApplicationName

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
ApplicationNamesummarize
EndTimeUtcsummarize
LocalHostIpAddrsummarize
LocalPortNumbersummarize
RemoteHostIpAddrsummarize
RemotePortNumbersummarize
RuleNamesummarize
ServerNamesummarize
StartTimeUtcsummarize
Totalsummarize
TrafficDirectionsummarize
UserNamesummarize

GitHub Signin Burst from Multiple Locations

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

This detection triggers when there is a Signin burst from multiple locations in GitHub (Entra ID SSO). This detection is based on configurable threshold which can be prone to false positives. To view the anomaly based equivalent of thie detection, please see here https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Microsoft Entra ID/Analytic Rules/AnomalousUserAppSigninLocationIncrease-detection.yaml.

MITRE ATT&CK Coverage

TacticTechniques
Credential Access

Telemetry Coverage

Rule Body

id: d3980830-dd9d-40a5-911f-76b44dfdce16
name: GitHub Signin Burst from Multiple Locations
description: |
  'This detection triggers when there is a Signin burst from multiple locations in GitHub (Entra ID SSO).
   This detection is based on configurable threshold which can be prone to false positives. To view the anomaly based equivalent of thie detection, please see here https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Microsoft%20Entra%20ID/Analytic%20Rules/AnomalousUserAppSigninLocationIncrease-detection.yaml. '
severity: Medium
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - SigninLogs
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AADNonInteractiveUserSignInLogs
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - CredentialAccess
relevantTechniques:
  - T1110
query: |
  let locationThreshold = 1;
  let aadFunc = (tableName:string){
  table(tableName)
  | where AppDisplayName =~ "GitHub.com"
  | where ResultType == 0
  | summarize CountOfLocations = dcount(Location), Locations = make_set(Location,100), BurstStartTime = min(TimeGenerated), BurstEndTime = max(TimeGenerated) by UserPrincipalName, Type
  | where CountOfLocations > locationThreshold
  | extend timestamp = BurstStartTime
  };
  let aadSignin = aadFunc("SigninLogs");
  let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");
  union isfuzzy=true aadSignin, aadNonInt
  | extend Name = tostring(split(UserPrincipalName,'@',0)[0]), UPNSuffix = tostring(split(UserPrincipalName,'@',1)[0])
entityMappings:
  - entityType: Account
    fieldMappings:
    - identifier: FullName
      columnName: UserPrincipalName
    - identifier: Name
      columnName: Name
    - identifier: UPNSuffix
      columnName: UPNSuffix
version: 1.0.3
kind: Scheduled

Stages and Predicates

Parameters

let locationThreshold = 1;
let aadSignin = aadFunc("SigninLogs");
let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");

Let Binding: aadFunc

let aadFunc = (tableName:string){
table(tableName)
| where AppDisplayName =~ "GitHub.com"
| where ResultType == 0
| summarize CountOfLocations = dcount(Location), Locations = make_set(Location,100), BurstStartTime = min(TimeGenerated), BurstEndTime = max(TimeGenerated) by UserPrincipalName, Type
| where CountOfLocations > locationThreshold
| extend timestamp = BurstStartTime
};

Union isfuzzy=true (2 Sources)

Each leg below queries one source; the rule matches if any leg does. Sources: aadSignin, aadNonInt

Leg 1: aadSignin

Leg 2: aadNonInt

Applied to the Combined Result

| extend Name = tostring(split(UserPrincipalName,'@',0)[0]), UPNSuffix = tostring(split(UserPrincipalName,'@',1)[0])

Output Fields

These fields are emitted when the rule matches.

FieldSource
Nameextend
UPNSuffixextend

High severity malicious activity detected

#
Status
available
Severity
high
Time window
1d
Group by
SourceIp, ThreatCategory
Source
github.com/Azure/Azure-Sentinel

Identifies high severity malicious activity in Azure Firewall IDPS logs.

MITRE ATT&CK Coverage

Rule Body

id: 504257c1-81e2-4609-8d40-b395e62f11c7
name: High severity malicious activity detected
description: |
  Identifies high severity malicious activity in Azure Firewall IDPS logs.
severity: High
status: Available
requiredDataConnectors:
  - connectorId: AzureFirewall
    dataTypes:
      - AZFWIdpsSignature
queryFrequency: 1h
queryPeriod: 24h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
  - Exfiltration
  - CredentialAccess
  - CommandAndControl
  - Execution
relevantTechniques:
  - T1190
  - T1041
  - T1003
  - T1204
query: |
  let TimeWindow   = 90d;    // How far back to look 
  let HitThreshold = 10;     // Minimum hits to alert per SourceIp + Category
  let MinSeverity  = 1;      // Set Minimum Severity
  let EnableCategoryFilter    = true;   // Filter 1: use CategoriesOfInterest
  let EnableDescriptionFilter = false;  // Filter 2: use DescriptionsOfInterest
  let EnableActionFilter      = false;  // Filter 3: use MatchActions
  let CategoriesOfInterest    = dynamic([
      "Targeted Malicious Activity was Detected",
      "Exploit Kit Activity Detected",
      "Domain Observed Used for C2 Detected",
      "Successful Credential Theft Detected",
      "Malware Command and Control Activity Detected",
      "Executable code was detected",
      "A Network Trojan was detected"
  ]);
  let DescriptionsOfInterest  = dynamic([
      "targeted-activity",
      "exploit-kit",
      "domain-c2",
      "credential-theft",
      "command-and-control",
      "shellcode-detect",
      "trojan-activity"
  ]);
  let MatchActions = dynamic(["Deny", "alert"]);
  AZFWIdpsSignature
  | where TimeGenerated >= ago(TimeWindow)
  | where Severity >= MinSeverity
  // Filter 1: Category filter (optional)
  | where (EnableCategoryFilter == false) or (Category has_any (CategoriesOfInterest))
  // Filter 2: Description filter (optional)
  | where (EnableDescriptionFilter == false) or (Description has_any (DescriptionsOfInterest))
  // Filter 3: Action filter (optional)
  | where (EnableActionFilter == false) or (Action in~ (MatchActions))
  | summarize
      StartTime   = min(TimeGenerated),
      EndTime     = max(TimeGenerated),
      TotalHits   = count(),
      MaxSeverity = max(Severity),
      Actions     = make_set(Action, 5),
      Signatures  = make_set(SignatureId, 20),
      Description = make_set(substring(tostring(Description), 0, 120), 3)
      by SourceIp, ThreatCategory = Category
  | where TotalHits >= HitThreshold
  | project
      StartTime,
      EndTime,
      SourceIp,
      ThreatCategory,
      TotalHits,
      MaxSeverity,
      Actions,
      Signatures,
      Description
  | order by MaxSeverity desc, TotalHits desc
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIp
version: 1.0.1
kind: Scheduled

Stages and Predicates

Parameters

let TimeWindow = 90d;
let HitThreshold = 10;
let MinSeverity = 1;
let EnableCategoryFilter = true;
let EnableDescriptionFilter = false;
let EnableActionFilter = false;
let MatchActions = dynamic(["Deny", "alert"]);

Let Binding: CategoriesOfInterest used in Stage 4

let CategoriesOfInterest = dynamic([
    "Targeted Malicious Activity was Detected",
    "Exploit Kit Activity Detected",
    "Domain Observed Used for C2 Detected",
    "Successful Credential Theft Detected",
    "Malware Command and Control Activity Detected",
    "Executable code was detected",
    "A Network Trojan was detected"
]);

Let Binding: DescriptionsOfInterest used in Stage 5

let DescriptionsOfInterest = dynamic([
    "targeted-activity",
    "exploit-kit",
    "domain-c2",
    "credential-theft",
    "command-and-control",
    "shellcode-detect",
    "trojan-activity"
]);

Stage 1: source

AZFWIdpsSignature

Stage 2: where

| where TimeGenerated >= ago(TimeWindow)

Stage 3: where

| where Severity >= MinSeverity

Stage 4: where

| where (EnableCategoryFilter == false) or (Category has_any (CategoriesOfInterest))

Stage 5: where

| where (EnableDescriptionFilter == false) or (Description has_any (DescriptionsOfInterest))

Stage 6: where

| where (EnableActionFilter == false) or (Action in~ (MatchActions))

Stage 7: summarize

| summarize
    StartTime   = min(TimeGenerated),
    EndTime     = max(TimeGenerated),
    TotalHits   = count(),
    MaxSeverity = max(Severity),
    Actions     = make_set(Action, 5),
    Signatures  = make_set(SignatureId, 20),
    Description = make_set(substring(tostring(Description), 0, 120), 3)
    by SourceIp, ThreatCategory = Category

Stage 8: where

| where TotalHits >= HitThreshold

Stage 9: project

| project
    StartTime,
    EndTime,
    SourceIp,
    ThreatCategory,
    TotalHits,
    MaxSeverity,
    Actions,
    Signatures,
    Description

Stage 10: sort

| order by MaxSeverity desc, TotalHits desc

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Categorymatch
  • A Network Trojan was detected transforms: term
  • Domain Observed Used for C2 Detected transforms: term
  • Executable code was detected transforms: term
  • Exploit Kit Activity Detected transforms: term
  • Malware Command and Control Activity Detected transforms: term
  • Successful Credential Theft Detected transforms: term
  • Targeted Malicious Activity was Detected transforms: term
field:"Category" kind:match
Severityge
  • 1 transforms: number
field:"Severity" kind:ge value:"1"
TotalHitsge
  • 10 transforms: number
field:"TotalHits" kind:ge value:"10"

Output Fields

These fields are emitted when the rule matches.

FieldSource
Actionsproject
Descriptionproject
EndTimeproject
MaxSeverityproject
Signaturesproject
SourceIpproject
StartTimeproject
ThreatCategoryproject
TotalHitsproject

Infoblox - SOC Insight Detected - CDC Source

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

'Infoblox SOC Insight detected in logs sourced via Infoblox CDC. Customize scheduling, responses and more. This rule depends on a parser based on a Kusto Function to work as expected called InfobloxCDC_SOCInsights.'

MITRE ATT&CK Coverage

Rule Body

id: a4bdd81e-afc8-4410-a3d1-8478fa810537
name: Infoblox - SOC Insight Detected - CDC Source
description: |
  'Infoblox SOC Insight detected in logs sourced via Infoblox CDC. Customize scheduling, responses and more. This rule depends on a parser based on a Kusto Function to work as expected called [**InfobloxCDC_SOCInsights**](https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Infoblox%20SOC%20Insights/Parsers/InfobloxCDC_SOCInsights.yaml).'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: InfobloxSOCInsightsDataConnector_Legacy
    dataTypes: 
      - CommonSecurityLog (InfobloxCDC_SOCInsights)
  - connectorId: InfobloxSOCInsightsDataConnector_AMA
    dataTypes: 
      - CommonSecurityLog (InfobloxCDC_SOCInsights)
  - connectorId: CefAma
    dataTypes:
      - CommonSecurityLog
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Impact
relevantTechniques:
  - T1498
  - T1565
query: |
  InfobloxCDC_SOCInsights
  | summarize arg_max(TimeGenerated, *) by InfobloxInsightID
entityMappings:
  - entityType: SecurityGroup
    fieldMappings:
      - identifier: ObjectGuid
        columnName: InfobloxInsightID
  - entityType: Malware
    fieldMappings:
      - identifier: Name
        columnName: ThreatClass
      - identifier: Category
        columnName: ThreatProperty
customDetails:
  InfobloxInsightID: InfobloxInsightID
  FeedSource: FeedSource
  Status: Status
  BlockedHits: BlockedCount
  UnblockedHits: NotBlockedCount
  TotalHits: EventsCount
eventGroupingSettings:
  aggregationKind: AlertPerResult
alertDetailsOverride:
  alertDisplayNameFormat: 'Infoblox - SOC Insight - {{ThreatClass}} {{ThreatProperty}}'
  alertDescriptionFormat: 'Observed via CDC. {{ThreatFamily}}. {{Message}}'
  alertSeverityColumnName: IncidentSeverity
incidentConfiguration:
  createIncident: true
version: 1.0.1
kind: Scheduled

Stages and Predicates

Stage 1: source

InfobloxCDC_SOCInsights

Stage 2: summarize

| summarize arg_max(TimeGenerated, *) by InfobloxInsightID

Output Fields

These fields are emitted when the rule matches.

FieldSource
InfobloxInsightIDsummarize

License Limit Exceeded

#
Status
available
Severity
medium
Time window
3h
Group by
$left.Edition, $left.Type, $right.Edition, $right.Type
Source
github.com/Azure/Azure-Sentinel

Detects when the Veeam license limit is exceeded.

Rule Body

id: 31ffa231-72da-49f0-88cd-de08eaf3bc52
name: License Limit Exceeded
description: Detects when the Veeam license limit is exceeded.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: Syslog
  dataTypes:
  - Syslog
- connectorId: SyslogAma
  dataTypes:
  - Syslog
queryFrequency: 3h
queryPeriod: 3h
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
  aggregationKind: AlertPerResult
tactics: []
relevantTechniques: []
query: "let license_editions_lookup = union isfuzzy=true (datatable(Edition:string,\
  \ EditionDescription:string)[]), (_GetWatchlist(\"license_editions_lookup\")); \n\
  let license_types_lookup = union isfuzzy=true (datatable(Type:string, TypeDescription:string)[]),\
  \ (_GetWatchlist(\"license_types_lookup\")); \nVeeam_GetSecurityEvents\n| where\
  \ instanceId == 24070\n| extend Edition = extract(\"Edition=\\\"([^\\\"]*)\\\"\"\
  , 1, SyslogMessage)\n| lookup kind=leftouter (license_editions_lookup) \n    on\
  \ $left.Edition == $right.Edition\n| extend Type = extract(\"Type=\\\"([^\\\"]*)\\\
  \"\", 1, SyslogMessage)\n| lookup kind=leftouter (license_types_lookup) \n    on\
  \ $left.Type == $right.Type\n| extend TenantID = extract(\"TenantID=\\\"([^\\\"\
  ]*)\\\"\", 1, SyslogMessage)\n| extend DaysLeft = extract(\"DaysLeft=\\\"([^\\\"\
  ]*)\\\"\", 1, SyslogMessage)\n| extend SupportLeft = extract(\"SupportLeft=\\\"\
  ([^\\\"]*)\\\"\", 1, SyslogMessage)\n| project\n    Date = format_datetime(TimeGenerated,\
  \ 'dd.MM.yyyy HH:mm'),\n    DataSource = original_host,\n    EventId = instanceId,\n\
  \    [\"License Edition\"] = EditionDescription,\n    [\"License Type\"] = TypeDescription,\n\
  \    [\"Days Left\"] = DaysLeft,\n    [\"Days of Support Left\"] = SupportLeft,\n\
  \    MessageDetails = Description,\n    Severity = SeverityDescription\n"
version: 1.0.1
kind: Scheduled
customDetails:
  Date: Date
  VbrHostName: DataSource
  EventId: EventId
  MessageDetails: MessageDetails
  Severity: Severity

Stages and Predicates

Parameters

let license_editions_lookup = union isfuzzy=true (datatable(Edition:string, EditionDescription:string)[]), (_GetWatchlist("license_editions_lookup"));
let license_types_lookup = union isfuzzy=true (datatable(Type:string, TypeDescription:string)[]), (_GetWatchlist("license_types_lookup"));

Stage 1: source

Veeam_GetSecurityEvents

Stage 2: where

| where instanceId == 24070

Stage 3: extend

| extend Edition = extract("Edition=\"([^\"]*)\"", 1, SyslogMessage)

Stage 4: kusto:lookup

lookup kind=leftouter (license_editions_lookup) on $left.Edition, $right.Edition

Stage 5: extend

extend Type

Stage 6: kusto:lookup

lookup kind=leftouter (license_types_lookup) on $left.Type, $right.Type

Stage 7: extend (3 consecutive steps)

extend DaysLeft, SupportLeft, TenantID

Stage 8: project

project DataSource, Date, Days Left, Days of Support Left, EventId, License Edition, License Type, MessageDetails, Severity

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
DataSourceproject
Dateproject
Days Leftproject
Days of Support Leftproject
EventIdproject
License Editionproject
License Typeproject
MessageDetailsproject
Severityproject

Midnight Blizzard - suspicious rundll32.exe execution of vbscript (Normalized Process Events)

#
Severity
medium
Time window
1d
Author
Yuval Naor
Source
github.com/Azure/Azure-Sentinel

This query idenifies when rundll32.exe executes a specific set of inline VBScript commands References: https://www.microsoft.com/security/blog/2021/03/04/goldmax-goldfinder-sibot-analyzing-nobelium-malware/ To use this analytics rule, make sure you have deployed the ASIM normalization parsers

MITRE ATT&CK Coverage

TacticTechniques
Persistence

Telemetry Coverage

Rule Body

id: bdf04f58-242b-4729-b376-577c4bdf5d3a
name: Midnight Blizzard - suspicious rundll32.exe execution of vbscript (Normalized Process Events)
description: |
  'This query idenifies when rundll32.exe executes a specific set of inline VBScript commands
  References: https://www.microsoft.com/security/blog/2021/03/04/goldmax-goldfinder-sibot-analyzing-nobelium-malware/
  To use this analytics rule, make sure you have deployed the [ASIM normalization parsers](https://aka.ms/ASimProcessEvent)'
severity: Medium
requiredDataConnectors: []
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Persistence
relevantTechniques:
  - T1547
tags:
  - Id: d82e1987-4356-4a7b-bc5e-064f29b143c0
    version: 1.0.0
  - Schema: ASIMProcessEvent
    SchemaVersion: 0.1.0
  - Midnight Blizzard

query: |
  imProcessCreate
  | where Process hassuffix 'rundll32.exe'
  | where CommandLine  has_any ('Execute','RegRead','window.close')
  | project TimeGenerated, Dvc, User, Process, CommandLine, ActingProcessName, EventVendor, EventProduct
  | extend AccountName = tostring(split(User, @'\')[1]), AccountNTDomain = tostring(split(User, @'\')[0])
  | extend HostName = tostring(split(Dvc, ".")[0]), DomainIndex = toint(indexof(Dvc, '.'))
  | extend HostNameDomain = iff(DomainIndex != -1, substring(Dvc, DomainIndex + 1), Dvc)
  | project-away DomainIndex
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: User
      - identifier: Name
        columnName: AccountName
      - identifier: NTDomain
        columnName: AccountNTDomain
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: Dvc
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: HostNameDomain
version: 1.1.6
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: Yuval Naor
    support:
        tier: Community
    categories:
        domains: [ "Security - Threat Protection" ]

Stages and Predicates

Stage 1: source

imProcessCreate

Stage 2: where

| where Process hassuffix 'rundll32.exe'

Stage 3: where

| where CommandLine  has_any ('Execute','RegRead','window.close')

Stage 4: project

| project TimeGenerated, Dvc, User, Process, CommandLine, ActingProcessName, EventVendor, EventProduct

Stage 5: extend (3 consecutive steps)

| extend AccountName = tostring(split(User, @'\')[1]), AccountNTDomain = tostring(split(User, @'\')[0])
| extend HostName = tostring(split(Dvc, ".")[0]), DomainIndex = toint(indexof(Dvc, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Dvc, DomainIndex + 1), Dvc)

Stage 6: project-away

| project-away DomainIndex

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
CommandLinematch
  • Execute transforms: term corpus 3 (kusto 3)
  • RegRead transforms: term corpus 3 (kusto 3)
  • window.close transforms: term corpus 3 (kusto 3)
field:"CommandLine" kind:match
Processends_with
  • rundll32.exe
field:"Process" kind:ends_with value:"rundll32.exe"

Output Fields

These fields are emitted when the rule matches.

FieldSource
ActingProcessNameproject
CommandLineproject
Dvcproject
EventProductproject
EventVendorproject
Processproject
TimeGeneratedproject
Userproject
AccountNTDomainextend
AccountNameextend
HostNameextend
HostNameDomainextend

Palo Alto Prisma Cloud - High severity alert opened for several days

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

'Detects high severity alert which is opened for several days.'

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Rule Body

id: c5bf680f-fa37-47c3-9f38-e839a9b99c05
name: Palo Alto Prisma Cloud - High severity alert opened for several days
description: |
  'Detects high severity alert which is opened for several days.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: PaloAltoPrismaCloud
    dataTypes:
      - PaloAltoPrismaCloud
queryFrequency: 1d
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1133
query: |
  let ResolvedAlerts = PaloAltoPrismaCloud
  | summarize result = make_set(Status) by AlertId
  | where result has 'resolved'
  | project AlertId;
  PaloAltoPrismaCloud
  | where Reason =~ 'NEW_ALERT'
  | where AlertSeverity =~ 'high' 
  | where Status =~ 'open'
  | where AlertId !in (ResolvedAlerts)
  | extend alert_time = now() - TimeGenerated
  | where alert_time > 1d
  | extend ['Opened Days'] = strcat('Alert opened for ', strcat(toint(alert_time / 1d), ' days'))
  | distinct AlertId, AlertMessage, AlertSeverity, ['Opened Days'], ResourceId, UserName
  | extend AccountCustomEntity = UserName
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountCustomEntity
version: 1.0.2
kind: Scheduled

Stages and Predicates

Let Binding: ResolvedAlerts used in Stage 5

let ResolvedAlerts = PaloAltoPrismaCloud
| summarize result = make_set(Status) by AlertId
| where result has 'resolved'
| project AlertId;

Stage 1: source

PaloAltoPrismaCloud

Stage 2: where

| where Reason =~ 'NEW_ALERT'

Stage 3: where

| where AlertSeverity =~ 'high'

Stage 4: where

| where Status =~ 'open'

Stage 5: where

| where AlertId !in (ResolvedAlerts)

Stage 6: extend

| extend alert_time = now() - TimeGenerated

Stage 7: where

| where alert_time > 1d

Stage 8: extend

| extend ['Opened Days'] = strcat('Alert opened for ', strcat(toint(alert_time / 1d), ' days'))

Stage 9: distinct

| distinct AlertId, AlertMessage, AlertSeverity, ['Opened Days'], ResourceId, UserName

Stage 10: extend

| extend AccountCustomEntity = UserName

Indicators

These rows show field, operator, and value matches.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
AlertIdeqResolvedAlertsexcludes:AlertId field:"AlertId" value:"ResolvedAlerts"

Output Fields

These fields are emitted when the rule matches.

FieldSource
alert_timeextend
Opened Daysextend
AccountCustomEntityextend

Pathlock TDnR - SAP Router Log Events

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

Detects security-relevant events from the SAP Router log, forwarded by Pathlock Threat Detection and Response. SAP Router events may reveal unauthorized external connections, suspicious routing patterns, or attempts to use the SAP Router as a pivot point for lateral movement.

MITRE ATT&CK Coverage

TacticTechniques
Lateral Movement
Command & Control

Rule Body

id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c49
name: Pathlock TDnR - SAP Router Log Events
kind: Scheduled
description: >-
  Detects security-relevant events from the SAP Router log, forwarded by Pathlock Threat Detection
  and Response. SAP Router events may reveal unauthorized external connections, suspicious routing
  patterns, or attempts to use the SAP Router as a pivot point for lateral movement.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: Pathlock_TDnR
    dataTypes:
      - Pathlock_TDnR_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - LateralMovement
  - CommandAndControl
relevantTechniques:
  - T1021
  - T1572
query: |
  Pathlock_TDnR_CL
  | where DataSource == "SAPROUTER"
  | project TimeGenerated, Sysid, DataSource, Eventid, Instance, Hostname, Bname,
            Tcode, Report, Area, Subid, SrcIp, DestIp, AffectedUser, LogLine,
            MsgType, MsgId, MsgNo, MessageV1, MessageV2, MessageV3, MessageV4, CentralTs
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: Bname
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: Hostname
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SrcIp
eventGroupingSettings:
  aggregationKind: SingleAlert
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: 5h
    matchingMethod: AnyAlert
    groupByEntities: []
    groupByAlertDetails: []
    groupByCustomDetails: []
suppressionDuration: 5h
suppressionEnabled: false
version: 1.0.0

Stages and Predicates

Stage 1: source

Pathlock_TDnR_CL

Stage 2: where

| where DataSource == "SAPROUTER"

Stage 3: project

| project TimeGenerated, Sysid, DataSource, Eventid, Instance, Hostname, Bname,
          Tcode, Report, Area, Subid, SrcIp, DestIp, AffectedUser, LogLine,
          MsgType, MsgId, MsgNo, MessageV1, MessageV2, MessageV3, MessageV4, CentralTs

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AffectedUserproject
Areaproject
Bnameproject
CentralTsproject
DataSourceproject
DestIpproject
Eventidproject
Hostnameproject
Instanceproject
LogLineproject
MessageV1project
MessageV2project
MessageV3project
MessageV4project
MsgIdproject
MsgNoproject
MsgTypeproject
Reportproject
SrcIpproject
Subidproject
Sysidproject
Tcodeproject
TimeGeneratedproject

Theom - Shadow DB with atypical accesses

#

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
5m
Source
github.com/Azure/Azure-Sentinel

"Creates Sentinel incidents for critical/high Theom risks, associated with ruleId TRIS0036 (Theom has observed shadow or clone databases/tables. Additionally, it has observed atypical accesses to these data stores. As per this requirement, use this information to apply data access control lists or access permissions and enforce data retention policies)"

MITRE ATT&CK Coverage

Rule Body

id: 02bff937-ca52-4f52-a9cd-b826f8602694
name: Theom - Shadow DB with atypical accesses
description: |
   "Creates Sentinel incidents for critical/high Theom risks, associated with ruleId TRIS0036 (Theom has observed shadow or clone databases/tables. Additionally, it has observed atypical accesses to these data stores. As per this requirement, use this information to apply data access control lists or access permissions and enforce data retention policies)"
severity: High
status : Available
requiredDataConnectors: 
  - connectorId: Theom
    dataTypes:
      - TheomAlerts_CL
queryPeriod: 5m
queryFrequency: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Collection
  - PrivilegeEscalation
relevantTechniques:
  - T1560
  - T1530
  - T1078
query: |
   TheomAlerts_CL
     | where customProps_RuleId_s == "TRIS0036" and (priority_s == "P1" or priority_s == "P2")
eventGroupingSettings:
  aggregationKind: AlertPerResult
alertDetailsOverride:
  alertDisplayNameFormat: "Theom Alert ID: {{id_s}} "
  alertDescriptionFormat: "\nSummary: {{summary_s}}  \nAdditional info: {{details_s}}\nPlease investigate further on Theom UI at {{deepLink_s}}\n"
entityMappings:
  - entityType: CloudApplication
    fieldMappings:
      - identifier: Name
        columnName: customProps_AssetName_s
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: deepLink_s
version: 1.0.2
kind: Scheduled

Stages and Predicates

Stage 1: source

TheomAlerts_CL

Stage 2: where

| where customProps_RuleId_s == "TRIS0036" and (priority_s == "P1" or priority_s == "P2")

Indicators

These rows show field, operator, and value matches.

Threats detected by Eset

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

'Escalates threats detected by Eset.'

MITRE ATT&CK Coverage

Rule Body

id: 2d8a60aa-c15e-442e-9ce3-ee924889d2a6
name: Threats detected by Eset
description: |
  'Escalates threats detected by Eset.'
severity: Low
status: Available
requiredDataConnectors:
  - connectorId: EsetSMC
    dataTypes:
      - eset_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Execution
  - CredentialAccess
  - PrivilegeEscalation
relevantTechniques:
  - T1204
  - T1212
  - T1548
query: |
  eset_CL
  | where event_type_s == "Threat_Event"
  | extend HostCustomEntity = hostname_s, AccountCustomEntity = username_s, IPCustomEntity = ipv4_s
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: AccountCustomEntity
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: HostCustomEntity
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPCustomEntity
version: 1.0.1
kind: Scheduled

Stages and Predicates

Stage 1: source

eset_CL

Stage 2: where

| where event_type_s == "Threat_Event"

Stage 3: extend

| extend HostCustomEntity = hostname_s, AccountCustomEntity = username_s, IPCustomEntity = ipv4_s

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountCustomEntityextend
HostCustomEntityextend
IPCustomEntityextend

TI map Email entity to AzureActivity

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

'Identifies a match in AzureActivity table from any Email IOC from TI'

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Rule Body

id: cca3b4d9-ac39-4109-8b93-65bb284003e6
name: TI map Email entity to AzureActivity
description: |
  'Identifies a match in AzureActivity table from any Email IOC from TI'
severity: Medium
requiredDataConnectors:
  - connectorId: AzureActivity
    dataTypes:
      - AzureActivity
  - connectorId: ThreatIntelligence
    dataTypes:
      - ThreatIntelligenceIndicator
  - connectorId: ThreatIntelligenceTaxii
    dataTypes:
      - ThreatIntelligenceIndicator
  - connectorId: MicrosoftDefenderThreatIntelligence
    dataTypes:
      - ThreatIntelligenceIndicator
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1566
query: |
  let dt_lookBack = 1h;
  let ioc_lookBack = 14d;
  let emailregex = @'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$';
  ThreatIntelligenceIndicator
  //Filtering the table for Email related IOCs
  | where isnotempty(EmailSenderAddress)
  | where TimeGenerated >= ago(ioc_lookBack)
  | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
  | where Active == true and ExpirationDateTime > now()
  // using innerunique to keep perf fast and result set low, we only need one match to indicate potential malicious activity that needs to be investigated
  | join kind=innerunique (
      AzureActivity | where TimeGenerated >= ago(dt_lookBack) and isnotempty(Caller)
      | extend Caller = tolower(Caller)
      | where Caller matches regex emailregex
      | extend AzureActivity_TimeGenerated = TimeGenerated
  )
  on $left.EmailSenderAddress == $right.Caller
  | where AzureActivity_TimeGenerated < ExpirationDateTime
  | summarize AzureActivity_TimeGenerated = arg_max(AzureActivity_TimeGenerated, *) by IndicatorId, Caller
  | project AzureActivity_TimeGenerated, Description, ActivityGroupNames, IndicatorId, ThreatType, ExpirationDateTime, ConfidenceScore, Url, EmailSenderName, EmailRecipient,
  EmailSourceDomain, EmailSourceIpAddress, EmailSubject, FileHashValue, FileHashType, Caller, Level, CallerIpAddress, CategoryValue, OperationNameValue, ActivityStatusValue,
  ResourceGroup, SubscriptionId
  | extend Name = tostring(split(Caller, '@', 0)[0]), UPNSuffix = tostring(split(Caller, '@', 1)[0])
  | extend timestamp = AzureActivity_TimeGenerated
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: Caller
      - identifier: Name
        columnName: Name
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: CallerIpAddress
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: Url
version: 1.2.7
kind: Scheduled

Stages and Predicates

Parameters

let dt_lookBack = 1h;
let ioc_lookBack = 14d;
let emailregex = @'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$';

Stage 1: source

ThreatIntelligenceIndicator

Stage 2: where

| where isnotempty(EmailSenderAddress)

Stage 3: where

| where TimeGenerated >= ago(ioc_lookBack)

Stage 4: summarize

| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId

Stage 5: where

| where Active == true and ExpirationDateTime > now()

Stage 6: join

| join kind=innerunique (
    AzureActivity | where TimeGenerated >= ago(dt_lookBack) and isnotempty(Caller)
    | extend Caller = tolower(Caller)
    | where Caller matches regex emailregex
    | extend AzureActivity_TimeGenerated = TimeGenerated
)
on $left.EmailSenderAddress == $right.Caller

Stage 7: where

where AzureActivity_TimeGenerated < ExpirationDateTime

Stage 8: summarize

summarize AzureActivity_TimeGenerated by IndicatorId, Caller

Stage 9: project

project ActivityGroupNames, ActivityStatusValue, AzureActivity_TimeGenerated, Caller, CallerIpAddress, CategoryValue, ConfidenceScore, Description, EmailRecipient, EmailSenderName, EmailSourceDomain, EmailSourceIpAddress, EmailSubject, ExpirationDateTime, FileHashType, FileHashValue, IndicatorId, Level, OperationNameValue, ResourceGroup, SubscriptionId, ThreatType, Url

Stage 10: extend

extend Name, UPNSuffix

Stage 11: extend

extend timestamp

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
ActivityGroupNamesproject
ActivityStatusValueproject
AzureActivity_TimeGeneratedproject
Callerproject
CallerIpAddressproject
CategoryValueproject
ConfidenceScoreproject
Descriptionproject
EmailRecipientproject
EmailSenderNameproject
EmailSourceDomainproject
EmailSourceIpAddressproject
EmailSubjectproject
ExpirationDateTimeproject
FileHashTypeproject
FileHashValueproject
IndicatorIdproject
Levelproject
OperationNameValueproject
ResourceGroupproject
SubscriptionIdproject
ThreatTypeproject
Urlproject
Nameextend
UPNSuffixextend
timestampextend

UniFi Site Manager: ISP Packet Loss

#
Status
available
Severity
medium
Time window
30m
Group by
SiteId, ispAsn, ispName, metricTime
Source
github.com/Azure/Azure-Sentinel

Identifies when WAN packet loss occurs. Even small loss percentages can significantly degrade VoIP calls and video conferencing quality.

MITRE ATT&CK Coverage

Rule Body

id: 6f2d71d6-e6c4-0da4-91da-e8192dc5b12c
name: "UniFi Site Manager: ISP Packet Loss"
description: |
  Identifies when WAN packet loss occurs. Even small loss percentages can significantly degrade VoIP calls and video conferencing quality.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: UniFiSiteManagerConnectorDefinition
    dataTypes:
      - Unifi_SiteManager_ISPMetrics_CL
queryFrequency: 15m
queryPeriod: 30m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Impact
relevantTechniques:
  - T1498
  - T1499
subTechniques:
  - T1498.001
  - T1499.002
query: |
  // UniFi ISP Packet Loss Detection
  let PacketLossThreshold = 5;
  let MinTotalPacketLoss = 10;
  Unifi_SiteManager_ISPMetrics_CL
  | where TimeGenerated > ago(30m)
  | mv-expand period = Periods
  | extend
      metricTime = todatetime(period.metricTime),
      packetLoss = toint(period.data.wan.packetLoss),
      ispName = tostring(period.data.wan.ispName),
      ispAsn = tostring(period.data.wan.ispAsn),
      avgLatency = toint(period.data.wan.avgLatency)
  // De-duplicate Periods: each poll returns the same hour buckets, so collapse to latest value per metricTime
  | summarize arg_max(TimeGenerated, packetLoss, avgLatency, ispAsn) by tostring(SiteId), ispName, metricTime
  | where metricTime > ago(30m)
  | where packetLoss >= PacketLossThreshold
  | summarize
      TotalPacketLoss = sum(packetLoss),
      EventCount = count(),
      AvgLatency = avg(avgLatency),
      FirstSeen = min(metricTime),
      LastSeen = max(metricTime)
      by SiteId, ispName, ispAsn
  | where TotalPacketLoss >= MinTotalPacketLoss
  | extend TimeGenerated = now()
  | project
      TimeGenerated,
      SiteId = SiteId,
      ISPName = ispName,
      ISPAsn = ispAsn,
      TotalPacketLoss,
      EventCount,
      AvgLatencyMs = round(AvgLatency, 1),
      FirstSeen,
      LastSeen
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: SiteId
  - entityType: CloudApplication
    fieldMappings:
      - identifier: Name
        columnName: ISPName
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: PT4H
    matchingMethod: AllEntities
kind: Scheduled
version: 1.0.1

Stages and Predicates

Parameters

let PacketLossThreshold = 5;
let MinTotalPacketLoss = 10;

Stage 1: source

Unifi_SiteManager_ISPMetrics_CL

Stage 2: where

| where TimeGenerated > ago(30m)

Stage 3: mv-expand

| mv-expand period = Periods

Stage 4: extend

| extend
    metricTime = todatetime(period.metricTime),
    packetLoss = toint(period.data.wan.packetLoss),
    ispName = tostring(period.data.wan.ispName),
    ispAsn = tostring(period.data.wan.ispAsn),
    avgLatency = toint(period.data.wan.avgLatency)

Stage 5: summarize

| summarize arg_max(TimeGenerated, packetLoss, avgLatency, ispAsn) by tostring(SiteId), ispName, metricTime

Stage 6: where

| where metricTime > ago(30m)

Stage 7: where

| where packetLoss >= PacketLossThreshold

Stage 8: summarize

| summarize
    TotalPacketLoss = sum(packetLoss),
    EventCount = count(),
    AvgLatency = avg(avgLatency),
    FirstSeen = min(metricTime),
    LastSeen = max(metricTime)
    by SiteId, ispName, ispAsn

Stage 9: where

| where TotalPacketLoss >= MinTotalPacketLoss

Stage 10: extend

| extend TimeGenerated = now()

Stage 11: project

| project
    TimeGenerated,
    SiteId = SiteId,
    ISPName = ispName,
    ISPAsn = ispAsn,
    TotalPacketLoss,
    EventCount,
    AvgLatencyMs = round(AvgLatency, 1),
    FirstSeen,
    LastSeen

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AvgLatencyMsproject
EventCountproject
FirstSeenproject
ISPAsnproject
ISPNameproject
LastSeenproject
SiteIdproject
TimeGeneratedproject
TotalPacketLossproject

User Login from Different Countries within 3 hours

#
Status
available
Severity
high
Time window
3h
Group by
actor_alternateId_s
Source
github.com/Azure/Azure-Sentinel

This rule detects successful user logins to the Okta Console from different countries within 3 hours. This may indicate a compromised account or an account being used by multiple individuals in different locations.

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: 2954d424-f786-4677-9ffc-c24c44c6e7d5
name: User Login from Different Countries within 3 hours
description: |
  This rule detects successful user logins to the Okta Console from different countries within 3 hours. This may indicate a compromised account or an account being used by multiple individuals in different locations.
severity: High
status: Available
requiredDataConnectors:
  - connectorId: OktaSSO
    dataTypes:
      - Okta_CL
  - connectorId: OktaSSOv2
    dataTypes:
      - OktaSSO
queryFrequency: 3h
queryPeriod: 3h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1078.004
query: |
  let timeframe = ago(3h);
  let threshold = 2;
  OktaSSO
  | where column_ifexists('published_t', now()) >= timeframe
  | where eventType_s =~ "user.session.start"
  | where outcome_result_s =~ "SUCCESS"
  | where isnotempty(column_ifexists('client_geographicalContext_country_s', ''))
  | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), NumOfCountries = dcount(tostring(column_ifexists('client_geographicalContext_country_s', ''))) by actor_alternateId_s
  | where NumOfCountries >= threshold
  | 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
customDetails:
  StartTime: StartTime
  EndTime: EndTime
  NumOfCountries: NumOfCountries
alertDetailsOverride:
  alertDisplayNameFormat: 'User {{actor_alternateId_s}} logged in from {{NumOfCountries}} countries within 3 hours'
  alertDescriptionFormat: 'User {{actor_alternateId_s}} authenticated successfully from {{NumOfCountries}} countries within 3 hours.'
version: 1.1.2
kind: Scheduled

Stages and Predicates

Parameters

let timeframe = ago(3h);
let threshold = 2;

Stage 1: source

OktaSSO

Stage 2: where

| where column_ifexists('published_t', now()) >= timeframe

Stage 3: where

| where eventType_s =~ "user.session.start"

Stage 4: where

| where outcome_result_s =~ "SUCCESS"

Stage 5: where

| where isnotempty(column_ifexists('client_geographicalContext_country_s', ''))

Stage 6: summarize

| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), NumOfCountries = dcount(tostring(column_ifexists('client_geographicalContext_country_s', ''))) by actor_alternateId_s

Stage 7: where

| where NumOfCountries >= threshold

Stage 8: extend

| extend AccountName = tostring(split(actor_alternateId_s, "@")[0]), AccountUPNSuffix = tostring(split(actor_alternateId_s, "@")[1])

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
NumOfCountriesge
  • 2 transforms: number
field:"NumOfCountries" kind:ge value:"2"
eventType_seq
  • user.session.start corpus 4 (kusto 3, elastic 1)
field:"okta::eventType" kind:eq value:"user.session.start"
outcome_result_seq
  • SUCCESS corpus 3 (kusto 2, elastic 1)
field:"okta::outcome.result" kind:eq value:"SUCCESS"
published_tge
  • timeframe transforms: column_ifexists
field:"published_t" kind:ge value:"timeframe"

Output Fields

These fields are emitted when the rule matches.

FieldSource
EndTimesummarize
NumOfCountriessummarize
StartTimesummarize
actor_alternateId_ssummarize
AccountNameextend
AccountUPNSuffixextend

Back to all Kusto rules