Detection rules › Kusto

Kusto rules: 2b

Alsid Password Guessing

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

'Searches for bruteforce Password Guessing attacks'

MITRE ATT&CK Coverage

TacticTechniques
Credential Access

Rule Body

id: ba239935-42c2-472d-80ba-689186099ea1
name: Alsid Password Guessing
description: |
    'Searches for bruteforce Password Guessing attacks'
severity: High
requiredDataConnectors:
  - connectorId: AlsidForAD
    dataTypes:
      - AlsidForADLog_CL
queryFrequency: 2h
queryPeriod: 2h
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - CredentialAccess
relevantTechniques:
  - T1110
query: |
    afad_parser
    | where MessageType == 2 and Codename == "Password Guessing"
    | 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 Guessing"

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 Guessing corpus 3 (kusto 3)
field:"Codename" kind:eq value:"Password Guessing"
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

Azure secure score user risk policy

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

'This query searches for an active Azure Premium P2 license is required to use and edit this policy. You will be required to have setup the MFA Policy before activating this policy'

MITRE ATT&CK Coverage

TacticTechniques
Impact

Rule Body

id: 1C07A4CB-E31B-4917-BD2A-3572E42F602C
name: Azure secure score user risk policy
description: |
  'This query searches for an active Azure Premium P2 license is required to use and edit this policy. 
   You will be required to have setup the MFA Policy before activating this policy'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: SenservaPro
    dataTypes:
      - SenservaPro_CL
queryFrequency: 6h
queryPeriod: 6h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Impact
relevantTechniques:
  - T1529
query: |
  SenservaPro_CL
  | where ControlName_s == 'AzureSecureScoreUserRiskPolicy'
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: ControlName_s
      - identifier: AadTenantId
        columnName: TenantId
      - identifier: DisplayName
        columnName: TenantDisplayName_s 
  - entityType: SecurityGroup
    fieldMappings:
      - identifier: DistinguishedName
        columnName: Group_s
  - entityType: AzureResource
    fieldMappings:
      - identifier: ResourceId
        columnName: SourceSystem 
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

SenservaPro_CL

Stage 2: where

| where ControlName_s == 'AzureSecureScoreUserRiskPolicy'

Indicators

These rows show field, operator, and value matches.

BloodHound Attack Path Finding - Add Members to Tier Zero Group

#

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 - Add Members to Tier Zero Group

Rule Body

id: 357d3dee-3b61-4de9-9084-fb245ba23d81
name: BloodHound Attack Path Finding - Add Members to Tier Zero Group
description: BloodHound Attack Path Finding - Add Members to Tier Zero Group
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 == "AzureT0AddMembers"'
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 == "AzureT0AddMembers"

Indicators

These rows show field, operator, and value matches.

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

BloodHound Attack Path Finding - Non-Tier Zero Principals With ADCS ESC6 Scenario A Privileges

#

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 - Non-Tier Zero Principals With ADCS ESC6 Scenario A Privileges

Rule Body

id: ca313efd-032c-4825-b687-86c50e92200f
name: BloodHound Attack Path Finding - Non-Tier Zero Principals With ADCS ESC6 Scenario
  A Privileges
description: BloodHound Attack Path Finding - Non-Tier Zero Principals With ADCS ESC6
  Scenario A Privileges
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 == "T0ADCSESC6a"
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 == "T0ADCSESC6a"

Indicators

These rows show field, operator, and value matches.

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

Cisco WSA - Unexpected URL

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

'Detects unexpected URL.'

MITRE ATT&CK Coverage

TacticTechniques
Command & Control

Rule Body

id: 010644fd-2830-4451-9e0e-606cc192f2e7
name: Cisco WSA - Unexpected URL
description: |
  'Detects unexpected URL.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: SyslogAma
    datatypes:
      - Syslog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
relevantTechniques:
  - T1102
query: |
  let threshold = 5;
  CiscoWSAEvent
  | where UrlOriginal matches regex @'\Ahttp(s)?[:][/][/]\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}'
  | extend URLCustomEntity = UrlOriginal, AccountCustomEntity = SrcUserName
entityMappings:
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: URLCustomEntity
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountCustomEntity
version: 1.0.2
kind: Scheduled

Stages and Predicates

Parameters

let threshold = 5;

Stage 1: source

CiscoWSAEvent

Stage 2: where

| where UrlOriginal matches regex @'\Ahttp(s)?[:][/][/]\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}'

Stage 3: extend

| extend URLCustomEntity = UrlOriginal, AccountCustomEntity = SrcUserName

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
UrlOriginalregex_match
  • \Ahttp(s)?[:][/][/]\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}
field:"UrlOriginal" kind:regex_match value:"\Ahttp(s)?[:][/][/]\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}"

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountCustomEntityextend
URLCustomEntityextend

Cloudflare - Unexpected POST requests

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

'Detects post requests to unusual extensions.'

MITRE ATT&CK Coverage

TacticTechniques
Persistence
Command & Control

Rule Body

id: 7313352a-09f6-4a84-88bd-6f17f1cbeb8f
name: Cloudflare - Unexpected POST requests
description: |
  'Detects post requests to unusual extensions.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: CloudflareDataConnector
    dataTypes:
      - Cloudflare
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Persistence
  - CommandAndControl
relevantTechniques:
  - T1505
  - T1071
query: |
  Cloudflare
  | where HttpRequestMethod in~ ('POST', 'PUT')
  | where tostring(HttpStatusCode) startswith '2'
  | where DstBytes != 0 or SrcBytes != 0
  | extend fe = extract(@'.*(\.\w+)$', 1, ClientRequestURI)
  | where fe in~ ('.jpg', '.jpeg', '.gif', '.png', '.icon', '.ico', '.xml', '.swf', '.svg', '.ppt', '.pttx', '.doc', '.docx', '.rtf', '.pdf', '.tif', '.zip', '.mov')
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SrcIpAddr
version: 1.0.1
kind: Scheduled

Stages and Predicates

Stage 1: source

Cloudflare

Stage 2: where

| where HttpRequestMethod in~ ('POST', 'PUT')

Stage 3: where

| where tostring(HttpStatusCode) startswith '2'

Stage 4: where

| where DstBytes != 0 or SrcBytes != 0

Stage 5: extend

| extend fe = extract(@'.*(\.\w+)$', 1, ClientRequestURI)

Stage 6: where

| where fe in~ ('.jpg', '.jpeg', '.gif', '.png', '.icon', '.ico', '.xml', '.swf', '.svg', '.ppt', '.pttx', '.doc', '.docx', '.rtf', '.pdf', '.tif', '.zip', '.mov')

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
DstBytesne
  • 0
field:"DstBytes" kind:ne value:"0"
HttpRequestMethodin
  • POST
  • PUT
field:"HttpRequestMethod" kind:in
HttpStatusCodestarts_with
  • 2 transforms: tostring
field:"HttpStatusCode" kind:starts_with value:"2"
SrcBytesne
  • 0
field:"SrcBytes" kind:ne value:"0"
fein
  • .doc
  • .docx
  • .gif
  • .ico
  • .icon
  • .jpeg
  • .jpg
  • .mov
  • .pdf
  • .png
  • .ppt
  • .pttx
  • .rtf
  • .svg
  • .swf
  • .tif
  • .xml
  • .zip
field:"fe" kind:in

Output Fields

These fields are emitted when the rule matches.

FieldSource
feextend

Dynatrace Application Security - Code-Level runtime vulnerability detection

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

Detect Code-level runtime vulnerabilities in your environment

MITRE ATT&CK Coverage

Rule Body

id: 305093b4-0fa2-57bc-bced-caea782a6e9c
name: Dynatrace Application Security - Code-Level runtime vulnerability detection
description: 'Detect Code-level runtime vulnerabilities in your environment'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: DynatraceRuntimeVulnerabilities
    dataTypes:
      - DynatraceSecurityProblems
  - connectorId: DTRunVulnCCPDefinition
    dataTypes:
      - DynatraceSecurityProblemsV2_CL
  - connectorId: DTRunVulnV3CCPDefinition
    dataTypes:
      - DynatraceSecurityProblemsV3_CL
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - DefenseEvasion
  - Execution
  - Impact
  - InitialAccess
  - LateralMovement
  - Persistence
  - PrivilegeEscalation
relevantTechniques:
  - T1140
  - T1059
  - T1565
  - T1659
  - T1210
  - T1554
  - T1548
eventGroupingSettings:
  aggregationKind: AlertPerResult
query: |
  DynatraceSecurityProblems
  | where VulnerabilityType == "CODE_LEVEL" and DAVISRiskLevel == "CRITICAL" and Muted == false
  | summarize  arg_max(LastUpdatedTimeStamp, *) by SecurityProblemId
entityMappings:
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: Url
alertDetailsOverride:
  alertDisplayNameFormat: 'Dynatrace Code-Level runtime vulnerability detected - {{DisplayId}} : {{Title}}'
  alertDescriptionFormat: |
    Code-Level runtime vulnerability ({{ExternalVulnerabilityId}}) detected in package ({{PackageName}}), more details available from Dynatrace at {{Url}}.
  alertSeverityColumnName: Severity
customDetails:
  SecProbIdentifier: SecurityProblemId
  DisplayIdentifier: DisplayId
  SecurityProblemUrl: Url
  DAVISRiskLevel: DAVISRiskLevel
  PackageName: PackageName
  Technology: Technology
  VulnerabilityType: VulnerabilityType
  CVEIds: CVEIds
  ExternVulnIdentifier: ExternalVulnerabilityId
  DAVISRiskScore: DAVISRiskScore
  DAVISRiskVector: DAVISRiskVector
  DAVISExposure: DAVISExposure
  DAVISPublicExploit: DAVISPublicExploit
  DAVISDataAssets: DAVISDataAssets
  DAVISVulnFuncUsage: DAVISVulnerableFunctionUsage
