Detection rules › Kusto

Kusto rules: 2r

RuleSeverity
A host is potentially running a crypto miner (ASIM Web Session schema)medium
AIShield - Tabular classification AI Model Evasion low suspicious vulnerability detectionmedium
AIShield - Timeseries Forecasting AI Model extraction high suspicious vulnerability detectionhigh
Alsid Password Sprayinghigh
AWSCloudTrail - Privilege escalation via DataPipeline policymedium
BloodHound Attack Path Finding - App Admin Control of Tier Zero Principalmedium
BloodHound Attack Path Finding - Owner Role on Tier Zero Resourcemedium
Cisco Cloud Security - Rare User Agent Detectedmedium
CYFIRMA - Attack Surface - Cloud Weakness High Rulehigh
CYFIRMA - Medium severity Command & Control Network Indicators with Block Recommendation Rulemedium
Cyren Feed Outage Detectionmedium
Detect changes to Connect Sync Application
Failed login attempts to Azure Portallow
Failed Logins from Unknown or Invalid Usermedium
Failed logon attempts by valid accounts within 10 minslow
Field Effect MDR Alert: ARO Alertmedium
Flare marketplace resultsmedium
Four-Eyes Authorization Request Createdhigh
Illegal Function Codes for ICS traffic (Microsoft Defender for IoT)medium
Local Admin Group Changeshigh
Malformed user agentmedium
McAfee ePO - Task errormedium
OracleDBAudit - New user accountlow
Power Platform - DLP policy updated or removedlow
PROD (TM012.1) - GPO - Enforced Outside of Tier Modelhigh
Security Event log clearedmedium
Trust Monitor Eventmedium
Ubiquiti - Connection to known malicious IP or C2medium
VMware ESXi - Root password changedhigh
WAN Accelerator Deletedinformational

A host is potentially running a crypto miner (ASIM Web Session schema)

#
Severity
medium
Time window
15m
Group by
HttpUserAgent, SrcIpAddr, SrcUsername, TimeGenerated, Url
Author
Yaron
Source
github.com/Azure/Azure-Sentinel

'This rule identifies a web request with a user agent header known to belong to a crypto miner. This indicates a crypto miner may have infected the client machine.<br>You can add custom crypto mining indicating User-Agent headers using a watchlist, for more information refer to the UnusualUserAgents Watchlist.<br><br> This analytic rule uses ASIM and supports any built-in or custom source that supports the ASIM WebSession schema (ASIM WebSession Schema)'

MITRE ATT&CK Coverage

TacticTechniques
Impact

Rule Body

id: 8cbc3215-fa58-4bd6-aaaa-f0029c351730
name: A host is potentially running a crypto miner (ASIM Web Session schema)
description: |
  'This rule identifies a web request with a user agent header known to belong to a crypto miner. This indicates a crypto miner may have infected the client machine.<br>You can add custom crypto mining indicating User-Agent headers using a watchlist, for more information refer to the [UnusualUserAgents Watchlist](https://aka.ms/ASimUnusualUserAgentsWatchlist).<br><br>   This analytic rule uses [ASIM](https://aka.ms/AboutASIM) and supports any built-in or custom source that supports the ASIM WebSession schema (ASIM WebSession Schema)'
severity: Medium
tags:
    - ParentAlert: https://github.com/Azure/Azure-Sentinel/blob/master/Detections/CiscoUmbrella/CiscoUmbrellaCryptoMinerUserAgentDetected.yaml
      version: 1.0.0
    - Schema: ASimWebSession
      SchemaVersion: 0.2.1
requiredDataConnectors:
  - connectorId: SquidProxy
    dataTypes:
      - SquidProxy_CL
  - connectorId: Zscaler
    dataTypes:
      - CommonSecurityLog

queryFrequency: 15m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Impact
relevantTechniques:
  - T1496
query: |
    let threatCategory="Cryptominer";
    let knownUserAgentsIndicators = materialize(externaldata(UserAgent:string, Category:string)
        [ @"https://raw.githubusercontent.com/Azure/Azure-Sentinel/master/Sample%20Data/Feeds/UnusualUserAgents.csv"] 
            with(format="csv", ignoreFirstRecord=True));
    let knownUserAgents=toscalar(knownUserAgentsIndicators | where Category==threatCategory | where isnotempty(UserAgent) | summarize make_list(UserAgent));
    let customUserAgents=toscalar(_GetWatchlist("UnusualUserAgents") | where SearchKey==threatCategory | extend UserAgent=column_ifexists("UserAgent","") | where isnotempty(UserAgent) | summarize make_list(UserAgent));
    let fullUAList = array_concat(knownUserAgents,customUserAgents);
    _Im_WebSession(httpuseragent_has_any=fullUAList)
    | summarize N_Events=count() by  SrcIpAddr, Url, TimeGenerated, HttpUserAgent, SrcUsername
    | extend AccountName = tostring(split(SrcUsername, "@")[0]), AccountUPNSuffix = tostring(split(SrcUsername, "@")[1])
entityMappings:
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: Url
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SrcIpAddr
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: SrcUsername
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: AccountUPNSuffix

alertDetailsOverride:
  alertDisplayNameFormat: The host {{SrcIpAddr}} is potentially running a crypto miner
  alertDescriptionFormat: The host at address {{SrcIpAddr}} sent an HTTP request to the URL {{Url}} with the HTTP user agent header {{HttpUserAgent}}. This user agent is known to be used by crypto miners and indicates crypto mining activity on the client.
customDetails:
  UserAgent: HttpUserAgent

eventGroupingSettings:
  aggregationKind: AlertPerResult
version: 1.1.4
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: Yaron
    support:
        tier: Community
    categories:
        domains: [ "Security - Threat Protection" ]

Stages and Predicates

Parameters

let threatCategory = "Cryptominer";
let fullUAList = array_concat(knownUserAgents,customUserAgents);

Let Binding: knownUserAgentsIndicators

let knownUserAgentsIndicators = materialize(externaldata(UserAgent:string, Category:string)
    [ @"https://raw.githubusercontent.com/Azure/Azure-Sentinel/master/Sample%20Data/Feeds/UnusualUserAgents.csv"] 
        with(format="csv", ignoreFirstRecord=True));

Let Binding: knownUserAgents

let knownUserAgents = toscalar(knownUserAgentsIndicators | where Category==threatCategory | where isnotempty(UserAgent) | summarize make_list(UserAgent));

Let Binding: customUserAgents

let customUserAgents = toscalar(_GetWatchlist("UnusualUserAgents") | where SearchKey==threatCategory | extend UserAgent=column_ifexists("UserAgent","") | where isnotempty(UserAgent) | summarize make_list(UserAgent));

Stage 1: source

_Im_WebSession(httpuseragent_has_any=fullUAList)

Stage 2: summarize

| summarize N_Events=count() by  SrcIpAddr, Url, TimeGenerated, HttpUserAgent, SrcUsername

Stage 3: extend

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

Output Fields

These fields are emitted when the rule matches.

FieldSource
HttpUserAgentsummarize
N_Eventssummarize
SrcIpAddrsummarize
SrcUsernamesummarize
TimeGeneratedsummarize
Urlsummarize
AccountNameextend
AccountUPNSuffixextend

AIShield - Tabular classification AI Model Evasion low suspicious vulnerability detection

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

'This alert creates an incident when Tabular classification AI Model Evasion Low suspicious, medium severity vulnerability detected from the AIShield.'

Rule Body

id: af245eff-0db9-4df8-82e6-998185cac332
name: AIShield - Tabular classification AI Model Evasion low suspicious vulnerability detection
description: |
  'This alert creates an incident when Tabular classification AI Model Evasion Low suspicious, medium severity vulnerability detected from the AIShield.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: BoschAIShield
    dataTypes:
      - AIShield
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: |
  AIShield
  | where Message has 'Tabular Classification AI Model Evasion Attack Identified'
  | where Severity =~ 'Medium'
  | where SuspiciousLevel =~ 'low suspicious attack'
  | extend NTDomain = tostring(split(Computer, '\\')[0]), HostName = tostring(split(Computer, '.')[0])
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: HostName
      - identifier: NTDomain
        columnName: NTDomain
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceName
eventGroupingSettings:
  aggregationKind: SingleAlert
alertDetailsOverride:
  alertDisplayNameFormat: AIShield - Tabular Classification AI Model Evasion low suspicious vulnerability detected.
  alertDescriptionFormat: |
    This query detects Tabular Classification AI Model Evasion low suspicious, medium severity alert from AIShield generated at {{TimeGenerated}}.\n\nPlease check the source for more information and investigate further.
  alertTacticsColumnName: null
  alertSeverityColumnName: Severity
version: 1.0.4
kind: Scheduled

Stages and Predicates

Stage 1: source

AIShield

Stage 2: where

| where Message has 'Tabular Classification AI Model Evasion Attack Identified'

Stage 3: where

| where Severity =~ 'Medium'

Stage 4: where

| where SuspiciousLevel =~ 'low suspicious attack'

Stage 5: extend

| extend NTDomain = tostring(split(Computer, '\\')[0]), HostName = tostring(split(Computer, '.')[0])

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
HostNameextend
NTDomainextend

AIShield - Timeseries Forecasting AI Model extraction high suspicious vulnerability detection

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

'This alert creates an incident when Timeseries Forecasting AI Model extraction high suspicious, high severity vulnerability detected from the AIShield.'

Rule Body

id: 37118ef6-73b4-49aa-b13b-cdeeeea580df
name: AIShield - Timeseries Forecasting AI Model extraction high suspicious vulnerability detection
description: |
  'This alert creates an incident when Timeseries Forecasting AI Model extraction high suspicious, high severity vulnerability detected from the AIShield.'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: BoschAIShield
    dataTypes:
      - AIShield
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: |
  AIShield
  | where Message has 'Timeseries Forecasting AI Model Extraction Attack Identified'
  | where Severity =~ 'High'
  | where SuspiciousLevel =~ 'high suspicious attack'
  | extend NTDomain = tostring(split(Computer, '\\')[0]), HostName = tostring(split(Computer, '.')[0])
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: HostName
      - identifier: NTDomain
        columnName: NTDomain
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceName
eventGroupingSettings:
  aggregationKind: SingleAlert
alertDetailsOverride:
  alertDisplayNameFormat: AIShield -Timeseries Forecasting AI Model extraction high suspicious vulnerability detected.
  alertDescriptionFormat: |
    This query detects Timeseries Forecasting AI Model extraction high suspicious, high severity alert from AIShield generated at {{TimeGenerated}}.\n\nPlease check the source for more information and investigate further.
  alertTacticsColumnName: null
  alertSeverityColumnName: Severity
version: 1.0.4
kind: Scheduled

Stages and Predicates

Stage 1: source

AIShield

Stage 2: where

| where Message has 'Timeseries Forecasting AI Model Extraction Attack Identified'

Stage 3: where

| where Severity =~ 'High'

Stage 4: where

| where SuspiciousLevel =~ 'high suspicious attack'

Stage 5: extend

| extend NTDomain = tostring(split(Computer, '\\')[0]), HostName = tostring(split(Computer, '.')[0])

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
HostNameextend
NTDomainextend

Alsid Password Spraying

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

'Searches for Password spraying attacks'

MITRE ATT&CK Coverage

TacticTechniques
Credential Access

Rule Body

id: 9e20eb4e-cc0d-4349-a99d-cad756859dfb
name: Alsid Password Spraying
description: |
    'Searches for Password spraying attacks'
severity: High
requiredDataConnectors:
  - connectorId: AlsidForAD
    dataTypes:
      - AlsidForADLog_CL
queryFrequency: 2h
queryPeriod: 2h
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - CredentialAccess
relevantTechniques:
  - T1110.003
query: |
    afad_parser
    | where MessageType == 2 and Codename == "Password Spraying"
    | extend HostName = tostring(split(Host, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(Host, '.'), 1, -1), '.'))
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: DnsDomain
version: 1.0.1
kind: Scheduled

Stages and Predicates

Stage 1: source

afad_parser

Stage 2: where

| where MessageType == 2 and Codename == "Password Spraying"

Stage 3: extend

| extend HostName = tostring(split(Host, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(Host, '.'), 1, -1), '.'))

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Codenameeq
  • Password Spraying corpus 3 (kusto 3)
field:"Codename" kind:eq value:"Password Spraying"
MessageTypeeq
  • 2 corpus 21 (kusto 21)
field:"MessageType" kind:eq value:"2"

Output Fields

These fields are emitted when the rule matches.

FieldSource
DnsDomainextend
HostNameextend

AWSCloudTrail - Privilege escalation via DataPipeline policy

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

Detects inline IAM policy updates that combine Data Pipeline permissions with IAM privileges such as iam:PassRole. This sequence can enable indirect execution and elevated access pathways, indicating potential privilege escalation activity.

MITRE ATT&CK Coverage

