Detection rules › Kusto
Kusto rules: 24
Anomaly Sign In Event from an IP
#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
| Tactic | Techniques |
|---|---|
| Initial Access |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Entra ID | SigninLogs sign-in event: Sign-in record (SigninLogs, any result) |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
Anomalies | gt |
| field:"Anomalies" kind:gt value:"0" |
NetworkLocationDetails[0].networkType | ne |
| field:"NetworkLocationDetails[0].networkType" kind:ne value:"trustedNamedLocation" |
PasswordResult | match |
| field:"PasswordResult" kind:match value:"Correct Password" |
UserCount | gt |
| field:"UserCount" kind:gt value:"1" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
EventCounts | summarize |
IPAddress | summarize |
Anomalies | extend |
Baseline | extend |
Score | extend |
ApexOne - C&C callback events
#'Detects C&C callback events.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| 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.
| Field | Kind | Values | Search |
|---|---|---|---|
EventMessage | match |
| field:"EventMessage" kind:match value:"CnC Callback" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountCustomEntity | extend |
IPCustomEntity | extend |
AWSCloudTrail - Privilege escalation via Lambda policy
#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
| Tactic | Techniques |
|---|---|
| Privilege Escalation |
Telemetry Coverage
Rules Detecting the Same Action
These rules filter on the same operation.
- AWS IAM Access Key Compromise Detection (Panther)
- AWS Lateral Movement from Kubernetes SA via AssumeRoleWithWebIdentity (Elastic)
- AWSCloudTrail - Privilege escalation via CloudFormation policy (Kusto)
- AWSCloudTrail - Privilege escalation via CRUD DynamoDB policy (Kusto)
- AWSCloudTrail - Privilege escalation via CRUD IAM policy (Kusto)
- AWSCloudTrail - Privilege escalation via CRUD KMS policy (Kusto)
- AWSCloudTrail - Privilege escalation via CRUD Lambda policy (Kusto)
- AWSCloudTrail - Privilege escalation via CRUD S3 policy (Kusto)
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.
| Field | Kind | Values | Search |
|---|---|---|---|
Action | contains |
| field:"Action" kind:contains |
Effect | eq |
| field:"Effect" kind:eq value:"Allow" |
ErrorCode | is_null | field:"aws::errorCode" kind:is_null | |
ErrorMessage | is_null | field:"aws::errorMessage" kind:is_null | |
EventName | in |
| field:"aws::eventName" kind:in |
Resource | eq |
| field:"Resource" kind:eq value:"*" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
PolicyName | extend |
Statement | extend |
Action | extend |
Condition | extend |
Effect | extend |
Resource | extend |
UserIdentityArn | extend |
UserName | extend |
AccountName | extend |
AccountUPNSuffix | extend |
Azure RBAC (Elevate Access)
#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
| Tactic | Techniques |
|---|---|
| Privilege Escalation |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Azure | User has elevated their access to User Access Administrator for their Azure Resources |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
ActivityDisplayName | eq |
| field:"azure_ad::activity_display_name" kind:eq value:"User has elevated their access to User Access Administrator for their Azure Resources" |
Category | eq |
| field:"Category" kind:eq value:"AzureRBACRoleManagementElevateAccess" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Actor | project |
IPAddress | project |
LoggedByService | project |
OperationName | project |
Result | project |
TimeGenerated | project |
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.
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.
| Field | Kind | Values | Search |
|---|---|---|---|
Finding | eq |
| field:"Finding" kind:eq value:"AzureT0MGAddSecret" |
Cisco SE - Multiple malware on host
#'This rule triggers when multiple malware where detected on host.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
ThreatName | is_not_null | field:"ThreatName" kind:is_not_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DstHostname | summarize |
infected | summarize |
HostCustomEntity | extend |
Contrast Probes
#'Creates Incidents for Probed events sourced from the Contrast Protect agent.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| 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
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.
| Field | Kind | Values | Search |
|---|---|---|---|
AdditionalExtensions | contains |
| field:"AdditionalExtensions" kind:contains |
DeviceVendor | eq |
| field:"DeviceVendor" kind:eq value:"Contrast Security" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DeviceProduct | extend |
SourceIP | extend |
DeviceVersion | extend |
Activity | extend |
ApplicationProtocol | extend |
RequestURL | extend |
RequestMethod | extend |
Rule | extend |
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.
'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.
| Field | Kind | Values | Search |
|---|---|---|---|
host_count_d | gt |
| field:"host_count_d" kind:gt value:"0" |
severity_s | contains |
| field:"okta::severity" kind:contains value:"medium" |
user_count_d | gt |
| field:"user_count_d" kind:gt value:"0" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
status_s | contains | resolved_auto | excludes:status_s field:"status_s" value:"resolved_auto" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
TimeGenerated | project |
alert_categories_s | project |
alert_count_d | project |
creation_time_d | project |
description_s | project |
host_count_d | project |
hosts_s | project |
incident_id_s | project |
incident_sources_s | project |
modification_time_d | project |
severity_s | project |
status_s | project |
user_count_d | project |
users_s | project |
xdr_url_s | project |
Excessive Blocked Traffic Events Generated by User
#'Creates an incident when a Symantec Endpoint Proection agent detects excessive amounts of blocked traffic generated by a single user.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Lateral Movement | |
| Command & Control | |
| Exfiltration |
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
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.
| Field | Kind | Values | Search |
|---|---|---|---|
Action | eq |
| field:"Action" kind:eq value:"Blocked" |
LogType | eq |
| field:"LogType" kind:eq |
TotalBlockedEvents | gt |
| field:"TotalBlockedEvents" kind:gt value:"15" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ApplicationName | summarize |
EndTimeUtc | summarize |
LocalHostIpAddr | summarize |
LocalPortNumber | summarize |
RemoteHostIpAddr | summarize |
RemotePortNumber | summarize |
RuleName | summarize |
ServerName | summarize |
StartTimeUtc | summarize |
Total | summarize |
TrafficDirection | summarize |
UserName | summarize |
GitHub Signin Burst from Multiple Locations
#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
| Tactic | Techniques |
|---|---|
| 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.
| Field | Source |
|---|---|
Name | extend |
UPNSuffix | extend |
High severity malicious activity detected
#Identifies high severity malicious activity in Azure Firewall IDPS logs.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Execution | |
| Credential Access | |
| Exfiltration |
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
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
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.
| Field | Kind | Values | Search |
|---|---|---|---|
Category | match |
| field:"Category" kind:match |
Severity | ge |
| field:"Severity" kind:ge value:"1" |
TotalHits | ge |
| field:"TotalHits" kind:ge value:"10" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Actions | project |
Description | project |
EndTime | project |
MaxSeverity | project |
Signatures | project |
SourceIp | project |
StartTime | project |
ThreatCategory | project |
TotalHits | project |
Infoblox - SOC Insight Detected - CDC Source
#'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
| Tactic | Techniques |
|---|---|
| Impact |
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.
| Field | Source |
|---|---|
InfobloxInsightID | summarize |
License Limit Exceeded
#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.
| Field | Kind | Values | Search |
|---|---|---|---|
instanceId | eq |
| field:"instanceId" kind:eq value:"24070" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DataSource | project |
Date | project |
Days Left | project |
Days of Support Left | project |
EventId | project |
License Edition | project |
License Type | project |
MessageDetails | project |
Severity | project |
Midnight Blizzard - suspicious rundll32.exe execution of vbscript (Normalized Process Events)
#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
| Tactic | Techniques |
|---|---|
| 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.
| Field | Kind | Values | Search |
|---|---|---|---|
CommandLine | match |
| field:"CommandLine" kind:match |
Process | ends_with |
| field:"Process" kind:ends_with value:"rundll32.exe" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActingProcessName | project |
CommandLine | project |
Dvc | project |
EventProduct | project |
EventVendor | project |
Process | project |
TimeGenerated | project |
User | project |
AccountNTDomain | extend |
AccountName | extend |
HostName | extend |
HostNameDomain | extend |
Palo Alto Prisma Cloud - High severity alert opened for several days
#'Detects high severity alert which is opened for several days.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| 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
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.
| Field | Kind | Values | Search |
|---|---|---|---|
AlertSeverity | eq |
| field:"AlertSeverity" kind:eq value:"high" |
Reason | eq |
| field:"Reason" kind:eq value:"NEW_ALERT" |
Status | eq |
| field:"Status" kind:eq value:"open" |
alert_time | gt |
| field:"TimeGenerated" kind:gt value:"1d" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
AlertId | eq | ResolvedAlerts | excludes:AlertId field:"AlertId" value:"ResolvedAlerts" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
alert_time | extend |
Opened Days | extend |
AccountCustomEntity | extend |
Pathlock TDnR - SAP Router Log Events
#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
| Tactic | Techniques |
|---|---|
| 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.
| Field | Kind | Values | Search |
|---|---|---|---|
DataSource | eq |
| field:"DataSource" kind:eq value:"SAPROUTER" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AffectedUser | project |
Area | project |
Bname | project |
CentralTs | project |
DataSource | project |
DestIp | project |
Eventid | project |
Hostname | project |
Instance | project |
LogLine | project |
MessageV1 | project |
MessageV2 | project |
MessageV3 | project |
MessageV4 | project |
MsgId | project |
MsgNo | project |
MsgType | project |
Report | project |
SrcIp | project |
Subid | project |
Sysid | project |
Tcode | project |
TimeGenerated | project |
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.
"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
| Tactic | Techniques |
|---|---|
| Privilege Escalation | |
| Collection |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
customProps_RuleId_s | eq |
| field:"customProps_RuleId_s" kind:eq value:"TRIS0036" |
priority_s | eq |
| field:"priority_s" kind:eq |
Threats detected by Eset
#'Escalates threats detected by Eset.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Execution | |
| Privilege Escalation | |
| Credential Access |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
event_type_s | eq |
| field:"event_type_s" kind:eq value:"Threat_Event" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountCustomEntity | extend |
HostCustomEntity | extend |
IPCustomEntity | extend |
TI map Email entity to AzureActivity
#'Identifies a match in AzureActivity table from any Email IOC from TI'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| 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.
| Field | Kind | Values | Search |
|---|---|---|---|
Active | eq |
| field:"Active" kind:eq value:"true" |
Caller | is_not_null | field:"Caller" kind:is_not_null | |
Caller | regex_match |
| field:"Caller" kind:regex_match value:"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+$" |
EmailSenderAddress | is_not_null | field:"EmailSenderAddress" kind:is_not_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActivityGroupNames | project |
ActivityStatusValue | project |
AzureActivity_TimeGenerated | project |
Caller | project |
CallerIpAddress | project |
CategoryValue | project |
ConfidenceScore | project |
Description | project |
EmailRecipient | project |
EmailSenderName | project |
EmailSourceDomain | project |
EmailSourceIpAddress | project |
EmailSubject | project |
ExpirationDateTime | project |
FileHashType | project |
FileHashValue | project |
IndicatorId | project |
Level | project |
OperationNameValue | project |
ResourceGroup | project |
SubscriptionId | project |
ThreatType | project |
Url | project |
Name | extend |
UPNSuffix | extend |
timestamp | extend |
UniFi Site Manager: ISP Packet Loss
#Identifies when WAN packet loss occurs. Even small loss percentages can significantly degrade VoIP calls and video conferencing quality.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Impact |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
TotalPacketLoss | ge |
| field:"TotalPacketLoss" kind:ge value:"10" |
packetLoss | ge |
| field:"packetLoss" kind:ge value:"5" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AvgLatencyMs | project |
EventCount | project |
FirstSeen | project |
ISPAsn | project |
ISPName | project |
LastSeen | project |
SiteId | project |
TimeGenerated | project |
TotalPacketLoss | project |
User Login from Different Countries within 3 hours
#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
| Tactic | Techniques |
|---|---|
| Initial Access |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Okta | System Log event type user.session.start: User logged in to Okta |
Rules Detecting the Same Action
These rules filter on the same operation.
- Failed Logins from Unknown or Invalid User (Kusto)
- First Occurrence of Okta User Session Started via Proxy (Elastic)
- High-Risk Admin Activity (Kusto)
- Multiple Okta Sessions Detected for a Single User (Elastic)
- Multiple Okta User Authentication Events with Same Device Token Hash (Elastic)
- New Device/Location sign-in along with critical operation (Kusto)
- Okta AiTM Session Cookie Replay (Elastic)
- Okta Login From CrowdStrike Unmanaged Device (Panther)
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.
| Field | Kind | Values | Search |
|---|---|---|---|
NumOfCountries | ge |
| field:"NumOfCountries" kind:ge value:"2" |
eventType_s | eq |
| field:"okta::eventType" kind:eq value:"user.session.start" |
outcome_result_s | eq |
| field:"okta::outcome.result" kind:eq value:"SUCCESS" |
published_t | ge |
| field:"published_t" kind:ge value:"timeframe" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
EndTime | summarize |
NumOfCountries | summarize |
StartTime | summarize |
actor_alternateId_s | summarize |
AccountName | extend |
AccountUPNSuffix | extend |