incidentConfiguration:
  createIncident: false
  groupingConfiguration:
    enabled: false
    reopenClosedIncident: false
    lookbackDuration: PT5H
    matchingMethod: AllEntities
version: 1.0.5
kind: Scheduled

Stages and Predicates

Stage 1: source

DynatraceSecurityProblems

Stage 2: where

| where VulnerabilityType == "CODE_LEVEL" and DAVISRiskLevel == "CRITICAL" and Muted == false

Stage 3: summarize

| summarize  arg_max(LastUpdatedTimeStamp, *) by SecurityProblemId

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
SecurityProblemIdsummarize

Dynatrace Application Security - Third-Party runtime vulnerability detection

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

'Detect Third-Party runtime vulnerabilities in your environment insights by snyk'

MITRE ATT&CK Coverage

Rule Body

id: af99b078-124b-543a-9a50-66ef87c09f6a
name: Dynatrace Application Security - Third-Party runtime vulnerability detection
description: |
  'Detect Third-Party runtime vulnerabilities in your environment insights by snyk'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: DynatraceRuntimeVulnerabilities
    dataTypes:
      - DynatraceSecurityProblems
  - connectorId: DTRunVulnCCPDefinition
    dataTypes:
      - DynatraceSecurityProblemsV2_CL
  - connectorId: DTRunVulnV3CCPDefinition
    dataTypes:
      - DynatraceSecurityProblemsV3_CL
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - DefenseEvasion
  - Execution
  - Impact
  - InitialAccess
  - LateralMovement
  - Persistence
  - PrivilegeEscalation
relevantTechniques:
  - T1140
  - T1059
  - T1565
  - T1659
  - T1210
  - T1554
  - T1548
eventGroupingSettings:
  aggregationKind: AlertPerResult
query: |
  DynatraceSecurityProblems
  | where VulnerabilityType != "CODE_LEVEL" and DAVISRiskLevel == "CRITICAL"  and Muted == false
  | summarize  arg_max(LastUpdatedTimeStamp, *) by SecurityProblemId
entityMappings:
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: Url
alertDetailsOverride:
  alertDisplayNameFormat: 'Dynatrace Third-party runtime vulnerability detected - {{DisplayId}} : {{Title}}'
  alertDescriptionFormat: |
    Third-party runtime vulnerability ({{ExternalVulnerabilityId}}) detected in package ({{PackageName}}), more details available from Dynatrace at {{Url}}.
  alertSeverityColumnName: Severity
customDetails:
  SecProbIdentifier: SecurityProblemId
  DisplayIdentifier: DisplayId
  SecurityProblemUrl: Url
  DAVISRiskLevel: DAVISRiskLevel
  PackageName: PackageName
  Technology: Technology
  VulnerabilityType: VulnerabilityType
  CVEIds: CVEIds
  ExternVulnIdentifier: ExternalVulnerabilityId
  DAVISRiskScore: DAVISRiskScore
  DAVISRiskVector: DAVISRiskVector
  DAVISExposure: DAVISExposure
  DAVISPublicExploit: DAVISPublicExploit
  DAVISDataAssets: DAVISDataAssets
  DAVISVulnFuncUsage: DAVISVulnerableFunctionUsage
incidentConfiguration:
  createIncident: false
  groupingConfiguration:
    enabled: false
    reopenClosedIncident: false
    lookbackDuration: PT5H
    matchingMethod: AllEntities
version: 1.0.4
kind: Scheduled

Stages and Predicates

Stage 1: source

DynatraceSecurityProblems

Stage 2: where

| where VulnerabilityType != "CODE_LEVEL" and DAVISRiskLevel == "CRITICAL"  and Muted == false

Stage 3: summarize

| summarize  arg_max(LastUpdatedTimeStamp, *) by SecurityProblemId

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
SecurityProblemIdsummarize

External User Access Enabled

#
Severity
low
Time window
1d
Author
Microsoft Security Research
Source
github.com/Azure/Azure-Sentinel

'This alerts when the account setting is changed to allow either external domain access or anonymous access to meetings.'

MITRE ATT&CK Coverage

Rule Body

id: 8e267e91-6bda-4b3c-bf68-9f5cbdd103a3
name: External User Access Enabled
description: |
  'This alerts when the account setting is changed to allow either external domain access or anonymous access to meetings.'
severity: Low
requiredDataConnectors: []
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CredentialAccess
  - Persistence
relevantTechniques:
  - T1098
  - T1556
query: |
  ZoomLogs
  | where Event =~ "account.settings_updated"
  | extend EnforceLogin = columnifexists("payload_object_settings_schedule_meeting_enfore_login_b", "")
  | extend EnforceLoginDomain = columnifexists("payload_object_settings_schedule_meeting_enfore_login_b", "")
  | extend GuestAlerts = columnifexists("payload_object_settings_in_meeting_alert_guest_join_b", "")
  | where EnforceLogin == 'false' or EnforceLoginDomain == 'false' or GuestAlerts == 'false'
  | extend SettingChanged = case(EnforceLogin == 'false' and EnforceLoginDomain == 'false' and GuestAlerts == 'false', "All settings changed",
                              EnforceLogin == 'false' and EnforceLoginDomain == 'false', "Enforced Logons and Restricted Domains Changed",
                              EnforceLoginDomain == 'false' and GuestAlerts == 'false', "Enforced Domains Changed",
                              EnforceLoginDomain == 'false', "Enfored Domains Changed",
                              GuestAlerts == 'false', "Guest Join Alerts Changed",
                              EnforceLogin == 'false', "Enforced Logins Changed",
                              "No Changes")
  | extend AccountName = tostring(split(User, "@")[0]), AccountUPNSuffix = tostring(split(User, "@")[1])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: User
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: AccountUPNSuffix
version: 1.0.4
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: Microsoft Security Research
    support:
        tier: Community
    categories:
        domains: [ "Security - Others", "Identity" ]

Stages and Predicates

Stage 1: source

ZoomLogs

Stage 2: where

| where Event =~ "account.settings_updated"

Stage 3: extend (3 consecutive steps)

| extend EnforceLogin = columnifexists("payload_object_settings_schedule_meeting_enfore_login_b", "")
| extend EnforceLoginDomain = columnifexists("payload_object_settings_schedule_meeting_enfore_login_b", "")
| extend GuestAlerts = columnifexists("payload_object_settings_in_meeting_alert_guest_join_b", "")

Stage 4: where

| where EnforceLogin == 'false' or EnforceLoginDomain == 'false' or GuestAlerts == 'false'

Stage 5: extend

| extend SettingChanged = case(EnforceLogin == 'false' and EnforceLoginDomain == 'false' and GuestAlerts == 'false', "All settings changed",
                            EnforceLogin == 'false' and EnforceLoginDomain == 'false', "Enforced Logons and Restricted Domains Changed",
                            EnforceLoginDomain == 'false' and GuestAlerts == 'false', "Enforced Domains Changed",
                            EnforceLoginDomain == 'false', "Enfored Domains Changed",
                            GuestAlerts == 'false', "Guest Join Alerts Changed",
                            EnforceLogin == 'false', "Enforced Logins Changed",
                            "No Changes")
SettingChanged =
if(EnforceLogin == false and EnforceLoginDomain == false) and GuestAlerts == false"All settings changed"
elifEnforceLogin == false and EnforceLoginDomain == false"Enforced Logons and Restricted Domains Changed"
elifEnforceLoginDomain == false and GuestAlerts == false"Enforced Domains Changed"
elifEnforceLoginDomain == false"Enfored Domains Changed"
elifGuestAlerts == false"Guest Join Alerts Changed"
elifEnforceLogin == false"Enforced Logins Changed"
else"No Changes"

Stage 6: extend

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

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
EnforceLoginextend
EnforceLoginDomainextend
GuestAlertsextend
SettingChangedextend
AccountNameextend
AccountUPNSuffixextend

Guardian- Gender Bias Policy Violation Detection

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

'This alert creates an incident when Gender Bias Policy Violation detected from the Guardian.'

Rule Body

id: cae24b9e-a614-4213-b382-00698e89b037
name: Guardian- Gender Bias Policy Violation Detection
description: |
  'This alert creates an incident when Gender Bias Policy Violation detected from the Guardian.'
severity: Low
status: Available
requiredDataConnectors:
  - connectorId: BoschAIShield
    dataTypes:
      - Guardian
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: |
  Guardian
  | where PolicyViolatedControlFeature =~ 'Gender Bias'
  | where Severity =~ 'Low'
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: HostName
      - identifier: NTDomain
        columnName: NTDomain
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIP
eventGroupingSettings:
  aggregationKind: SingleAlert
alertDetailsOverride:
  alertDisplayNameFormat: Guardian- Gender Bias Policy Violation detection
  alertDescriptionFormat: |
    This query detects Gender Bias Policy Violation detected from the Guardian generated at {{TimeGenerated}}.\n\nPlease check the source for more information and investigate further.
  alertTacticsColumnName: null
  alertSeverityColumnName: Severity
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

Guardian

Stage 2: where

| where PolicyViolatedControlFeature =~ 'Gender Bias'

Stage 3: where

| where Severity =~ 'Low'

Indicators

These rows show field, operator, and value matches.

New access credential added to Application or Service Principal

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

