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.
'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.
| Field | Kind | Values | Search |
|---|---|---|---|
avl_rule_mode | ne |
| field:"avl_rule_mode" kind:ne value:"Warn" |
Apache - Command in URI
#'Detects command in URI'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
UrlOriginal | match |
| field:"UrlOriginal" kind:match |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
UrlCustomEntity | extend |
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.
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.
| Field | Kind | Values | Search |
|---|---|---|---|
Finding | eq |
| 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.
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.
| Field | Kind | Values | Search |
|---|---|---|---|
Finding | eq |
| 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.
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.
| Field | Kind | Values | Search |
|---|---|---|---|
Finding | eq |
| field:"Finding" kind:eq value:"T0Owns" |
Cisco SEG - Suspicious sender domain
#'Detects suspicious sender domain age.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| 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.
| Field | Kind | Values | Search |
|---|---|---|---|
AdditionalFields | match |
| field:"AdditionalFields" kind:match value:"ESASDRDomainAge" |
NetworkDirection | eq |
| field:"NetworkDirection" kind:eq value:"Incoming" |
mm | is_null | field:"mm" kind:is_null | |
mm | le |
| field:"mm" kind:le value:"2" |
yy | is_null | field:"yy" kind:is_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
rec_domain | extend |
s_domain | extend |
domain_age | extend |
yy | extend |
mm | extend |
dd | extend |
AccountCustomEntity | extend |
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.
"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
| Tactic | Techniques |
|---|---|
| Persistence | |
| Privilege Escalation | |
| Discovery | |
| Impact |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
severity | eq |
| field:"severity" kind:eq value:"High" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AlertUID | project |
Country | project |
Description | project |
Domain | project |
FirstSeen | project |
HostProvider | project |
LastSeen | project |
NetworkIP | project |
OpenPorts | project |
ProductName | project |
ProviderName | project |
RiskScore | project |
Softwares | project |
TimeGenerated | project |
TopDomain | project |
UID | project |
Vulnerabilities | project |
VulnerabilityProducts | project |
WebServer | project |
WebServerVersion | project |
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.
"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
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Execution | |
| Stealth | |
| Impact |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
ConfidenceScore | ge |
| field:"ConfidenceScore" kind:ge value:"50" |
ConfidenceScore | lt |
| field:"ConfidenceScore" kind:lt value:"80" |
RecommendedActions | match |
| field:"RecommendedActions" kind:match value:"Monitor" |
Roles | is_null | field:"Roles" kind:is_null | |
pattern | contains |
| field:"pattern" kind:contains value:"file:hashes" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Algo_MD5 | project |
Algo_SHA1 | project |
Algo_SHA256 | project |
ConfidenceScore | project |
Country | project |
Description | project |
IndicatorID | project |
MD5 | project |
ProductName | project |
ProviderName | project |
RecommendedActions | project |
Roles | project |
SHA1 | project |
SHA256 | project |
SecurityVendors | project |
Sources | project |
Tags | project |
ThreatActors | project |
ThreatType | project |
TimeGenerated | project |
created | project |
modified | project |
name | project |
valid_from | project |
Dataverse - Mass export of records to Excel
#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
| Tactic | Techniques |
|---|---|
| Exfiltration |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Microsoft 365 | any: Dataverse and model-driven apps audit (catch-all) |
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
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.
| Field | Kind | Values | Search |
|---|---|---|---|
Message | eq |
| field:"Message" kind:eq value:"ExportToExcel" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountName | project |
BaselineThreshold | project |
ClientIp | project |
CloudAppId | project |
CurrentExportRate | project |
Details | project |
FirstEvent | project |
InstanceUrl | project |
SampleRecordIds | project |
UPNSuffix | project |
UserId | project |
GCP IAM - Service Account Keys Enumeration
#Detects possible service account keys enumeration.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Discovery |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| GCP | google.iam.admin.ListServiceAccountKeys: ListServiceAccountKeys |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
PayloadMethodname | eq |
| field:"gcp::method_name" kind:eq value:"google.iam.admin.v1.ListServiceAccountKeys" |
count_ | gt |
| field:"count_" kind:gt value:"5" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
PayloadAuthenticationinfoPrincipalemail | summarize |
AccountCustomEntity | extend |
GitLab - External User Added to GitLab
#'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
| Tactic | Techniques |
|---|---|
| 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.
| Field | Kind | Values | Search |
|---|---|---|---|
AddAction | eq |
| field:"AddAction" kind:eq value:"user" |
UserAdded | eq |
| field:"UserAdded" kind:eq value:"1" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AuthorName | project |
DomainName | project |
EmailAddress | project |
IPAddress | project |
User | project |
Guardian- Blocks specific strings of text Policy Violation Detection
#'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.
| Field | Kind | Values | Search |
|---|---|---|---|
PolicyViolatedControlFeature | eq |
| field:"PolicyViolatedControlFeature" kind:eq value:"Block Substring" |
Severity | eq |
| field:"Severity" kind:eq value:"Low" |
Microsoft Entra ID Health Service Agents Registry Keys Access
#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
| Tactic | Techniques |
|---|---|
| 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.
| Field | Kind | Values | Search |
|---|---|---|---|
EventData | match |
| field:"EventData" kind:match value:"\REGISTRY\MACHINE\SOFTWARE\Microsoft\MicrosoftOnline\Reporting\MonitoringAgent" |
EventID | eq |
| field:"EventID" kind:eq |
ObjectName | eq |
| field:"ObjectName" kind:eq value:"\REGISTRY\MACHINE\SOFTWARE\Microsoft\MicrosoftOnline\Reporting\MonitoringAgent" |
ObjectType | eq |
| field:"ObjectType" kind:eq value:"Key" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
Process | in | Microsoft.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.exe | excludes:Process |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Account | summarize |
Computer | summarize |
EndTime | summarize |
EventID | summarize |
ObjectName | summarize |
ObjectType | summarize |
Process | summarize |
ProcessName | summarize |
StartTime | summarize |
SubjectDomainName | summarize |
SubjectUserName | summarize |
DomainIndex | extend |
HostName | extend |
HostNameDomain | extend |
NTDomain | extend |
Name | extend |
PaloAlto - User privileges was changed
#'Detects changing of user privileges.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
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
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.
| Field | Source |
|---|---|
DstUsername | summarize |
NewPrivileges | summarize |
AccountCustomEntity | extend |
Pathlock TDnR - Switchable Authorization Runtime Changes
#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
| Tactic | Techniques |
|---|---|
| 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.
| Field | Kind | Values | Search |
|---|---|---|---|
DataSource | eq |
| field:"DataSource" kind:eq value:"SACF_CHANGES_RUNTIME" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AffectedUser | project |
Area | project |
Bname | project |
CentralTs | project |
DataSource | project |
DestIp | project |
Eventid | project |
Hostname | project |
Instance | project |
LogLine | project |
MessageV1 | project |
MessageV2 | project |
MessageV3 | project |
MessageV4 | project |
MsgId | project |
MsgNo | project |
MsgType | project |
Report | project |
SrcIp | project |
Subid | project |
Sysid | project |
Tcode | project |
TimeGenerated | project |
Ping Federate - Unusual mail domain.
#'Detects unusual mail domain in authentication requests.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| 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
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.
| Field | Kind | Values | Search |
|---|---|---|---|
m_domain | is_not_null | field:"m_domain" kind:is_not_null |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
m_domain | eq | known_domains | excludes:m_domain field:"m_domain" value:"known_domains" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
email | extend |
m_domain | extend |
AccountCustomEntity | extend |
IpCustomEntity | extend |
Security Service Registry ACL Modification
#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
| Tactic | Techniques |
|---|---|
| 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.
| Field | Kind | Values | Search |
|---|---|---|---|
CommandLine | match |
| field:"CommandLine" kind:match |
EventData | match |
| field:"EventData" kind:match |
EventID | eq |
| field:"EventID" kind:eq |
InitiatingProcessCommandLine | match |
| field:"ParentCommandLine" kind:match |
InitiatingProcessFileName | in |
| field:"parent_process_basename" kind:in |
ObjectName | match |
| field:"ObjectName" kind:match |
ObjectType | eq |
| field:"ObjectType" kind:eq value:"Key" |
ProcessName | in |
| field:"ProcessName" kind:in |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Account | project |
AccountDomain | project |
Activity | project |
CommandLine | project |
Computer | project |
InitiatingProcessParentFileName | project |
ProcessName | project |
ProcessNameFullPath | project |
TimeGenerated | project |
Type | project |
AccountNTDomain | extend |
AccountName | extend |
DomainIndex | extend |
HostName | extend |
HostNameDomain | extend |
Snowflake - Possible privileges discovery activity
#'Detects possible privileges discovery activity.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| 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.
| Field | Kind | Values | Search |
|---|---|---|---|
QueryText | match |
| field:"QueryText" kind:match |
QueryType | eq |
| field:"QueryType" kind:eq value:"SHOW" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountCustomEntity | extend |
User added to Microsoft Entra ID Privileged Groups
#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
| Tactic | Techniques |
|---|---|
| Persistence | |
| Privilege Escalation |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Azure | Add eligible member to role |
| Azure | Add member to role |
Rules Detecting the Same Action
These rules filter on the same operation.
- [Entra ID] Application Assigned Administrator Permissions Immediately After Obtaining Role Management Permissions (Kusto)
- [Entra ID] Mass Privileged Role Change Activity Detected (Kusto)
- [Entra ID] Privilege Elevation Request Denied (Kusto)
- [Entra ID] Privileged Role Assigned to a New User (Kusto)
- [Entra ID] Privileged Role Assigned to User (Kusto)
- Account Elevated to New Role (Kusto)
- Admin promotion after Role Management Application Permission Grant (Kusto)
- App Assigned To Azure RBAC/Microsoft Entra Role (Sigma)
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.
| Field | Kind | Values | Search |
|---|---|---|---|
Category | eq |
| field:"Category" kind:eq value:"RoleManagement" |
GroupName | in |
| field:"GroupName" kind:in |
OperationName | in |
| field:"OperationName" kind:in |
Property.displayName | eq |
| field:"Property.displayName" kind:eq value:"Role.WellKnownObjectName" |
TargetResource.type | eq |
| field:"TargetResource.type" kind:eq value:"User" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AADOperationType | project |
AADTenantId | project |
Category | project |
DisplayName | project |
GroupName | project |
InitiatingAadUserId | project |
InitiatingAppId | project |
InitiatingAppName | project |
InitiatingAppServicePrincipalId | project |
InitiatingAppServicePrincipalName | project |
InitiatingIpAddress | project |
InitiatingUserPrincipalName | project |
InitiatingUserRoles | project |
OperationName | project |
TargetUserPrincipalName | project |
TimeGenerated | project |
AccountName | extend |
AccountUPNSuffix | extend |
TargetName | extend |
TargetUPNSuffix | extend |