Detection rules › Kusto

Kusto rules: 2f

Anvilogic 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

'Alert generated by Anvilogic.'

Rule Body

id: 6ccc187a-42ee-4635-8bcc-3b299f8570df
name: Anvilogic Alert
description: |
  'Alert generated by Anvilogic.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: Anvilogic
    dataTypes:
      - Anvilogic_Alerts_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
suppressionDuration: 1h
suppressionEnabled: false
tactics: []
relevantTechniques: []
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: false
    reopenClosedIncident: false
    lookbackDuration: PT5M
    matchingMethod: AllEntities
eventGroupingSettings:
  aggregationKind: AlertPerResult
alertDetailsOverride:
  alertDisplayNameFormat: "{{avl_rule_id}} - {{avl_use_case_title}} - {{avl_use_case_type}}"
  alertDescriptionFormat: "avl_rule_id: {{avl_rule_id}} \n{{avl_definition}}"
  "alertTacticsColumnName": "avl_mitre_tactic"
  alertDynamicProperties: 
    - alertProperty: Techniques
      value: avl_mitre_technique
customDetails:
  raw: raw
  tactics: avl_mitre_tactic
  techniques: avl_mitre_technique
query: |
  Anvilogic_Alerts_CL | where avl_rule_mode!='Warn'
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: coi_account
      - identifier: NTDomain
        columnName: src_nt_domain
      - identifier: Sid
        columnName: ssid
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: coi_host
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: coi_ip
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: url
  - entityType: CloudApplication
    fieldMappings:
      - identifier: Name
        columnName: coi_app
  - entityType: File
    fieldMappings:
      - identifier: Name
        columnName: file_name
      - identifier: Directory
        columnName: file_path
  - entityType: Process
    fieldMappings:
      - identifier: ProcessId
        columnName: process_id
      - identifier: CommandLine
        columnName: process_exec
  - entityType: RegistryKey
    fieldMappings:
      - identifier: Hive
        columnName: registry_hive
      - identifier: Key
        columnName: registry_key_name
  - entityType: RegistryValue
    fieldMappings:
      - identifier: Name
        columnName: registry_value_name
      - identifier: Value
        columnName: registry_value_data
      - identifier: ValueType
        columnName: registry_value_type
version: 1.0.1
kind: Scheduled

Stages and Predicates

Stage 1: source

Anvilogic_Alerts_CL

Stage 2: where

| where avl_rule_mode!='Warn'

Indicators

These rows show field, operator, and value matches.

Apache - Command in URI

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

'Detects command in URI'

MITRE ATT&CK Coverage

Rule Body

id: 54da6a42-3b00-11ec-8d3d-0242ac130003
name: Apache - Command in URI
description: |
  'Detects command in URI'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: CustomLogsAma
    datatypes:
      - ApacheHTTPServer_CL
queryFrequency: 15m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1190
  - T1133
query: |
  let cmd_list = dynamic(['whoami', 'dpkg', 'useradd', 'sudo', 'cat']);
  ApacheHTTPServer
  | where UrlOriginal has_any (cmd_list)
  | extend UrlCustomEntity = UrlOriginal
entityMappings:
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: UrlCustomEntity
version: 1.0.3
kind: Scheduled

Stages and Predicates

Parameters

let cmd_list = dynamic(['whoami', 'dpkg', 'useradd', 'sudo', 'cat']);

Stage 1: source

ApacheHTTPServer

Stage 2: where

| where UrlOriginal has_any (cmd_list)

Stage 3: extend

| extend UrlCustomEntity = UrlOriginal

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
UrlOriginalmatch
  • cat transforms: term
  • dpkg transforms: term
  • sudo transforms: term
  • useradd transforms: term
  • whoami transforms: term
field:"UrlOriginal" kind:match

Output Fields

These fields are emitted when the rule matches.

FieldSource
UrlCustomEntityextend

BloodHound Attack Path Finding - Command Execution on Tier Zero Virtual Machine

#

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 - Command Execution on Tier Zero Virtual Machine

Rule Body

id: bdfa4b55-e385-4035-8af8-ea77df4a81c3
name: BloodHound Attack Path Finding - Command Execution on Tier Zero Virtual Machine
description: BloodHound Attack Path Finding - Command Execution on Tier Zero Virtual
  Machine
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 == "AzureT0ExecuteCommand"
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 == "AzureT0ExecuteCommand"

Indicators

These rows show field, operator, and value matches.

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

BloodHound Attack Path Finding - Limited Ownership Privileges on Tier Zero Objects

#

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 - Limited Ownership Privileges on Tier Zero Objects

Rule Body

id: a2150e6d-b75b-4440-9c7e-18e901fb0b13
name: BloodHound Attack Path Finding - Limited Ownership Privileges on Tier Zero Objects
description: BloodHound Attack Path Finding - Limited Ownership Privileges on Tier
  Zero Objects
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 == "T0OwnsLimitedRights"
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 == "T0OwnsLimitedRights"

Indicators

These rows show field, operator, and value matches.

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

BloodHound Attack Path Finding - Ownership Privileges on Tier Zero Objects

#

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 - Ownership Privileges on Tier Zero Objects

Rule Body

id: 4dcc4d52-947b-4680-89be-b08a71def3d4
name: BloodHound Attack Path Finding - Ownership Privileges on Tier Zero Objects
description: BloodHound Attack Path Finding - Ownership Privileges on Tier Zero Objects
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 == "T0Owns"
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 == "T0Owns"

Indicators

These rows show field, operator, and value matches.

Cisco SEG - Suspicious sender domain

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

'Detects suspicious sender domain age.'

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Rule Body

id: ef0a253c-95b5-48e1-8ebc-dbeb073b9338
name: Cisco SEG - Suspicious sender domain
description: |
  'Detects suspicious sender domain age.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: CefAma
    dataTypes:
      - CommonSecurityLog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1566
query: |
  CiscoSEGEvent
  | where NetworkDirection =~ 'Incoming'
  | extend rec_domain = extract(@'@(.*)', 1, DstUserName)
  | extend s_domain = extract(@'@(.*)', 1, SrcUserName)
  | where s_domain != rec_domain
  | where tostring(AdditionalFields) has 'ESASDRDomainAge'
  | extend domain_age = extract(@'ESASDRDomainAge":"(.*days)"', 1, tostring(AdditionalFields))
  | extend yy = toint(extract(@'(\d+)\syears', 1, domain_age))
  | extend mm = toint(extract(@'(\d+)\smonths', 1, domain_age))
  | extend dd = toint(extract(@'(\d+)\sdays', 1, domain_age))
  | where isempty(yy)
  | where isempty(mm) or mm <= 2
  | extend AccountCustomEntity = SrcUserName
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountCustomEntity
version: 1.0.3
kind: Scheduled

Stages and Predicates

Stage 1: source

CiscoSEGEvent

Stage 2: where

| where NetworkDirection =~ 'Incoming'

Stage 3: extend

| extend rec_domain = extract(@'@(.*)', 1, DstUserName)

Stage 4: extend

| extend s_domain = extract(@'@(.*)', 1, SrcUserName)

Stage 5: where

| where s_domain != rec_domain

Stage 6: where

| where tostring(AdditionalFields) has 'ESASDRDomainAge'

Stage 7: extend (4 consecutive steps)

| extend domain_age = extract(@'ESASDRDomainAge":"(.*days)"', 1, tostring(AdditionalFields))
| extend yy = toint(extract(@'(\d+)\syears', 1, domain_age))
| extend mm = toint(extract(@'(\d+)\smonths', 1, domain_age))
| extend dd = toint(extract(@'(\d+)\sdays', 1, domain_age))

Stage 8: where

| where isempty(yy)

Stage 9: where

| where isempty(mm) or mm <= 2

Stage 10: extend

| extend AccountCustomEntity = SrcUserName

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
AdditionalFieldsmatch
  • ESASDRDomainAge transforms: tostring, term
field:"AdditionalFields" kind:match value:"ESASDRDomainAge"
NetworkDirectioneq
  • Incoming
field:"NetworkDirection" kind:eq value:"Incoming"
mmis_null
  • (no value, null check)
field:"mm" kind:is_null
mmle
  • 2 transforms: number
field:"mm" kind:le value:"2"
yyis_null
  • (no value, null check)
field:"yy" kind:is_null

Output Fields

These fields are emitted when the rule matches.

FieldSource
rec_domainextend
s_domainextend
domain_ageextend
yyextend
mmextend
ddextend
AccountCustomEntityextend

CYFIRMA - Attack Surface - Domain/IP Vulnerability Exposure Medium 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
medium
Time window
5m
Group by
uid
Source
github.com/Azure/Azure-Sentinel

"This rule is triggered when CYFIRMA identifies publicly exposed vulnerabilities on domains or IP addresses within your organization's attack surface. These vulnerabilities may include outdated software, missing patches, insecure services, or misconfigurations that can be exploited by threat actors.Such exposure significantly increases the risk of exploitation, lateral movement, or data breach. Immediate investigation and remediation are recommended."

MITRE ATT&CK Coverage

Rule Body

id: a2f2c91b-5796-45e4-82c7-61763e6c2c9c
name: CYFIRMA - Attack Surface - Domain/IP Vulnerability Exposure Medium Rule
description: |
  "This rule is triggered when CYFIRMA identifies publicly exposed vulnerabilities on domains or IP addresses within your organization's attack surface. These vulnerabilities may include outdated software, missing patches, insecure services, or misconfigurations that can be exploited by threat actors.Such exposure significantly increases the risk of exploitation, lateral movement, or data breach. Immediate investigation and remediation are recommended."