This will alert when an admin or app owner account adds a new credential to an Application or Service Principal where a verify KeyCredential was already present for the app. If a threat actor obtains access to an account with sufficient privileges and adds the alternate authentication material triggering this event, the threat actor can now authenticate as the Application or Service Principal using this credential. Additional information on OAuth Credential Grants can be found in RFC 6749 Section 4.4 or https://docs.microsoft.com/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow For further information on AuditLogs please see https://docs.microsoft.com/azure/active-directory/reports-monitoring/reference-audit-activities.

MITRE ATT&CK Coverage

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: 79566f41-df67-4e10-a703-c38a6213afd8
name: New access credential added to Application or Service Principal
description: |
  'This will alert when an admin or app owner account adds a new credential to an Application or Service Principal where a verify KeyCredential was already present for the app.
  If a threat actor obtains access to an account with sufficient privileges and adds the alternate authentication material triggering this event, the threat actor can now authenticate as the Application or Service Principal using this credential.
  Additional information on OAuth Credential Grants can be found in RFC 6749 Section 4.4 or https://docs.microsoft.com/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow
  For further information on AuditLogs please see https://docs.microsoft.com/azure/active-directory/reports-monitoring/reference-audit-activities.'
severity: Medium
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AuditLogs
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - DefenseEvasion
relevantTechniques:
  - T1550.001
tags:
  - Solorigate
  - NOBELIUM
query: |
  AuditLogs
  | where OperationName has_any ("Add service principal", "Certificates and secrets management") // captures "Add service principal", "Add service principal credentials", and "Update application - Certificates and secrets management" events
  | where Result =~ "success"
  | where tostring(InitiatedBy.user.userPrincipalName) has "@" or tostring(InitiatedBy.app.displayName) has "@"
  | mv-apply TargetResource = TargetResources on 
    (
        where TargetResource.type =~ "Application"
        | extend targetDisplayName = tostring(TargetResource.displayName),
                 targetId = tostring(TargetResource.id),
                 targetType = tostring(TargetResource.type),
                 keyEvents = TargetResource.modifiedProperties
    )
  | mv-apply Property = keyEvents on 
    (
        where Property.displayName =~ "KeyDescription"
        | extend new_value_set = parse_json(tostring(Property.newValue)),
                 old_value_set = parse_json(tostring(Property.oldValue))
    )
  | where old_value_set != "[]"
  | extend diff = set_difference(new_value_set, old_value_set)
  | where isnotempty(diff)
  | parse diff with * "KeyIdentifier=" keyIdentifier:string ",KeyType=" keyType:string ",KeyUsage=" keyUsage:string ",DisplayName=" keyDisplayName:string "]" *
  | where keyUsage =~ "Verify"
  | mv-apply AdditionalDetail = AdditionalDetails on 
    (
        where AdditionalDetail.key =~ "User-Agent"
        | extend UserAgent = tostring(AdditionalDetail.value)
    )
  | extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
  | extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
  | extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
  | extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
  | extend InitiatingIpAddress = tostring(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))
  // The below line is currently commented out but Microsoft Sentinel users can modify this query to show only Application or only Service Principal events in their environment
  //| where targetType =~ "Application" // or targetType =~ "ServicePrincipal"
  | project-away diff, new_value_set, old_value_set
  | project-reorder TimeGenerated, OperationName, InitiatingAppName, InitiatingAppServicePrincipalId, InitiatingUserPrincipalName, InitiatingAadUserId, InitiatingIpAddress, UserAgent, targetDisplayName, targetId, targetType, keyDisplayName, keyType, keyUsage, keyIdentifier, CorrelationId, TenantId
  | extend Name = tostring(split(InitiatingUserPrincipalName,'@',0)[0]), UPNSuffix = tostring(split(InitiatingUserPrincipalName,'@',1)[0])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: InitiatingUserPrincipalName
      - identifier: Name
        columnName: Name
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: InitiatingIpAddress
  - entityType: Account
    fieldMappings:
      - identifier: AadUserId
        columnName: InitiatingAadUserId
  - entityType: Account
    fieldMappings:
      - identifier: AadUserId
        columnName: InitiatingAppServicePrincipalId
version: 1.1.2
kind: Scheduled

Stages and Predicates

Stage 1: source

AuditLogs

Stage 2: where

| where OperationName has_any ("Add service principal", "Certificates and secrets management")

Stage 3: where

| where Result =~ "success"

Stage 4: where

| where tostring(InitiatedBy.user.userPrincipalName) has "@" or tostring(InitiatedBy.app.displayName) has "@"

Stage 5: kusto:mv-apply

| mv-apply TargetResource = TargetResources on 
  (
      where TargetResource.type =~ "Application"
      | extend targetDisplayName = tostring(TargetResource.displayName),
               targetId = tostring(TargetResource.id),
               targetType = tostring(TargetResource.type),
               keyEvents = TargetResource.modifiedProperties
  )

Stage 6: kusto:mv-apply

| mv-apply Property = keyEvents on 
  (
      where Property.displayName =~ "KeyDescription"
      | extend new_value_set = parse_json(tostring(Property.newValue)),
               old_value_set = parse_json(tostring(Property.oldValue))
  )

Stage 7: where

| where old_value_set != "[]"

Stage 8: extend

| extend diff = set_difference(new_value_set, old_value_set)

Stage 9: where

| where isnotempty(diff)

Stage 10: parse

| parse diff with * "KeyIdentifier=" keyIdentifier:string ",KeyType=" keyType:string ",KeyUsage=" keyUsage:string ",DisplayName=" keyDisplayName:string "]" *

Stage 11: where

| where keyUsage =~ "Verify"

Stage 12: kusto:mv-apply

| mv-apply AdditionalDetail = AdditionalDetails on 
  (
      where AdditionalDetail.key =~ "User-Agent"
      | extend UserAgent = tostring(AdditionalDetail.value)
  )

Stage 13: extend (5 consecutive steps)

| extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
| extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
| extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend InitiatingIpAddress = tostring(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))

Stage 14: project-away

| project-away diff, new_value_set, old_value_set

Stage 15: project-reorder

| project-reorder TimeGenerated, OperationName, InitiatingAppName, InitiatingAppServicePrincipalId, InitiatingUserPrincipalName, InitiatingAadUserId, InitiatingIpAddress, UserAgent, targetDisplayName, targetId, targetType, keyDisplayName, keyType, keyUsage, keyIdentifier, CorrelationId, TenantId

Stage 16: extend

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

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
AdditionalDetail.keyeq
  • User-Agent transforms: kusto_dynamic_path
field:"AdditionalDetail.key" kind:eq value:"User-Agent"
InitiatedBy.app.displayNamematch
  • @ transforms: tostring, term, kusto_dynamic_path corpus 4 (kusto 4)
field:"InitiatedBy.app.displayName" kind:match value:"@"
InitiatedBy.user.userPrincipalNamematch
  • @ transforms: tostring, term, kusto_dynamic_path corpus 4 (kusto 4)
field:"InitiatedBy.user.userPrincipalName" kind:match value:"@"
OperationNamematch
  • Add service principal transforms: term corpus 4 (kusto 4)
  • Certificates and secrets management transforms: term corpus 5 (kusto 5)
field:"OperationName" kind:match
Property.displayNameeq
  • KeyDescription transforms: kusto_dynamic_path
field:"Property.displayName" kind:eq value:"KeyDescription"
Resulteq
  • success corpus 23 (kusto 23)
field:"Result" kind:eq value:"success"
TargetResource.typeeq
  • Application transforms: kusto_dynamic_path
field:"TargetResource.type" kind:eq value:"Application"
diffis_not_null
  • (no value, null check)
field:"diff" kind:is_not_null
keyUsageeq
  • Verify
field:"keyUsage" kind:eq value:"Verify"
old_value_setne
  • []
field:"old_value_set" kind:ne value:"[]"

Output Fields

These fields are emitted when the rule matches.

FieldSource
InitiatingAppNameextend
InitiatingAppServicePrincipalIdextend
InitiatingUserPrincipalNameextend
InitiatingAadUserIdextend
InitiatingIpAddressextend
Nameextend
UPNSuffixextend

Pathlock TDnR - CUA Settings Changes

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

Detects changes to SAP Central User Administration (CUA) settings, forwarded by Pathlock Threat Detection and Response. Unauthorized CUA modifications may affect centralized user management across connected SAP systems and could be used to establish persistent access.

MITRE ATT&CK Coverage

TacticTechniques
Persistence

Rule Body

id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c17
name: Pathlock TDnR - CUA Settings Changes
kind: Scheduled
description: >-
  Detects changes to SAP Central User Administration (CUA) settings, forwarded by Pathlock Threat
  Detection and Response. Unauthorized CUA modifications may affect centralized user management
  across connected SAP systems and could be used to establish persistent access.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: Pathlock_TDnR
    dataTypes:
      - Pathlock_TDnR_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Persistence
relevantTechniques:
  - T1098
query: |
  Pathlock_TDnR_CL
  | where DataSource == "CHANGEDOC_USER_CUA"
  | project TimeGenerated, Sysid, DataSource, Eventid, Instance, Hostname, Bname,
            Tcode, Report, Area, Subid, SrcIp, DestIp, AffectedUser, LogLine,
            MsgType, MsgId, MsgNo, MessageV1, MessageV2, MessageV3, MessageV4, CentralTs
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: Bname
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: Hostname
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SrcIp
eventGroupingSettings:
  aggregationKind: SingleAlert
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: 5h
    matchingMethod: AnyAlert
    groupByEntities: []
    groupByAlertDetails: []
    groupByCustomDetails: []
suppressionDuration: 5h
suppressionEnabled: false
version: 1.0.0

Stages and Predicates

Stage 1: source