TacticTechniques
Privilege Escalation

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: 48896551-1c28-4a09-8388-e51e5a927d23
name: AWSCloudTrail - Privilege escalation via DataPipeline policy
description: |
  Detects inline IAM policy updates that combine Data Pipeline permissions with IAM privileges such as
  iam:PassRole. This sequence can enable indirect execution and elevated access pathways, indicating potential
  privilege escalation activity.
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 has "iam:*" or Action has "iam:PassRole") and Action has "datapipeline:*") or ((Action has "iam:*" or Action has "iam:PassRole") and Action has "datapipeline:CreatePipeline" and Action has "datapipeline:PutPipelineDefinition" and Action has "datapipeline:ActivatePipeline") or ((Action contains "iam:*" or Action contains "iam:PassRole") and Action contains "datapipeline:Create*" and Action contains "datapipeline:Put*" and Action contains "datapipeline:Activate*")) and Resource == "*" and isempty(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 Data Pipeline privilege escalation policy update by {{AccountName}}'
  alertDescriptionFormat: 'Detected {{EventName}} Event, updating inline Data Pipeline 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 has "iam:*" or Action has "iam:PassRole") and Action has "datapipeline:*") or ((Action has "iam:*" or Action has "iam:PassRole") and Action has "datapipeline:CreatePipeline" and Action has "datapipeline:PutPipelineDefinition" and Action has "datapipeline:ActivatePipeline") or ((Action contains "iam:*" or Action contains "iam:PassRole") and Action contains "datapipeline:Create*" and Action contains "datapipeline:Put*" and Action contains "datapipeline:Activate*")) and Resource == "*" and isempty(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
  • datapipeline:Activate*
  • datapipeline:Create*
  • datapipeline:Put*
  • iam:*
  • iam:PassRole
field:"Action" kind:contains
Actionmatch
  • datapipeline:* transforms: term
  • datapipeline:ActivatePipeline transforms: term
  • datapipeline:CreatePipeline transforms: term
  • datapipeline:PutPipelineDefinition transforms: term
  • iam:* transforms: term
  • iam:PassRole transforms: term
field:"Action" kind:match
Conditionis_null
  • (no value, null check)
field:"Condition" kind:is_null
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

BloodHound Attack Path Finding - App Admin Control of Tier Zero Principal

#

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 - App Admin Control of Tier Zero Principal

Rule Body

id: ba785c29-37f7-469b-b437-495bfdf136eb
name: BloodHound Attack Path Finding - App Admin Control of Tier Zero Principal
description: BloodHound Attack Path Finding - App Admin Control of Tier Zero Principal
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: BloodHoundEnterprise
    dataTypes:
      - BHEAttackPathsData_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: BHEAttackPathsData_CL | where Finding == "AzureT0AppAdmin"
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 == "AzureT0AppAdmin"

Indicators

These rows show field, operator, and value matches.

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

BloodHound Attack Path Finding - Owner Role on Tier Zero Resource

#

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 - Owner Role on Tier Zero Resource

Rule Body

id: cc168d7f-59d1-4472-be98-854869e62907
name: BloodHound Attack Path Finding - Owner Role on Tier Zero Resource
description: BloodHound Attack Path Finding - Owner Role on Tier Zero Resource
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: BloodHoundEnterprise
    dataTypes:
      - BHEAttackPathsData_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: BHEAttackPathsData_CL | where Finding == "AzureT0Owner"
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 == "AzureT0Owner"

Indicators

These rows show field, operator, and value matches.

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

Cisco Cloud Security - Rare User Agent Detected

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

'Rule helps to detect a rare user-agents indicating web browsing activity by an unusual process other than a web browser.'

MITRE ATT&CK Coverage

Rule Body

id: 8c8de3fa-6425-4623-9cd9-45de1dd0569a
name: Cisco Cloud Security - Rare User Agent Detected
description: |
  'Rule helps to detect a rare user-agents indicating web browsing activity by an unusual process other than a web browser.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: CiscoUmbrellaDataConnector
    dataTypes:
      - Cisco_Umbrella_proxy_CL
queryFrequency: 1d
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
  - Exfiltration
relevantTechniques:
  - T1071.001
  - T1041
query: |
  let user_agents_list = Cisco_Umbrella
  | where EventType == "proxylogs"
  | summarize count() by HttpUserAgentOriginal
  | summarize make_list(HttpUserAgentOriginal);
  Cisco_Umbrella
  | where EventType == "proxylogs"
  | where HttpUserAgentOriginal !in (user_agents_list)
  | extend Message = "Rare User Agent"
  | project TimeGenerated, Message, SrcIpAddr, DstIpAddr, UrlOriginal, HttpUserAgentOriginal
entityMappings:
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: UrlOriginal
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SrcIpAddr
version: 1.1.3
kind: Scheduled

Stages and Predicates

Let Binding: user_agents_list used in Stage 3

let user_agents_list = Cisco_Umbrella
| where EventType == "proxylogs"
| summarize count() by HttpUserAgentOriginal
| summarize make_list(HttpUserAgentOriginal);

Stage 1: source

Cisco_Umbrella

Stage 2: where

| where EventType == "proxylogs"

Stage 3: where

| where HttpUserAgentOriginal !in (user_agents_list)

Stage 4: extend

| extend Message = "Rare User Agent"

Stage 5: project

| project TimeGenerated, Message, SrcIpAddr, DstIpAddr, UrlOriginal, HttpUserAgentOriginal

Indicators

These rows show field, operator, and value matches.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
HttpUserAgentOriginalequser_agents_listexcludes:HttpUserAgentOriginal field:"HttpUserAgentOriginal" value:"user_agents_list"

Output Fields

These fields are emitted when the rule matches.

FieldSource
DstIpAddrproject
HttpUserAgentOriginalproject
Messageproject
SrcIpAddrproject
TimeGeneratedproject
UrlOriginalproject

CYFIRMA - Attack Surface - Cloud Weakness High Rule

#

This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.

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

"This rule detects cloud storage buckets (e.g., AWS S3) that are publicly accessible without authentication. Such misconfigurations can lead to data exfiltration, compliance violations, and reputational damage. The detection is based on Cyfirma's Attack Surface Intelligence."

MITRE ATT&CK Coverage

Rule Body

id: 87cd8b10-90f6-4967-a4a7-2142e848ec8f
name: CYFIRMA - Attack Surface - Cloud Weakness High Rule
description: |
  "This rule detects cloud storage buckets (e.g., AWS S3) that are publicly accessible without authentication. 
  Such misconfigurations can lead to data exfiltration, compliance violations, and reputational damage. 
  The detection is based on Cyfirma's Attack Surface Intelligence."
version: 1.0.1
kind: Scheduled
severity: High
requiredDataConnectors:
  - connectorId: CyfirmaAttackSurfaceAlertsConnector
    dataTypes:
      - CyfirmaASCloudWeaknessAlerts_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - InitialAccess
  - Collection
  - Discovery
  - Exfiltration
relevantTechniques:
  - T1087
  - T1087.004
query: |
  // High Severity - Attack Surface - Cloud Weakness - Unauthorized Public Cloud Storage Exposure Detected
  let timeFrame = 5m;
  CyfirmaASCloudWeaknessAlerts_CL
  | where severity == 'Critical' and TimeGenerated between (ago(timeFrame) .. now())
  | extend
      Description=description,
      FirstSeen=first_seen,
      LastSeen=last_seen,
      RiskScore=risk_score,
      Domain=asset_name,
      AlertUID=alert_uid,
      UID=uid,
      Source=source,
      SourceType=source_type,
      CreatedDate=created_date,
      Impact=impact,
      ProviderName='CYFIRMA',
      ProductName='DeCYFIR/DeTCT'
  | project
      TimeGenerated,
      Description,
      Domain,
      RiskScore,
      FirstSeen,
      LastSeen,
      AlertUID,
      UID,
      Source,
      SourceType,
      CreatedDate,
      Impact,
      ProviderName,
      ProductName
entityMappings:
  - entityType: DNS
    fieldMappings:
      - identifier: DomainName
        columnName: Domain
customDetails:
  TimeGenerated: TimeGenerated
  RiskScore: RiskScore
  FirstSeen: FirstSeen
  LastSeen: LastSeen
  AlertUID: AlertUID
  UID: UID
  Source: Source
  SourceType: SourceType
  CreatedDate: CreatedDate
  Impact: Impact
alertDetailsOverride:
  alertDisplayNameFormat: "CYFIRMA - High Severity Alert - Unauthorized Public Cloud Storage Exposure Detected - Domain: {{Domain}} "
  alertDescriptionFormat: "CYFIRMA - High Severity Alert - Unauthorized Public Cloud Storage Exposure Detected - {{Description}} "
  alertDynamicProperties:
    - alertProperty: ProductName
      value: ProductName
    - alertProperty: ProviderName
      value: ProviderName
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: false
    reopenClosedIncident: false
    lookbackDuration: PT5H
    matchingMethod: AllEntities
eventGroupingSettings:
  aggregationKind: AlertPerResult
  

Stages and Predicates

Parameters

let timeFrame = 5m;

Stage 1: source

CyfirmaASCloudWeaknessAlerts_CL

Stage 2: where

| where severity == 'Critical' and TimeGenerated between (ago(timeFrame) .. now())

Stage 3: extend

| extend
    Description=description,
    FirstSeen=first_seen,
    LastSeen=last_seen,
    RiskScore=risk_score,
    Domain=asset_name,
    AlertUID=alert_uid,
    UID=uid,
    Source=source,
    SourceType=source_type,
    CreatedDate=created_date,
    Impact=impact,
    ProviderName='CYFIRMA',
    ProductName='DeCYFIR/DeTCT'

Stage 4: project

| project
    TimeGenerated,
    Description,
    Domain,
    RiskScore,
    FirstSeen,
    LastSeen,
    AlertUID,
    UID,
    Source,
    SourceType,
    CreatedDate,
    Impact,
    ProviderName,
    ProductName

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AlertUIDproject
CreatedDateproject
Descriptionproject
Domainproject
FirstSeenproject
Impactproject
LastSeenproject
ProductNameproject
ProviderNameproject
RiskScoreproject
Sourceproject
SourceTypeproject
TimeGeneratedproject
UIDproject

CYFIRMA - Medium severity Command & Control Network Indicators with Block Recommendation Rule

#

This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.

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

"This analytics rule detects network-based indicators identified by CYFIRMA threat intelligence as associated with Command & Control (C2) infrastructure. These may represent attacker-controlled endpoints used to maintain persistence, exfiltrate data, or receive commands from malware-infected hosts."

MITRE ATT&CK Coverage

Rule Body

id: b278be0f-0662-47b4-9ab5-71a0f1435c3f
name: CYFIRMA - Medium severity Command & Control Network Indicators with Block Recommendation Rule
description: |
  "This analytics rule detects network-based indicators identified by CYFIRMA threat intelligence as associated with Command & Control (C2) infrastructure.
  These may represent attacker-controlled endpoints used to maintain persistence, exfiltrate data, or receive commands from malware-infected hosts."
version: 1.0.1
kind: Scheduled
severity: Medium
enabled: false
requiredDataConnectors:
  - connectorId: CyfirmaCyberIntelligenceDC
    dataTypes:
      - CyfirmaIndicators_CL
query: |
  // Network Indicators query with Block Recommended Action and C2 Roles
  let timeFrame= 5m;
  CyfirmaIndicators_CL 
  | where (ConfidenceScore < 80 and ConfidenceScore >= 50)
      and TimeGenerated between (ago(timeFrame) .. now())
      and pattern !contains 'file:hashes' and RecommendedActions has 'Block' and (Roles contains 'c2' or Roles contains 'Command & Control')
  | extend IPv4 = extract(@"ipv4-addr:value\s*=\s*'([^']+)'", 1, pattern)
  | extend IPv6 = extract(@"ipv6-addr:value\s*=\s*'([^']+)'", 1, pattern)
  | extend URL = extract(@"url:value\s*=\s*'([^']+)'", 1, pattern)
  | extend Domain = extract(@"domain-name:value\s*=\s*'([^']+)'", 1, pattern)
  | extend parsed = parse_json(extensions)
  | extend extensionKeys = bag_keys(parsed)
  | mv-expand extensionKeys
  | extend extensionKeyStr = tostring(extensionKeys)
  | extend ext = parsed[extensionKeyStr]
  | extend props = ext.properties
  | extend 
      extension_id = extensionKeyStr,
      ASN_Owner = props.asn_owner,
      ASN = props.asn,
      ProviderName = 'CYFIRMA',
      ProductName = 'DeCYFIR/DeTCT'
  | project
      IPv4,
      IPv6,
      URL,
      Domain,
      ThreatActors,
      RecommendedActions,
      Sources,
      Roles,
      Country,
      IPAbuse,
      name,
      Description,
      ConfidenceScore,
      IndicatorID,
      created,
      modified,
      valid_from,
      Tags,
      ThreatType,
      TimeGenerated,
      SecurityVendors,
      ProductName,
      ProviderName
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
suppressionDuration: 5m
suppressionEnabled: true
tactics:
  - CommandAndControl
  - InitialAccess
  - Exfiltration
relevantTechniques:
  - T1071
  - T1090
  - T1573
  - T1105
  - T1568
  - T1566
  - T1041
  - T1071.001
  - T1573.001
  - T1568.002
  - T1566.002
alertDetailsOverride:
  alertDisplayNameFormat: "High-Confidence Command & Control Network Indicators with Block Recommendation Rule - {{name}} "
  alertDescriptionFormat: "{{Description}} - {{name}} "
  alertDynamicProperties:
    - alertProperty: ProductName
      value: ProductName
    - alertProperty: ProviderName
      value: ProviderName
customDetails:
  ThreatActors: ThreatActors
  Sources: Sources
  RecommendedActions: RecommendedActions
  Roles: Roles
  Country: Country
  Description: Description
  ConfidenceScore: ConfidenceScore
  SecurityVendors: SecurityVendors
  IndicatorID: IndicatorID
  Created: created
  Modified: modified
  ValidFrom: valid_from
  Tags: Tags
  ThreatType: ThreatType
  TimeGenerated: TimeGenerated
  IPAbuse: IPAbuse
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPv4
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPv6
  - entityType: DNS
    fieldMappings:
      - identifier: DomainName
        columnName: Domain
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: URL
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: false
    reopenClosedIncident: false
    lookbackDuration: PT5H
    matchingMethod: AllEntities
eventGroupingSettings:
  aggregationKind: AlertPerResult

Stages and Predicates

Parameters

let timeFrame = 5m;

Stage 1: source

CyfirmaIndicators_CL

Stage 2: where

| where (ConfidenceScore < 80 and ConfidenceScore >= 50)
    and TimeGenerated between (ago(timeFrame) .. now())
    and pattern !contains 'file:hashes' and RecommendedActions has 'Block' and (Roles contains 'c2' or Roles contains 'Command & Control')

Stage 3: extend (6 consecutive steps)

| extend IPv4 = extract(@"ipv4-addr:value\s*=\s*'([^']+)'", 1, pattern)
| extend IPv6 = extract(@"ipv6-addr:value\s*=\s*'([^']+)'", 1, pattern)
| extend URL = extract(@"url:value\s*=\s*'([^']+)'", 1, pattern)
| extend Domain = extract(@"domain-name:value\s*=\s*'([^']+)'", 1, pattern)
| extend parsed = parse_json(extensions)
| extend extensionKeys = bag_keys(parsed)

Stage 4: mv-expand

| mv-expand extensionKeys

Stage 5: extend (4 consecutive steps)

| extend extensionKeyStr = tostring(extensionKeys)
| extend ext = parsed[extensionKeyStr]
| extend props = ext.properties
| extend 
    extension_id = extensionKeyStr,
    ASN_Owner = props.asn_owner,
    ASN = props.asn,
    ProviderName = 'CYFIRMA',
    ProductName = 'DeCYFIR/DeTCT'

Stage 6: project

| project
    IPv4,
    IPv6,
    URL,
    Domain,
    ThreatActors,
    RecommendedActions,
    Sources,
    Roles,
    Country,
    IPAbuse,
    name,
    Description,
    ConfidenceScore,
    IndicatorID,
    created,
    modified,
    valid_from,
    Tags,
    ThreatType,
    TimeGenerated,
    SecurityVendors,
    ProductName,
    ProviderName

Indicators

These rows show field, operator, and value matches.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
patterncontainsfile:hashesexcludes:pattern field:"pattern" value:"file:hashes"

Output Fields

These fields are emitted when the rule matches.

FieldSource
ConfidenceScoreproject
Countryproject
Descriptionproject
Domainproject
IPAbuseproject
IPv4project
IPv6project
IndicatorIDproject
ProductNameproject
ProviderNameproject
RecommendedActionsproject
Rolesproject
SecurityVendorsproject
Sourcesproject
Tagsproject
ThreatActorsproject
ThreatTypeproject
TimeGeneratedproject
URLproject
createdproject
modifiedproject
nameproject
valid_fromproject

Cyren Feed Outage Detection

#

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
1d
Source
github.com/Azure/Azure-Sentinel

'Detects when the Cyren threat intelligence feed has not ingested any data for 6 or more hours. This may indicate a connectivity issue with the data connector, API authentication problems, or upstream service disruption.'

MITRE ATT&CK Coverage

TacticTechniques
Defense Impairment

Rule Body

id: 7f9a0d5c-3b4c-6d7e-1f2a-e3f4a5b6c7d8
name: Cyren Feed Outage Detection
description: |
  'Detects when the Cyren threat intelligence feed has not ingested any data for 6 or more hours.
  This may indicate a connectivity issue with the data connector, API authentication problems, or upstream service disruption.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: CyrenThreatIntel
    dataTypes:
      - Cyren_Indicators_CL
queryFrequency: 1h
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - DefenseEvasion
relevantTechniques:
  - T1562
query: |
  Cyren_Indicators_CL
  | where TimeGenerated >= ago(24h)
  | summarize 
      Total = count(), 
      Latest = max(TimeGenerated)
  | extend HoursAgo = datetime_diff('hour', now(), Latest)
  | where isnotempty(Latest) and HoursAgo >= 6
  | project 
      LatestEventTime = Latest, 
      HoursSinceLastEvent = HoursAgo, 
      TotalEventsLast24h = Total,
      AlertMessage = strcat("Cyren feed has not received data for ", HoursAgo, " hours")
suppressionDuration: 1h
suppressionEnabled: false
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: false
    reopenClosedIncident: false
    lookbackDuration: 1d
    matchingMethod: Selected
eventGroupingSettings:
  aggregationKind: SingleAlert
customDetails:
  LatestEventTime: LatestEventTime
  HoursSinceLastEvent: HoursSinceLastEvent
  TotalEventsLast24h: TotalEventsLast24h
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

Cyren_Indicators_CL

Stage 2: where

| where TimeGenerated >= ago(24h)

Stage 3: summarize

| summarize 
    Total = count(), 
    Latest = max(TimeGenerated)

Stage 4: extend

| extend HoursAgo = datetime_diff('hour', now(), Latest)

Stage 5: where

| where isnotempty(Latest) and HoursAgo >= 6

Stage 6: project

| project 
    LatestEventTime = Latest, 
    HoursSinceLastEvent = HoursAgo, 
    TotalEventsLast24h = Total,
    AlertMessage = strcat("Cyren feed has not received data for ", HoursAgo, " hours")

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
HoursAgoge
  • 6 transforms: number
field:"HoursAgo" kind:ge value:"6"
Latestis_not_null
  • (no value, null check)
field:"Latest" kind:is_not_null

Output Fields

These fields are emitted when the rule matches.

FieldSource
AlertMessageproject
HoursSinceLastEventproject
LatestEventTimeproject
TotalEventsLast24hproject

Detect changes to Connect Sync Application

#
Group by
CorrelationId
Author
Robbe Van den Daele
Source
github.com/HybridBrothers/Hunting-Queries-Detection-Rules

This detection flags any changes happening on the Connect Sync Application in Entra ID. Since this is a very interesting account for attackers to abuse when moving laterally between AD DS and Entra ID, any change to the account should be investigated. We try to exclude legitimate certificate renewal processes, and a new account onboarding in the detection rule.

MITRE ATT&CK Coverage

References

Telemetry Coverage

Rule Body

// Flag everything except a renewal process and onboarding
let base = materialize (
    AuditLogs
    // Search events happening on the Sync Account
    | extend AppName = tostring(TargetResources[0].displayName)
    | where AppName startswith "ConnectSyncProvisioning_"
    // Only get cretificate or secret changes
    | where OperationName contains "Update application – Certificates and secrets management"
    // Expand the target resources and modified properties, and only use events ralted to KeyDescription
    | mv-expand TargetResources
    | extend ModifiedProperties = TargetResources.modifiedProperties
    | mv-expand ModifiedProperties
    | where ModifiedProperties.displayName == "KeyDescription"
    // Save the old and new values of the secrets on the application
    | extend OldValue = tostring(ModifiedProperties.oldValue), 
        NewValue = tostring(ModifiedProperties.newValue)
    // Save the old and new credential names in an array
    | extend OldCredentialNames = extract_all(@"DisplayName=([^\]]+)", dynamic([1]), OldValue),
        NewCredentialNames = extract_all(@"DisplayName=([^\]]+)", dynamic([1]), NewValue)
);
let newCredsAdd = (
    base
    // Flag when there are more new credentials than old ones
    | where array_length(OldCredentialNames) < array_length(NewCredentialNames)
    | project-rename NewCredAddTimeGenerated = TimeGenerated
);
let credRemove = (
    base
    // Get events where credentials are being removed
    | where array_length(OldCredentialNames) > array_length(NewCredentialNames)
    | project-rename CredRemoveTimeGenerated = TimeGenerated
);
let credRenewal = (
    // Find legitimate credential renewals
    newCredsAdd
    | join kind=leftouter credRemove on AppName
    | where CredRemoveTimeGenerated - NewCredAddTimeGenerated <= 1m
);
AuditLogs
| extend AppName = tostring(TargetResources[0].displayName)
| where AppName startswith "ConnectSyncProvisioning_"
| join kind=leftanti credRenewal on CorrelationId
// Exclude cred removes (duplicate / not interesting)
| join kind=leftanti credRemove on CorrelationId
// Exclude new deployment
| where OperationName !in ("Add service principal", "Add application")

Stages and Predicates

Let Binding: base

let base = materialize (
    AuditLogs
    | extend AppName = tostring(TargetResources[0].displayName)
    | where AppName startswith "ConnectSyncProvisioning_"
    | where OperationName contains "Update application – Certificates and secrets management"
    | mv-expand TargetResources
    | extend ModifiedProperties = TargetResources.modifiedProperties
    | mv-expand ModifiedProperties
    | where ModifiedProperties.displayName == "KeyDescription"
    | extend OldValue = tostring(ModifiedProperties.oldValue), 
        NewValue = tostring(ModifiedProperties.newValue)
    | extend OldCredentialNames = extract_all(@"DisplayName=([^\]]+)", dynamic([1]), OldValue),
        NewCredentialNames = extract_all(@"DisplayName=([^\]]+)", dynamic([1]), NewValue)
);

Let Binding: newCredsAdd

let newCredsAdd = (
    base
    | where array_length(OldCredentialNames) < array_length(NewCredentialNames)
    | project-rename NewCredAddTimeGenerated = TimeGenerated
);

Let Binding: credRemove used in Stage 5

let credRemove = (
    base
    | where array_length(OldCredentialNames) > array_length(NewCredentialNames)
    | project-rename CredRemoveTimeGenerated = TimeGenerated
);

Let Binding: credRenewal used in Stage 4

let credRenewal = (
    newCredsAdd
    | join kind=leftouter credRemove on AppName
    | where CredRemoveTimeGenerated - NewCredAddTimeGenerated <= 1m
);

Stage 1: source

AuditLogs

Stage 2: extend

| extend AppName = tostring(TargetResources[0].displayName)

Stage 3: where

| where AppName startswith "ConnectSyncProvisioning_"

Stage 4: join (negated)

join kind=leftanti (credRenewal) on CorrelationId

Stage 5: join (negated)

join kind=leftanti (credRemove) on CorrelationId

Stage 6: where

where not (OperationName in ("Add application", "Add service principal"))

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
AppNamestarts_with
  • ConnectSyncProvisioning_
field:"AppName" kind:starts_with value:"ConnectSyncProvisioning_"

Exclusions

The rule actively suppresses these predicates.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AppNameextend

Failed login attempts to Azure Portal

#
Status
available
Severity
low
Time window
7d
Source
github.com/Azure/Azure-Sentinel

Identifies failed login attempts in the Microsoft Entra ID SigninLogs to the Azure Portal. Many failed logon attempts or some failed logon attempts from multiple IPs could indicate a potential brute force attack. The following are excluded due to success and non-failure results: References: https://docs.microsoft.com/azure/active-directory/reports-monitoring/reference-sign-ins-error-codes 0 - successful logon 50125 - Sign-in was interrupted due to a password reset or password registration entry. 50140 - This error occurred due to 'Keep me signed in' interrupt when the user was signing-in.

MITRE ATT&CK Coverage

TacticTechniques
Credential Access

Telemetry Coverage

Rule Body

id: 223db5c1-1bf8-47d8-8806-bed401b356a4
name: Failed login attempts to Azure Portal
description: |
  'Identifies failed login attempts in the Microsoft Entra ID SigninLogs to the Azure Portal.  Many failed logon attempts or some failed logon attempts from multiple IPs could indicate a potential brute force attack.
  The following are excluded due to success and non-failure results:
  References: https://docs.microsoft.com/azure/active-directory/reports-monitoring/reference-sign-ins-error-codes
  0 - successful logon
  50125 - Sign-in was interrupted due to a password reset or password registration entry.
  50140 - This error occurred due to 'Keep me signed in' interrupt when the user was signing-in.'
severity: Low
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - SigninLogs
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AADNonInteractiveUserSignInLogs
queryFrequency: 1d
queryPeriod: 7d
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - CredentialAccess
relevantTechniques:
  - T1110
query: |
  let timeRange = 1d;
  let lookBack = 7d;
  let threshold_Failed = 5;
  let threshold_FailedwithSingleIP = 20;
  let threshold_IPAddressCount = 2;
  let isGUID = "[0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12}";
  let aadFunc = (tableName:string){
  let azPortalSignins = materialize(table(tableName)
  | where TimeGenerated >= ago(lookBack)
  // Azure Portal only
  | where AppDisplayName =~ "Azure Portal")
  ;
  let successPortalSignins = azPortalSignins
  | where TimeGenerated >= ago(timeRange)
  // Azure Portal only and exclude non-failure Result Types
  | where ResultType in ("0", "50125", "50140")
  // Tagging identities not resolved to friendly names
  //| extend Unresolved = iff(Identity matches regex isGUID, true, false)
  | distinct TimeGenerated, UserPrincipalName
  ;
  let failPortalSignins = azPortalSignins
  | where TimeGenerated >= ago(timeRange)
  // Azure Portal only and exclude non-failure Result Types
  | where ResultType !in ("0", "50125", "50140", "70044", "70043")
  // Tagging identities not resolved to friendly names
  | extend Unresolved = iff(Identity matches regex isGUID, true, false)
  ;
  // Verify there is no success for the same connection attempt after the fail
  let failnoSuccess = failPortalSignins | join kind= leftouter (
     successPortalSignins
  ) on UserPrincipalName
  | where TimeGenerated > TimeGenerated1 or isempty(TimeGenerated1)
  | project-away TimeGenerated1, UserPrincipalName1
  ;
  // Lookup up resolved identities from last 7 days
  let identityLookup = azPortalSignins
  | where TimeGenerated >= ago(lookBack)
  | where not(Identity matches regex isGUID)
  | summarize by UserId, lu_UserDisplayName = UserDisplayName, lu_UserPrincipalName = UserPrincipalName;
  // Join resolved names to unresolved list from portal signins
  let unresolvedNames = failnoSuccess | where Unresolved == true | join kind= inner (
     identityLookup
  ) on UserId
  | extend UserDisplayName = lu_UserDisplayName, UserPrincipalName = lu_UserPrincipalName
  | project-away lu_UserDisplayName, lu_UserPrincipalName;
  // Join Signins that had resolved names with list of unresolved that now have a resolved name
  let u_azPortalSignins = failnoSuccess | where Unresolved == false | union unresolvedNames;
  u_azPortalSignins
  | extend DeviceDetail = todynamic(DeviceDetail), Status = todynamic(DeviceDetail), LocationDetails = todynamic(LocationDetails)
  | extend Status = strcat(ResultType, ": ", ResultDescription), OS = tostring(DeviceDetail.operatingSystem), Browser = tostring(DeviceDetail.browser)
  | extend State = tostring(LocationDetails.state), City = tostring(LocationDetails.city), Region = tostring(LocationDetails.countryOrRegion)
  | extend FullLocation = strcat(Region,'|', State, '|', City)  
  | summarize TimeGenerated = make_list(TimeGenerated,100), Status = make_list(Status,100), IPAddresses = make_list(IPAddress,100), IPAddressCount = dcount(IPAddress), FailedLogonCount = count()
  by UserPrincipalName, UserId, UserDisplayName, AppDisplayName, Browser, OS, FullLocation, Type
  | mvexpand TimeGenerated, IPAddresses, Status
  | extend TimeGenerated = todatetime(tostring(TimeGenerated)), IPAddress = tostring(IPAddresses), Status = tostring(Status)
  | project-away IPAddresses
  | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by UserPrincipalName, UserId, UserDisplayName, Status, FailedLogonCount, IPAddress, IPAddressCount, AppDisplayName, Browser, OS, FullLocation, Type
  | where (IPAddressCount >= threshold_IPAddressCount and FailedLogonCount >= threshold_Failed) or FailedLogonCount >= threshold_FailedwithSingleIP
  | extend Name = tostring(split(UserPrincipalName,'@',0)[0]), UPNSuffix = tostring(split(UserPrincipalName,'@',1)[0])
  };
  let aadSignin = aadFunc("SigninLogs");
  let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");
  union isfuzzy=true aadSignin, aadNonInt
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: UserPrincipalName
      - identifier: Name
        columnName: Name
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPAddress
version: 1.0.7
kind: Scheduled

Stages and Predicates

Parameters

let timeRange = 1d;
let lookBack = 7d;
let threshold_Failed = 5;
let threshold_FailedwithSingleIP = 20;
let threshold_IPAddressCount = 2;
let isGUID = "[0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12}";
let aadSignin = aadFunc("SigninLogs");
let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");

let failPortalSignins, let failnoSuccess, let unresolvedNames and let u_azPortalSignins are inlined into the numbered stages below.

Let Binding: aadFunc used in Stages 1, 2, 3

let aadFunc = (tableName:string){
let azPortalSignins = materialize(table(tableName)
| where TimeGenerated >= ago(lookBack)
| where AppDisplayName =~ "Azure Portal");

Let Binding: successPortalSignins

let successPortalSignins = azPortalSignins
| where TimeGenerated >= ago(timeRange)
| where ResultType in ("0", "50125", "50140")
| distinct TimeGenerated, UserPrincipalName;

Let Binding: identityLookup

let identityLookup = azPortalSignins
| where TimeGenerated >= ago(lookBack)
| where not(Identity matches regex isGUID)
| summarize by UserId, lu_UserDisplayName = UserDisplayName, lu_UserPrincipalName = UserPrincipalName;

Stage 1: union

union of 2 branches

Stage 2: source

aadFunc(...)

Stage 3: source

aadFunc(...)

Failed Logins from Unknown or Invalid User

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

This rule alerts when an unknown or invalid user generates more than 15 failed Okta management console login attempts from the same IP address within 5 minutes.

MITRE ATT&CK Coverage

TacticTechniques
Credential Access

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: 884be6e7-e568-418e-9c12-89229865ffde
name: Failed Logins from Unknown or Invalid User
description: |
  This rule alerts when an unknown or invalid user generates more than 15 failed Okta management console login attempts from the same IP address within 5 minutes.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: OktaSSO
    dataTypes:
      - Okta_CL
  - connectorId: OktaSSOv2
    dataTypes:
      - OktaSSO
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CredentialAccess
relevantTechniques:
  - T1110.001
query: |
  // Adjust threshold for failed logins to reduce noise. This can be customized based on the organization's typical login patterns.
  let FailureThreshold = 15;
  // Adjust list of allowed users to reduce false positives. This can include service accounts, known test accounts, or any other accounts that are expected to generate failed login attempts.
  let AllowedUsers = dynamic([]);
  let FailedLogins = OktaSSO
  | where eventType_s =~ "user.session.start" and outcome_reason_s =~ "VERIFICATION_ERROR"
  | where isnotempty(actor_alternateId_s) and actor_alternateId_s !in (AllowedUsers)
  | summarize count() by actor_alternateId_s, client_ipAddress_s, bin(TimeGenerated, 5m)
  | where count_ > FailureThreshold
  | project client_ipAddress_s, actor_alternateId_s;
  OktaSSO
  | join kind=inner (FailedLogins) on client_ipAddress_s, actor_alternateId_s
  | where eventType_s =~ "user.session.start" and outcome_reason_s =~ "VERIFICATION_ERROR"
  | summarize FailedLoginCount = count(), City = take_any(column_ifexists('client_geographicalContext_city_s', "")), Country = take_any(column_ifexists('client_geographicalContext_country_s', "")), LastSeen = max(column_ifexists('TimeGenerated', now())) by actor_alternateId_s, client_ipAddress_s
  | sort by LastSeen desc
  | extend AccountName = tostring(split(actor_alternateId_s, "@")[0]), AccountUPNSuffix = tostring(split(actor_alternateId_s, "@")[1])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: AccountUPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: client_ipAddress_s
customDetails:
  FailedLoginCount: FailedLoginCount
  City: City
  Country: Country
alertDetailsOverride:
  alertDisplayNameFormat: 'Failed logins from unknown or invalid user: {{actor_alternateId_s}} from IP {{client_ipAddress_s}}'
  alertDescriptionFormat: 'Account {{actor_alternateId_s}} generated {{FailedLoginCount}} failed logins from IP {{client_ipAddress_s}} in the last 5 minutes.'
version: 1.1.2
kind: Scheduled

Stages and Predicates

Parameters

let FailureThreshold = 15;
let AllowedUsers = dynamic([]);

Let Binding: FailedLogins used in Stage 2

let FailedLogins = OktaSSO
| where eventType_s =~ "user.session.start" and outcome_reason_s =~ "VERIFICATION_ERROR"
| where isnotempty(actor_alternateId_s) and actor_alternateId_s !in (AllowedUsers)
| summarize count() by actor_alternateId_s, client_ipAddress_s, bin(TimeGenerated, 5m)
| where count_ > FailureThreshold
| project client_ipAddress_s, actor_alternateId_s;

Stage 1: source

OktaSSO

Stage 2: join

| join kind=inner (FailedLogins) on client_ipAddress_s, actor_alternateId_s

Stage 3: where

where eventType_s =~ "user.session.start" and outcome_reason_s =~ "VERIFICATION_ERROR"

Stage 4: summarize

summarize City, Country, FailedLoginCount, LastSeen by actor_alternateId_s, client_ipAddress_s

Stage 5: sort

sort by LastSeen

Stage 6: extend

extend AccountName, AccountUPNSuffix

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
actor_alternateId_sis_not_null
  • (no value, null check)
field:"actor_alternateId_s" kind:is_not_null
count_gt
  • 15 transforms: number
field:"count_" kind:gt value:"15"
eventType_seq
  • user.session.start
  • user.session.start corpus 4 (kusto 3, elastic 1)
field:"eventType_s" kind:eq
outcome_reason_seq
  • VERIFICATION_ERROR
  • VERIFICATION_ERROR
field:"outcome_reason_s" kind:eq

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
actor_alternateId_seq[]excludes:actor_alternateId_s field:"actor_alternateId_s" value:"[]"

Output Fields

These fields are emitted when the rule matches.

FieldSource
Citysummarize
Countrysummarize
FailedLoginCountsummarize
LastSeensummarize
actor_alternateId_ssummarize
client_ipAddress_ssummarize
AccountNameextend
AccountUPNSuffixextend

Failed logon attempts by valid accounts within 10 mins

#
Severity
low
Time window
10m
Group by
Account, Activity, Computer, EventID, IpAddress, LogonProcessName, LogonType, LogonTypeName, Reason, ResourceId, SourceComputerId, Status, SubStatus, TargetAccount, TargetDomainName, TargetUserName, WorkstationName
Author
Microsoft Security Research
Source
github.com/Azure/Azure-Sentinel

Identifies when failed logon attempts are 20 or higher during a 10 minute period (2 failed logons per minute minimum) from valid account.

MITRE ATT&CK Coverage

TacticTechniques
Credential Access

Telemetry Coverage

Rule Body

id: 0777f138-e5d8-4eab-bec1-e11ddfbc2be2
name: Failed logon attempts by valid accounts within 10 mins
description: |
  'Identifies when failed logon attempts are 20 or higher during a 10 minute period (2 failed logons per minute minimum) from valid account.'
severity: Low
requiredDataConnectors:
  - connectorId: SecurityEvents
    dataTypes:
      - SecurityEvent
  - connectorId: WindowsSecurityEvents
    dataTypes:
      - SecurityEvent
  - connectorId: WindowsForwardedEvents
    dataTypes:
      - WindowsEvent
queryFrequency: 10m
queryPeriod: 10m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CredentialAccess
relevantTechniques:
  - T1110
query: |
  let threshold = 20;
  let ReasontoSubStatus = datatable(SubStatus: string, Reason: string) [
      "0xc000005e", "There are currently no logon servers available to service the logon request.",
      "0xc0000064", "User logon with misspelled or bad user account",
      "0xc000006a", "User logon with misspelled or bad password",
      "0xc000006d", "Bad user name or password",
      "0xc000006e", "Unknown user name or bad password",
      "0xc000006f", "User logon outside authorized hours",
      "0xc0000070", "User logon from unauthorized workstation",
      "0xc0000071", "User logon with expired password",
      "0xc0000072", "User logon to account disabled by administrator",
      "0xc00000dc", "Indicates the Sam Server was in the wrong state to perform the desired operation",
      "0xc0000133", "Clocks between DC and other computer too far out of sync",
      "0xc000015b", "The user has not been granted the requested logon type (aka logon right) at this machine",
      "0xc000018c", "The logon request failed because the trust relationship between the primary domain and the trusted domain failed",
      "0xc0000192", "An attempt was made to logon, but the Netlogon service was not started",
      "0xc0000193", "User logon with expired account",
      "0xc0000224", "User is required to change password at next logon",
      "0xc0000225", "Evidently a bug in Windows and not a risk",
      "0xc0000234", "User logon with account locked",
      "0xc00002ee", "Failure Reason: An Error occurred during Logon",
      "0xc0000413", "Logon Failure: The machine you are logging onto is protected by an authentication firewall. The specified account is not allowed to authenticate to the machine"
  ];
  (union isfuzzy=true
      (SecurityEvent
      | where EventID == 4625
      | where AccountType =~ "User"
      | where SubStatus !~ '0xc0000064' and Account !in ('\\', '-\\-')
      // SubStatus '0xc0000064' signifies 'Account name does not exist'
      | extend
          ResourceId = column_ifexists("_ResourceId", _ResourceId),
          SourceComputerId = column_ifexists("SourceComputerId", SourceComputerId),
          SubStatus = tolower(SubStatus)
      | lookup ReasontoSubStatus on SubStatus
      | extend coalesce(Reason, strcat('Unknown reason substatus: ', SubStatus))
      | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), FailedLogonCount = count() by bin(TimeGenerated,10m), EventID,
          Activity, Computer, Account, TargetAccount, TargetUserName, TargetDomainName,
          LogonType, LogonTypeName, LogonProcessName, Status, SubStatus, Reason, ResourceId, SourceComputerId, WorkstationName, IpAddress
      | where FailedLogonCount >= threshold
      ),
      (
      (WindowsEvent
      | where EventID == 4625 and not(EventData has '0xc0000064')
      | extend TargetAccount = strcat(tostring(EventData.TargetDomainName), "\\", tostring(EventData.TargetUserName))
      | extend TargetUserSid = tostring(EventData.TargetUserSid)
      | extend AccountType=case(EventData.TargetUserName endswith "$" or TargetUserSid in ("S-1-5-18", "S-1-5-19", "S-1-5-20"), "Machine", isempty(TargetUserSid), "", "User")
      | where AccountType =~ "User"
      | extend SubStatus = tostring(EventData.SubStatus)
      | where SubStatus !~ '0xc0000064' and TargetAccount !in ('\\', '-\\-')
      // SubStatus '0xc0000064' signifies 'Account name does not exist'
      | extend
          ResourceId = column_ifexists("_ResourceId", _ResourceId),
          SourceComputerId = column_ifexists("SourceComputerId", ""),
          SubStatus = tolower(SubStatus)
      | lookup ReasontoSubStatus on SubStatus
      | extend coalesce(Reason, strcat('Unknown reason substatus: ', SubStatus))
      | extend Activity="4625 - An account failed to log on."
      | extend TargetUserName = tostring(EventData.TargetUserName)
      | extend TargetDomainName = tostring(EventData.TargetDomainName)
      | extend LogonType = tostring(EventData.LogonType)
      | extend Status= tostring(EventData.Status)
      | extend LogonProcessName = tostring(EventData.LogonProcessName)
      | extend WorkstationName = tostring(EventData.WorkstationName)
      | extend IpAddress = tostring(EventData.IpAddress)
      | extend LogonTypeName=case(
        LogonType == 2, "2 - Interactive",
        LogonType == 3, "3 - Network",
        LogonType == 4, "4 - Batch",
        LogonType == 5, "5 - Service",
        LogonType == 7, "7 - Unlock",
        LogonType == 8, "8 - NetworkCleartext",
        LogonType == 9, "9 - NewCredentials",
        LogonType == 10, "10 - RemoteInteractive",
        LogonType == 11, "11 - CachedInteractive",
        tostring(LogonType)
      )
      | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), FailedLogonCount = count() by bin(TimeGenerated,10m), EventID,
          Activity, Computer, TargetAccount, TargetUserName, TargetDomainName,
          LogonType, LogonTypeName, LogonProcessName, Status, SubStatus, Reason, ResourceId, SourceComputerId, WorkstationName, IpAddress
      | where FailedLogonCount >= threshold
      )))
  | summarize arg_max(TimeGenerated, *) by Computer, TargetAccount, TargetUserName, TargetDomainName
  | extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
  | extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: TargetAccount
      - identifier: Name
        columnName: TargetUserName
      - identifier: NTDomain
        columnName: TargetDomainName
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: Computer
      - identifier: HostName
        columnName: HostName
      - identifier: NTDomain
        columnName: HostNameDomain
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IpAddress
version: 1.2.5
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: Microsoft Security Research
    support:
        tier: Community
    categories:
        domains: [ "Security - Others", "Identity" ]