version: 1.0.1
kind: Scheduled
severity: Medium
requiredDataConnectors:
  - connectorId: CyfirmaAttackSurfaceAlertsConnector
    dataTypes:
      - CyfirmaASDomainIPVulnerabilityAlerts_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - InitialAccess
  - Discovery
  - DefenseEvasion
  - Persistence
  - Execution
  - Impact
  - PrivilegeEscalation
relevantTechniques:
  - T1505
  - T1068
  - T1046
  - T1499
query: |
  // Medium Severity Domain/IP Vulnerability Exposure Detected
  let timeFrame = 5m;
  CyfirmaASDomainIPVulnerabilityAlerts_CL
  | where severity == 'High' and TimeGenerated between (ago(timeFrame) .. now())
  | mv-expand pvuln = possible_vulnerabilities
  | extend cve = tostring(pvuln.cveNo), vulProducts = pvuln.products
  | mv-expand vul_Products = vulProducts
  | summarize cveList = make_set(cve, 100), vul_products1 = make_set(vul_Products, 100)    by uid
  | join kind=inner (CyfirmaASDomainIPVulnerabilityAlerts_CL
      | where severity == 'High'
      | summarize arg_max(TimeGenerated, *) by uid)
      on uid
  | extend
      Vulnerabilities = strcat_array(cveList, ', '),
      VulnerabilityProducts = strcat_array(vul_products1, ', '),
      Description=description,
      FirstSeen=first_seen,
      LastSeen=last_seen,
      RiskScore=risk_score,
      Domain=sub_domain,
      TopDomain=top_domain,
      NetworkIP=ip,
      AlertUID=alert_uid,
      UID=uid,
      OpenPorts=open_ports,
      HostProvider=host_provider,
      Country=country,
      Softwares=softwares,
      WebServer=web_server,
      WebServerVersion=web_server_version,
      ProviderName='CYFIRMA',
      ProductName='DeCYFIR/DeTCT'
  | project
      TimeGenerated,
      Description,
      Domain,
      TopDomain,
      RiskScore,
      FirstSeen,
      LastSeen,
      NetworkIP,
      AlertUID,
      UID,
      Vulnerabilities,
      VulnerabilityProducts,
      OpenPorts,
      HostProvider,
      Country,
      Softwares,
      WebServer,
      WebServerVersion,
      ProviderName,
      ProductName
entityMappings:
  - entityType: DNS
    fieldMappings:
      - identifier: DomainName
        columnName: Domain
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: TopDomain
      - identifier: DnsDomain
        columnName: Domain
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: NetworkIP
customDetails:
  TimeGenerated: TimeGenerated
  RiskScore: RiskScore
  FirstSeen: FirstSeen
  LastSeen: LastSeen
  Vulnerabilities: Vulnerabilities
  UID: UID
  OpenPorts: OpenPorts
  WebServer: WebServer
  WebServerVersion: WebServerVersion
  Softwares: Softwares
  HostProvider: HostProvider
  AlertUID: AlertUID
  Country: Country
  vulnerableProducts: VulnerabilityProducts
alertDetailsOverride:
  alertDisplayNameFormat: "CYFIRMA - Medium Severity Domain/IP Vulnerability Exposure Detected - Domain: {{Domain}}, IP: {{NetworkIP}}"
  alertDescriptionFormat: "CYFIRMA - Medium Severity Domain/IP Vulnerability 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

CyfirmaASDomainIPVulnerabilityAlerts_CL

Stage 2: where

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

Stage 3: mv-expand

| mv-expand pvuln = possible_vulnerabilities

Stage 4: extend

| extend cve = tostring(pvuln.cveNo), vulProducts = pvuln.products

Stage 5: mv-expand

| mv-expand vul_Products = vulProducts

Stage 6: summarize

| summarize cveList = make_set(cve, 100), vul_products1 = make_set(vul_Products, 100)    by uid

Stage 7: join

| join kind=inner (CyfirmaASDomainIPVulnerabilityAlerts_CL
    | where severity == 'High'
    | summarize arg_max(TimeGenerated, *) by uid)
    on uid

Stage 8: extend

extend AlertUID, Country, Description, Domain, FirstSeen, HostProvider, LastSeen, NetworkIP, OpenPorts, ProductName, ProviderName, RiskScore, Softwares, TopDomain, UID, Vulnerabilities, VulnerabilityProducts, WebServer, WebServerVersion

Stage 9: project

project AlertUID, Country, Description, Domain, FirstSeen, HostProvider, LastSeen, NetworkIP, OpenPorts, ProductName, ProviderName, RiskScore, Softwares, TimeGenerated, TopDomain, UID, Vulnerabilities, VulnerabilityProducts, WebServer, WebServerVersion

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AlertUIDproject
Countryproject
Descriptionproject
Domainproject
FirstSeenproject
HostProviderproject
LastSeenproject
NetworkIPproject
OpenPortsproject
ProductNameproject
ProviderNameproject
RiskScoreproject
Softwaresproject
TimeGeneratedproject
TopDomainproject
UIDproject
Vulnerabilitiesproject
VulnerabilityProductsproject
WebServerproject
WebServerVersionproject

CYFIRMA - Medium severity File Hash Indicators with Monitor Action 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 query retrieves file hash indicators marked for Monitoring, with no assigned role, from the CyfirmaIndicators_CL table. It extracts MD5, SHA1, and SHA256 hashes and includes threat metadata for use in preventive security controls such as EDRs, threat intel platforms."

MITRE ATT&CK Coverage

Rule Body

id: 5347c581-eb36-4bf5-a7e5-be1fb2d617f5
name: CYFIRMA - Medium severity File Hash Indicators with Monitor Action Rule
description: |
  "This query retrieves file hash indicators marked for Monitoring, with no assigned role, from the CyfirmaIndicators_CL table. 
  It extracts MD5, SHA1, and SHA256 hashes and includes threat metadata for use in preventive security controls such as EDRs, threat intel platforms."
version: 1.0.1
kind: Scheduled
severity: Medium
enabled: false
requiredDataConnectors:
  - connectorId: CyfirmaCyberIntelligenceDC
    dataTypes:
      - CyfirmaIndicators_CL
query: |
  // File Hash Indicators with Monitor Action
  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 'Monitor' and (isempty(Roles) or not(Roles has_any ('Malware', 'Trojan')))
  | extend MD5 = extract(@"file:hashes\.md5\s*=\s*'([a-fA-F0-9]{32})'", 1, pattern)
  | extend SHA1 = extract(@"file:hashes\.'SHA-1'\s*=\s*'([a-fA-F0-9]{40})'", 1, pattern)
  | extend SHA256 = extract(@"file:hashes\.'SHA-256'\s*=\s*'([a-fA-F0-9]{64})'", 1, pattern)
  | extend
      Algo_MD5='MD5',
      Algo_SHA1= 'SHA1',
      Algo_SHA256='SHA256',
      ProviderName = 'CYFIRMA',
      ProductName = 'DeCYFIR/DeTCT'
  | project  
      MD5,
      SHA1,
      SHA256,
      Algo_MD5,
      Algo_SHA1,
      Algo_SHA256,
      ThreatActors,
      Sources,
      RecommendedActions,
      Roles,
      Country,
      name,
      Description,
      ConfidenceScore,
      SecurityVendors,
      IndicatorID,
      created,
      modified,
      valid_from,
      Tags,
      ThreatType,
      TimeGenerated,
      ProductName,
      ProviderName
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: GreaterThan
triggerThreshold: 0
suppressionDuration: 5m
suppressionEnabled: true
tactics:
  - Execution
  - InitialAccess
  - DefenseEvasion
  - Impact
relevantTechniques:
  - T1204
  - T1566
  - T1027
  - T1486
  - T1566.001
alertDetailsOverride:
  alertDisplayNameFormat: "High-Confidence File Hash Indicators with Monitor Action - {{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
entityMappings:
  - entityType: FileHash
    fieldMappings:
      - identifier: Algorithm
        columnName: Algo_MD5
      - identifier: Value
        columnName: MD5
  - entityType: FileHash
    fieldMappings:
      - identifier: Algorithm
        columnName: Algo_SHA1
      - identifier: Value
        columnName: SHA1
  - entityType: FileHash
    fieldMappings:
      - identifier: Algorithm
        columnName: Algo_SHA256
      - identifier: Value
        columnName: SHA256
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 'Monitor' and (isempty(Roles) or not(Roles has_any ('Malware', 'Trojan')))

Stage 3: extend (4 consecutive steps)

| extend MD5 = extract(@"file:hashes\.md5\s*=\s*'([a-fA-F0-9]{32})'", 1, pattern)
| extend SHA1 = extract(@"file:hashes\.'SHA-1'\s*=\s*'([a-fA-F0-9]{40})'", 1, pattern)
| extend SHA256 = extract(@"file:hashes\.'SHA-256'\s*=\s*'([a-fA-F0-9]{64})'", 1, pattern)
| extend
    Algo_MD5='MD5',
    Algo_SHA1= 'SHA1',
    Algo_SHA256='SHA256',
    ProviderName = 'CYFIRMA',
    ProductName = 'DeCYFIR/DeTCT'

Stage 4: project

| project  
    MD5,
    SHA1,
    SHA256,
    Algo_MD5,
    Algo_SHA1,
    Algo_SHA256,
    ThreatActors,
    Sources,
    RecommendedActions,
    Roles,
    Country,
    name,
    Description,
    ConfidenceScore,
    SecurityVendors,
    IndicatorID,
    created,
    modified,
    valid_from,
    Tags,
    ThreatType,
    TimeGenerated,
    ProductName,
    ProviderName

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
Algo_MD5project
Algo_SHA1project
Algo_SHA256project
ConfidenceScoreproject
Countryproject
Descriptionproject
IndicatorIDproject
MD5project
ProductNameproject
ProviderNameproject
RecommendedActionsproject
Rolesproject
SHA1project
SHA256project
SecurityVendorsproject
Sourcesproject
Tagsproject
ThreatActorsproject
ThreatTypeproject
TimeGeneratedproject
createdproject
modifiedproject
nameproject
valid_fromproject