Pathlock_TDnR_CL

Stage 2: where

| where DataSource == "CHANGEDOC_USER_CUA"

Stage 3: project

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

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

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

Possible AiTM Phishing Attempt Against Microsoft Entra ID

#
Status
available
Severity
medium
Time window
1d
Group by
$left.IPAddress, $right.DstIpAddr
Source
github.com/Azure/Azure-Sentinel

Threat actors may attempt to phish users in order to hijack a users sign-in session, and skip the authentication process even if the user had enabled multifactor authentication (MFA) by stealing and replaying stolen credentials and session cookies. This detection looks for successful Microsoft Entra ID sign ins that had a high risk profile, indicating it had suspicious characteristics such as an unusual location, ISP, user agent, or use of anonymizer services. It then looks for a network connection to the IP address that made the sign in immediately before the sign in, that may indicate a user connecting to a phishing site at that IP address and having their authentication session hijacked. Ref: https://www.microsoft.com/security/blog/2022/07/12/from-cookie-theft-to-bec-attackers-use-aitm-phishing-sites-as-entry-point-to-further-financial-fraud/

MITRE ATT&CK Coverage

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: 16daa67c-b137-48dc-8eb7-76598a44791a
name: Possible AiTM Phishing Attempt Against Microsoft Entra ID
description: |
  'Threat actors may attempt to phish users in order to hijack a users sign-in session, and skip the authentication process even if the user had enabled multifactor authentication (MFA) by stealing and replaying stolen credentials and session cookies.
  This detection looks for successful Microsoft Entra ID sign ins that had a high risk profile, indicating it had suspicious characteristics such as an unusual location, ISP, user agent, or use of anonymizer services.
  It then looks for a network connection to the IP address that made the sign in immediately before the sign in, that may indicate a user connecting to a phishing site at that IP address and having their authentication session hijacked.
  Ref: https://www.microsoft.com/security/blog/2022/07/12/from-cookie-theft-to-bec-attackers-use-aitm-phishing-sites-as-entry-point-to-further-financial-fraud/'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - SigninLogs
  - connectorId: Zscaler
    dataTypes:
      - CommonSecurityLog (Zscaler)
queryFrequency: 1h
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
  - DefenseEvasion
  - CredentialAccess
relevantTechniques:
  - T1078.004
  - T1557
  - T1111
query: |
  let time_threshold = 10m;
  let RiskySignins = materialize (SigninLogs
  | where TimeGenerated > ago(1d)
  | where ResultType == 0
  | where RiskLevelDuringSignIn =~ "high" or RiskLevelAggregated =~ "high"
  | extend SignInTime = TimeGenerated, Name=split(UserPrincipalName, "@")[0], UPNSuffix=split(UserPrincipalName, "@")[1]);
  let ips = todynamic(toscalar(RiskySignins | summarize make_list(IPAddress)));
  RiskySignins
  | join kind=inner (_Im_WebSession(starttime=ago(1d), ipaddr_has_any_prefix=ips, eventresult="Success", pack=True))
  on $left.IPAddress == $right.DstIpAddr
  | where EventStartTime < TimeGenerated
  | extend TimeDelta = TimeGenerated - EventStartTime
  | where TimeDelta <= time_threshold
  | extend NetworkEventStartTime = EventStartTime, NetworkEventEndTime = EventEndTime
  | extend SrcUsername = column_ifexists("SrcUsername", "Unknown")
  | project-reorder SignInTime, UserPrincipalName, IPAddress, AppDisplayName, ClientAppUsed, DeviceDetail, LocationDetails, NetworkLocationDetails, RiskEventTypes, UserAgent, NetworkEventStartTime, NetworkEventEndTime, SrcIpAddr, DstIpAddr, DstPortNumber, Dvc, DvcHostname, SrcBytes, NetworkProtocol, SrcUsername
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: Name
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPAddress
alertDetailsOverride:
  alertDisplayNameFormat: Possible AiTM Phishing Attempt Against {{UserPrincipalName}} From {{IPAddress}}
  alertDescriptionFormat: |
    Threat actors may attempt to phish users in order to hijack a users sign-in session, and skip the authentication process even if the user had enabled multifactor authentication (MFA) by stealing and replaying stolen credentials and session cookies.
    This detection looks for successful Microsoft Entra ID sign ins (in this case from {{UserPrincipalName}}) that had a high risk profile, indicating it had suspicious characteristics such as an unusual location, ISP, user agent, or use of anonymizer services.
    It then looks for a network connection to the IP address (in this case {{IPAddress}}) that made the sign in immediately before the sign in, that may indicate a user connecting to a phishing site at that IP address and having their authentication session hijacked.
    Ref: https://www.microsoft.com/security/blog/2022/07/12/from-cookie-theft-to-bec-attackers-use-aitm-phishing-sites-as-entry-point-to-further-financial-fraud/
version: 1.0.4
kind: Scheduled

Stages and Predicates

Parameters

let time_threshold = 10m;

let RiskySignins is inlined into the numbered stages below.

Let Binding: ips used in Stage 6

let ips = todynamic(toscalar(RiskySignins | summarize make_list(IPAddress)));

Stages 1 to 5 define let RiskySignins as the rule's main pipeline source. Stages 6 to 12 use it.

Stage 1: source

SigninLogs

Stage 2: where

| where TimeGenerated > ago(1d)

Stage 3: where

| where ResultType == 0

Stage 4: where

| where RiskLevelDuringSignIn =~ "high" or RiskLevelAggregated =~ "high"

Stage 5: extend

| extend SignInTime = TimeGenerated, Name=split(UserPrincipalName, "@")[0], UPNSuffix=split(UserPrincipalName, "@")[1]

Stage 6: join

RiskySignins
| join kind=inner (_Im_WebSession(starttime=ago(1d), ipaddr_has_any_prefix=ips, eventresult="Success", pack=True))
on $left.IPAddress == $right.DstIpAddr

Stage 7: where

where EventStartTime < TimeGenerated

Stage 8: extend

extend TimeDelta

Stage 9: where

where TimeDelta <= "10m"

Stage 10: extend

extend NetworkEventEndTime, NetworkEventStartTime

Stage 11: extend

extend SrcUsername

Stage 12: project-reorder

project-reorder

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
Nameextend
SignInTimeextend
UPNSuffixextend
TimeDeltaextend
NetworkEventEndTimeextend
NetworkEventStartTimeextend
SrcUsernameextend

Privileged Accounts - Sign in Failure Spikes

#
Status
available
Severity
high
Time window
14d
Group by
$left.AccountUPN, $left.AnomalyHour, $right.DateHour, $right.UserPrincipalName, AccountUPN, Roles, UserPrincipalName
Source
github.com/Azure/Azure-Sentinel

Identifies spike in failed sign-ins from Privileged accounts. Privileged accounts list can be based on IdentityInfo UEBA table. Spike is determined based on Time series anomaly which will look at historical baseline values. Ref : https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-privileged-accounts#things-to-monitor

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Telemetry Coverage

Rule Body

id: 34c5aff9-a8c2-4601-9654-c7e46342d03b
name: Privileged Accounts - Sign in Failure Spikes
description: |
  ' Identifies spike in failed sign-ins from Privileged accounts. Privileged accounts list can be based on IdentityInfo UEBA table.
  Spike is determined based on Time series anomaly which will look at historical baseline values.
  Ref : https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-privileged-accounts#things-to-monitor'
severity: High
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - SigninLogs
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AADNonInteractiveUserSignInLogs
  - connectorId: BehaviorAnalytics
    dataTypes:
      - IdentityInfo
queryFrequency: 1d
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - InitialAccess
relevantTechniques:
  - T1078.004
tags:
  - AADSecOpsGuide
query: |
  let starttime = 14d;
  let timeframe = 1d;
  let scorethreshold = 3;
  let baselinethreshold = 5;
  let aadFunc = (tableName:string){
      IdentityInfo
      | where TimeGenerated > ago(starttime)
      | summarize arg_max(TimeGenerated, *) by AccountUPN
      | mv-expand AssignedRoles
      | where AssignedRoles contains 'Admin' or GroupMembership has "Admin"
      | summarize Roles = make_list(AssignedRoles) by AccountUPN = tolower(AccountUPN)
      | join kind=inner (
          table(tableName)
          | where TimeGenerated between (startofday(ago(starttime))..startofday(now()))
          | where ResultType != 0
          | extend UserPrincipalName = tolower(UserPrincipalName)
      ) on $left.AccountUPN == $right.UserPrincipalName
      | extend timestamp = TimeGenerated, AccountCustomEntity = UserPrincipalName, Roles = tostring(Roles)
  };
  let aadSignin = aadFunc("SigninLogs");
  let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");
  let allSignins = union isfuzzy=true aadSignin, aadNonInt;
  let TimeSeriesAlerts = 
      allSignins
      | make-series HourlyCount=count() on TimeGenerated from startofday(ago(starttime)) to startofday(now()) step 1h by UserPrincipalName, Roles
      | extend (anomalies, score, baseline) = series_decompose_anomalies(HourlyCount, scorethreshold, -1, 'linefit')
      | mv-expand HourlyCount to typeof(double), TimeGenerated to typeof(datetime), anomalies to typeof(double), score to typeof(double), baseline to typeof(long)
      // Filtering low count events per baselinethreshold
      | where anomalies > 0 and baseline > baselinethreshold
      | extend AnomalyHour = TimeGenerated
      | project UserPrincipalName, Roles, AnomalyHour, TimeGenerated, HourlyCount, baseline, anomalies, score;
  // Filter the alerts for specified timeframe
  TimeSeriesAlerts
  | where TimeGenerated > startofday(ago(timeframe))
  | join kind=inner ( 
      allSignins
      | where TimeGenerated > startofday(ago(timeframe))
      // create a new column and round to hour
      | extend DateHour = bin(TimeGenerated, 1h)
      | summarize PartialFailedSignins = count(), LatestAnomalyTime = arg_max(TimeGenerated, *) by bin(TimeGenerated, 1h), OperationName, Category, ResultType, ResultDescription, UserPrincipalName, Roles, UserDisplayName, AppDisplayName, ClientAppUsed, IPAddress, ResourceDisplayName
  ) on UserPrincipalName, $left.AnomalyHour == $right.DateHour
  | project LatestAnomalyTime, OperationName, Category, UserPrincipalName, Roles = todynamic(Roles), UserDisplayName, ResultType, ResultDescription, AppDisplayName, ClientAppUsed, UserAgent, IPAddress, Location, AuthenticationRequirement, ConditionalAccessStatus, ResourceDisplayName, PartialFailedSignins, TotalFailedSignins = HourlyCount, baseline, anomalies, score
  | extend Name = tostring(split(UserPrincipalName,'@',0)[0]), UPNSuffix = tostring(split(UserPrincipalName,'@',1)[0])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: UserPrincipalName    
      - identifier: Name
        columnName: Name      
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPAddress
version: 1.1.1
kind: Scheduled