Stages and Predicates

Parameters

let threshold = 20;

Let Binding: ReasontoSubStatus

let ReasontoSubStatus = datatable(SubStatus: string, Reason: string) [
    "0xc000005e", "There are currently no logon servers available to service the logon request.",
    "0xc0000064", "User logon with misspelled or bad user account",
    "0xc000006a", "User logon with misspelled or bad password",
    "0xc000006d", "Bad user name or password",
    "0xc000006e", "Unknown user name or bad password",
    "0xc000006f", "User logon outside authorized hours",
    "0xc0000070", "User logon from unauthorized workstation",
    "0xc0000071", "User logon with expired password",
    "0xc0000072", "User logon to account disabled by administrator",
    "0xc00000dc", "Indicates the Sam Server was in the wrong state to perform the desired operation",
    "0xc0000133", "Clocks between DC and other computer too far out of sync",
    "0xc000015b", "The user has not been granted the requested logon type (aka logon right) at this machine",
    "0xc000018c", "The logon request failed because the trust relationship between the primary domain and the trusted domain failed",
    "0xc0000192", "An attempt was made to logon, but the Netlogon service was not started",
    "0xc0000193", "User logon with expired account",
    "0xc0000224", "User is required to change password at next logon",
    "0xc0000225", "Evidently a bug in Windows and not a risk",
    "0xc0000234", "User logon with account locked",
    "0xc00002ee", "Failure Reason: An Error occurred during Logon",
    "0xc0000413", "Logon Failure: The machine you are logging onto is protected by an authentication firewall. The specified account is not allowed to authenticate to the machine"
];