Dataverse - Mass export of records to Excel

#
Status
available
Severity
low
Time window
14d
Group by
$left.UserId, $right.HistoricalUserId, InstanceUrl, UserId
Source
github.com/Azure/Azure-Sentinel

Identifies users exporting a large amount of records from Dynamics 365 to Excel, significantly more records exported than any other recent activity by that user. Large exports from users with no recent activity are identified using a predefined threshold.

MITRE ATT&CK Coverage

TacticTechniques
Exfiltration

Telemetry Coverage

Rule Body

id: 57000f0d-ff5d-4166-94b6-aa5fb62b16ec
kind: Scheduled
name: Dataverse - Mass export of records to Excel
description: Identifies users exporting a large amount of records from Dynamics 365
  to Excel, significantly more records exported than any other recent activity by
  that user. Large exports from users with no recent activity are identified using
  a predefined threshold.
severity: Low
status: Available
requiredDataConnectors:
  - connectorId: Dataverse
    dataTypes:
      - DataverseActivity
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Exfiltration
relevantTechniques:
  - T1567
query: |
  // Set a mass export threshold for users who have no historical activity.
  let mass_export_threshold = 10000;
  let query_lookback = 14d;
  let query_frequency = 1h;
  let export_activity = DataverseActivity
      | where Message == "ExportToExcel"
      | extend QueryCount = iif(QueryResults has ",", todouble(countof(tostring(QueryResults), ',') + 1), double(1));
  let current_activity = export_activity
      | where TimeGenerated > ago(query_frequency)
      | extend RecordId = split(QueryResults, ",")
      | summarize
          FirstEvent = min(TimeGenerated),
          CurrentExportRate = sum(QueryCount),
          SampleRecordIds = make_set(RecordId, 1000)
          by UserId, InstanceUrl;
  let historical_activity = export_activity
      | where TimeGenerated between(ago(query_lookback) .. ago(query_frequency))
      | summarize HistoricalBaseline = sum(QueryCount) by HistoricalUserId = UserId, InstanceUrl;
  current_activity
  | join kind=leftouter(historical_activity) on $left.UserId == $right.HistoricalUserId, InstanceUrl
  | extend BaselineThreshold = iif(isnotnull(HistoricalBaseline), HistoricalBaseline, todouble(mass_export_threshold))
  | where CurrentExportRate > BaselineThreshold
  | join kind=inner(export_activity
      | where TimeGenerated > ago(query_frequency)
      | summarize EntityCount = sum(QueryCount) by UserId, ClientIp, InstanceUrl, EntityName
      | extend Details = bag_pack("EntityName", EntityName, "EntityCount", EntityCount)
      | summarize Details = make_set(Details, 100) by UserId, ClientIp, InstanceUrl)
      on UserId, InstanceUrl
  | extend
      CloudAppId = int(32780),
      AccountName = tostring(split(UserId, '@')[0]),
      UPNSuffix = tostring(split(UserId, '@')[1])
  | project
      FirstEvent,
      UserId,
      ClientIp,
      BaselineThreshold,
      CurrentExportRate,
      Details,
      SampleRecordIds,
      InstanceUrl,
      AccountName,
      UPNSuffix,
      CloudAppId