Stages and Predicates

Parameters

let starttime = 14d;
let timeframe = 1d;
let scorethreshold = 3;
let baselinethreshold = 5;
let aadSignin = aadFunc("SigninLogs");
let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");

let allSignins and let TimeSeriesAlerts are inlined into the numbered stages below.

Let Binding: aadFunc

let aadFunc = (tableName:string){
    IdentityInfo
    | where TimeGenerated > ago(starttime)
    | summarize arg_max(TimeGenerated, *) by AccountUPN
    | mv-expand AssignedRoles
    | where AssignedRoles contains 'Admin' or GroupMembership has "Admin"
    | summarize Roles = make_list(AssignedRoles) by AccountUPN = tolower(AccountUPN)
    | join kind=inner (
        table(tableName)
        | where TimeGenerated between (startofday(ago(starttime))..startofday(now()))
        | where ResultType != 0
        | extend UserPrincipalName = tolower(UserPrincipalName)
    ) on $left.AccountUPN == $right.UserPrincipalName
    | extend timestamp = TimeGenerated, AccountCustomEntity = UserPrincipalName, Roles = tostring(Roles)
};

Stage 1: source

let aadSignin

Stage 2: source

let aadNonInt

Stage 3: source

let allSignins

Stage 4: source

let TimeSeriesAlerts

Stage 5: union

union of 2 branches

Stage 6: source

IdentityInfo

Stage 7: where

where TimeGenerated > ago(1209600s)

Stage 8: summarize

summarize by AccountUPN

Stage 9: mv-expand

mv-expand AssignedRoles

Stage 10: where

where (AssignedRoles contains "Admin" or GroupMembership has "Admin")

Stage 11: summarize

summarize AccountUPN, Roles by AccountUPN

Stage 12: join

join kind=inner (...) on $left.AccountUPN, $right.UserPrincipalName

Stage 13: extend

extend AccountCustomEntity, Roles, timestamp

Stage 14: source

IdentityInfo

Stage 15: where

where TimeGenerated > ago(1209600s)

Stage 16: summarize

summarize by AccountUPN

Stage 17: mv-expand

mv-expand AssignedRoles

Stage 18: where

where (AssignedRoles contains "Admin" or GroupMembership has "Admin")

Stage 19: summarize

summarize AccountUPN, Roles by AccountUPN

Stage 20: join

join kind=inner (...) on $left.AccountUPN, $right.UserPrincipalName

Stage 21: extend

extend AccountCustomEntity, Roles, timestamp

Stage 22: summarize

summarize HourlyCount by UserPrincipalName, Roles

Stage 23: extend

extend anomalies, baseline, score

Stage 24: mv-expand

mv-expand HourlyCount

Stage 25: where

where anomalies > 0 and baseline > 5

Stage 26: extend

extend AnomalyHour

Stage 27: project

project AnomalyHour, HourlyCount, Roles, TimeGenerated, UserPrincipalName, anomalies, baseline, score

Stage 28: where

where TimeGenerated > ago(86400s)

Stage 29: join

join kind=inner (allSignins) on UserPrincipalName, $left.AnomalyHour, $right.DateHour

Stage 30: project

project AppDisplayName, AuthenticationRequirement, Category, ClientAppUsed, ConditionalAccessStatus, IPAddress, LatestAnomalyTime, Location, OperationName, PartialFailedSignins, ResourceDisplayName, ResultDescription, ResultType, Roles, TotalFailedSignins, UserAgent, UserDisplayName, UserPrincipalName, anomalies, baseline, score

Stage 31: extend

extend Name, UPNSuffix

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AppDisplayNameproject
AuthenticationRequirementproject
Categoryproject
ClientAppUsedproject
ConditionalAccessStatusproject
IPAddressproject
LatestAnomalyTimeproject
Locationproject
OperationNameproject
PartialFailedSigninsproject
ResourceDisplayNameproject
ResultDescriptionproject
ResultTypeproject
Rolesproject
TotalFailedSigninsproject
UserAgentproject
UserDisplayNameproject
UserPrincipalNameproject
anomaliesproject
baselineproject
scoreproject
Nameextend
UPNSuffixextend

Silk Typhoon Suspicious UM Service Error

#
Severity
low
Time window
1d
Author
Microsoft Security Research
Source
github.com/Azure/Azure-Sentinel

'This query looks for errors that may indicate that an attacker is attempting to exploit a vulnerability in the service. Reference: https://www.microsoft.com/security/blog/2021/03/02/hafnium-targeting-exchange-servers/'

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Rule Body

id: 0625fcce-6d52-491e-8c68-1d9b801d25b9
name: Silk Typhoon Suspicious UM Service Error
description: |
  'This query looks for errors that may indicate that an attacker is attempting to exploit a vulnerability in the service. 
  Reference: https://www.microsoft.com/security/blog/2021/03/02/hafnium-targeting-exchange-servers/'
severity: Low
requiredDataConnectors: []
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1190
query: |
  Event
  | where EventLog =~ "Application"
  | where Source startswith "MSExchange"
  | where EventLevelName =~ "error"
  | where (RenderedDescription startswith "Watson report" and RenderedDescription contains "umworkerprocess" and RenderedDescription contains "TextFormattingRunProperties") or RenderedDescription startswith "An unhandled exception occurred in a UM worker process" or RenderedDescription startswith "The Microsoft Exchange Unified Messaging service" or RenderedDescription contains "MSExchange Unified Messaging"
  | where RenderedDescription !contains "System.OutOfMemoryException"
  | extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
  | extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
  | project-away DomainIndex
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: Computer
      - identifier: HostName
        columnName: HostName
      - identifier: NTDomain
        columnName: HostNameDomain
version: 1.0.4
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: Microsoft Security Research
    support:
        tier: Community
    categories:
        domains: [ "Security - Threat Intelligence" ]

Stages and Predicates

Stage 1: source

Event

Stage 2: where

| where EventLog =~ "Application"

Stage 3: where

| where Source startswith "MSExchange"

Stage 4: where

| where EventLevelName =~ "error"

Stage 5: where

| where (RenderedDescription startswith "Watson report" and RenderedDescription contains "umworkerprocess" and RenderedDescription contains "TextFormattingRunProperties") or RenderedDescription startswith "An unhandled exception occurred in a UM worker process" or RenderedDescription startswith "The Microsoft Exchange Unified Messaging service" or RenderedDescription contains "MSExchange Unified Messaging"

Stage 6: where

| where RenderedDescription !contains "System.OutOfMemoryException"

Stage 7: extend

| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))

Stage 8: extend

| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
HostNameDomain =
ifDomainIndex != -1substring(Computer, (DomainIndex + 1))
elseComputer

Stage 9: project-away

| project-away DomainIndex

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
EventLevelNameeq
  • error
field:"EventLevelName" kind:eq value:"error"
EventLogeq
  • Application
field:"EventLog" kind:eq value:"Application"
RenderedDescriptioncontains
  • MSExchange Unified Messaging
  • TextFormattingRunProperties
  • umworkerprocess
field:"RenderedDescription" kind:contains
RenderedDescriptionstarts_with
  • An unhandled exception occurred in a UM worker process
  • The Microsoft Exchange Unified Messaging service
  • Watson report
field:"RenderedDescription" kind:starts_with

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
RenderedDescriptioncontainsSystem.OutOfMemoryExceptionexcludes:RenderedDescription field:"RenderedDescription" value:"System.OutOfMemoryException"

Output Fields

These fields are emitted when the rule matches.

FieldSource
HostNameextend
HostNameDomainextend

Silverfort - Log4Shell Incident

#
Severity
high
Time window
15m
Source
github.com/Azure/Azure-Sentinel

'Vulnerability allows attackers to execute arbitrary code on affected systems by exploiting a flaw in the way Log4j handles log messages containing specially crafted strings'

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Rule Body

id: d6abed70-4043-46da-9304-a98f3446fa5f
name: Silverfort - Log4Shell Incident
description: |
  'Vulnerability allows attackers to execute arbitrary code on affected systems by exploiting a flaw in the way Log4j handles log messages containing specially crafted strings'
severity: High
requiredDataConnectors:
  - connectorId: SilverfortAma
    dataTypes:
      - CommonSecurityLog
tactics:
  - InitialAccess
relevantTechniques:
  - T1190 #Exploit Public-Facing Application