Union isfuzzy=true (2 Sources)

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

Leg 1: SecurityEvent

SecurityEvent
    | where EventID == 4625
    | where AccountType =~ "User"
    | where SubStatus !~ '0xc0000064' and Account !in ('\\', '-\\-')
    | extend
        ResourceId = column_ifexists("_ResourceId", _ResourceId),
        SourceComputerId = column_ifexists("SourceComputerId", SourceComputerId),
        SubStatus = tolower(SubStatus)
    | lookup ReasontoSubStatus on SubStatus
    | extend coalesce(Reason, strcat('Unknown reason substatus: ', SubStatus))
    | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), FailedLogonCount = count() by bin(TimeGenerated,10m), EventID,
        Activity, Computer, Account, TargetAccount, TargetUserName, TargetDomainName,
        LogonType, LogonTypeName, LogonProcessName, Status, SubStatus, Reason, ResourceId, SourceComputerId, WorkstationName, IpAddress
    | where FailedLogonCount >= threshold

Leg 2: WindowsEvent

(WindowsEvent
    | where EventID == 4625 and not(EventData has '0xc0000064')
    | extend TargetAccount = strcat(tostring(EventData.TargetDomainName), "\\", tostring(EventData.TargetUserName))
    | extend TargetUserSid = tostring(EventData.TargetUserSid)
    | extend AccountType=case(EventData.TargetUserName endswith "$" or TargetUserSid in ("S-1-5-18", "S-1-5-19", "S-1-5-20"), "Machine", isempty(TargetUserSid), "", "User")
    | where AccountType =~ "User"
    | extend SubStatus = tostring(EventData.SubStatus)
    | where SubStatus !~ '0xc0000064' and TargetAccount !in ('\\', '-\\-')
    | extend
        ResourceId = column_ifexists("_ResourceId", _ResourceId),
        SourceComputerId = column_ifexists("SourceComputerId", ""),
        SubStatus = tolower(SubStatus)
    | lookup ReasontoSubStatus on SubStatus
    | extend coalesce(Reason, strcat('Unknown reason substatus: ', SubStatus))
    | extend Activity="4625 - An account failed to log on."
    | extend TargetUserName = tostring(EventData.TargetUserName)
    | extend TargetDomainName = tostring(EventData.TargetDomainName)
    | extend LogonType = tostring(EventData.LogonType)
    | extend Status= tostring(EventData.Status)
    | extend LogonProcessName = tostring(EventData.LogonProcessName)
    | extend WorkstationName = tostring(EventData.WorkstationName)
    | extend IpAddress = tostring(EventData.IpAddress)
    | extend LogonTypeName=case(
      LogonType == 2, "2 - Interactive",
      LogonType == 3, "3 - Network",
      LogonType == 4, "4 - Batch",
      LogonType == 5, "5 - Service",
      LogonType == 7, "7 - Unlock",
      LogonType == 8, "8 - NetworkCleartext",
      LogonType == 9, "9 - NewCredentials",
      LogonType == 10, "10 - RemoteInteractive",
      LogonType == 11, "11 - CachedInteractive",
      tostring(LogonType)
    )
    | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), FailedLogonCount = count() by bin(TimeGenerated,10m), EventID,
        Activity, Computer, TargetAccount, TargetUserName, TargetDomainName,
        LogonType, LogonTypeName, LogonProcessName, Status, SubStatus, Reason, ResourceId, SourceComputerId, WorkstationName, IpAddress
    | where FailedLogonCount >= threshold
    )

Applied to the Combined Result

| summarize arg_max(TimeGenerated, *) by Computer, TargetAccount, TargetUserName, TargetDomainName
| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)

Indicators

These rows show field, operator, and value matches.

Exclusions

The rule actively suppresses these predicates.

Output Fields

These fields are emitted when the rule matches.

FieldSource
Computersummarize
TargetAccountsummarize
TargetDomainNamesummarize
TargetUserNamesummarize
DomainIndexextend
HostNameextend
HostNameDomainextend

Field Effect MDR Alert: ARO Alert

#

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

Creates an incident for each Field Effect MDR ARO alert ingested into the workspace.

MITRE ATT&CK Coverage

Rule Body

id: 6d2d6b3f-7d7b-4d4a-9b2b-9f7f3b8c2a11
name: "Field Effect MDR Alert: ARO Alert"
description: "Creates an incident for each Field Effect MDR ARO alert ingested into the workspace."
severity: Medium
status: Available
tactics:
  - Execution
  - DefenseEvasion
relevantTechniques:
  - T1059
  - T1562
requiredDataConnectors:
  - connectorId: FieldEffectCCF
    dataTypes:
      - FieldEffectAROAlerts_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
suppressionDuration: 1h
suppressionEnabled: false

query: |
  FieldEffectAROAlerts_CL
  | where TimeGenerated >= ago(5m)
  | project
      TimeGenerated,
      ARO = ID,
      TITLE = Title,
      DETAILS = DetailsMarkdown,
      URL = PortalUrl,
      SEVERITY = Severity,
      Hostname,
      IPAddress,
      LastUser,
      PRODUCT_NAME = "Field Effect MDR"

eventGroupingSettings:
  aggregationKind: SingleAlert

incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: false
    reopenClosedIncident: false
    lookbackDuration: 5h
    matchingMethod: AllEntities

alertDetailsOverride:
  alertDisplayNameFormat: "Field Effect MDR Alert: ARO-{{ARO}}: {{TITLE}}"
  alertDescriptionFormat: "{{DETAILS}}\n\nFor full information, visit: {{URL}}\n"
  alertSeverityColumnName: SEVERITY
  alertDynamicProperties:
    - alertProperty: ProductName
      value: PRODUCT_NAME

entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPAddress
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: LastUser
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: Hostname

version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

FieldEffectAROAlerts_CL

Stage 2: where

| where TimeGenerated >= ago(5m)

Stage 3: project