eventGroupingSettings:
  aggregationKind: AlertPerResult
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: CloudApplication
    fieldMappings:
      - identifier: AppId
        columnName: CloudAppId
      - identifier: InstanceName
        columnName: InstanceUrl
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: ClientIp
alertDetailsOverride:
  alertDisplayNameFormat: 'Dataverse - mass export to Excel activity in {{{InstanceUrl}} '
  alertDescriptionFormat: User {{UserId}} exported {{{CurrentExportRate}} records
    using the ExportToExcel function in Dataverse.
version: 3.2.0

Stages and Predicates

Parameters

let mass_export_threshold = 10000;
let query_lookback = 14d;
let query_frequency = 1h;

let export_activity and let current_activity are inlined into the numbered stages below.

Let Binding: historical_activity used in Stage 7

let historical_activity = export_activity
    | where TimeGenerated between(ago(query_lookback) .. ago(query_frequency))
    | summarize HistoricalBaseline = sum(QueryCount) by HistoricalUserId = UserId, InstanceUrl;

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

Stage 1: source

DataverseActivity

Stage 2: where

| where Message == "ExportToExcel"

Stage 3: extend

| extend QueryCount = iif(QueryResults has ",", todouble(countof(tostring(QueryResults), ',') + 1), double(1))

Stage 4: where

| where TimeGenerated > ago(query_frequency)

Stage 5: extend

| extend RecordId = split(QueryResults, ",")

Stage 6: summarize

| summarize
        FirstEvent = min(TimeGenerated),
        CurrentExportRate = sum(QueryCount),
        SampleRecordIds = make_set(RecordId, 1000)
        by UserId, InstanceUrl

Stage 7: join

current_activity
| join kind=leftouter(historical_activity) on $left.UserId == $right.HistoricalUserId, InstanceUrl

Stage 8: extend

extend BaselineThreshold

Stage 9: where

where CurrentExportRate > BaselineThreshold

Stage 10: join

| join kind=inner(export_activity
    | where TimeGenerated > ago(query_frequency)
    | summarize EntityCount = sum(QueryCount) by UserId, ClientIp, InstanceUrl, EntityName
    | extend Details = bag_pack("EntityName", EntityName, "EntityCount", EntityCount)
    | summarize Details = make_set(Details, 100) by UserId, ClientIp, InstanceUrl)
    on UserId, InstanceUrl

Stage 11: extend

extend AccountName, CloudAppId, UPNSuffix

Stage 12: project

project AccountName, BaselineThreshold, ClientIp, CloudAppId, CurrentExportRate, Details, FirstEvent, InstanceUrl, SampleRecordIds, UPNSuffix, UserId

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Messageeq
  • ExportToExcel
field:"Message" kind:eq value:"ExportToExcel"

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountNameproject
BaselineThresholdproject
ClientIpproject
CloudAppIdproject
CurrentExportRateproject
Detailsproject
FirstEventproject
InstanceUrlproject
SampleRecordIdsproject
UPNSuffixproject
UserIdproject

GCP IAM - Service Account Keys Enumeration

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

Detects possible service account keys enumeration.

MITRE ATT&CK Coverage

TacticTechniques
Discovery

Telemetry Coverage

Rule Body

id: 7ad3cfed-18c0-44af-9e9d-9fb5472a2321
name: GCP IAM - Service Account Keys Enumeration
description: |
  'Detects possible service account keys enumeration.'
severity: Low
status: Available
requiredDataConnectors:
  - connectorId: GCPIAMDataConnector
    dataTypes:
      - GCP_IAM
queryFrequency: 12h
queryPeriod: 12h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Discovery
relevantTechniques:
  - T1069
query: |
  let threshold = 5;
  GCP_IAM
  | where PayloadMethodname =~ 'google.iam.admin.v1.ListServiceAccountKeys'
  | summarize count() by PayloadAuthenticationinfoPrincipalemail, bin(TimeGenerated, 1h)
  | where count_ > threshold
  | extend AccountCustomEntity = PayloadAuthenticationinfoPrincipalemail
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: AccountCustomEntity
version: 1.0.1
kind: Scheduled

Stages and Predicates

Parameters

let threshold = 5;

Stage 1: source

GCP_IAM

Stage 2: where

| where PayloadMethodname =~ 'google.iam.admin.v1.ListServiceAccountKeys'

Stage 3: summarize

| summarize count() by PayloadAuthenticationinfoPrincipalemail, bin(TimeGenerated, 1h)

Stage 4: where

| where count_ > threshold

Stage 5: extend

| extend AccountCustomEntity = PayloadAuthenticationinfoPrincipalemail

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
PayloadAuthenticationinfoPrincipalemailsummarize
AccountCustomEntityextend

GitLab - External User Added to GitLab

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

'This queries GitLab Application logs to list external user accounts (i.e.: account not in allow-listed domains) which have been added to GitLab users.'

MITRE ATT&CK Coverage

TacticTechniques
Persistence

Rule Body

id: c1544d8f-cbbd-4e35-8d32-5b9312279833
name: GitLab - External User Added to GitLab
description: |
  'This queries GitLab Application logs to list external user accounts (i.e.: account not in allow-listed domains) which have been added to GitLab users.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: SyslogAma
    dataTypes: 
      - Syslog
queryFrequency: 1h
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Persistence
relevantTechniques:
  - T1136
query: |
  // List of allow-listed domains
  let allowedDomain = pack_array("mydomain.com");
  GitLabAudit
  | where AddAction == "user"
  | project AuthorName, IPAddress, User = EntityName
  | join (GitLabApp 
  | where UserAdded == 1
  | parse kind=regex Message with "User \"" User "\"" EmailAddress " was created"
  | project tostring(User), EmailAddress = substring(EmailAddress,2,strlen(EmailAddress)-3)) on User
  | project  AuthorName, IPAddress, User, EmailAddress, DomainName = tostring(extract("@(.*)$", 1, EmailAddress))
  | where allowedDomain !contains DomainName
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPAddress
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: User
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: AuthorName        
  - entityType: DNS
    fieldMappings:
      - identifier: DomainName
        columnName: DomainName        
version: 1.0.1
kind: Scheduled

Stages and Predicates

Parameters

let allowedDomain = pack_array("mydomain.com");

Stage 1: source

GitLabAudit

Stage 2: where

| where AddAction == "user"

Stage 3: project

| project AuthorName, IPAddress, User = EntityName

Stage 4: join

| join (GitLabApp 
| where UserAdded == 1
| parse kind=regex Message with "User \"" User "\"" EmailAddress " was created"
| project tostring(User), EmailAddress = substring(EmailAddress,2,strlen(EmailAddress)-3)) on User

Stage 5: project

project AuthorName, DomainName, EmailAddress, IPAddress, User

Stage 6: where

where allowedDomain !contains DomainName

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AuthorNameproject
DomainNameproject
EmailAddressproject
IPAddressproject
Userproject

Guardian- Blocks specific strings of text Policy Violation Detection

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

'This alert creates an incident when Blocks specific strings of text Policy Violation detected from the Guardian.'

Rule Body

id: 9c63e0b5-bb83-4873-947a-4385386a6c21
name: Guardian- Blocks specific strings of text Policy Violation Detection
description: |
  'This alert creates an incident when Blocks specific strings of text 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 =~ 'Block Substring'
  | 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- Blocks specific strings of text Policy Violation Detection
  alertDescriptionFormat: |
    This query detects Blocks specific strings of text 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 =~ 'Block Substring'

Stage 3: where

| where Severity =~ 'Low'

Indicators

These rows show field, operator, and value matches.

Microsoft Entra ID Health Service Agents Registry Keys Access

#
Severity
medium
Time window
1d
Group by
Account, Computer, EventID, ObjectName, ObjectType, Process, ProcessName, SubjectDomainName, SubjectUserName
Author
Microsoft Security Research
Source
github.com/Azure/Azure-Sentinel

This detection uses Windows security events to detect suspicious access attempts to the registry key values and sub-keys of Microsoft Entra ID Health service agents (e.g AD FS). Information from AD Health service agents can be used to potentially abuse some of the features provided by those services in the cloud (e.g. Federation). This detection requires an access control entry (ACE) on the system access control list (SACL) of the following securable object: HKLM:\SOFTWARE\Microsoft\ADHealthAgent. Make sure you set the SACL to propagate to its sub-keys. You can find more information in here https://github.com/OTRF/Set-AuditRule/blob/master/rules/registry/aad_connect_health_service_agent.yml

MITRE ATT&CK Coverage

TacticTechniques
Collection

Telemetry Coverage

Rule Body

id: 06bbf969-fcbe-43fa-bac2-b2fa131d113a
name: Microsoft Entra ID Health Service Agents Registry Keys Access
description: |
  'This detection uses Windows security events to detect suspicious access attempts to the registry key values and sub-keys of Microsoft Entra ID Health service agents (e.g AD FS).
  Information from AD Health service agents can be used to potentially abuse some of the features provided by those services in the cloud (e.g. Federation).
  This detection requires an access control entry (ACE) on the system access control list (SACL) of the following securable object: HKLM:\SOFTWARE\Microsoft\ADHealthAgent.
  Make sure you set the SACL to propagate to its sub-keys. You can find more information in here https://github.com/OTRF/Set-AuditRule/blob/master/rules/registry/aad_connect_health_service_agent.yml
  '
severity: Medium
requiredDataConnectors:
  - connectorId: SecurityEvents
    dataTypes:
      - SecurityEvent
  - connectorId: WindowsSecurityEvents
    dataTypes: 
      - SecurityEvents 
  - connectorId: WindowsForwardedEvents
    dataTypes: 
      - WindowsEvent 
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Collection
relevantTechniques:
  - T1005
tags:
  - SimuLand
query: |
  // ADHealth Monitoring Agent Registry Key
  let aadHealthMonAgentRegKey = "\\REGISTRY\\MACHINE\\SOFTWARE\\Microsoft\\MicrosoftOnline\\Reporting\\MonitoringAgent";
  // Filter out known processes
  let aadConnectHealthProcs = dynamic ([
      'Microsoft.Identity.Health.Adfs.DiagnosticsAgent.exe',
      'Microsoft.Identity.Health.Adfs.InsightsService.exe',
      'Microsoft.Identity.Health.Adfs.MonitoringAgent.Startup.exe',
      'Microsoft.Identity.Health.Adfs.PshSurrogate.exe',
      'Microsoft.Identity.Health.Common.Clients.ResourceMonitor.exe',
      'Microsoft.Identity.Health.AadSync.MonitoringAgent.Startup.exe',
      'Microsoft.Identity.AadConnect.Health.AadSync.Host.exe',
      'Microsoft.Azure.ActiveDirectory.Synchronization.Upgrader.exe',
      'miiserver.exe'
  ]);
  (union isfuzzy=true
  (
  SecurityEvent
  | where EventID == '4656'
  | where EventData has aadHealthMonAgentRegKey
  | extend EventData = parse_xml(EventData).EventData.Data
  | mv-expand bagexpansion=array EventData
  | evaluate bag_unpack(EventData)
  | extend Key = tostring(column_ifexists('@Name', "")), Value = column_ifexists('#text', "")
  | evaluate pivot(Key, any(Value), TimeGenerated, Computer, EventID)
  | extend ObjectName = column_ifexists("ObjectName", ""),
      ObjectType = column_ifexists("ObjectType", "")
  | where ObjectType == 'Key'
  | where ObjectName == aadHealthMonAgentRegKey
  | extend SubjectUserName = column_ifexists("SubjectUserName", ""),
      SubjectDomainName = column_ifexists("SubjectDomainName", ""),
      ProcessName = column_ifexists("ProcessName", "")
  | extend Process = split(ProcessName, '\\', -1)[-1],
      Account = strcat(SubjectDomainName, "\\", SubjectUserName)
  | where Process !in (aadConnectHealthProcs)
  | summarize StartTime = max(TimeGenerated), EndTime = min(TimeGenerated), count() by EventID, Account, Computer, Process, SubjectUserName, SubjectDomainName, ObjectName, ObjectType, ProcessName
  ),
    ( WindowsEvent
  | where EventID == '4656' and EventData has aadHealthMonAgentRegKey
  | extend ObjectType = tostring(EventData.ObjectType)
  | where ObjectType == 'Key'
  | extend ObjectName = tostring(EventData.ObjectName)
  | where ObjectName == aadHealthMonAgentRegKey
  | extend ProcessName = tostring(EventData.ProcessName)
  | extend Process = tostring(split(ProcessName, '\\')[-1])
  | where Process !in (aadConnectHealthProcs)
  | extend Account =  strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
  | extend SubjectUserName = tostring(EventData.SubjectUserName)
  | extend SubjectDomainName = tostring(EventData.SubjectDomainName)
  | summarize StartTime = max(TimeGenerated), EndTime = min(TimeGenerated), count() by EventID, Account, Computer, Process, SubjectUserName, SubjectDomainName, ObjectName, ObjectType, ProcessName
  ),
  (
  SecurityEvent
  | where EventID == '4663'
  | where ObjectType == 'Key'
  | where ObjectName == aadHealthMonAgentRegKey
  | extend Process = tostring(split(ProcessName, '\\', -1)[-1])
  | where Process !in (aadConnectHealthProcs)
  | summarize StartTime = max(TimeGenerated), EndTime = min(TimeGenerated), count() by EventID, Account, Computer, Process, SubjectUserName, SubjectDomainName, ObjectName, ObjectType, ProcessName
  ),
  ( WindowsEvent
  | where EventID == '4663' and EventData has aadHealthMonAgentRegKey
  | extend ObjectType = tostring(EventData.ObjectType)
  | where ObjectType == 'Key'
  | extend ObjectName = tostring(EventData.ObjectName)
  | where ObjectName == aadHealthMonAgentRegKey
  | extend ProcessName = tostring(EventData.ProcessName)
  | extend Process = tostring(split(ProcessName, '\\')[-1])
  | where Process !in (aadConnectHealthProcs)
  | extend Account =  strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
  | extend SubjectUserName = tostring(EventData.SubjectUserName)
  | extend SubjectDomainName = tostring(EventData.SubjectDomainName)
  | summarize StartTime = max(TimeGenerated), EndTime = min(TimeGenerated), count() by EventID, Account, Computer, Process, SubjectUserName, SubjectDomainName, ObjectName, ObjectType, ProcessName
  )
  )
  // You can filter out potential machine accounts
  //| where AccountType != 'Machine'
  | extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
  | extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
  | extend Name = tostring(split(Account, "\\")[1]), NTDomain = tostring(split(Account, "\\")[0])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: Account
      - identifier: Name
        columnName: Name
      - identifier: NTDomain
        columnName: NTDomain
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: Computer
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: HostNameDomain
version: 1.1.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 aadHealthMonAgentRegKey = "\\REGISTRY\\MACHINE\\SOFTWARE\\Microsoft\\MicrosoftOnline\\Reporting\\MonitoringAgent";

Let Binding: aadConnectHealthProcs

let aadConnectHealthProcs = dynamic ([
    'Microsoft.Identity.Health.Adfs.DiagnosticsAgent.exe',
    'Microsoft.Identity.Health.Adfs.InsightsService.exe',
    'Microsoft.Identity.Health.Adfs.MonitoringAgent.Startup.exe',
    'Microsoft.Identity.Health.Adfs.PshSurrogate.exe',
    'Microsoft.Identity.Health.Common.Clients.ResourceMonitor.exe',
    'Microsoft.Identity.Health.AadSync.MonitoringAgent.Startup.exe',
    'Microsoft.Identity.AadConnect.Health.AadSync.Host.exe',
    'Microsoft.Azure.ActiveDirectory.Synchronization.Upgrader.exe',
    'miiserver.exe'
]);

Union isfuzzy=true (4 Sources)

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

Leg 1: SecurityEvent

SecurityEvent
| where EventID == '4656'
| where EventData has aadHealthMonAgentRegKey
| extend EventData = parse_xml(EventData).EventData.Data
| mv-expand bagexpansion=array EventData
| evaluate bag_unpack(EventData)
| extend Key = tostring(column_ifexists('@Name', "")), Value = column_ifexists('#text', "")
| evaluate pivot(Key, any(Value), TimeGenerated, Computer, EventID)
| extend ObjectName = column_ifexists("ObjectName", ""),
    ObjectType = column_ifexists("ObjectType", "")
| where ObjectType == 'Key'
| where ObjectName == aadHealthMonAgentRegKey
| extend SubjectUserName = column_ifexists("SubjectUserName", ""),
    SubjectDomainName = column_ifexists("SubjectDomainName", ""),
    ProcessName = column_ifexists("ProcessName", "")
| extend Process = split(ProcessName, '\\', -1)[-1],
    Account = strcat(SubjectDomainName, "\\", SubjectUserName)
| where Process !in (aadConnectHealthProcs)
| summarize StartTime = max(TimeGenerated), EndTime = min(TimeGenerated), count() by EventID, Account, Computer, Process, SubjectUserName, SubjectDomainName, ObjectName, ObjectType, ProcessName

Leg 2: WindowsEvent

WindowsEvent
| where EventID == '4656' and EventData has aadHealthMonAgentRegKey
| extend ObjectType = tostring(EventData.ObjectType)
| where ObjectType == 'Key'
| extend ObjectName = tostring(EventData.ObjectName)
| where ObjectName == aadHealthMonAgentRegKey
| extend ProcessName = tostring(EventData.ProcessName)
| extend Process = tostring(split(ProcessName, '\\')[-1])
| where Process !in (aadConnectHealthProcs)
| extend Account =  strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
| extend SubjectUserName = tostring(EventData.SubjectUserName)
| extend SubjectDomainName = tostring(EventData.SubjectDomainName)
| summarize StartTime = max(TimeGenerated), EndTime = min(TimeGenerated), count() by EventID, Account, Computer, Process, SubjectUserName, SubjectDomainName, ObjectName, ObjectType, ProcessName

Leg 3: SecurityEvent

SecurityEvent
| where EventID == '4663'
| where ObjectType == 'Key'
| where ObjectName == aadHealthMonAgentRegKey
| extend Process = tostring(split(ProcessName, '\\', -1)[-1])
| where Process !in (aadConnectHealthProcs)
| summarize StartTime = max(TimeGenerated), EndTime = min(TimeGenerated), count() by EventID, Account, Computer, Process, SubjectUserName, SubjectDomainName, ObjectName, ObjectType, ProcessName

Leg 4: WindowsEvent

WindowsEvent
| where EventID == '4663' and EventData has aadHealthMonAgentRegKey
| extend ObjectType = tostring(EventData.ObjectType)
| where ObjectType == 'Key'
| extend ObjectName = tostring(EventData.ObjectName)
| where ObjectName == aadHealthMonAgentRegKey
| extend ProcessName = tostring(EventData.ProcessName)
| extend Process = tostring(split(ProcessName, '\\')[-1])
| where Process !in (aadConnectHealthProcs)
| extend Account =  strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
| extend SubjectUserName = tostring(EventData.SubjectUserName)
| extend SubjectDomainName = tostring(EventData.SubjectDomainName)
| summarize StartTime = max(TimeGenerated), EndTime = min(TimeGenerated), count() by EventID, Account, Computer, Process, SubjectUserName, SubjectDomainName, ObjectName, ObjectType, ProcessName

Applied to the Combined Result

| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
| extend Name = tostring(split(Account, "\\")[1]), NTDomain = tostring(split(Account, "\\")[0])

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
EventDatamatch
  • \REGISTRY\MACHINE\SOFTWARE\Microsoft\MicrosoftOnline\Reporting\MonitoringAgent transforms: term
field:"EventData" kind:match value:"\REGISTRY\MACHINE\SOFTWARE\Microsoft\MicrosoftOnline\Reporting\MonitoringAgent"
EventIDeq
  • 4656 corpus 4 (kusto 4)
  • 4663 corpus 3 (kusto 3)
field:"EventID" kind:eq
ObjectNameeq
  • \REGISTRY\MACHINE\SOFTWARE\Microsoft\MicrosoftOnline\Reporting\MonitoringAgent
field:"ObjectName" kind:eq value:"\REGISTRY\MACHINE\SOFTWARE\Microsoft\MicrosoftOnline\Reporting\MonitoringAgent"
ObjectTypeeq
  • Key
field:"ObjectType" kind:eq value:"Key"

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
ProcessinMicrosoft.Azure.ActiveDirectory.Synchronization.Upgrader.exe, Microsoft.Identity.AadConnect.Health.AadSync.Host.exe, Microsoft.Identity.Health.AadSync.MonitoringAgent.Startup.exe, Microsoft.Identity.Health.Adfs.DiagnosticsAgent.exe, Microsoft.Identity.Health.Adfs.InsightsService.exe, Microsoft.Identity.Health.Adfs.MonitoringAgent.Startup.exe, Microsoft.Identity.Health.Adfs.PshSurrogate.exe, Microsoft.Identity.Health.Common.Clients.ResourceMonitor.exe, miiserver.exeexcludes:Process

Output Fields

These fields are emitted when the rule matches.

FieldSource
Accountsummarize
Computersummarize
EndTimesummarize
EventIDsummarize
ObjectNamesummarize
ObjectTypesummarize
Processsummarize
ProcessNamesummarize
StartTimesummarize
SubjectDomainNamesummarize
SubjectUserNamesummarize
DomainIndexextend
HostNameextend
HostNameDomainextend
NTDomainextend
Nameextend

PaloAlto - User privileges was changed

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

'Detects changing of user privileges.'

MITRE ATT&CK Coverage

Rule Body

id: 38f9e010-51ca-11ec-bf63-0242ac130002
name: PaloAlto - User privileges was changed
description: |
  'Detects changing of user privileges.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: CefAma
    dataTypes:
      - CommonSecurityLog
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1190
  - T1133
query: |
  let q_period = 14d;
  let dt_lookBack = 24h;
  let p = PaloAltoCDLEvent
  | where TimeGenerated between (ago(q_period)..ago(dt_lookBack))
  | summarize OldPrivileges = make_set(DestinationUserPrivileges) by DstUsername;
  PaloAltoCDLEvent
  | where TimeGenerated > ago(dt_lookBack)
  | summarize NewPrivileges = make_set(DestinationUserPrivileges) by DstUsername
  | join kind=innerunique (p) on DstUsername
  | where tostring(OldPrivileges) != tostring(NewPrivileges)
  | extend AccountCustomEntity = DstUsername
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountCustomEntity
version: 1.0.4
kind: Scheduled

Stages and Predicates

Parameters

let q_period = 14d;
let dt_lookBack = 24h;

Let Binding: p used in Stage 4

let p = PaloAltoCDLEvent
| where TimeGenerated between (ago(q_period)..ago(dt_lookBack))
| summarize OldPrivileges = make_set(DestinationUserPrivileges) by DstUsername;

Stage 1: source

PaloAltoCDLEvent

Stage 2: where

| where TimeGenerated > ago(dt_lookBack)

Stage 3: summarize

| summarize NewPrivileges = make_set(DestinationUserPrivileges) by DstUsername

Stage 4: join

| join kind=innerunique (p) on DstUsername

Stage 5: where

where <unknown:tostring(, OldPrivileges)> != <unknown:tostring(, NewPrivileges)>

Stage 6: extend

extend AccountCustomEntity

Output Fields

These fields are emitted when the rule matches.

FieldSource
DstUsernamesummarize
NewPrivilegessummarize
AccountCustomEntityextend

Pathlock TDnR - Switchable Authorization Runtime Changes

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

Detects runtime changes to Switchable Authorizations (SACF) in SAP, forwarded by Pathlock Threat Detection and Response. Runtime SACF modifications take immediate effect and can instantly disable authorization checks in production, representing a critical real-time security control bypass.

MITRE ATT&CK Coverage

TacticTechniques
Privilege Escalation
Defense Impairment

Rule Body

id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c48
name: Pathlock TDnR - Switchable Authorization Runtime Changes
kind: Scheduled
description: >-
  Detects runtime changes to Switchable Authorizations (SACF) in SAP, forwarded by Pathlock Threat
  Detection and Response. Runtime SACF modifications take immediate effect and can instantly disable
  authorization checks in production, representing a critical real-time security control bypass.
severity: High
status: Available
requiredDataConnectors:
  - connectorId: Pathlock_TDnR
    dataTypes:
      - Pathlock_TDnR_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - DefenseEvasion
  - PrivilegeEscalation
relevantTechniques:
  - T1562
  - T1548
query: |
  Pathlock_TDnR_CL
  | where DataSource == "SACF_CHANGES_RUNTIME"
  | 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 == "SACF_CHANGES_RUNTIME"

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

Ping Federate - Unusual mail domain.

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

'Detects unusual mail domain in authentication requests.'

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Rule Body

id: dc79de7d-2590-4852-95fb-f8e02b34f4da
name: Ping Federate - Unusual mail domain.
description: |
  'Detects unusual mail domain in authentication requests.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: CefAma
    dataTypes:
      - CommonSecurityLog
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1078
query: |
  let known_domains = 
  PingFederateEvent
  | where TimeGenerated between (ago(14d) .. (1d))
  | extend email = extract(@'email=(.*?),.*', 1, AdditionalExtensions)
  | extend m_domain = extract(@'@(.*)', 1, email)
  | where isnotempty(m_domain)
  | summarize makeset(m_domain);
  PingFederateEvent
  | extend email = extract(@'email=(.*?),.*', 1, AdditionalExtensions)
  | extend m_domain = extract(@'@(.*)', 1, email)
  | where isnotempty(m_domain)
  | where m_domain !in (known_domains)
  | extend AccountCustomEntity = DstUserName
  | extend IpCustomEntity = SrcIpAddr
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountCustomEntity
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IpCustomEntity
version: 1.0.3
kind: Scheduled

Stages and Predicates

Let Binding: known_domains used in Stage 5

let known_domains = PingFederateEvent
| where TimeGenerated between (ago(14d) .. (1d))
| extend email = extract(@'email=(.*?),.*', 1, AdditionalExtensions)
| extend m_domain = extract(@'@(.*)', 1, email)
| where isnotempty(m_domain)
| summarize makeset(m_domain);

Stage 1: source

PingFederateEvent

Stage 2: extend

| extend email = extract(@'email=(.*?),.*', 1, AdditionalExtensions)

Stage 3: extend

| extend m_domain = extract(@'@(.*)', 1, email)

Stage 4: where

| where isnotempty(m_domain)

Stage 5: where

| where m_domain !in (known_domains)

Stage 6: extend

| extend AccountCustomEntity = DstUserName

Stage 7: extend

| extend IpCustomEntity = SrcIpAddr

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
m_domainis_not_null
  • (no value, null check)
field:"m_domain" kind:is_not_null

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
m_domaineqknown_domainsexcludes:m_domain field:"m_domain" value:"known_domains"

Output Fields

These fields are emitted when the rule matches.

FieldSource
emailextend
m_domainextend
AccountCustomEntityextend
IpCustomEntityextend

Security Service Registry ACL Modification

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

Identifies attempts to modify registry ACL to evade security solutions. In the Solorigate attack, the attackers were found modifying registry permissions so services.exe cannot access the relevant registry keys to start the service. The detection leverages Security Event as well as MDE data to identify when specific security services registry permissions are modified. Only some portions of this detection are related to Solorigate, it also includes coverage for some common tools that perform this activity. Reference on guidance for enabling registry auditing: - https://docs.microsoft.com/windows/security/threat-protection/auditing/advanced-security-auditing-faq - https://docs.microsoft.com/windows/security/threat-protection/auditing/appendix-a-security-monitoring-recommendations-for-many-audit-events - https://docs.microsoft.com/windows/security/threat-protection/auditing/audit-registry - https://docs.microsoft.com/windows/security/threat-protection/auditing/event-4670 - For the event 4670 to be created the audit policy for the registry must have auditing enabled for Write DAC and/or Write Owner - https://github.com/OTRF/Set-AuditRule - https://docs.microsoft.com/dotnet/api/system.security.accesscontrol.registryrights?view=dotnet-plat-ext-5.0

MITRE ATT&CK Coverage

TacticTechniques
Defense Impairment

Telemetry Coverage

Rule Body

id: 473d57e6-f787-435c-a16b-b38b51fa9a4b
name: Security Service Registry ACL Modification
description: |
    'Identifies attempts to modify registry ACL to evade security solutions. In the Solorigate attack, the attackers were found modifying registry permissions so services.exe cannot access the relevant registry keys to start the service.
     The detection leverages Security Event as well as MDE data to identify when specific security services registry permissions are modified.
     Only some portions of this detection are related to Solorigate, it also includes coverage for some common tools that perform this activity.
     Reference on guidance for enabling registry auditing:
     - https://docs.microsoft.com/windows/security/threat-protection/auditing/advanced-security-auditing-faq
     - https://docs.microsoft.com/windows/security/threat-protection/auditing/appendix-a-security-monitoring-recommendations-for-many-audit-events
     - https://docs.microsoft.com/windows/security/threat-protection/auditing/audit-registry
     - https://docs.microsoft.com/windows/security/threat-protection/auditing/event-4670
       - For the event 4670 to be created the audit policy for the registry must have auditing enabled for Write DAC and/or Write Owner
     - https://github.com/OTRF/Set-AuditRule
     - https://docs.microsoft.com/dotnet/api/system.security.accesscontrol.registryrights?view=dotnet-plat-ext-5.0'
severity: High
requiredDataConnectors:
  - connectorId: SecurityEvents
    dataTypes:
      - SecurityEvent
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - DeviceProcessEvents
  - connectorId: WindowsSecurityEvents
    dataTypes:
      - SecurityEvents
  - connectorId: WindowsForwardedEvents
    dataTypes:
      - WindowsEvent
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - DefenseEvasion
relevantTechniques:
  - T1562
tags:
  - Solorigate
  - NOBELIUM
query: |
  let servicelist = dynamic(['Services\\HealthService', 'Services\\Sense', 'Services\\WinDefend', 'Services\\MsSecFlt', 'Services\\DiagTrack', 'Services\\SgrmBroker', 'Services\\SgrmAgent', 'Services\\AATPSensorUpdater' , 'Services\\AATPSensor', 'Services\\mpssvc']);
  let filename = dynamic(["subinacl.exe",'SetACL.exe']);
  let parameters = dynamic (['/deny=SYSTEM', '/deny=S-1-5-18', '/grant=SYSTEM=r', '/grant=S-1-5-18=r', 'n:SYSTEM;p:READ', 'n1:SYSTEM;ta:remtrst;w:dacl']);
  let FullAccess = dynamic(['A;CI;KA;;;SY', 'A;ID;KA;;;SY', 'A;CIID;KA;;;SY']);
  let ReadAccess = dynamic(['A;CI;KR;;;SY', 'A;ID;KR;;;SY', 'A;CIID;KR;;;SY']);
  let DenyAccess = dynamic(['D;CI;KR;;;SY', 'D;ID;KR;;;SY', 'D;CIID;KR;;;SY']);
  let timeframe = 1d;
  (union isfuzzy=true
  (
  SecurityEvent
  | where TimeGenerated >= ago(timeframe)
  | where EventID == 4670
  | where ObjectType == 'Key'
  | where ObjectName has_any (servicelist)
  | parse EventData with * 'OldSd">' OldSd "<" *
  | parse EventData with * 'NewSd">' NewSd "<" *
  | extend Reason = case( (OldSd has ';;;SY' and NewSd !has ';;;SY'), 'System Account is removed', (OldSd has_any (FullAccess) and NewSd has_any (ReadAccess)) , 'System permission has been changed to read from full access', (OldSd has_any (FullAccess) and NewSd has_any (DenyAccess)), 'System account has been given denied permission', 'None')
  | project TimeGenerated, Computer, Account,  ProcessName, ProcessId, ObjectName, EventData, Activity, HandleId, SubjectLogonId, OldSd, NewSd , Reason
  ),
  (
  SecurityEvent
  | where TimeGenerated >= ago(timeframe)
  | where EventID == 4688
  | extend ProcessName = tostring(split(NewProcessName, '\\')[-1])
  | where ProcessName in~ (filename)
  | where CommandLine has_any (servicelist) and CommandLine has_any (parameters)
  | project TimeGenerated, Computer, Account, AccountDomain, ProcessName, ProcessNameFullPath = NewProcessName, EventID, Activity, CommandLine, EventSourceName, Type
  ),
  (
  WindowsEvent
  | where TimeGenerated >= ago(timeframe)
  | where EventID == 4670 and EventData has_any (servicelist) and EventData has 'Key'
  | extend ObjectType = tostring(EventData.ObjectType)
  | where ObjectType == 'Key'
  | extend ObjectName = tostring(EventData.ObjectName)
  | where ObjectName has_any (servicelist)
  | extend OldSd = tostring(EventData.OldSd)
  | extend NewSd = tostring(EventData.NewSd)
  | extend Reason = case( (OldSd has ';;;SY' and NewSd !has ';;;SY'), 'System Account is removed', (OldSd has_any (FullAccess) and NewSd has_any (ReadAccess)) , 'System permission has been changed to read from full access', (OldSd has_any (FullAccess) and NewSd has_any (DenyAccess)), 'System account has been given denied permission', 'None')
  | extend Account =  strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
  | extend ProcessName = tostring(EventData.ProcessName)
  | extend ProcessId = tostring(EventData.ProcessId)
  | extend Activity= "4670 - Permissions on an object were changed."
  | extend HandleId = tostring(EventData.HandleId)
  | extend  SubjectLogonId = tostring(EventData.SubjectLogonId)
  | project TimeGenerated, Computer, Account,  ProcessName, ProcessId, ObjectName, EventData, Activity, HandleId, SubjectLogonId, OldSd, NewSd , Reason
  ),
  (
  WindowsEvent
  | where TimeGenerated >= ago(timeframe)
  | where EventID == 4688 and EventData has_any (filename) and EventData has_any (servicelist) and EventData has_any (parameters)
  | extend NewProcessName = tostring(EventData.NewProcessName)
  | extend ProcessName = tostring(split(NewProcessName, '\\')[-1])
  | where ProcessName in~ (filename)
  | extend CommandLine = tostring(EventData.CommandLine)
  | where CommandLine has_any (servicelist) and CommandLine has_any (parameters)
  | extend  Account =  strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
  | extend  AccountDomain = tostring(EventData.AccountDomain)
  | extend  Activity="4688 - A new process has been created."
  | extend  EventSourceName=Provider
  | project TimeGenerated, Computer, Account, AccountDomain, ProcessName, ProcessNameFullPath = NewProcessName, EventID, Activity, CommandLine, EventSourceName, Type
  ),
  (
  DeviceProcessEvents
  | where TimeGenerated >= ago(timeframe)
  | where InitiatingProcessFileName in~ (filename)
  | where InitiatingProcessCommandLine has_any(servicelist) and InitiatingProcessCommandLine has_any (parameters)
  | extend Account = iff(isnotempty(InitiatingProcessAccountUpn), InitiatingProcessAccountUpn, InitiatingProcessAccountName), Computer = DeviceName
  | project TimeGenerated, Computer, Account, AccountDomain, ProcessName = InitiatingProcessFileName, ProcessNameFullPath = FolderPath, Activity = ActionType, CommandLine = InitiatingProcessCommandLine, Type, InitiatingProcessParentFileName
  )
  )
  | extend AccountName = tostring(split(Account, "\\")[0]), AccountNTDomain = tostring(split(Account, "\\")[1])
  | 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: Account
      - identifier: Name
        columnName: AccountName
      - identifier: NTDomain
        columnName: AccountNTDomain
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: Computer
      - identifier: HostName
        columnName: HostName
      - identifier: NTDomain
        columnName: HostNameDomain
version: 1.1.4
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: Microsoft Security Research
    support:
        tier: Community
    categories:
        domains: [ "Security - Others" ]

Stages and Predicates

Parameters

let filename = dynamic(["subinacl.exe",'SetACL.exe']);
let parameters = dynamic (['/deny=SYSTEM', '/deny=S-1-5-18', '/grant=SYSTEM=r', '/grant=S-1-5-18=r', 'n:SYSTEM;p:READ', 'n1:SYSTEM;ta:remtrst;w:dacl']);
let FullAccess = dynamic(['A;CI;KA;;;SY', 'A;ID;KA;;;SY', 'A;CIID;KA;;;SY']);
let ReadAccess = dynamic(['A;CI;KR;;;SY', 'A;ID;KR;;;SY', 'A;CIID;KR;;;SY']);
let DenyAccess = dynamic(['D;CI;KR;;;SY', 'D;ID;KR;;;SY', 'D;CIID;KR;;;SY']);
let timeframe = 1d;

Let Binding: servicelist

let servicelist = dynamic(['Services\\HealthService', 'Services\\Sense', 'Services\\WinDefend', 'Services\\MsSecFlt', 'Services\\DiagTrack', 'Services\\SgrmBroker', 'Services\\SgrmAgent', 'Services\\AATPSensorUpdater' , 'Services\\AATPSensor', 'Services\\mpssvc']);

Union isfuzzy=true (5 Sources)

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

Leg 1: SecurityEvent

SecurityEvent
| where TimeGenerated >= ago(timeframe)
| where EventID == 4670
| where ObjectType == 'Key'
| where ObjectName has_any (servicelist)
| parse EventData with * 'OldSd">' OldSd "<" *
| parse EventData with * 'NewSd">' NewSd "<" *
| extend Reason = case( (OldSd has ';;;SY' and NewSd !has ';;;SY'), 'System Account is removed', (OldSd has_any (FullAccess) and NewSd has_any (ReadAccess)) , 'System permission has been changed to read from full access', (OldSd has_any (FullAccess) and NewSd has_any (DenyAccess)), 'System account has been given denied permission', 'None')
| project TimeGenerated, Computer, Account,  ProcessName, ProcessId, ObjectName, EventData, Activity, HandleId, SubjectLogonId, OldSd, NewSd , Reason

Leg 2: SecurityEvent

SecurityEvent
| where TimeGenerated >= ago(timeframe)
| where EventID == 4688
| extend ProcessName = tostring(split(NewProcessName, '\\')[-1])
| where ProcessName in~ (filename)
| where CommandLine has_any (servicelist) and CommandLine has_any (parameters)
| project TimeGenerated, Computer, Account, AccountDomain, ProcessName, ProcessNameFullPath = NewProcessName, EventID, Activity, CommandLine, EventSourceName, Type

Leg 3: WindowsEvent

WindowsEvent
| where TimeGenerated >= ago(timeframe)
| where EventID == 4670 and EventData has_any (servicelist) and EventData has 'Key'
| extend ObjectType = tostring(EventData.ObjectType)
| where ObjectType == 'Key'
| extend ObjectName = tostring(EventData.ObjectName)
| where ObjectName has_any (servicelist)
| extend OldSd = tostring(EventData.OldSd)
| extend NewSd = tostring(EventData.NewSd)
| extend Reason = case( (OldSd has ';;;SY' and NewSd !has ';;;SY'), 'System Account is removed', (OldSd has_any (FullAccess) and NewSd has_any (ReadAccess)) , 'System permission has been changed to read from full access', (OldSd has_any (FullAccess) and NewSd has_any (DenyAccess)), 'System account has been given denied permission', 'None')
| extend Account =  strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
| extend ProcessName = tostring(EventData.ProcessName)
| extend ProcessId = tostring(EventData.ProcessId)
| extend Activity= "4670 - Permissions on an object were changed."
| extend HandleId = tostring(EventData.HandleId)
| extend  SubjectLogonId = tostring(EventData.SubjectLogonId)
| project TimeGenerated, Computer, Account,  ProcessName, ProcessId, ObjectName, EventData, Activity, HandleId, SubjectLogonId, OldSd, NewSd , Reason

Leg 4: WindowsEvent

WindowsEvent
| where TimeGenerated >= ago(timeframe)
| where EventID == 4688 and EventData has_any (filename) and EventData has_any (servicelist) and EventData has_any (parameters)
| extend NewProcessName = tostring(EventData.NewProcessName)
| extend ProcessName = tostring(split(NewProcessName, '\\')[-1])
| where ProcessName in~ (filename)
| extend CommandLine = tostring(EventData.CommandLine)
| where CommandLine has_any (servicelist) and CommandLine has_any (parameters)
| extend  Account =  strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
| extend  AccountDomain = tostring(EventData.AccountDomain)
| extend  Activity="4688 - A new process has been created."
| extend  EventSourceName=Provider
| project TimeGenerated, Computer, Account, AccountDomain, ProcessName, ProcessNameFullPath = NewProcessName, EventID, Activity, CommandLine, EventSourceName, Type

Leg 5: DeviceProcessEvents

DeviceProcessEvents
| where TimeGenerated >= ago(timeframe)
| where InitiatingProcessFileName in~ (filename)
| where InitiatingProcessCommandLine has_any(servicelist) and InitiatingProcessCommandLine has_any (parameters)
| extend Account = iff(isnotempty(InitiatingProcessAccountUpn), InitiatingProcessAccountUpn, InitiatingProcessAccountName), Computer = DeviceName
| project TimeGenerated, Computer, Account, AccountDomain, ProcessName = InitiatingProcessFileName, ProcessNameFullPath = FolderPath, Activity = ActionType, CommandLine = InitiatingProcessCommandLine, Type, InitiatingProcessParentFileName

Applied to the Combined Result

| extend AccountName = tostring(split(Account, "\\")[0]), AccountNTDomain = tostring(split(Account, "\\")[1])
| 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.

FieldKindValuesSearch
CommandLinematch
  • /deny=S-1-5-18 transforms: term
  • /deny=SYSTEM transforms: term
  • /grant=S-1-5-18=r transforms: term
  • /grant=SYSTEM=r transforms: term
  • Services\AATPSensor transforms: term
  • Services\AATPSensorUpdater transforms: term
  • Services\DiagTrack transforms: term
  • Services\HealthService transforms: term
  • Services\MsSecFlt transforms: term
  • Services\Sense transforms: term
  • Services\SgrmAgent transforms: term
  • Services\SgrmBroker transforms: term
  • Services\WinDefend transforms: term
  • Services\mpssvc transforms: term
  • n1:SYSTEM;ta:remtrst;w:dacl transforms: term
  • n:SYSTEM;p:READ transforms: term
field:"CommandLine" kind:match
EventDatamatch
  • /deny=S-1-5-18 transforms: term
  • /deny=SYSTEM transforms: term
  • /grant=S-1-5-18=r transforms: term
  • /grant=SYSTEM=r transforms: term
  • Key transforms: term
  • Services\AATPSensor transforms: term
  • Services\AATPSensorUpdater transforms: term
  • Services\DiagTrack transforms: term
  • Services\HealthService transforms: term
  • Services\MsSecFlt transforms: term
  • Services\Sense transforms: term
  • Services\SgrmAgent transforms: term
  • Services\SgrmBroker transforms: term
  • Services\WinDefend transforms: term
  • Services\mpssvc transforms: term
  • SetACL.exe transforms: term
  • n1:SYSTEM;ta:remtrst;w:dacl transforms: term
  • n:SYSTEM;p:READ transforms: term
  • subinacl.exe transforms: term
field:"EventData" kind:match
EventIDeq
  • 4670
  • 4688 corpus 23 (kusto 23)
field:"EventID" kind:eq
InitiatingProcessCommandLinematch
  • /deny=S-1-5-18 transforms: term
  • /deny=SYSTEM transforms: term
  • /grant=S-1-5-18=r transforms: term
  • /grant=SYSTEM=r transforms: term
  • Services\AATPSensor transforms: term
  • Services\AATPSensorUpdater transforms: term
  • Services\DiagTrack transforms: term
  • Services\HealthService transforms: term
  • Services\MsSecFlt transforms: term
  • Services\Sense transforms: term
  • Services\SgrmAgent transforms: term
  • Services\SgrmBroker transforms: term
  • Services\WinDefend transforms: term
  • Services\mpssvc transforms: term
  • n1:SYSTEM;ta:remtrst;w:dacl transforms: term
  • n:SYSTEM;p:READ transforms: term
field:"ParentCommandLine" kind:match
InitiatingProcessFileNamein
  • SetACL.exe
  • subinacl.exe
field:"parent_process_basename" kind:in
ObjectNamematch
  • Services\AATPSensor transforms: term
  • Services\AATPSensorUpdater transforms: term
  • Services\DiagTrack transforms: term
  • Services\HealthService transforms: term
  • Services\MsSecFlt transforms: term
  • Services\Sense transforms: term
  • Services\SgrmAgent transforms: term
  • Services\SgrmBroker transforms: term
  • Services\WinDefend transforms: term
  • Services\mpssvc transforms: term
field:"ObjectName" kind:match
ObjectTypeeq
  • Key corpus 4 (kusto 4)
field:"ObjectType" kind:eq value:"Key"
ProcessNamein
  • SetACL.exe
  • subinacl.exe
field:"ProcessName" kind:in

Output Fields

These fields are emitted when the rule matches.

FieldSource
Accountproject
AccountDomainproject
Activityproject
CommandLineproject
Computerproject
InitiatingProcessParentFileNameproject
ProcessNameproject
ProcessNameFullPathproject
TimeGeneratedproject
Typeproject
AccountNTDomainextend
AccountNameextend
DomainIndexextend
HostNameextend
HostNameDomainextend

Snowflake - Possible privileges discovery activity

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

'Detects possible privileges discovery activity.'

MITRE ATT&CK Coverage

TacticTechniques
Discovery

Rule Body

id: 627a4ff1-036b-4375-a9f9-288d5e1d7d37
name: Snowflake - Possible privileges discovery activity
description: |
  'Detects possible privileges discovery activity.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: Snowflake
    dataTypes:
      - Snowflake
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Discovery
relevantTechniques:
  - T1087
query: |
  Snowflake
  | where QueryType =~ 'SHOW'
  | where QueryText has_all ('SHOW', 'ROLES')
  | extend AccountCustomEntity = TargetUsername
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountCustomEntity
version: 1.0.1
kind: Scheduled

Stages and Predicates

Stage 1: source

Snowflake

Stage 2: where

| where QueryType =~ 'SHOW'

Stage 3: where

| where QueryText has_all ('SHOW', 'ROLES')

Stage 4: extend

| extend AccountCustomEntity = TargetUsername

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
QueryTextmatch
  • ROLES transforms: term
  • SHOW transforms: term
field:"QueryText" kind:match
QueryTypeeq
  • SHOW
field:"QueryType" kind:eq value:"SHOW"

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountCustomEntityextend

User added to Microsoft Entra ID Privileged Groups

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

This will alert when a user is added to any of the Privileged Groups. For further information on AuditLogs please see https://docs.microsoft.com/azure/active-directory/reports-monitoring/reference-audit-activities. For Administrator role permissions in Microsoft Entra ID please see https://docs.microsoft.com/azure/active-directory/users-groups-roles/directory-assign-admin-roles

MITRE ATT&CK Coverage

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: 4d94d4a9-dc96-410a-8dea-4d4d4584188b
name: User added to Microsoft Entra ID Privileged Groups
description: |
  'This will alert when a user is added to any of the Privileged Groups.
  For further information on AuditLogs please see https://docs.microsoft.com/azure/active-directory/reports-monitoring/reference-audit-activities.
  For Administrator role permissions in Microsoft Entra ID please see https://docs.microsoft.com/azure/active-directory/users-groups-roles/directory-assign-admin-roles'
severity: Medium
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AuditLogs
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - Persistence
  - PrivilegeEscalation
relevantTechniques:
  - T1098
  - T1078
tags:
  - DEV-0537
query: |
  let OperationList = dynamic(["Add member to role","Add eligible member to role"]);
  let PrivilegedGroups = dynamic(["UserAccountAdmins","PrivilegedRoleAdmins","TenantAdmins","PrivilegedAuthenticationAdmins"]);
  AuditLogs
  | where Category =~ "RoleManagement"
  | where OperationName in~ (OperationList)
  | mv-apply TargetResource = TargetResources on 
    (
        where TargetResource.type =~ "User"
        | extend TargetUserPrincipalName = tostring(TargetResource.userPrincipalName),
                 modProps = TargetResource.modifiedProperties
    )
  | mv-apply Property = modProps on 
    (
        where Property.displayName =~ "Role.WellKnownObjectName"
        | extend DisplayName = trim('"',tostring(Property.displayName)),
                 GroupName = trim('"',tostring(Property.newValue))
    )
  | extend InitiatingAppId = InitiatedBy.app.appId
  | extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
  | extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
  | extend InitiatingAppServicePrincipalName = tostring(InitiatedBy.app.servicePrincipalName)
  | 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))
  | extend InitiatingUserRoles = InitiatedBy.user.roles
  | where GroupName in~ (PrivilegedGroups)
  // If you don't want to alert for operations from PIM, remove below filtering for MS-PIM.
  //| where InitiatingAppName != "MS-PIM" and InitiatingAppName != "MS-PIM-Fairfax"
  | project TimeGenerated, AADOperationType, Category, OperationName, AADTenantId, InitiatingUserPrincipalName, InitiatingAadUserId, InitiatingAppId, InitiatingAppName, InitiatingAppServicePrincipalName, InitiatingAppServicePrincipalId, InitiatingIpAddress, DisplayName, GroupName, InitiatingUserRoles, TargetUserPrincipalName
  | extend AccountName = tostring(split(InitiatingUserPrincipalName,'@',0)[0]), AccountUPNSuffix = tostring(split(InitiatingUserPrincipalName,'@',1)[0])
  | extend TargetName = tostring(split(TargetUserPrincipalName,'@',0)[0]), TargetUPNSuffix = tostring(split(TargetUserPrincipalName,'@',1)[0])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: InitiatingUserPrincipalName   
      - identifier: Name
        columnName: AccountName      
      - identifier: UPNSuffix
        columnName: AccountUPNSuffix
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: TargetUserPrincipalName   
      - identifier: Name
        columnName: TargetName      
      - identifier: UPNSuffix
        columnName: TargetUPNSuffix
  - entityType: Account
    fieldMappings:
      - identifier: AadUserId
        columnName: InitiatingAadUserId
  - entityType: Account
    fieldMappings:
      - identifier: AadUserId
        columnName: InitiatingAppServicePrincipalId
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: InitiatingIpAddress
version: 1.0.7
kind: Scheduled