query: |-
  CommonSecurityLog 
  | where DeviceVendor has 'Silverfort'
  | where DeviceProduct has 'Admin Console'
  | where DeviceEventClassID == "NewIncident"
  | where Message has "Log4Shell"
  | extend UserName = parse_json(replace('^""|""$', '', Message))['userName']
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: UserName
queryPeriod: 15m
queryFrequency: 15m
triggerOperator: gt
triggerThreshold: 0
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

CommonSecurityLog

Stage 2: where

| where DeviceVendor has 'Silverfort'

Stage 3: where

| where DeviceProduct has 'Admin Console'

Stage 4: where

| where DeviceEventClassID == "NewIncident"

Stage 5: where

| where Message has "Log4Shell"

Stage 6: extend

| extend UserName = parse_json(replace('^""|""$', '', Message))['userName']

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
UserNameextend

Suspicious office child process created

#
Group by
DeviceFileEvents_FileName
Author
FalconForce
Source
github.com/FalconForceTeam/FalconFriday

This query obtains a list of downloaded Office documents (doc, xls, etc.) by looking at files written by commonly used web browsers. It then searches for invocations of an Office program by double-clicking on these files. If these processes spawn an uncommon child process this is reported as suspicious.

Known False Positives

  • Some Office plugins might cause false positives if they change the Office program behavior.

MITRE ATT&CK Coverage

TacticTechniques
Execution

Telemetry Coverage

Rule Body