| project
    TimeGenerated,
    ARO = ID,
    TITLE = Title,
    DETAILS = DetailsMarkdown,
    URL = PortalUrl,
    SEVERITY = Severity,
    Hostname,
    IPAddress,
    LastUser,
    PRODUCT_NAME = "Field Effect MDR"

Output Fields

These fields are emitted when the rule matches.

FieldSource
AROproject
DETAILSproject
Hostnameproject
IPAddressproject
LastUserproject
PRODUCT_NAMEproject
SEVERITYproject
TITLEproject
TimeGeneratedproject
URLproject

Flare marketplace results

#

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

'This query searches for underground markets and shops where illicit goods and services are bought and sold.'

MITRE ATT&CK Coverage

TacticTechniques
Reconnaissance

Rule Body

id: 9265ae4d-6bb0-4c18-961d-f7aae67d1546
name: Flare marketplace results
description: |
    'This query searches for underground markets and shops where illicit goods and services are bought and sold.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: Flare
    dataTypes:
      - FireworkV2_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Reconnaissance
relevantTechniques:
  - T1593
query: |
  FireworkV2_CL
  | where notempty(uid) and RiskScore >= 3
  | extend index_name = split(uid, "/")[0]
  | where index_name == "listing"
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

FireworkV2_CL

Stage 2: where

| where notempty(uid) and RiskScore >= 3

Stage 3: extend

| extend index_name = split(uid, "/")[0]

Stage 4: where

| where index_name == "listing"

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
RiskScorege
  • 3 transforms: number
field:"RiskScore" kind:ge value:"3"
index_nameeq
  • listing
field:"index_name" kind:eq value:"listing"
uidis_not_null
  • (no value, null check)
field:"uid" kind:is_not_null

Output Fields

These fields are emitted when the rule matches.

FieldSource
index_nameextend

Four-Eyes Authorization Request Created

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

Detects when a four-eyes authorization request is created.

Rule Body

id: fe6226cd-fee7-4d0d-88d2-160311207b57
name: Four-Eyes Authorization Request Created
description: Detects when a four-eyes authorization request is created.
severity: High
status: Available
requiredDataConnectors:
- connectorId: Syslog
  dataTypes:
  - Syslog
- connectorId: SyslogAma
  dataTypes:
  - Syslog
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
  aggregationKind: AlertPerResult
tactics: []
relevantTechniques: []
query: "Veeam_GetSecurityEvents\n| where instanceId == 42402\n| extend Operation =\
  \ extract(\"Operation=\\\"([^\\\"]*)\\\"\", 1, SyslogMessage)\n| project\n    Date\
  \ = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),\n    DataSource = original_host,\n\
  \    EventId = instanceId,\n    [\"User Name\"] = user,\n    [\"Operation Name\"\
  ] = Operation,\n    MessageDetails = Description,\n   Severity = SeverityDescription"
version: 1.0.1
kind: Scheduled
customDetails:
  Date: Date
  VbrHostName: DataSource
  EventId: EventId
  MessageDetails: MessageDetails
  Severity: Severity

Stages and Predicates

Stage 1: source

Veeam_GetSecurityEvents

Stage 2: where

| where instanceId == 42402

Stage 3: extend

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

Stage 4: project

| project
    Date = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),
    DataSource = original_host,
    EventId = instanceId,
    ["User Name"] = user,
    ["Operation Name"] = Operation,
    MessageDetails = Description,
   Severity = SeverityDescription

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
DataSourceproject
Dateproject
EventIdproject
MessageDetailsproject
Operation Nameproject
Severityproject
User Nameproject

Illegal Function Codes for ICS traffic (Microsoft Defender for IoT)

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

'This alert leverages Defender for IoT to detect Illegal function codes in SCADA equipment indicating improper application configuration or malicious activity such using illegal values within a protocol to exploit a PLC vulnerability.'

MITRE ATT&CK Coverage

TacticTechniques
Impair Process Control

Rule Body

id: 70be4a31-9d2b-433b-bdc7-da8928988069
name: Illegal Function Codes for ICS traffic (Microsoft Defender for IoT)
description: |
  'This alert leverages Defender for IoT to detect Illegal function codes in SCADA equipment indicating improper application configuration or malicious activity such using illegal values within a protocol to exploit a PLC vulnerability.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: IoT
    dataTypes:
      - SecurityAlert (ASC for IoT)
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - ImpairProcessControl
relevantTechniques:
  - T0855
query: |
  let alertList = dynamic(["Function Code Not Supported by Outstation", "Illegal BACNet message", "Illegal Connection Attempt on Port 0", "Illegal DNP3 Operation", "Illegal MODBUS Operation (Exception Raised by Master)",  "Illegal MODBUS Operation (Function Code Zero)", "Incorrect Parameter Sent to Outstation", "Initiation of an Obsolete Function Code (Initialize Data)", "Initiation of an Obsolete Function Code (Save Config)", "Modbus Exception", "Unknown Object Sent to Outstation", "Usage of a Reserved Function Code",  "Usage of Improper Formatting by Outstation", "Usage of Reserved Status Flags (IIN)", "Unauthorized communication was detected by a User Defined Protocol Rule", "Unauthorized Operation was detected by a User Defined whitelist Alert", "Illegal Protocol Version", "New Activity Detected - LonTalk Network Variable", "New Activity Detected - Ovation Data Request", "New Activity Detected - Read/Write Command (AMS Index Group)", "New Activity Detected - Read/Write Command (AMS Index Offset)", "New Activity Detected - Unauthorized DeltaV Message Type", "New Activity Detected - Unauthorized DeltaV ROC Operation", "New Activity Detected - Using AMS Protocol Command", "New Activity Detected - Using Siemens SICAM Command", "New Activity Detected - Using Suitelink Protocol command", "New Activity Detected - Using Suitelink Protocol sessions", "New Activity Detected - Using Yokogawa VNetIP Command", "Omron FINS Unauthorized Command", "Toshiba Computer Link Unauthorized Command", "Unauthorized ABB Totalflow File Operation", "Unauthorized ABB Totalflow Register Operation", "Unauthorized Access to Siemens S7 Plus Object", "Unauthorized BACNet Object Access", "Unauthorized BACNet Route", "Unauthorized Emerson ROC Operation", "Unauthorized GE SRTP File Access", "Unauthorized GE SRTP Protocol Command", "Unauthorized GE SRTP System Memory Operation", "Unauthorized Mitsubishi MELSEC Command", "Unauthorized MMS Service", "Unauthorized OPC UA Activity", "Unauthorized OPC UA Request/Response", "Unauthorized Profinet Frame Type", "Unauthorized SAIA S-Bus Command", "Unauthorized Siemens S7 Execution of Control Function", "Unauthorized Siemens S7 Execution of User Defined Function", "Unauthorized Siemens S7 Plus Block Access", "Unauthorized Siemens S7 Plus Operation", "Unauthorized SNMP Operation", "Unpermitted Modbus Schneider Electric Extension", "Unpermitted Usage of ASDU Types", "Unpermitted Usage of DNP3 Function Code", "Unpermitted Usage of Modbus Function Code", "Unauthorized Operation was detected by a User Defined Rule", "Unauthorized PLC Configuration Read", "Unauthorized PLC Programming", "Unauthorized PLC Configuration Write", "Unauthorized PLC Program Upload", "Slave Device Received Illegal"]);
  SecurityAlert
  | where ProviderName == "IoTSecurity"
  | where AlertName has_any (alertList)
  | extend ExtendedProperties = parse_json(ExtendedProperties)
  | where tostring(ExtendedProperties.isNew) == "True"
  | extend DeviceId = tostring(ExtendedProperties.DeviceId), 
           SourceDeviceAddress = tostring(ExtendedProperties.SourceDeviceAddress), 
           DestDeviceAddress = tostring(ExtendedProperties.DestinationDeviceAddress), 
           RemediationSteps = tostring(parse_json(RemediationSteps)[0]), 
           Protocol = tostring(ExtendedProperties.Protocol), 
           AlertManagementUri = tostring(ExtendedProperties.AlertManagementUri)
  | project
    TimeGenerated,
    DeviceId,
    ProductName,
    ProductComponentName,
    AlertSeverity,
    AlertName,
    Description,
    Protocol,
    SourceDeviceAddress,
    DestDeviceAddress,
    RemediationSteps,
    Tactics,
    Entities,
    VendorOriginalId,
    AlertLink,
    AlertManagementUri,
    Techniques
entityMappings:
sentinelEntitiesMappings:
  - columnName: Entities
eventGroupingSettings:
  aggregationKind: AlertPerResult
customDetails:
  Sensor: DeviceId
  Protocol: Protocol
  VendorOriginalId: VendorOriginalId
  AlertManagementUri: AlertManagementUri
alertDetailsOverride:
  alertDisplayNameFormat: (MDIoT) {{AlertName}}
  alertDescriptionFormat: (MDIoT) {{Description}}
  alertTacticsColumnName: Tactics
  alertSeverityColumnName: AlertSeverity
  alertDynamicProperties: 
    - alertProperty: ProductName
      value: ProductName
    - alertProperty: RemediationSteps
      value: RemediationSteps
    - alertProperty: Techniques
      value: Techniques
    - alertProperty: ProductComponentName
      value: ProductComponentName
    - alertProperty: AlertLink
      value: AlertLink
version: 1.0.3
kind: Scheduled

Stages and Predicates

Let Binding: alertList used in Stage 3

let alertList = dynamic(["Function Code Not Supported by Outstation", "Illegal BACNet message", "Illegal Connection Attempt on Port 0", "Illegal DNP3 Operation", "Illegal MODBUS Operation (Exception Raised by Master)",  "Illegal MODBUS Operation (Function Code Zero)", "Incorrect Parameter Sent to Outstation", "Initiation of an Obsolete Function Code (Initialize Data)", "Initiation of an Obsolete Function Code (Save Config)", "Modbus Exception", "Unknown Object Sent to Outstation", "Usage of a Reserved Function Code",  "Usage of Improper Formatting by Outstation", "Usage of Reserved Status Flags (IIN)", "Unauthorized communication was detected by a User Defined Protocol Rule", "Unauthorized Operation was detected by a User Defined whitelist Alert", "Illegal Protocol Version", "New Activity Detected - LonTalk Network Variable", "New Activity Detected - Ovation Data Request", "New Activity Detected - Read/Write Command (AMS Index Group)", "New Activity Detected - Read/Write Command (AMS Index Offset)", "New Activity Detected - Unauthorized DeltaV Message Type", "New Activity Detected - Unauthorized DeltaV ROC Operation", "New Activity Detected - Using AMS Protocol Command", "New Activity Detected - Using Siemens SICAM Command", "New Activity Detected - Using Suitelink Protocol command", "New Activity Detected - Using Suitelink Protocol sessions", "New Activity Detected - Using Yokogawa VNetIP Command", "Omron FINS Unauthorized Command", "Toshiba Computer Link Unauthorized Command", "Unauthorized ABB Totalflow File Operation", "Unauthorized ABB Totalflow Register Operation", "Unauthorized Access to Siemens S7 Plus Object", "Unauthorized BACNet Object Access", "Unauthorized BACNet Route", "Unauthorized Emerson ROC Operation", "Unauthorized GE SRTP File Access", "Unauthorized GE SRTP Protocol Command", "Unauthorized GE SRTP System Memory Operation", "Unauthorized Mitsubishi MELSEC Command", "Unauthorized MMS Service", "Unauthorized OPC UA Activity", "Unauthorized OPC UA Request/Response", "Unauthorized Profinet Frame Type", "Unauthorized SAIA S-Bus Command", "Unauthorized Siemens S7 Execution of Control Function", "Unauthorized Siemens S7 Execution of User Defined Function", "Unauthorized Siemens S7 Plus Block Access", "Unauthorized Siemens S7 Plus Operation", "Unauthorized SNMP Operation", "Unpermitted Modbus Schneider Electric Extension", "Unpermitted Usage of ASDU Types", "Unpermitted Usage of DNP3 Function Code", "Unpermitted Usage of Modbus Function Code", "Unauthorized Operation was detected by a User Defined Rule", "Unauthorized PLC Configuration Read", "Unauthorized PLC Programming", "Unauthorized PLC Configuration Write", "Unauthorized PLC Program Upload", "Slave Device Received Illegal"]);

Stage 1: source

SecurityAlert

Stage 2: where

| where ProviderName == "IoTSecurity"

Stage 3: where

| where AlertName has_any (alertList)

Stage 4: extend

| extend ExtendedProperties = parse_json(ExtendedProperties)

Stage 5: where

| where tostring(ExtendedProperties.isNew) == "True"

Stage 6: extend

| extend DeviceId = tostring(ExtendedProperties.DeviceId), 
         SourceDeviceAddress = tostring(ExtendedProperties.SourceDeviceAddress), 
         DestDeviceAddress = tostring(ExtendedProperties.DestinationDeviceAddress), 
         RemediationSteps = tostring(parse_json(RemediationSteps)[0]), 
         Protocol = tostring(ExtendedProperties.Protocol), 
         AlertManagementUri = tostring(ExtendedProperties.AlertManagementUri)

Stage 7: project

| project
  TimeGenerated,
  DeviceId,
  ProductName,
  ProductComponentName,
  AlertSeverity,
  AlertName,
  Description,
  Protocol,
  SourceDeviceAddress,
  DestDeviceAddress,
  RemediationSteps,
  Tactics,
  Entities,
  VendorOriginalId,
  AlertLink,
  AlertManagementUri,
  Techniques

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
AlertNamematch
  • Function Code Not Supported by Outstation transforms: term
  • Illegal BACNet message transforms: term
  • Illegal Connection Attempt on Port 0 transforms: term
  • Illegal DNP3 Operation transforms: term
  • Illegal MODBUS Operation (Exception Raised by Master) transforms: term
  • Illegal MODBUS Operation (Function Code Zero) transforms: term
  • Illegal Protocol Version transforms: term
  • Incorrect Parameter Sent to Outstation transforms: term
  • Initiation of an Obsolete Function Code (Initialize Data) transforms: term
  • Initiation of an Obsolete Function Code (Save Config) transforms: term
  • Modbus Exception transforms: term
  • New Activity Detected - LonTalk Network Variable transforms: term corpus 2 (kusto 2)
  • New Activity Detected - Ovation Data Request transforms: term corpus 2 (kusto 2)
  • New Activity Detected - Read/Write Command (AMS Index Group) transforms: term corpus 2 (kusto 2)
  • New Activity Detected - Read/Write Command (AMS Index Offset) transforms: term corpus 2 (kusto 2)
  • New Activity Detected - Unauthorized DeltaV Message Type transforms: term corpus 2 (kusto 2)
  • New Activity Detected - Unauthorized DeltaV ROC Operation transforms: term corpus 2 (kusto 2)
  • New Activity Detected - Using AMS Protocol Command transforms: term corpus 2 (kusto 2)
  • New Activity Detected - Using Siemens SICAM Command transforms: term corpus 2 (kusto 2)
  • New Activity Detected - Using Suitelink Protocol command transforms: term corpus 2 (kusto 2)
  • New Activity Detected - Using Suitelink Protocol sessions transforms: term corpus 2 (kusto 2)
  • New Activity Detected - Using Yokogawa VNetIP Command transforms: term corpus 2 (kusto 2)
  • Omron FINS Unauthorized Command transforms: term corpus 2 (kusto 2)
  • Slave Device Received Illegal transforms: term
  • Toshiba Computer Link Unauthorized Command transforms: term corpus 2 (kusto 2)
  • Unauthorized ABB Totalflow File Operation transforms: term corpus 2 (kusto 2)
  • Unauthorized ABB Totalflow Register Operation transforms: term corpus 2 (kusto 2)
  • Unauthorized Access to Siemens S7 Plus Object transforms: term corpus 2 (kusto 2)
  • Unauthorized BACNet Object Access transforms: term corpus 2 (kusto 2)
  • Unauthorized BACNet Route transforms: term corpus 2 (kusto 2)
  • Unauthorized Emerson ROC Operation transforms: term corpus 2 (kusto 2)
  • Unauthorized GE SRTP File Access transforms: term corpus 2 (kusto 2)
  • Unauthorized GE SRTP Protocol Command transforms: term corpus 2 (kusto 2)
  • Unauthorized GE SRTP System Memory Operation transforms: term corpus 2 (kusto 2)
  • Unauthorized MMS Service transforms: term corpus 2 (kusto 2)
  • Unauthorized Mitsubishi MELSEC Command transforms: term corpus 2 (kusto 2)
  • Unauthorized OPC UA Activity transforms: term corpus 2 (kusto 2)
  • Unauthorized OPC UA Request/Response transforms: term corpus 2 (kusto 2)
  • Unauthorized Operation was detected by a User Defined Rule transforms: term corpus 2 (kusto 2)
  • Unauthorized Operation was detected by a User Defined whitelist Alert transforms: term
  • Unauthorized PLC Configuration Read transforms: term corpus 2 (kusto 2)
  • Unauthorized PLC Configuration Write transforms: term corpus 2 (kusto 2)
  • Unauthorized PLC Program Upload transforms: term corpus 2 (kusto 2)
  • Unauthorized PLC Programming transforms: term corpus 2 (kusto 2)
  • Unauthorized Profinet Frame Type transforms: term corpus 2 (kusto 2)
  • Unauthorized SAIA S-Bus Command transforms: term corpus 2 (kusto 2)
  • Unauthorized SNMP Operation transforms: term corpus 2 (kusto 2)
  • Unauthorized Siemens S7 Execution of Control Function transforms: term corpus 2 (kusto 2)
  • Unauthorized Siemens S7 Execution of User Defined Function transforms: term corpus 2 (kusto 2)
  • Unauthorized Siemens S7 Plus Block Access transforms: term corpus 2 (kusto 2)
  • Unauthorized Siemens S7 Plus Operation transforms: term corpus 2 (kusto 2)
  • Unauthorized communication was detected by a User Defined Protocol Rule transforms: term
  • Unknown Object Sent to Outstation transforms: term
  • Unpermitted Modbus Schneider Electric Extension transforms: term corpus 2 (kusto 2)
  • Unpermitted Usage of ASDU Types transforms: term corpus 2 (kusto 2)
  • Unpermitted Usage of DNP3 Function Code transforms: term corpus 2 (kusto 2)
  • Unpermitted Usage of Modbus Function Code transforms: term corpus 2 (kusto 2)
  • Usage of Improper Formatting by Outstation transforms: term
  • Usage of Reserved Status Flags (IIN) transforms: term
  • Usage of a Reserved Function Code transforms: term