Stages and Predicates

Parameters

let OperationList = dynamic(["Add member to role","Add eligible member to role"]);
let PrivilegedGroups = dynamic(["UserAccountAdmins","PrivilegedRoleAdmins","TenantAdmins","PrivilegedAuthenticationAdmins"]);

Stage 1: source

AuditLogs

Stage 2: where

| where Category =~ "RoleManagement"

Stage 3: where

| where OperationName in~ (OperationList)

Stage 4: kusto:mv-apply

| mv-apply TargetResource = TargetResources on 
  (
      where TargetResource.type =~ "User"
      | extend TargetUserPrincipalName = tostring(TargetResource.userPrincipalName),
               modProps = TargetResource.modifiedProperties
  )

Stage 5: kusto:mv-apply

| mv-apply Property = modProps on 
  (
      where Property.displayName =~ "Role.WellKnownObjectName"
      | extend DisplayName = trim('"',tostring(Property.displayName)),
               GroupName = trim('"',tostring(Property.newValue))
  )

Stage 6: extend (8 consecutive steps)

| extend InitiatingAppId = InitiatedBy.app.appId
| extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
| extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
| extend InitiatingAppServicePrincipalName = tostring(InitiatedBy.app.servicePrincipalName)
| 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))
| extend InitiatingUserRoles = InitiatedBy.user.roles