let timeframe = 2*1d;
let browsers = dynamic(["iexplore.exe", "chrome.exe", "firefox.exe", "msedge.exe", "msedgewebview2.exe"]);
let ext = dynamic([".docm", ".xlsm", ".xls", ".doc", ".pptm", ".ppt"]);
let officeApps = dynamic(["winword.exe", "excel.exe", "powerpnt.exe"]);
let whitelist = dynamic(["MSOSYNC.exe", "splwow64.exe", "csc.exe", "outlook.exe", "AcroRd32.exe", "Acrobat.exe", "explorer.exe", "DW20.exe",
"Microsoft.Mashup.Container.Loader.exe", "Microsoft.Mashup.Container.NetFX40.exe", "WerFault.exe", "CLVIEW.exe", "wermgr.exe"]);
let whitelistedDomains = dynamic([""]);
let binPeriodForSearch = 1h;
let timeDiffFileCreateNetworkEvent = 15; // In seconds. Don't make this more than 15s because of comparison later on.
// List all filecreate events where the filename has a known Office extension which can contain macros.
let fileDownloads = materialize(
  DeviceFileEvents
  | where ingestion_time() >= ago(timeframe)
  // We need to have FileCreated and FileRenamed here because some browsers first download the file under a different name and rename it when it's done.
  // For example, the Chrome .crdownload files are all renamed to the intended name after the download has finished.
  | where ActionType in~ ("FileCreated", "FileRenamed") and InitiatingProcessFileName in~ (browsers) and FileName has_any (ext)
  // We need to do this to limit the search of deviceNetworkEvents. Otherwise, the dataset becomes too big to join in MDE.
  | extend period=bin(Timestamp, binPeriodForSearch)
  // Optimizations to keep MDE happy. Otherwise the database gets too big.
  | project DeviceId, InitiatingProcessFileName, InitiatingProcessId, period, FileName, Timestamp
  // The renames are meant to avoid confusions as there will be a lot of FileNames from different tables.
  | project-rename DeviceFileEvents_InitiatingProcessFileName = InitiatingProcessFileName,
                   DeviceFileEvents_FileName = FileName,
                   DeviceFileEvents_Timestamp = Timestamp
);
// Now we need to find the network event that triggered the the filewrite.
// This is an approximation based on timestamp, deviceid, pid and process name.
let downloadSource = materialize(
  DeviceNetworkEvents
  | where ingestion_time() >= ago(timeframe)
  | where DeviceId in~ ((fileDownloads | project DeviceId)) and RemotePort in (80, 443)
  | extend period=bin(Timestamp, binPeriodForSearch)
  // Exclude allow-listed domains. Here you want to allow-list your internal Sharepoint environment.
  // This can be useful if you want to use this rule for external attacks.
  // There is a trade-off, as an internal attacker might abuse your Sharepoint for malware.
  | where parse_url(RemoteUrl).Host !in~ (whitelistedDomains)
  | lookup kind=inner fileDownloads on DeviceId, InitiatingProcessId, $left.InitiatingProcessFileName == $right.DeviceFileEvents_InitiatingProcessFileName, period
  | extend TimeDiff = datetime_diff('second', Timestamp, DeviceFileEvents_Timestamp)
  // The filecreate and network event should happen within max 15 second of each other.
  | where  -timeDiffFileCreateNetworkEvent < TimeDiff and TimeDiff < timeDiffFileCreateNetworkEvent
  // We're now only interested in the unique filenames of the downloads and the location they're possibly downloaded from.
  | summarize possibelURLs=make_set(RemoteUrl) by DeviceFileEvents_FileName
);
// Final step in tying everything together.
// Find Office applications that create a child process which is in our previously generated list.
// We don't filter on devicename because we want to see all instances of this file being run.
DeviceProcessEvents
| where ingestion_time() >= ago(timeframe)
| where ActionType =~ "ProcessCreated"
| where InitiatingProcessFileName in~ (officeApps) and FileName !in~ (officeApps) and FileName !in~ (whitelist) and FileName !in~ (browsers)
| project-rename DeviceProcessEvents_InitiatingProcessCommandLine = InitiatingProcessCommandLine
| where DeviceProcessEvents_InitiatingProcessCommandLine has_any (( downloadSource | project DeviceFileEvents_FileName))
| where not(FolderPath matches regex @"C:\\Program Files \(x86\)\\TechSmith\\Camtasia Studio \d+\\TscHelp\.exe")
| where not(ProcessCommandLine matches regex @"^""?rundll32""? C:\\WINDOWS\\system32\\spool\\DRIVERS\\((x64)|(x86))\\\d\\hpmsn\d+.((dll)|(DLL)),")
// Verclsid cannot be filtered as it's a LOLBIN which allows creating arbitrary COM objects.
// We can only filter the execution of very specific COM objects.
// Embedded Outlook item in an Office file.
//00020D0B-0000-0000-C000-000000000046 == Outlook 97-2003 Object. 00000112-0000-0000-C000-000000000046 == IOLEInterface 0x5 == CLSCTX_INPROC_SERVER	| CLSCTX_LOCAL_SERVER
| where not(ProcessCommandLine matches regex @"^""?verclsid\.exe""? \/S \/C \{00020D0B-0000-0000-C000-000000000046\} \/I \{00000112-0000-0000-C000-000000000046\} \/X 0x5$")
// Embedded Adobe document in Office file.
| where not(ProcessCommandLine matches regex @"^""?verclsid\.exe""? \/S \/C \{B801CA65-A1FC-11D0-85AD-444553540000\} \/I \{00000112-0000-0000-C000-000000000046\} \/X 0x5$")
// Embedded XML file in Office file.
| where not(ProcessCommandLine matches regex @"^""?verclsid\.exe""? \/S \/C \{48123BC4-99D9-11D1-A6B3-00C04FD91555\} \/I \{00000112-0000-0000-C000-000000000046\} \/X 0x5$")
// Bitmap image.
| where not(ProcessCommandLine matches regex @"^""?verclsid\.exe""? \/S \/C \{D3E34B21-9D75-101A-8C3D-00AA001A1652\} \/I \{00000112-0000-0000-C000-000000000046\} \/X 0x5$")
// ZIP folder.
| where not(ProcessCommandLine matches regex @"^""?verclsid\.exe""? \/S \/C \{E88DCCE0-B7B3-11D1-A9F0-00AA0060FA31\} \/I \{00000112-0000-0000-C000-000000000046\} \/X 0x5$")
// HTML document.
| where not(ProcessCommandLine matches regex @"^""?verclsid\.exe""? \/S \/C \{25336920-03F9-11CF-8FD0-00AA00686F13\} \/I \{00000112-0000-0000-C000-000000000046\} \/X 0x5$")
// FileProfile is case-sensitive and works on lower-case hashes.
| extend SHA1=tolower(SHA1)
| invoke FileProfile(SHA1, 1000)
| where not(FolderPath startswith @"C:\Windows\System32\spool\drivers\" and IsCertificateValid and IsRootSignerMicrosoft) // All drivers signed by Microsoft are trusted.
// Begin environment-specific filter.
// End environment-specific filter.

Stages and Predicates

Parameters

let timeframe = 2*1d;
let browsers = dynamic(["iexplore.exe", "chrome.exe", "firefox.exe", "msedge.exe", "msedgewebview2.exe"]);
let ext = dynamic([".docm", ".xlsm", ".xls", ".doc", ".pptm", ".ppt"]);
let officeApps = dynamic(["winword.exe", "excel.exe", "powerpnt.exe"]);
let whitelistedDomains = dynamic([""]);
let binPeriodForSearch = 1h;
let timeDiffFileCreateNetworkEvent = 15;

Let Binding: whitelist

let whitelist = dynamic(["MSOSYNC.exe", "splwow64.exe", "csc.exe", "outlook.exe", "AcroRd32.exe", "Acrobat.exe", "explorer.exe", "DW20.exe",
"Microsoft.Mashup.Container.Loader.exe", "Microsoft.Mashup.Container.NetFX40.exe", "WerFault.exe", "CLVIEW.exe", "wermgr.exe"]);

Let Binding: fileDownloads used in Stage 1

let fileDownloads = materialize(
  DeviceFileEvents
  | where ingestion_time() >= ago(timeframe)
  | where ActionType in~ ("FileCreated", "FileRenamed") and InitiatingProcessFileName in~ (browsers) and FileName has_any (ext)
  | extend period=bin(Timestamp, binPeriodForSearch)
  | project DeviceId, InitiatingProcessFileName, InitiatingProcessId, period, FileName, Timestamp
  | project-rename DeviceFileEvents_InitiatingProcessFileName = InitiatingProcessFileName,
                   DeviceFileEvents_FileName = FileName,
                   DeviceFileEvents_Timestamp = Timestamp
);

Let Binding: downloadSource used in Stages 2, 8

let downloadSource = materialize(
  DeviceNetworkEvents
  | where ingestion_time() >= ago(timeframe)
  | where DeviceId in~ ((fileDownloads | project DeviceId)) and RemotePort in (80, 443)
  | extend period=bin(Timestamp, binPeriodForSearch)
  | where parse_url(RemoteUrl).Host !in~ (whitelistedDomains)
  | lookup kind=inner fileDownloads on DeviceId, InitiatingProcessId, $left.InitiatingProcessFileName == $right.DeviceFileEvents_InitiatingProcessFileName, period
  | extend TimeDiff = datetime_diff('second', Timestamp, DeviceFileEvents_Timestamp)
  | where  -timeDiffFileCreateNetworkEvent < TimeDiff and TimeDiff < timeDiffFileCreateNetworkEvent
  | summarize possibelURLs=make_set(RemoteUrl) by DeviceFileEvents_FileName
);

Stage 1: source

let fileDownloads

Stage 2: source

let downloadSource

Stage 3: source

DeviceProcessEvents

Stage 4: where

where ...

Stage 5: where

where ActionType =~ "ProcessCreated"

Stage 6: where

where not (FileName in~ ("AcroRd32.exe", "Acrobat.exe", "CLVIEW.exe", "DW20.exe", "MSOSYNC.exe", "Microsoft.Mashup.Container.Loader.exe", "Microsoft.Mashup.Container.NetFX40.exe", "WerFault.exe", "csc.exe", "explorer.exe", "outlook.exe", "splwow64.exe", "wermgr.exe")) and not (FileName in~ ("chrome.exe", "firefox.exe", "iexplore.exe", "msedge.exe", "msedgewebview2.exe")) and not (FileName in~ ("excel.exe", "powerpnt.exe", "winword.exe")) and InitiatingProcessFileName in~ ("excel.exe", "powerpnt.exe", "winword.exe")

Stage 7: project-rename

project-rename

Stage 8: where

where /* macro: (DeviceProcessEvents_InitiatingProcessCommandLine has_any (downloadSource | project)) */

Stage 9: where

where not (FolderPath matches regex @"C:\\Program Files \(x86\)\\TechSmith\\Camtasia Studio \d+\\TscHelp\.exe")

Stage 10: where

where not (ProcessCommandLine matches regex @"^""?rundll32""? C:\\WINDOWS\\system32\\spool\\DRIVERS\\((x64)|(x86))\\\d\\hpmsn\d+.((dll)|(DLL)),")

Stage 11: where

where not (ProcessCommandLine matches regex @"^""?verclsid\.exe""? \/S \/C \{00020D0B-0000-0000-C000-000000000046\} \/I \{00000112-0000-0000-C000-000000000046\} \/X 0x5$")

Stage 12: where

where not (ProcessCommandLine matches regex @"^""?verclsid\.exe""? \/S \/C \{B801CA65-A1FC-11D0-85AD-444553540000\} \/I \{00000112-0000-0000-C000-000000000046\} \/X 0x5$")

Stage 13: where

where not (ProcessCommandLine matches regex @"^""?verclsid\.exe""? \/S \/C \{48123BC4-99D9-11D1-A6B3-00C04FD91555\} \/I \{00000112-0000-0000-C000-000000000046\} \/X 0x5$")

Stage 14: where

where not (ProcessCommandLine matches regex @"^""?verclsid\.exe""? \/S \/C \{D3E34B21-9D75-101A-8C3D-00AA001A1652\} \/I \{00000112-0000-0000-C000-000000000046\} \/X 0x5$")

Stage 15: where

where not (ProcessCommandLine matches regex @"^""?verclsid\.exe""? \/S \/C \{E88DCCE0-B7B3-11D1-A9F0-00AA0060FA31\} \/I \{00000112-0000-0000-C000-000000000046\} \/X 0x5$")

Stage 16: where

where not (ProcessCommandLine matches regex @"^""?verclsid\.exe""? \/S \/C \{25336920-03F9-11CF-8FD0-00AA00686F13\} \/I \{00000112-0000-0000-C000-000000000046\} \/X 0x5$")

Stage 17: extend

extend SHA1

Stage 18: invoke

invoke

Stage 19: where

where not ((FolderPath startswith @"C:\Windows\System32\spool\drivers\" and IsCertificateValid == true and IsRootSignerMicrosoft == true))

Indicators

These rows show field, operator, and value matches.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
FileNameinAcroRd32.exe, Acrobat.exe, CLVIEW.exe, DW20.exe, MSOSYNC.exe, Microsoft.Mashup.Container.Loader.exe, Microsoft.Mashup.Container.NetFX40.exe, WerFault.exe, csc.exe, explorer.exe, outlook.exe, splwow64.exe, wermgr.exeexcludes:FileName
FileNameinchrome.exe, firefox.exe, iexplore.exe, msedge.exe, msedgewebview2.exeexcludes:FileName
FileNameinexcel.exe, powerpnt.exe, winword.exeexcludes:FileName field:"FileName" value:"excel.exe" field:"FileName" value:"powerpnt.exe" field:"FileName" value:"winword.exe"
FolderPathregex_matchC:\Program Files (x86)\TechSmith\Camtasia Studio \d+\TscHelp.exeexcludes:FolderPath field:"FolderPath" value:"C:\Program Files (x86)\TechSmith\Camtasia Studio \d+\TscHelp.exe"
ProcessCommandLineregex_match^"?rundll32"? C:\WINDOWS\system32\spool\DRIVERS\((x64)|(x86))\\d\hpmsn\d+.((dll)|(DLL)),excludes:ProcessCommandLine
ProcessCommandLineregex_match^"?verclsid.exe"? \/S \/C {00020D0B-0000-0000-C000-000000000046} \/I {00000112-0000-0000-C000-000000000046} \/X 0x5$excludes:ProcessCommandLine
ProcessCommandLineregex_match^"?verclsid.exe"? \/S \/C {B801CA65-A1FC-11D0-85AD-444553540000} \/I {00000112-0000-0000-C000-000000000046} \/X 0x5$excludes:ProcessCommandLine
ProcessCommandLineregex_match^"?verclsid.exe"? \/S \/C {48123BC4-99D9-11D1-A6B3-00C04FD91555} \/I {00000112-0000-0000-C000-000000000046} \/X 0x5$excludes:ProcessCommandLine
ProcessCommandLineregex_match^"?verclsid.exe"? \/S \/C {D3E34B21-9D75-101A-8C3D-00AA001A1652} \/I {00000112-0000-0000-C000-000000000046} \/X 0x5$excludes:ProcessCommandLine
ProcessCommandLineregex_match^"?verclsid.exe"? \/S \/C {E88DCCE0-B7B3-11D1-A9F0-00AA0060FA31} \/I {00000112-0000-0000-C000-000000000046} \/X 0x5$excludes:ProcessCommandLine
ProcessCommandLineregex_match^"?verclsid.exe"? \/S \/C {25336920-03F9-11CF-8FD0-00AA00686F13} \/I {00000112-0000-0000-C000-000000000046} \/X 0x5$excludes:ProcessCommandLine
FolderPathstarts_withC:\Windows\System32\spool\drivers\excludes:FolderPath field:"FolderPath" value:"C:\Windows\System32\spool\drivers\"
IsCertificateValideqtrueexcludes:IsCertificateValid field:"IsCertificateValid" value:"true"
IsRootSignerMicrosofteqtrueexcludes:IsRootSignerMicrosoft field:"IsRootSignerMicrosoft" value:"true"

Output Fields

These fields are emitted when the rule matches.

FieldSource
DeviceProcessEvents_InitiatingProcessCommandLineproject-rename
SHA1extend

TI map Domain entity to Syslog

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

Identifies a match in Syslog table from any Domain IOC from TI

MITRE ATT&CK Coverage

TacticTechniques
Command & Control

Rule Body

id: 532f62c1-fba6-4baa-bbb6-4a32a4ef32fa
name: TI map Domain entity to Syslog
description: |
  Identifies a match in Syslog table from any Domain IOC from TI
severity: Medium
requiredDataConnectors:
  - connectorId: Syslog
    dataTypes:
      - Syslog
  - connectorId: ThreatIntelligence
    dataTypes:
      - ThreatIntelligenceIndicator
  - connectorId: ThreatIntelligenceTaxii
    dataTypes:
      - ThreatIntelligenceIndicator
  - connectorId: MicrosoftDefenderThreatIntelligence
    dataTypes:
      - ThreatIntelligenceIndicator
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
relevantTechniques:
  - T1071
query: |
  let dt_lookBack = 1h;  // Define the time range to look back for syslog data (1 hour)
  let ioc_lookBack = 14d;  // Define the time range to look back for threat intelligence indicators (14 days)
  // Create a list of top-level domains (TLDs) from the threat feed for later validation
  let list_tlds = ThreatIntelligenceIndicator
    | where isnotempty(DomainName)
    | where TimeGenerated > ago(ioc_lookBack)
    | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
    | where Active == true and ExpirationDateTime > now()
    | extend parts = split(DomainName, '.')
    | extend tld = parts[(array_length(parts)-1)]
    | summarize count() by tostring(tld)
    | summarize make_list(tld);
  // Fetch the latest active domain indicators from the threat intelligence data within the specified time range
  let Domain_Indicators = ThreatIntelligenceIndicator
    | where isnotempty(DomainName)
    | where TimeGenerated >= ago(ioc_lookBack)
    | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
    | where Active == true and ExpirationDateTime > now()
    | extend TI_DomainEntity = DomainName;
  // Join the threat intelligence indicators with syslog data on matching domain entities
  Domain_Indicators
    | join kind=innerunique (
      Syslog
      | where TimeGenerated > ago(dt_lookBack)
      // Extract domain patterns from syslog messages
      | extend domain = extract("(([a-z0-9]+(-[a-z0-9]+)*\\.)+[a-z]{2,})",1, tolower(SyslogMessage))
      | where isnotempty(domain)
      | extend parts = split(domain, '.')
      // Split out the top-level domain (TLD)
      | extend tld = parts[(array_length(parts)-1)]
      // Validate parsed domain by checking if the TLD is in the list of TLDs in our threat feed
      | where tld in~ (list_tlds)
      | extend Syslog_TimeGenerated = TimeGenerated
    ) on $left.TI_DomainEntity==$right.domain
    | where Syslog_TimeGenerated < ExpirationDateTime
    // Retrieve the latest syslog timestamp for each indicator and domain combination
    | summarize Syslog_TimeGenerated = arg_max(Syslog_TimeGenerated, *) by IndicatorId, domain
    // Select the desired columns for the final result set
    | project Syslog_TimeGenerated, Description, ActivityGroupNames, IndicatorId, ThreatType, ExpirationDateTime, ConfidenceScore, SyslogMessage, Computer, ProcessName, domain, HostIP, Url, Type, TI_DomainEntity
    // Extract the hostname from the Computer field
    | extend HostName = tostring(split(Computer, '.', 0)[0])
    // Extract the DNS domain from the Computer field
    | extend DnsDomain = tostring(strcat_array(array_slice(split(Computer, '.'), 1, -1), '.'))
    // Assign the Syslog_TimeGenerated value to the timestamp field
    | extend timestamp = Syslog_TimeGenerated
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: Computer
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: DnsDomain
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: HostIP
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: Url
version: 1.4.3
kind: Scheduled

Stages and Predicates

Parameters

let dt_lookBack = 1h;
let ioc_lookBack = 14d;

let Domain_Indicators is inlined into the numbered stages below.

Let Binding: list_tlds used in Stage 7

let list_tlds = ThreatIntelligenceIndicator
  | where isnotempty(DomainName)
  | where TimeGenerated > ago(ioc_lookBack)
  | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
  | where Active == true and ExpirationDateTime > now()
  | extend parts = split(DomainName, '.')
  | extend tld = parts[(array_length(parts)-1)]
  | summarize count() by tostring(tld)
  | summarize make_list(tld);

Stages 1 to 6 define let Domain_Indicators as the rule's main pipeline source. Stages 7 to 11 use it.

Stage 1: source

ThreatIntelligenceIndicator

Stage 2: where

| where isnotempty(DomainName)

Stage 3: where

| where TimeGenerated >= ago(ioc_lookBack)

Stage 4: summarize

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

Stage 5: where

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

Stage 6: extend

| extend TI_DomainEntity = DomainName

Stage 7: join

Domain_Indicators
| join kind=innerunique (
    Syslog
    | where TimeGenerated > ago(dt_lookBack)
    | extend domain = extract("(([a-z0-9]+(-[a-z0-9]+)*\\.)+[a-z]{2,})",1, tolower(SyslogMessage))
    | where isnotempty(domain)
    | extend parts = split(domain, '.')
    | extend tld = parts[(array_length(parts)-1)]
    | where tld in~ (list_tlds)
    | extend Syslog_TimeGenerated = TimeGenerated
  ) on $left.TI_DomainEntity==$right.domain

Stage 8: where

where Syslog_TimeGenerated < ExpirationDateTime

Stage 9: summarize

summarize Syslog_TimeGenerated by IndicatorId, domain

Stage 10: project

project ActivityGroupNames, Computer, ConfidenceScore, Description, ExpirationDateTime, HostIP, IndicatorId, ProcessName, SyslogMessage, Syslog_TimeGenerated, TI_DomainEntity, ThreatType, Type, Url, domain

Stage 11: extend (3 consecutive steps)

extend DnsDomain, HostName, timestamp

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
ActivityGroupNamesproject
Computerproject
ConfidenceScoreproject
Descriptionproject
ExpirationDateTimeproject
HostIPproject
IndicatorIdproject
ProcessNameproject
SyslogMessageproject
Syslog_TimeGeneratedproject
TI_DomainEntityproject
ThreatTypeproject
Typeproject
Urlproject
domainproject
HostNameextend
DnsDomainextend
timestampextend

UniFi Site Manager: Multiple Devices Offline

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

Identifies when multiple UniFi devices go offline simultaneously, typically signaling a power outage, upstream connectivity failure, or infrastructure issue requiring investigation.

MITRE ATT&CK Coverage

Rule Body

id: 9283b576-5350-fca1-3979-dacb6acd1d16
name: "UniFi Site Manager: Multiple Devices Offline"
description: |
  Identifies when multiple UniFi devices go offline simultaneously, typically signaling a power outage, upstream connectivity failure, or infrastructure issue requiring investigation.
severity: High
status: Available
requiredDataConnectors:
  - connectorId: UniFiSiteManagerConnectorDefinition
    dataTypes:
      - Unifi_SiteManager_Devices_CL
queryFrequency: 15m
queryPeriod: 30m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Impact
relevantTechniques:
  - T1489
  - T1499
subTechniques:
  - T1499.002
query: |
  let prev_offline_ids = Unifi_SiteManager_Devices_CL
      | where TimeGenerated between (ago(45m) .. ago(15m))
      | summarize arg_max(TimeGenerated, *) by Id
      | where Status == "offline"
      | distinct Id;
  Unifi_SiteManager_Devices_CL
  | where TimeGenerated > ago(15m)
  | summarize arg_max(TimeGenerated, *) by Id
  | where Status == "offline"
  | extend IsNew = Id !in (prev_offline_ids)
  | summarize
      OfflineDeviceCount = count(),
      NewlyOfflineCount = countif(IsNew),
      OfflineDevices = make_list(coalesce(Name, Id)),
      NewlyOfflineDevices = make_list_if(coalesce(Name, Id), IsNew),
      ProductLines = make_set(ProductLine)
  | where OfflineDeviceCount >= 3
  | where NewlyOfflineCount > 0
  | extend
      TimeGenerated = now(),
      OfflineDeviceList = strcat_array(OfflineDevices, ", "),
      NewlyOfflineList = strcat_array(NewlyOfflineDevices, ", "),
      AffectedProductLines = strcat_array(ProductLines, ", ")
  | extend Activity = strcat(NewlyOfflineCount, ' device(s) newly offline (', OfflineDeviceCount, ' total offline): ', NewlyOfflineList)
  | project
      TimeGenerated,
      OfflineDeviceCount,
      NewlyOfflineCount,
      OfflineDeviceList,
      NewlyOfflineList,
      AffectedProductLines,
      Activity
entityMappings:
  - entityType: CloudApplication
    fieldMappings:
      - identifier: Name
        columnName: AffectedProductLines
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: true
    lookbackDuration: PT4H
    matchingMethod: AllEntities
kind: Scheduled
version: 1.0.3

Stages and Predicates

Let Binding: prev_offline_ids used in Stage 5

let prev_offline_ids = Unifi_SiteManager_Devices_CL
    | where TimeGenerated between (ago(45m) .. ago(15m))
    | summarize arg_max(TimeGenerated, *) by Id
    | where Status == "offline"
    | distinct Id;

Stage 1: source

Unifi_SiteManager_Devices_CL

Stage 2: where

| where TimeGenerated > ago(15m)

Stage 3: summarize

| summarize arg_max(TimeGenerated, *) by Id

Stage 4: where

| where Status == "offline"

Stage 5: extend

| extend IsNew = Id !in (prev_offline_ids)

Stage 6: summarize

| summarize
    OfflineDeviceCount = count(),
    NewlyOfflineCount = countif(IsNew),
    OfflineDevices = make_list(coalesce(Name, Id)),
    NewlyOfflineDevices = make_list_if(coalesce(Name, Id), IsNew),
    ProductLines = make_set(ProductLine)

Stage 7: where

| where OfflineDeviceCount >= 3

Stage 8: where

| where NewlyOfflineCount > 0

Stage 9: extend

| extend
    TimeGenerated = now(),
    OfflineDeviceList = strcat_array(OfflineDevices, ", "),
    NewlyOfflineList = strcat_array(NewlyOfflineDevices, ", "),
    AffectedProductLines = strcat_array(ProductLines, ", ")

Stage 10: extend

| extend Activity = strcat(NewlyOfflineCount, ' device(s) newly offline (', OfflineDeviceCount, ' total offline): ', NewlyOfflineList)

Stage 11: project

| project
    TimeGenerated,
    OfflineDeviceCount,
    NewlyOfflineCount,
    OfflineDeviceList,
    NewlyOfflineList,
    AffectedProductLines,
    Activity

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
Activityproject
AffectedProductLinesproject
NewlyOfflineCountproject
NewlyOfflineListproject
OfflineDeviceCountproject
OfflineDeviceListproject
TimeGeneratedproject

Back to all Kusto rules