field:"AlertName" kind:match
ExtendedProperties.isNeweq
  • True transforms: tostring, kusto_dynamic_path corpus 15 (kusto 15)
field:"ExtendedProperties.isNew" kind:eq value:"True"
ProviderNameeq
  • IoTSecurity corpus 15 (kusto 15)
field:"ProviderName" kind:eq value:"IoTSecurity"

Output Fields

These fields are emitted when the rule matches.

FieldSource
AlertLinkproject
AlertManagementUriproject
AlertNameproject
AlertSeverityproject
Descriptionproject
DestDeviceAddressproject
DeviceIdproject
Entitiesproject
ProductComponentNameproject
ProductNameproject
Protocolproject
RemediationStepsproject
SourceDeviceAddressproject
Tacticsproject
Techniquesproject
TimeGeneratedproject
VendorOriginalIdproject

Local Admin Group Changes

#
Status
available
Severity
high
Time window
1h
Group by
$left.AddedAccountSID, $right.NewUserSID, $right.OnPremSid
Source
github.com/Azure/Azure-Sentinel

This query searches for changes to the local administrators group. Blogpost: https://www.verboon.info/2020/09/hunting-for-local-group-membership-changes.

MITRE ATT&CK Coverage

TacticTechniques
Persistence

Telemetry Coverage

Rule Body

id: 63aa43c2-e88e-4102-aea5-0432851c541a
name: Local Admin Group Changes
description: | 
  This query searches for changes to the local administrators group.
  Blogpost: https://www.verboon.info/2020/09/hunting-for-local-group-membership-changes.
severity: High
status: Available
requiredDataConnectors:
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - IdentityInfo
      - DeviceEvents
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Persistence
relevantTechniques:
  - T1098
query: |
  let machineAccountSIDs = dynamic([
    "S-1-5-18",
    "S-1-5-20",
    "S-1-5-19"]);
  let ADAZUsers =  IdentityInfo 
  | extend DirectoryDomain = AccountDomain 
  | extend DirectoryAccount = AccountName 
  | extend OnPremSid = AccountSID
  | distinct DirectoryDomain , DirectoryAccount , OnPremSid , AccountCloudSID, AccountUPN, GivenName, Surname;
   // check for any new created or modified local accounts 
  let NewUsers =  DeviceEvents
  | where ActionType contains "UserAccountCreated" or ActionType contains "UserAccountModified"
  | extend lUserAdded = AccountName
  | extend NewUserSID = AccountSid
  | extend laccountdomain = AccountDomain
  | distinct NewUserSID, lUserAdded,laccountdomain;
  // Check for any local group changes and enrich the data with the account name obtained from the previous query
  DeviceEvents 
  | where ActionType == 'UserAccountAddedToLocalGroup'
  // Exclude machine and wellknown SIDs 
  | where (AccountSid !in (machineAccountSIDs)) and (AccountSid matches regex @"S-\d-\d+-\d+-(\d+-){1,5}\d+")
  | extend LocalGroupSID = tostring(parse_json(AdditionalFields).GroupSid)
  | extend LocalGroup = tostring(parse_json(AdditionalFields).GroupName)
  | extend AddedAccountSID = AccountSid
  | extend Actor = trim(@"[^\w]+",InitiatingProcessAccountName)
  // limit to local administrators group
  // | where LocalGroupSID contains "S-1-5-32-544"
  | join kind=leftouter    (NewUsers)
  on $left.AddedAccountSID == $right.NewUserSID
  | project TimeGenerated, DeviceName, LocalGroup,LocalGroupSID, AddedAccountSID, lUserAdded , Actor, ActionType , laccountdomain 
  | join kind=innerunique  (ADAZUsers)
  on $left.AddedAccountSID == $right.OnPremSid
  | extend UserAdded = iff(isnotempty(lUserAdded),strcat(laccountdomain,"\\", lUserAdded), strcat(DirectoryDomain,"\\", DirectoryAccount))
  | extend AccountName = iff(isnotempty(lUserAdded), lUserAdded, DirectoryAccount)
  | project TimeGenerated, DeviceName, LocalGroup, LocalGroupSID, AddedAccountSID, UserAdded ,Actor, ActionType, AccountName, laccountdomain  
  | where DeviceName !contains Actor
  // Provide details on actors that added users
  // | summarize count()  by Actor 
  // | join ADAZUsers
  // on $left.Actor == $right.DirectoryAccount 
  // | render piechart
  | extend HostName = iff(DeviceName has '.', substring(DeviceName, 0, indexof(DeviceName, '.')), DeviceName)
  | extend DnsDomain = iff(DeviceName has '.', substring(DeviceName, indexof(DeviceName, '.') + 1), "")
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: DeviceName
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: DnsDomain
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: UserAdded
      - identifier: Name
        columnName: AccountName
      - identifier: NTDomain
        columnName: laccountdomain
version: 1.0.2
kind: Scheduled

Stages and Predicates

Let Binding: machineAccountSIDs

let machineAccountSIDs = dynamic([
  "S-1-5-18",
  "S-1-5-20",
  "S-1-5-19"]);

Let Binding: ADAZUsers used in Stages 1, 9

let ADAZUsers = IdentityInfo 
| extend DirectoryDomain = AccountDomain 
| extend DirectoryAccount = AccountName 
| extend OnPremSid = AccountSID
| distinct DirectoryDomain , DirectoryAccount , OnPremSid , AccountCloudSID, AccountUPN, GivenName, Surname;

Let Binding: NewUsers used in Stages 2, 7

let NewUsers = DeviceEvents
| where ActionType contains "UserAccountCreated" or ActionType contains "UserAccountModified"
| extend lUserAdded = AccountName
| extend NewUserSID = AccountSid
| extend laccountdomain = AccountDomain
| distinct NewUserSID, lUserAdded,laccountdomain;

Stage 1: source

let ADAZUsers

Stage 2: source

let NewUsers

Stage 3: source

DeviceEvents

Stage 4: where

where ActionType == "UserAccountAddedToLocalGroup"

Stage 5: where

where not (AccountSid in ("S-1-5-18", "S-1-5-19", "S-1-5-20")) and AccountSid matches regex @"S-\d-\d+-\d+-(\d+-){1,5}\d+"

Stage 6: extend (4 consecutive steps)

extend Actor, AddedAccountSID, LocalGroup, LocalGroupSID

Stage 7: join

join kind=leftouter (NewUsers) on $left.AddedAccountSID, $right.NewUserSID

Stage 8: project

project ActionType, Actor, AddedAccountSID, DeviceName, LocalGroup, LocalGroupSID, TimeGenerated, lUserAdded, laccountdomain

Stage 9: join

join kind=innerunique (ADAZUsers) on $left.AddedAccountSID, $right.OnPremSid

Stage 10: extend

extend UserAdded
UserAdded =
ifisnotempty(lUserAdded)strcat(laccountdomain, "\\", lUserAdded)
elsestrcat(DirectoryDomain, "\\", DirectoryAccount)

Stage 11: extend

extend AccountName
AccountName =
ifisnotempty(lUserAdded)lUserAdded
elseDirectoryAccount

Stage 12: project

project AccountName, ActionType, Actor, AddedAccountSID, DeviceName, LocalGroup, LocalGroupSID, TimeGenerated, UserAdded, laccountdomain

Stage 13: where

where DeviceName !contains Actor

Stage 14: extend

extend HostName
HostName =
ifDeviceName has "."substring(DeviceName, 0, indexof(DeviceName, '.'))
elseDeviceName

Stage 15: extend

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

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
AccountSidregex_match
  • S-\d-\d+-\d+-(\d+-){1,5}\d+
field:"AccountSid" kind:regex_match value:"S-\d-\d+-\d+-(\d+-){1,5}\d+"
ActionTypecontains
  • UserAccountCreated
  • UserAccountModified
field:"ActionType" kind:contains
ActionTypeeq
  • UserAccountAddedToLocalGroup
field:"ActionType" kind:eq value:"UserAccountAddedToLocalGroup"

Exclusions

The rule actively suppresses these predicates.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountNameproject
ActionTypeproject
Actorproject
AddedAccountSIDproject
DeviceNameproject
LocalGroupproject
LocalGroupSIDproject
TimeGeneratedproject
UserAddedproject
laccountdomainproject
HostNameextend
DnsDomainextend

Malformed user agent

#
Severity
medium
Time window
1d
Group by
Account, AppDisplayName, ClientAppUsed, EventName, EventSource, Operation, OperationName, RecordType, SourceIP, Type, UserAgent, csMethod, csUriStem, host_s, httpMethod_s, requestQuery_s, requestUri_s, sSiteName
Author
Microsoft Security Research
Source
github.com/Azure/Azure-Sentinel

Malware authors will sometimes hardcode user agent string values when writing the network communication component of their malware. Malformed user agents can be an indication of such malware.

MITRE ATT&CK Coverage

Telemetry Coverage

Rule Body

id: a357535e-f722-4afe-b375-cff362b2b376
name: Malformed user agent
description: |
  'Malware authors will sometimes hardcode user agent string values when writing the network communication component of their malware. Malformed user agents can be an indication of such malware.'
severity: Medium
requiredDataConnectors:
  - connectorId: WAF
    dataTypes:
      - AzureDiagnostics
  - connectorId: Office365
    dataTypes:
      - OfficeActivity
  - connectorId: AzureActiveDirectory
    dataTypes:
      - SigninLogs
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AADNonInteractiveUserSignInLogs
  - connectorId: AWS
    dataTypes:
      - AWSCloudTrail
  - connectorId: AzureMonitor(IIS)
    dataTypes:
      - W3CIISLog
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
  - CommandAndControl
  - Execution
relevantTechniques:
  - T1189
  - T1071
  - T1203
query: |
  (union isfuzzy=true
  (OfficeActivity | where UserAgent != ""),
  (OfficeActivity
  | where RecordType in ("AzureActiveDirectory", "AzureActiveDirectoryStsLogon")
  | extend OperationName = Operation
  | parse ExtendedProperties with * 'User-Agent\\":\\"' UserAgent2 '\\' *
  | parse ExtendedProperties with * 'UserAgent",      "Value": "' UserAgent1 '"' *
  | where isnotempty(UserAgent1) or isnotempty(UserAgent2)
  | extend UserAgent = iff( RecordType == 'AzureActiveDirectoryStsLogon', UserAgent1, UserAgent2)
  | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by UserAgent, SourceIP = ClientIP, Account = UserId, Type, RecordType, Operation
  ),
  (AzureDiagnostics
  | where ResourceType =~ "APPLICATIONGATEWAYS"
  | where OperationName =~ "ApplicationGatewayAccess"
  | extend ClientIP = columnifexists("clientIP_s", "None"), UserAgent = columnifexists("userAgent_s", "None")
  | where UserAgent != '-'
  | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by UserAgent, SourceIP = ClientIP,  requestUri_s, httpMethod_s, host_s, requestQuery_s, Type
  ),
  (
  W3CIISLog
  | where isnotempty(csUserAgent)
  | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by UserAgent = csUserAgent, SourceIP = cIP, Account = csUserName, Type, sSiteName, csMethod, csUriStem
  ),
  (
  AWSCloudTrail
  | where isnotempty(UserAgent)
  | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by UserAgent, SourceIP = SourceIpAddress, Account = UserIdentityUserName, Type, EventSource, EventName
  ),
  (SigninLogs
  | where isnotempty(UserAgent)
  | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by UserAgent, SourceIP = IPAddress, Account = UserPrincipalName, Type, OperationName, tostring(LocationDetails), tostring(DeviceDetail), AppDisplayName, ClientAppUsed
  ),
  (AADNonInteractiveUserSignInLogs
  | where isnotempty(UserAgent)
  | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by UserAgent, SourceIP = IPAddress, Account = UserPrincipalName, Type, OperationName, tostring(LocationDetails), tostring(DeviceDetail), AppDisplayName, ClientAppUsed
  )
  )
  // Likely artefact of hardcoding
  | where UserAgent startswith "User" or UserAgent startswith '\"'
  // Incorrect casing
  or (UserAgent startswith "Mozilla" and not(UserAgent contains_cs "Mozilla"))
  // Incorrect casing
  or UserAgent contains_cs  "(Compatible;"
  // Missing MSIE version
  or UserAgent matches regex @"MSIE\s?;"
  // Incorrect spacing around MSIE version
  or UserAgent matches regex  @"MSIE(?:\d|.{1,5}?\d\s;)"
  | extend AccountName = split(Account, "@")[0], UPNSuffix = split(Account, "@")[1]
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: Account
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIP
version: 1.0.6
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: Microsoft Security Research
    support:
        tier: Community
    categories:
        domains: [ "Security - Threat Protection" ]

Stages and Predicates

Union isfuzzy=true (7 Sources)

Each leg below queries one source; the rule matches if any leg does. Sources: OfficeActivity, OfficeActivity, AzureDiagnostics, W3CIISLog, AWSCloudTrail, SigninLogs, AADNonInteractiveUserSignInLogs

Leg 1: OfficeActivity

OfficeActivity | where UserAgent != ""

Leg 2: OfficeActivity