Stage 7: where

| where GroupName in~ (PrivilegedGroups)

Stage 8: project

| project TimeGenerated, AADOperationType, Category, OperationName, AADTenantId, InitiatingUserPrincipalName, InitiatingAadUserId, InitiatingAppId, InitiatingAppName, InitiatingAppServicePrincipalName, InitiatingAppServicePrincipalId, InitiatingIpAddress, DisplayName, GroupName, InitiatingUserRoles, TargetUserPrincipalName

Stage 9: extend

| extend AccountName = tostring(split(InitiatingUserPrincipalName,'@',0)[0]), AccountUPNSuffix = tostring(split(InitiatingUserPrincipalName,'@',1)[0])

Stage 10: extend

| extend TargetName = tostring(split(TargetUserPrincipalName,'@',0)[0]), TargetUPNSuffix = tostring(split(TargetUserPrincipalName,'@',1)[0])

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Categoryeq
  • RoleManagement corpus 17 (kusto 16, sigma 1)
field:"Category" kind:eq value:"RoleManagement"
GroupNamein
  • PrivilegedAuthenticationAdmins
  • PrivilegedRoleAdmins
  • TenantAdmins
  • UserAccountAdmins
field:"GroupName" kind:in
OperationNamein
  • Add eligible member to role corpus 2 (kusto 2)
  • Add member to role corpus 3 (kusto 3)
field:"OperationName" kind:in
Property.displayNameeq
  • Role.WellKnownObjectName transforms: kusto_dynamic_path
field:"Property.displayName" kind:eq value:"Role.WellKnownObjectName"
TargetResource.typeeq
  • User transforms: kusto_dynamic_path
field:"TargetResource.type" kind:eq value:"User"

Output Fields

These fields are emitted when the rule matches.

FieldSource
AADOperationTypeproject
AADTenantIdproject
Categoryproject
DisplayNameproject
GroupNameproject
InitiatingAadUserIdproject
InitiatingAppIdproject
InitiatingAppNameproject
InitiatingAppServicePrincipalIdproject
InitiatingAppServicePrincipalNameproject
InitiatingIpAddressproject
InitiatingUserPrincipalNameproject
InitiatingUserRolesproject
OperationNameproject
TargetUserPrincipalNameproject
TimeGeneratedproject
AccountNameextend
AccountUPNSuffixextend
TargetNameextend
TargetUPNSuffixextend

Back to all Kusto rules