OfficeActivity
| where RecordType in ("AzureActiveDirectory", "AzureActiveDirectoryStsLogon")
| extend OperationName = Operation
| parse ExtendedProperties with * 'User-Agent\\":\\"' UserAgent2 '\\' *
| parse ExtendedProperties with * 'UserAgent",      "Value": "' UserAgent1 '"' *
| where isnotempty(UserAgent1) or isnotempty(UserAgent2)
| extend UserAgent = iff( RecordType == 'AzureActiveDirectoryStsLogon', UserAgent1, UserAgent2)
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by UserAgent, SourceIP = ClientIP, Account = UserId, Type, RecordType, Operation

Leg 3: AzureDiagnostics

AzureDiagnostics
| where ResourceType =~ "APPLICATIONGATEWAYS"
| where OperationName =~ "ApplicationGatewayAccess"
| extend ClientIP = columnifexists("clientIP_s", "None"), UserAgent = columnifexists("userAgent_s", "None")
| where UserAgent != '-'
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by UserAgent, SourceIP = ClientIP,  requestUri_s, httpMethod_s, host_s, requestQuery_s, Type

Leg 4: W3CIISLog

W3CIISLog
| where isnotempty(csUserAgent)
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by UserAgent = csUserAgent, SourceIP = cIP, Account = csUserName, Type, sSiteName, csMethod, csUriStem

Leg 5: AWSCloudTrail

AWSCloudTrail
| where isnotempty(UserAgent)
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by UserAgent, SourceIP = SourceIpAddress, Account = UserIdentityUserName, Type, EventSource, EventName

Leg 6: SigninLogs

SigninLogs
| where isnotempty(UserAgent)
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by UserAgent, SourceIP = IPAddress, Account = UserPrincipalName, Type, OperationName, tostring(LocationDetails), tostring(DeviceDetail), AppDisplayName, ClientAppUsed

Leg 7: AADNonInteractiveUserSignInLogs

AADNonInteractiveUserSignInLogs
| where isnotempty(UserAgent)
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by UserAgent, SourceIP = IPAddress, Account = UserPrincipalName, Type, OperationName, tostring(LocationDetails), tostring(DeviceDetail), AppDisplayName, ClientAppUsed

Applied to the Combined Result

| where UserAgent startswith "User" or UserAgent startswith '\"'
or (UserAgent startswith "Mozilla" and not(UserAgent contains_cs "Mozilla"))
or UserAgent contains_cs  "(Compatible;"
or UserAgent matches regex @"MSIE\s?;"
or UserAgent matches regex  @"MSIE(?:\d|.{1,5}?\d\s;)"
| extend AccountName = split(Account, "@")[0], UPNSuffix = split(Account, "@")[1]

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
Accountsummarize
AppDisplayNamesummarize
ClientAppUsedsummarize
EndTimesummarize
OperationNamesummarize
SourceIPsummarize
StartTimesummarize
Typesummarize
UserAgentsummarize
AccountNameextend
UPNSuffixextend

McAfee ePO - Task error

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

Detects when task error occurs.

MITRE ATT&CK Coverage

TacticTechniques
Stealth
Defense Impairment

Telemetry Coverage

Rule Body

id: 3e397e31-7964-417e-a3e0-0acfaa2056f4
name: McAfee ePO - Task error
description: |
  'Detects when task error occurs.'
severity: Medium
requiredDataConnectors:
  - connectorId: SyslogAma
    datatypes:
      - Syslog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - DefenseEvasion
relevantTechniques:
  - T1562
  - T1070
query: |
  McAfeeEPOEvent
  | where EventId in ('1003', '1067')
  | extend EventMessage = case(EventId == '1003', 'Error starting Task',
                                'Unable to start scheduled task')
  | project DvcIpAddr, EventId, EventMessage
  | extend IPCustomEntity = DvcIpAddr
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPCustomEntity
version: 1.0.2
kind: Scheduled

Stages and Predicates

Stage 1: source

McAfeeEPOEvent

Stage 2: where

| where EventId in ('1003', '1067')

Stage 3: extend

| extend EventMessage = case(EventId == '1003', 'Error starting Task',
                              'Unable to start scheduled task')
EventMessage =
ifEventId == 1003'Error starting Task'
else'Unable to start scheduled task'

Stage 4: project

| project DvcIpAddr, EventId, EventMessage

Stage 5: extend

| extend IPCustomEntity = DvcIpAddr

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
EventIdin
  • 1003
  • 1067
field:"EventId" kind:in

Output Fields

These fields are emitted when the rule matches.

FieldSource
DvcIpAddrproject
EventIdproject
EventMessageproject
IPCustomEntityextend

OracleDBAudit - New user account

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

'Detects when an action was made by new user.'

MITRE ATT&CK Coverage

TacticTechniques
Initial Access
Persistence

Rule Body

id: cca7b348-e904-4a7a-8f26-d22d4d477119
name: OracleDBAudit - New user account
description: |
  'Detects when an action was made by new user.'
severity: Low
status: Available
requiredDataConnectors:
  - connectorId: SyslogAma
    datatypes:
      - Syslog
queryFrequency: 3h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
  - Persistence
relevantTechniques:
  - T1078
query: |
  let lbtime_d = 14d;
  let lbtime_24h = 24h;
  let known_users = OracleDatabaseAuditEvent
  | where TimeGenerated between (ago(lbtime_d) .. ago(lbtime_24h))
  | where isnotempty(DstUserName)
  | where isnotempty(Action)
  | summarize makeset(DstUserName);
  OracleDatabaseAuditEvent
  | where isnotempty(DstUserName)
  | where isnotempty(Action)
  | where DstUserName !in (known_users)
  | project DstUserName
  | extend AccountCustomEntity = DstUserName
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: AccountCustomEntity
version: 1.0.2
kind: Scheduled

Stages and Predicates

Parameters

let lbtime_d = 14d;
let lbtime_24h = 24h;

Let Binding: known_users used in Stage 4

let known_users = OracleDatabaseAuditEvent
| where TimeGenerated between (ago(lbtime_d) .. ago(lbtime_24h))
| where isnotempty(DstUserName)
| where isnotempty(Action)
| summarize makeset(DstUserName);

Stage 1: source

OracleDatabaseAuditEvent

Stage 2: where

| where isnotempty(DstUserName)

Stage 3: where

| where isnotempty(Action)

Stage 4: where

| where DstUserName !in (known_users)

Stage 5: project

| project DstUserName

Stage 6: extend

| extend AccountCustomEntity = DstUserName

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Actionis_not_null
  • (no value, null check)
field:"Action" kind:is_not_null
DstUserNameis_not_null
  • (no value, null check)
field:"DstUserName" kind:is_not_null

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
DstUserNameeqknown_usersexcludes:DstUserName field:"DstUserName" value:"known_users"

Output Fields

These fields are emitted when the rule matches.

FieldSource
DstUserNameproject
AccountCustomEntityextend

Power Platform - DLP policy updated or removed

#
Status
available
Severity
low
Time window
1d
Group by
EventOriginalUid, PolicyId, TimeGenerated
Source
github.com/Azure/Azure-Sentinel

Identifies changes to DLP policy, specifically policies which are updated or removed.

MITRE ATT&CK Coverage

TacticTechniques
Stealth

Telemetry Coverage

Rule Body

id: 1b2e6172-85c5-417a-90c3-7cc80cb787f5
kind: Scheduled
name: Power Platform - DLP policy updated or removed
description: Identifies changes to DLP policy, specifically policies which are updated
  or removed.
severity: Low
status: Available
requiredDataConnectors:
  - connectorId: PowerPlatformAdmin
    dataTypes:
      - PowerPlatformAdminActivity
queryFrequency: 1h
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - DefenseEvasion
relevantTechniques:
  - T1480
query: |
  let create_policy_ignore_time_window = 10m;
  let query_frequency = 1h;
  let dlp_policy_events = PowerPlatformAdminActivity
      | where TimeGenerated >= ago(query_frequency)
      | where EventOriginalType == "GovernanceApiPolicyOperation"
      | where PropertyCollection has_any ("DeleteDlpPolicy", "UpdateDlpPolicy", "CreateDlpPolicy")
      | mv-expand PropertyCollection
      | extend
          Name = tostring(PropertyCollection.Name),
          Value = tostring(PropertyCollection.Value)
      | summarize Properties = make_bag(bag_pack(Name, Value))
          by
          TimeGenerated,
          EventOriginalUid
      | extend
          PolicyName = tostring(Properties['powerplatform.analytics.resource.display_name']),
          EventType = tostring(Properties['powerplatform.analytics.resource.tenant.governance.api_policy.operation_name']),
          ActorName = tostring(Properties['enduser.principal_name']),
          PolicyId = tostring(Properties['powerplatform.analytics.resource.id']),
          AdditionalInfo = Properties['powerplatform.analytics.resource.tenant.governance.api_policy.additional_resources'];
  let delete_events = dlp_policy_events
      | where EventType == "DeleteDlpPolicy";
  let update_events = dlp_policy_events
      | where EventType == "UpdateDlpPolicy";
  let create_events = dlp_policy_events
      | where EventType == "CreateDlpPolicy"
      | extend ignore_time = TimeGenerated + create_policy_ignore_time_window;
  union
      delete_events,
      (update_events
      | join kind=leftouter (
          create_events
          | project-away TimeGenerated
          )
          on PolicyId
      | where isempty(ignore_time) or TimeGenerated > ignore_time
      | project-away ignore_time)
  | where TimeGenerated >= ago(query_frequency)
  | extend
      AccountName = tostring(split(ActorName, "@")[0]),
      UPNSuffix = tostring(split(ActorName, "@")[1])
  | project
      TimeGenerated,
      ActorName,
      EventType,
      PolicyName,
      PolicyId,
      AccountName,
      UPNSuffix,
      AdditionalInfo
eventGroupingSettings:
  aggregationKind: SingleAlert
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: UPNSuffix
alertDetailsOverride:
  alertDisplayNameFormat: PowerPlatform - DLP policy {{EventType}} event detected.
  alertDescriptionFormat: A DLP policy {{PolicyName}} was as modfiied or deleted.
    Event type {{EventType}}
customDetails:
  Policy: PolicyId
  PolicyName: PolicyName
version: 3.2.0

Stages and Predicates

Parameters

let create_policy_ignore_time_window = 10m;
let query_frequency = 1h;

let dlp_policy_events and let update_events are inlined into the numbered stages below.

Let Binding: delete_events

let delete_events = dlp_policy_events
    | where EventType == "DeleteDlpPolicy";

Let Binding: create_events used in Stage 20

let create_events = dlp_policy_events
    | where EventType == "CreateDlpPolicy"
    | extend ignore_time = TimeGenerated + create_policy_ignore_time_window;

Stage 1: union

union of 2 branches

Stage 2: source

PowerPlatformAdminActivity

Stage 3: where

where TimeGenerated >= ago(3600s)

Stage 4: where

where EventOriginalType == "GovernanceApiPolicyOperation"

Stage 5: where

where (PropertyCollection has "DeleteDlpPolicy" or PropertyCollection has "UpdateDlpPolicy" or PropertyCollection has "CreateDlpPolicy")

Stage 6: mv-expand

mv-expand PropertyCollection

Stage 7: extend

extend Name, Value

Stage 8: summarize

summarize Properties by TimeGenerated, EventOriginalUid

Stage 9: extend

extend ActorName, AdditionalInfo, EventType, PolicyId, PolicyName

Stage 10: where

where EventType == "DeleteDlpPolicy"

Stage 11: source

PowerPlatformAdminActivity

Stage 12: where

where TimeGenerated >= ago(3600s)

Stage 13: where

where EventOriginalType == "GovernanceApiPolicyOperation"

Stage 14: where

where (PropertyCollection has "DeleteDlpPolicy" or PropertyCollection has "UpdateDlpPolicy" or PropertyCollection has "CreateDlpPolicy")

Stage 15: mv-expand

mv-expand PropertyCollection

Stage 16: extend

extend Name, Value

Stage 17: summarize

summarize Properties by TimeGenerated, EventOriginalUid

Stage 18: extend

extend ActorName, AdditionalInfo, EventType, PolicyId, PolicyName

Stage 19: where

where EventType == "UpdateDlpPolicy"

Stage 20: join

join kind=leftouter (create_events) on PolicyId

Stage 21: where

where (TimeGenerated > ignore_time or isempty(ignore_time))

Stage 22: project-away

project-away ignore_time

Stage 23: where

where TimeGenerated >= ago(3600s)

Stage 24: extend

extend AccountName, UPNSuffix

Stage 25: project

project AccountName, ActorName, AdditionalInfo, EventType, PolicyId, PolicyName, TimeGenerated, UPNSuffix

Stage 26: summarize aggregation inside the join branch

summarize by TimeGenerated, EventOriginalUid

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
EventOriginalTypeeq
  • GovernanceApiPolicyOperation
field:"EventOriginalType" kind:eq value:"GovernanceApiPolicyOperation"
EventTypeeq
  • CreateDlpPolicy
  • DeleteDlpPolicy
  • UpdateDlpPolicy
field:"EventType" kind:eq
PropertyCollectionmatch
  • CreateDlpPolicy transforms: term
  • DeleteDlpPolicy transforms: term
  • UpdateDlpPolicy transforms: term
field:"PropertyCollection" kind:match
ignore_timeis_null
  • (no value, null check)
field:"ignore_time" kind:is_null

Output Fields

These fields are emitted when the rule matches.

FieldSource
EventOriginalUidsummarize
TimeGeneratedsummarize

PROD (TM012.1) - GPO - Enforced Outside of Tier Model

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

Enforcing of a GPO could bypass or override Tier Model GPOs.

MITRE ATT&CK Coverage

Telemetry Coverage

Rule Body

id: 05846968-15cc-4309-bddc-eaa296f84af1
name: 'PROD (TM012.1) - GPO - Enforced Outside of Tier Model'
description: |
  Enforcing of a GPO could bypass or override Tier Model GPOs.
severity: High
requiredDataConnectors:
  - connectorId: SecurityEvents
    dataTypes:
      - SecurityEvent
  - connectorId: WindowsSecurityEvents
    dataTypes:
      - SecurityEvent
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - DefenseEvasion
relevantTechniques:
  - T1484.001
query: |
  SecurityEvent 
  | where EventID == 5136 
      and not(EventData matches regex @"(?i)OU=Domain Controllers"
          or EventData matches regex @"(?i)OU=Tier 0 Member Servers"
          or EventData matches regex @"(?i)OU=Tier 1 Member Servers"
          or EventData matches regex @"(?i)OU=Tier 2 End-User Accounts"
          or EventData matches regex @"(?i)OU=Tier 2 End-User Devices"
          or EventData matches regex @"(?i)OU=Tier 2 End-User Groups"
          or EventData matches regex @"(?i)OU=Tier Model Administration"
          or EventData matches regex @"(?i)OU=Tier Model Computer Quarantine")
      and Activity has "modified" 
      and EventData has '"ObjectClass">organizationalUnit'
      and EventData has '<Data Name="AttributeLDAPDisplayName">gPLink</Data>' 
      and (EventData has ';0]' 
          or EventData has ';1]' 
          or EventData has ';2]')
      and EventData has '"OperationType">%%14674'
  | extend ObjectName = extract("<Data Name=\"ObjectDN\">((?i)OU=[^,]+(,OU=[^,]+)*),(?i)DC=", 1, EventData)
  | extend AttributeLDAP = extract("<Data Name=\"AttributeLDAPDisplayName\">([^<]+)</Data>", 1, EventData)
  | extend AttributeValue = extract("\\[([^\\]]+)\\]", 1, EventData)
  | extend LastThreeChars = substring(AttributeValue, strlen(AttributeValue) - 2, 3)
  | extend AttributeValueDescription = case(
      LastThreeChars == ";0", "Linked",
      LastThreeChars == ";1", "Unlinked",
      LastThreeChars == ";2", "Enforced",
      "Unknown")
  | where AttributeValueDescription == "Enforced"
  | extend ObjectClass = extract("<Data Name=\"ObjectClass\">([^<]+)</Data>", 1, EventData)
  | extend ObjectTier = "Outside Tier Model"
  | extend Domain = extract("([^.]+\\.[^.]+)$", 1, Computer)
  | project TimeGenerated
     , ObjectName
     , ObjectTier
     , ObjectClass
     , ActivityType = AttributeValueDescription
     , AttributeLDAP
     , Account
     , Domain
     , Computer
     , Channel
     , EventID
     , EventData
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: ObjectName
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: Computer
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: Account
customDetails:
  ObjectName: ObjectName
  ObjectTier: ObjectTier
  ObjectClass: ObjectClass
  ActivityType: ActivityType
  AttributeLDAP: AttributeLDAP
  Account: Account
  Domain: Domain
  Computer: Computer
  Channel: Channel
  EventID: EventID
  EventData: EventData
eventGroupingSettings:
  aggregationKind: AlertPerResult
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: false
    reopenClosedIncident: false
    lookbackDuration: PT5H
    matchingMethod: AllEntities
alertDetailsOverride:
  alertDisplayNameFormat: '(TM012.1) A GPO was enforced and is overwriting the Tier Model GPOs'
  alertDescriptionFormat: 'Enforcing a GPO outside the Tier Model can overwrite and bypass Tier Model GPO with security controls.'
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

SecurityEvent

Stage 2: where

| where EventID == 5136 
    and not(EventData matches regex @"(?i)OU=Domain Controllers"
        or EventData matches regex @"(?i)OU=Tier 0 Member Servers"
        or EventData matches regex @"(?i)OU=Tier 1 Member Servers"
        or EventData matches regex @"(?i)OU=Tier 2 End-User Accounts"
        or EventData matches regex @"(?i)OU=Tier 2 End-User Devices"
        or EventData matches regex @"(?i)OU=Tier 2 End-User Groups"
        or EventData matches regex @"(?i)OU=Tier Model Administration"
        or EventData matches regex @"(?i)OU=Tier Model Computer Quarantine")
    and Activity has "modified" 
    and EventData has '"ObjectClass">organizationalUnit'
    and EventData has '<Data Name="AttributeLDAPDisplayName">gPLink</Data>' 
    and (EventData has ';0]' 
        or EventData has ';1]' 
        or EventData has ';2]')
    and EventData has '"OperationType">%%14674'

Stage 3: extend (5 consecutive steps)

| extend ObjectName = extract("<Data Name=\"ObjectDN\">((?i)OU=[^,]+(,OU=[^,]+)*),(?i)DC=", 1, EventData)
| extend AttributeLDAP = extract("<Data Name=\"AttributeLDAPDisplayName\">([^<]+)</Data>", 1, EventData)
| extend AttributeValue = extract("\\[([^\\]]+)\\]", 1, EventData)
| extend LastThreeChars = substring(AttributeValue, strlen(AttributeValue) - 2, 3)
| extend AttributeValueDescription = case(
    LastThreeChars == ";0", "Linked",
    LastThreeChars == ";1", "Unlinked",
    LastThreeChars == ";2", "Enforced",
    "Unknown")

Stage 4: where

| where AttributeValueDescription == "Enforced"

Stage 5: extend (3 consecutive steps)

| extend ObjectClass = extract("<Data Name=\"ObjectClass\">([^<]+)</Data>", 1, EventData)
| extend ObjectTier = "Outside Tier Model"
| extend Domain = extract("([^.]+\\.[^.]+)$", 1, Computer)

Stage 6: project

| project TimeGenerated
   , ObjectName
   , ObjectTier
   , ObjectClass
   , ActivityType = AttributeValueDescription
   , AttributeLDAP
   , Account
   , Domain
   , Computer
   , Channel
   , EventID
   , EventData

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Activitymatch
  • modified transforms: term corpus 10 (kusto 10)
field:"Activity" kind:match value:"modified"
AttributeValueDescriptioneq
  • Enforced
field:"AttributeValueDescription" kind:eq value:"Enforced"
EventDatamatch
  • "ObjectClass">organizationalUnit transforms: term corpus 5 (kusto 5)
  • "OperationType">%%14674 transforms: term corpus 7 (kusto 7)
  • ;0] transforms: term corpus 4 (kusto 4)
  • ;1] transforms: term corpus 4 (kusto 4)
  • ;2] transforms: term corpus 4 (kusto 4)
  • <Data Name="AttributeLDAPDisplayName">gPLink</Data> transforms: term corpus 4 (kusto 4)
field:"EventData" kind:match
EventIDeq
  • 5136 corpus 20 (kusto 18, elastic 2)
field:"EventID" kind:eq value:"5136"

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
EventDataregex_match(?i)OU=Domain Controllersexcludes:EventData field:"EventData" value:"(?i)OU=Domain Controllers"
EventDataregex_match(?i)OU=Tier 0 Member Serversexcludes:EventData field:"EventData" value:"(?i)OU=Tier 0 Member Servers"
EventDataregex_match(?i)OU=Tier 1 Member Serversexcludes:EventData field:"EventData" value:"(?i)OU=Tier 1 Member Servers"
EventDataregex_match(?i)OU=Tier 2 End-User Accountsexcludes:EventData field:"EventData" value:"(?i)OU=Tier 2 End-User Accounts"
EventDataregex_match(?i)OU=Tier 2 End-User Devicesexcludes:EventData field:"EventData" value:"(?i)OU=Tier 2 End-User Devices"
EventDataregex_match(?i)OU=Tier 2 End-User Groupsexcludes:EventData field:"EventData" value:"(?i)OU=Tier 2 End-User Groups"
EventDataregex_match(?i)OU=Tier Model Administrationexcludes:EventData field:"EventData" value:"(?i)OU=Tier Model Administration"
EventDataregex_match(?i)OU=Tier Model Computer Quarantineexcludes:EventData field:"EventData" value:"(?i)OU=Tier Model Computer Quarantine"

Output Fields

These fields are emitted when the rule matches.

FieldSource
Accountproject
ActivityTypeproject
AttributeLDAPproject
Channelproject
Computerproject
Domainproject
EventDataproject
EventIDproject
ObjectClassproject
ObjectNameproject
ObjectTierproject
TimeGeneratedproject

Security Event log cleared

#
Status
available
Severity
medium
Time window
1d
Group by
Account, Activity, Computer, EventID
Source
github.com/Azure/Azure-Sentinel

Checks for event id 1102 which indicates the security event log was cleared. It uses Event Source Name "Microsoft-Windows-Eventlog" to avoid generating false positives from other sources, like AD FS servers for instance.

MITRE ATT&CK Coverage

TacticTechniques
Stealth

Telemetry Coverage

Rule Body

id: 80da0a8f-cfe1-4cd0-a895-8bc1771a720e
name: Security Event log cleared
description: |
  'Checks for event id 1102 which indicates the security event log was cleared.
  It uses Event Source Name "Microsoft-Windows-Eventlog" to avoid generating false positives from other sources, like AD FS servers for instance.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: SecurityEvents
    dataTypes:
      - SecurityEvent
  - connectorId: WindowsSecurityEvents
    dataTypes:
      - SecurityEvent
  - connectorId: WindowsForwardedEvents
    dataTypes:
      - WindowsEvent
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - DefenseEvasion
relevantTechniques:
  - T1070
query: |
  (union isfuzzy=true
  (
  SecurityEvent
  | where EventID == 1102 and EventSourceName =~ "Microsoft-Windows-Eventlog"
  | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), EventCount = count() by Computer, Account, EventID, Activity
  ),
  (
  WindowsEvent
  | where EventID == 1102 and Provider =~ "Microsoft-Windows-Eventlog"
  | extend Account =  strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
  | extend Activity= "1102 - The audit log was cleared."
  | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), EventCount = count() by Computer, Account, EventID, Activity
  )
  )
  | extend Name=tostring(split(Account, "@")[0]), UPNSuffix=tostring(split(Account, "@")[1])
  | extend HostName = iif(Computer has '.',substring(Computer,0,indexof(Computer,'.')),Computer) , DnsDomain = iif(Computer has '.',substring(Computer,indexof(Computer,'.')+1),'')
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: Name
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: Computer
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: DnsDomain
version: 1.1.5
kind: Scheduled

Stages and Predicates

Union isfuzzy=true (2 Sources)

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

Leg 1: SecurityEvent

SecurityEvent
| where EventID == 1102 and EventSourceName =~ "Microsoft-Windows-Eventlog"
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), EventCount = count() by Computer, Account, EventID, Activity

Leg 2: WindowsEvent

WindowsEvent
| where EventID == 1102 and Provider =~ "Microsoft-Windows-Eventlog"
| extend Account =  strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
| extend Activity= "1102 - The audit log was cleared."
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), EventCount = count() by Computer, Account, EventID, Activity

Applied to the Combined Result

| extend Name=tostring(split(Account, "@")[0]), UPNSuffix=tostring(split(Account, "@")[1])
| extend HostName = iif(Computer has '.',substring(Computer,0,indexof(Computer,'.')),Computer) , DnsDomain = iif(Computer has '.',substring(Computer,indexof(Computer,'.')+1),'')

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
Accountsummarize
Activitysummarize
Computersummarize
EndTimeUtcsummarize
EventCountsummarize
EventIDsummarize
StartTimeUtcsummarize
Nameextend
UPNSuffixextend
DnsDomainextend
HostNameextend

Trust Monitor Event

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

'This query identifies when a new trust monitor event is detected.'

MITRE ATT&CK Coverage

Rule Body

id: 8dcf7238-a7d0-4cfd-8d0c-b230e3cd9182
name: Trust Monitor Event
description: |
  'This query identifies when a new trust monitor event is detected.'
severity: Medium
requiredDataConnectors: []
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CredentialAccess
relevantTechniques:
  - T1528
  - T1555
query: |
  let timeframe = ago(5m);
  DuoSecurityTrustMonitor_CL
  | where TimeGenerated >= timeframe
  | extend AccountName = tostring(split(surfaced_auth_user_name_s, "@")[0]), AccountUPNSuffix = tostring(split(surfaced_auth_user_name_s, "@")[1])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: surfaced_auth_user_name_s
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: AccountUPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: surfaced_auth_access_device_ip_s
version: 1.0.4
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: SecurityJedi
    support:
        tier: Community
    categories:
        domains: [ "Security - Others" ]

Stages and Predicates

Parameters

let timeframe = ago(5m);

Stage 1: source

DuoSecurityTrustMonitor_CL

Stage 2: where

| where TimeGenerated >= timeframe

Stage 3: extend

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

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountNameextend
AccountUPNSuffixextend

Ubiquiti - Connection to known malicious IP or C2

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

'Detects allowed connections to IP addresses which are in TI list and are known to be malicious.'

MITRE ATT&CK Coverage

Rule Body

id: db60ca0b-b668-439b-b889-b63b57ef20fb
name: Ubiquiti - Connection to known malicious IP or C2
description: |
  'Detects allowed connections to IP addresses which are in TI list and are known to be malicious.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: CustomLogsAma
    dataTypes:
      - Ubiquiti_CL
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Exfiltration
  - CommandAndControl
relevantTechniques:
  - T1071
  - T1571
  - T1572
query: |
  let malicious_ips =
  ThreatIntelligenceIndicator
  | where isnotempty(NetworkIP)
  | summarize make_list(NetworkIP);
  UbiquitiAuditEvent
  | where EventCategory =~ 'firewall'
  | where ipv4_is_private(SrcIpAddr)
  | where ipv4_is_private(DstIpAddr) == 'False'
  | where DstIpAddr in (malicious_ips)
  | where DvcAction =~ 'Accepted' or DvcAction =~ 'Other'
  | extend IPCustomEntity = SrcIpAddr
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPCustomEntity
version: 1.0.2
kind: Scheduled

Stages and Predicates

Let Binding: malicious_ips used in Stage 5

let malicious_ips = ThreatIntelligenceIndicator
| where isnotempty(NetworkIP)
| summarize make_list(NetworkIP);

Stage 1: source

UbiquitiAuditEvent

Stage 2: where

| where EventCategory =~ 'firewall'

Stage 3: where

| where ipv4_is_private(SrcIpAddr)

Stage 4: where

| where ipv4_is_private(DstIpAddr) == 'False'

Stage 5: where

| where DstIpAddr in (malicious_ips)

Stage 6: where

| where DvcAction =~ 'Accepted' or DvcAction =~ 'Other'

Stage 7: extend

| extend IPCustomEntity = SrcIpAddr

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
DstIpAddrin
  • malicious_ips
field:"dest_ip" kind:in value:"malicious_ips"
DvcActioneq
  • Accepted
  • Other
field:"DvcAction" kind:eq
EventCategoryeq
  • firewall
field:"EventCategory" kind:eq value:"firewall"
SrcIpAddrcidr_match
  • 10.0.0.0/8
  • 127.0.0.0/8
  • 169.254.0.0/16
  • 172.16.0.0/12
  • 192.168.0.0/16
field:"src_ip" kind:cidr_match

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
DstIpAddrcidr_match10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16, 127.0.0.0/8excludes:DstIpAddr

Output Fields

These fields are emitted when the rule matches.

FieldSource
IPCustomEntityextend

VMware ESXi - Root password changed

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

'Detects when root user password is changed.'

MITRE ATT&CK Coverage

Rule Body

id: 17b0ea43-5aeb-4dc4-ac3a-be84acb8d5b7
name: VMware ESXi - Root password changed
description: |
  'Detects when root user password is changed.'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: SyslogAma
    datatypes:
      - Syslog
queryFrequency: 10m
queryPeriod: 10m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
  - Persistence
  - DefenseEvasion
relevantTechniques:
  - T1078
  - T1098
  - T1556
query: |
  VMwareESXi
  | where SyslogMessage has_any ("password changed for root","Password was changed for account root")
  | extend SrcIpAddr = extract(@'root@(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})', 1, SyslogMessage)
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SrcIpAddr
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

VMwareESXi

Stage 2: where

| where SyslogMessage has_any ("password changed for root","Password was changed for account root")

Stage 3: extend

| extend SrcIpAddr = extract(@'root@(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})', 1, SyslogMessage)

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
SyslogMessagematch
  • Password was changed for account root transforms: term
  • password changed for root transforms: term
field:"SyslogMessage" kind:match

Output Fields

These fields are emitted when the rule matches.

FieldSource
SrcIpAddrextend

WAN Accelerator Deleted

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

Detects when a WAN accelerator is deleted from Veeam Backup & Replication. This might indicate unauthorized removal of critical components.

Rule Body

id: 5b19f19b-d92c-486f-be98-ba2c5945e240
name: WAN Accelerator Deleted
description: Detects when a WAN accelerator is deleted from Veeam Backup & Replication.
  This might indicate unauthorized removal of critical components.
severity: Informational
status: Available
requiredDataConnectors:
- connectorId: Syslog
  dataTypes:
  - Syslog
- connectorId: SyslogAma
  dataTypes:
  - Syslog
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
  aggregationKind: AlertPerResult
tactics: []
relevantTechniques: []
query: "Veeam_GetSecurityEvents\n| where instanceId == 27300\n| project\n    Date\
  \ = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),\n    DataSource = original_host,\n\
  \    EventId = instanceId,\n    [\"User Name\"] = user,\n  MessageDetails = Description,\n\
  \   Severity = SeverityDescription"
version: 1.0.1
kind: Scheduled
customDetails:
  Date: Date
  VbrHostName: DataSource
  EventId: EventId
  MessageDetails: MessageDetails
  Severity: Severity

Stages and Predicates

Stage 1: source

Veeam_GetSecurityEvents

Stage 2: where

| where instanceId == 27300

Stage 3: project

| project
    Date = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),
    DataSource = original_host,
    EventId = instanceId,
    ["User Name"] = user,
  MessageDetails = Description,
   Severity = SeverityDescription

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
DataSourceproject
Dateproject
EventIdproject
MessageDetailsproject
Severityproject
User Nameproject

Back to all Kusto rules