Detection rules › Kusto
Kusto rules: 2b
Alsid Password Guessing
#'Searches for bruteforce Password Guessing attacks'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
Rule Body
id: ba239935-42c2-472d-80ba-689186099ea1
name: Alsid Password Guessing
description: |
'Searches for bruteforce Password Guessing attacks'
severity: High
requiredDataConnectors:
- connectorId: AlsidForAD
dataTypes:
- AlsidForADLog_CL
queryFrequency: 2h
queryPeriod: 2h
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- CredentialAccess
relevantTechniques:
- T1110
query: |
afad_parser
| where MessageType == 2 and Codename == "Password Guessing"
| extend HostName = tostring(split(Host, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(Host, '.'), 1, -1), '.'))
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: DnsDomain
version: 1.0.1
kind: Scheduled
Stages and Predicates
Stage 1: source
afad_parser
Stage 2: where
| where MessageType == 2 and Codename == "Password Guessing"
Stage 3: extend
| extend HostName = tostring(split(Host, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(Host, '.'), 1, -1), '.'))
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Codename | eq |
| field:"Codename" kind:eq value:"Password Guessing" |
MessageType | eq |
| field:"MessageType" kind:eq value:"2" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DnsDomain | extend |
HostName | extend |
Azure secure score user risk policy
#'This query searches for an active Azure Premium P2 license is required to use and edit this policy. You will be required to have setup the MFA Policy before activating this policy'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Impact |
Rule Body
id: 1C07A4CB-E31B-4917-BD2A-3572E42F602C
name: Azure secure score user risk policy
description: |
'This query searches for an active Azure Premium P2 license is required to use and edit this policy.
You will be required to have setup the MFA Policy before activating this policy'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: SenservaPro
dataTypes:
- SenservaPro_CL
queryFrequency: 6h
queryPeriod: 6h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Impact
relevantTechniques:
- T1529
query: |
SenservaPro_CL
| where ControlName_s == 'AzureSecureScoreUserRiskPolicy'
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: ControlName_s
- identifier: AadTenantId
columnName: TenantId
- identifier: DisplayName
columnName: TenantDisplayName_s
- entityType: SecurityGroup
fieldMappings:
- identifier: DistinguishedName
columnName: Group_s
- entityType: AzureResource
fieldMappings:
- identifier: ResourceId
columnName: SourceSystem
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
SenservaPro_CL
Stage 2: where
| where ControlName_s == 'AzureSecureScoreUserRiskPolicy'
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ControlName_s | eq |
| field:"ControlName_s" kind:eq value:"AzureSecureScoreUserRiskPolicy" |
BloodHound Attack Path Finding - Add Members to Tier Zero Group
#This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.
BloodHound Attack Path Finding - Add Members to Tier Zero Group
Rule Body
id: 357d3dee-3b61-4de9-9084-fb245ba23d81
name: BloodHound Attack Path Finding - Add Members to Tier Zero Group
description: BloodHound Attack Path Finding - Add Members to Tier Zero Group
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: BloodHoundEnterprise
dataTypes:
- BHEAttackPathsData_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: 'BHEAttackPathsData_CL
| where Finding == "AzureT0AddMembers"'
entityMappings:
- entityType: URL
fieldMappings:
- identifier: Url
columnName: domain_name
customDetails:
Description: ShortDescription
Remediation: ShortRemediation
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
BHEAttackPathsData_CL
Stage 2: where
| where Finding == "AzureT0AddMembers"
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Finding | eq |
| field:"Finding" kind:eq value:"AzureT0AddMembers" |
BloodHound Attack Path Finding - Non-Tier Zero Principals With ADCS ESC6 Scenario A Privileges
#This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.
BloodHound Attack Path Finding - Non-Tier Zero Principals With ADCS ESC6 Scenario A Privileges
Rule Body
id: ca313efd-032c-4825-b687-86c50e92200f
name: BloodHound Attack Path Finding - Non-Tier Zero Principals With ADCS ESC6 Scenario
A Privileges
description: BloodHound Attack Path Finding - Non-Tier Zero Principals With ADCS ESC6
Scenario A Privileges
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: BloodHoundEnterprise
dataTypes:
- BHEAttackPathsData_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: BHEAttackPathsData_CL | where Finding == "T0ADCSESC6a"
entityMappings:
- entityType: URL
fieldMappings:
- identifier: Url
columnName: domain_name
customDetails:
Description: ShortDescription
Remediation: ShortRemediation
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
BHEAttackPathsData_CL
Stage 2: where
| where Finding == "T0ADCSESC6a"
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Finding | eq |
| field:"Finding" kind:eq value:"T0ADCSESC6a" |
Cisco WSA - Unexpected URL
#'Detects unexpected URL.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control |
Rule Body
id: 010644fd-2830-4451-9e0e-606cc192f2e7
name: Cisco WSA - Unexpected URL
description: |
'Detects unexpected URL.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: SyslogAma
datatypes:
- Syslog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- CommandAndControl
relevantTechniques:
- T1102
query: |
let threshold = 5;
CiscoWSAEvent
| where UrlOriginal matches regex @'\Ahttp(s)?[:][/][/]\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}'
| extend URLCustomEntity = UrlOriginal, AccountCustomEntity = SrcUserName
entityMappings:
- entityType: URL
fieldMappings:
- identifier: Url
columnName: URLCustomEntity
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountCustomEntity
version: 1.0.2
kind: Scheduled
Stages and Predicates
Parameters
let threshold = 5;
Stage 1: source
CiscoWSAEvent
Stage 2: where
| where UrlOriginal matches regex @'\Ahttp(s)?[:][/][/]\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}'
Stage 3: extend
| extend URLCustomEntity = UrlOriginal, AccountCustomEntity = SrcUserName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
UrlOriginal | regex_match |
| field:"UrlOriginal" kind:regex_match value:"\Ahttp(s)?[:][/][/]\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountCustomEntity | extend |
URLCustomEntity | extend |
Cloudflare - Unexpected POST requests
#'Detects post requests to unusual extensions.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| Command & Control |
Rule Body
id: 7313352a-09f6-4a84-88bd-6f17f1cbeb8f
name: Cloudflare - Unexpected POST requests
description: |
'Detects post requests to unusual extensions.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: CloudflareDataConnector
dataTypes:
- Cloudflare
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
- CommandAndControl
relevantTechniques:
- T1505
- T1071
query: |
Cloudflare
| where HttpRequestMethod in~ ('POST', 'PUT')
| where tostring(HttpStatusCode) startswith '2'
| where DstBytes != 0 or SrcBytes != 0
| extend fe = extract(@'.*(\.\w+)$', 1, ClientRequestURI)
| where fe in~ ('.jpg', '.jpeg', '.gif', '.png', '.icon', '.ico', '.xml', '.swf', '.svg', '.ppt', '.pttx', '.doc', '.docx', '.rtf', '.pdf', '.tif', '.zip', '.mov')
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIpAddr
version: 1.0.1
kind: Scheduled
Stages and Predicates
Stage 1: source
Cloudflare
Stage 2: where
| where HttpRequestMethod in~ ('POST', 'PUT')
Stage 3: where
| where tostring(HttpStatusCode) startswith '2'
Stage 4: where
| where DstBytes != 0 or SrcBytes != 0
Stage 5: extend
| extend fe = extract(@'.*(\.\w+)$', 1, ClientRequestURI)
Stage 6: where
| where fe in~ ('.jpg', '.jpeg', '.gif', '.png', '.icon', '.ico', '.xml', '.swf', '.svg', '.ppt', '.pttx', '.doc', '.docx', '.rtf', '.pdf', '.tif', '.zip', '.mov')
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DstBytes | ne |
| field:"DstBytes" kind:ne value:"0" |
HttpRequestMethod | in |
| field:"HttpRequestMethod" kind:in |
HttpStatusCode | starts_with |
| field:"HttpStatusCode" kind:starts_with value:"2" |
SrcBytes | ne |
| field:"SrcBytes" kind:ne value:"0" |
fe | in |
| field:"fe" kind:in |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
fe | extend |
Dynatrace Application Security - Code-Level runtime vulnerability detection
#Detect Code-level runtime vulnerabilities in your environment
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Execution | |
| Persistence | |
| Privilege Escalation | |
| Stealth | |
| Lateral Movement | |
| Impact |
Rule Body
id: 305093b4-0fa2-57bc-bced-caea782a6e9c
name: Dynatrace Application Security - Code-Level runtime vulnerability detection
description: 'Detect Code-level runtime vulnerabilities in your environment'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: DynatraceRuntimeVulnerabilities
dataTypes:
- DynatraceSecurityProblems
- connectorId: DTRunVulnCCPDefinition
dataTypes:
- DynatraceSecurityProblemsV2_CL
- connectorId: DTRunVulnV3CCPDefinition
dataTypes:
- DynatraceSecurityProblemsV3_CL
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
- Execution
- Impact
- InitialAccess
- LateralMovement
- Persistence
- PrivilegeEscalation
relevantTechniques:
- T1140
- T1059
- T1565
- T1659
- T1210
- T1554
- T1548
eventGroupingSettings:
aggregationKind: AlertPerResult
query: |
DynatraceSecurityProblems
| where VulnerabilityType == "CODE_LEVEL" and DAVISRiskLevel == "CRITICAL" and Muted == false
| summarize arg_max(LastUpdatedTimeStamp, *) by SecurityProblemId
entityMappings:
- entityType: URL
fieldMappings:
- identifier: Url
columnName: Url
alertDetailsOverride:
alertDisplayNameFormat: 'Dynatrace Code-Level runtime vulnerability detected - {{DisplayId}} : {{Title}}'
alertDescriptionFormat: |
Code-Level runtime vulnerability ({{ExternalVulnerabilityId}}) detected in package ({{PackageName}}), more details available from Dynatrace at {{Url}}.
alertSeverityColumnName: Severity
customDetails:
SecProbIdentifier: SecurityProblemId
DisplayIdentifier: DisplayId
SecurityProblemUrl: Url
DAVISRiskLevel: DAVISRiskLevel
PackageName: PackageName
Technology: Technology
VulnerabilityType: VulnerabilityType
CVEIds: CVEIds
ExternVulnIdentifier: ExternalVulnerabilityId
DAVISRiskScore: DAVISRiskScore
DAVISRiskVector: DAVISRiskVector
DAVISExposure: DAVISExposure
DAVISPublicExploit: DAVISPublicExploit
DAVISDataAssets: DAVISDataAssets
DAVISVulnFuncUsage: DAVISVulnerableFunctionUsage
incidentConfiguration:
createIncident: false
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
version: 1.0.5
kind: Scheduled
Stages and Predicates
Stage 1: source
DynatraceSecurityProblems
Stage 2: where
| where VulnerabilityType == "CODE_LEVEL" and DAVISRiskLevel == "CRITICAL" and Muted == false
Stage 3: summarize
| summarize arg_max(LastUpdatedTimeStamp, *) by SecurityProblemId
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DAVISRiskLevel | eq |
| field:"DAVISRiskLevel" kind:eq value:"CRITICAL" |
Muted | eq |
| field:"Muted" kind:eq value:"false" |
VulnerabilityType | eq |
| field:"VulnerabilityType" kind:eq value:"CODE_LEVEL" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
SecurityProblemId | summarize |
Dynatrace Application Security - Third-Party runtime vulnerability detection
#'Detect Third-Party runtime vulnerabilities in your environment insights by snyk'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Execution | |
| Persistence | |
| Privilege Escalation | |
| Stealth | |
| Lateral Movement | |
| Impact |
Rule Body
id: af99b078-124b-543a-9a50-66ef87c09f6a
name: Dynatrace Application Security - Third-Party runtime vulnerability detection
description: |
'Detect Third-Party runtime vulnerabilities in your environment insights by snyk'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: DynatraceRuntimeVulnerabilities
dataTypes:
- DynatraceSecurityProblems
- connectorId: DTRunVulnCCPDefinition
dataTypes:
- DynatraceSecurityProblemsV2_CL
- connectorId: DTRunVulnV3CCPDefinition
dataTypes:
- DynatraceSecurityProblemsV3_CL
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
- Execution
- Impact
- InitialAccess
- LateralMovement
- Persistence
- PrivilegeEscalation
relevantTechniques:
- T1140
- T1059
- T1565
- T1659
- T1210
- T1554
- T1548
eventGroupingSettings:
aggregationKind: AlertPerResult
query: |
DynatraceSecurityProblems
| where VulnerabilityType != "CODE_LEVEL" and DAVISRiskLevel == "CRITICAL" and Muted == false
| summarize arg_max(LastUpdatedTimeStamp, *) by SecurityProblemId
entityMappings:
- entityType: URL
fieldMappings:
- identifier: Url
columnName: Url
alertDetailsOverride:
alertDisplayNameFormat: 'Dynatrace Third-party runtime vulnerability detected - {{DisplayId}} : {{Title}}'
alertDescriptionFormat: |
Third-party runtime vulnerability ({{ExternalVulnerabilityId}}) detected in package ({{PackageName}}), more details available from Dynatrace at {{Url}}.
alertSeverityColumnName: Severity
customDetails:
SecProbIdentifier: SecurityProblemId
DisplayIdentifier: DisplayId
SecurityProblemUrl: Url
DAVISRiskLevel: DAVISRiskLevel
PackageName: PackageName
Technology: Technology
VulnerabilityType: VulnerabilityType
CVEIds: CVEIds
ExternVulnIdentifier: ExternalVulnerabilityId
DAVISRiskScore: DAVISRiskScore
DAVISRiskVector: DAVISRiskVector
DAVISExposure: DAVISExposure
DAVISPublicExploit: DAVISPublicExploit
DAVISDataAssets: DAVISDataAssets
DAVISVulnFuncUsage: DAVISVulnerableFunctionUsage
incidentConfiguration:
createIncident: false
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
version: 1.0.4
kind: Scheduled
Stages and Predicates
Stage 1: source
DynatraceSecurityProblems
Stage 2: where
| where VulnerabilityType != "CODE_LEVEL" and DAVISRiskLevel == "CRITICAL" and Muted == false
Stage 3: summarize
| summarize arg_max(LastUpdatedTimeStamp, *) by SecurityProblemId
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DAVISRiskLevel | eq |
| field:"DAVISRiskLevel" kind:eq value:"CRITICAL" |
Muted | eq |
| field:"Muted" kind:eq value:"false" |
VulnerabilityType | ne |
| field:"VulnerabilityType" kind:ne value:"CODE_LEVEL" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
SecurityProblemId | summarize |
External User Access Enabled
#'This alerts when the account setting is changed to allow either external domain access or anonymous access to meetings.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| Credential Access |
Rule Body
id: 8e267e91-6bda-4b3c-bf68-9f5cbdd103a3
name: External User Access Enabled
description: |
'This alerts when the account setting is changed to allow either external domain access or anonymous access to meetings.'
severity: Low
requiredDataConnectors: []
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
- Persistence
relevantTechniques:
- T1098
- T1556
query: |
ZoomLogs
| where Event =~ "account.settings_updated"
| extend EnforceLogin = columnifexists("payload_object_settings_schedule_meeting_enfore_login_b", "")
| extend EnforceLoginDomain = columnifexists("payload_object_settings_schedule_meeting_enfore_login_b", "")
| extend GuestAlerts = columnifexists("payload_object_settings_in_meeting_alert_guest_join_b", "")
| where EnforceLogin == 'false' or EnforceLoginDomain == 'false' or GuestAlerts == 'false'
| extend SettingChanged = case(EnforceLogin == 'false' and EnforceLoginDomain == 'false' and GuestAlerts == 'false', "All settings changed",
EnforceLogin == 'false' and EnforceLoginDomain == 'false', "Enforced Logons and Restricted Domains Changed",
EnforceLoginDomain == 'false' and GuestAlerts == 'false', "Enforced Domains Changed",
EnforceLoginDomain == 'false', "Enfored Domains Changed",
GuestAlerts == 'false', "Guest Join Alerts Changed",
EnforceLogin == 'false', "Enforced Logins Changed",
"No Changes")
| extend AccountName = tostring(split(User, "@")[0]), AccountUPNSuffix = tostring(split(User, "@")[1])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: User
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: AccountUPNSuffix
version: 1.0.4
kind: Scheduled
metadata:
source:
kind: Community
author:
name: Microsoft Security Research
support:
tier: Community
categories:
domains: [ "Security - Others", "Identity" ]
Stages and Predicates
Stage 1: source
ZoomLogs
Stage 2: where
| where Event =~ "account.settings_updated"
Stage 3: extend (3 consecutive steps)
| extend EnforceLogin = columnifexists("payload_object_settings_schedule_meeting_enfore_login_b", "")
| extend EnforceLoginDomain = columnifexists("payload_object_settings_schedule_meeting_enfore_login_b", "")
| extend GuestAlerts = columnifexists("payload_object_settings_in_meeting_alert_guest_join_b", "")
Stage 4: where
| where EnforceLogin == 'false' or EnforceLoginDomain == 'false' or GuestAlerts == 'false'
Stage 5: extend
| extend SettingChanged = case(EnforceLogin == 'false' and EnforceLoginDomain == 'false' and GuestAlerts == 'false', "All settings changed",
EnforceLogin == 'false' and EnforceLoginDomain == 'false', "Enforced Logons and Restricted Domains Changed",
EnforceLoginDomain == 'false' and GuestAlerts == 'false', "Enforced Domains Changed",
EnforceLoginDomain == 'false', "Enfored Domains Changed",
GuestAlerts == 'false', "Guest Join Alerts Changed",
EnforceLogin == 'false', "Enforced Logins Changed",
"No Changes")
SettingChanged =(EnforceLogin == false and EnforceLoginDomain == false) and GuestAlerts == false"All settings changed"EnforceLogin == false and EnforceLoginDomain == false"Enforced Logons and Restricted Domains Changed"EnforceLoginDomain == false and GuestAlerts == false"Enforced Domains Changed"EnforceLoginDomain == false"Enfored Domains Changed"GuestAlerts == false"Guest Join Alerts Changed"EnforceLogin == false"Enforced Logins Changed""No Changes"Stage 6: extend
| extend AccountName = tostring(split(User, "@")[0]), AccountUPNSuffix = tostring(split(User, "@")[1])
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EnforceLogin | eq |
| field:"EnforceLogin" kind:eq value:"false" |
EnforceLoginDomain | eq |
| field:"EnforceLoginDomain" kind:eq value:"false" |
Event | eq |
| field:"Event" kind:eq value:"account.settings_updated" |
GuestAlerts | eq |
| field:"GuestAlerts" kind:eq value:"false" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
EnforceLogin | extend |
EnforceLoginDomain | extend |
GuestAlerts | extend |
SettingChanged | extend |
AccountName | extend |
AccountUPNSuffix | extend |
Guardian- Gender Bias Policy Violation Detection
#'This alert creates an incident when Gender Bias Policy Violation detected from the Guardian.'
Rule Body
id: cae24b9e-a614-4213-b382-00698e89b037
name: Guardian- Gender Bias Policy Violation Detection
description: |
'This alert creates an incident when Gender Bias Policy Violation detected from the Guardian.'
severity: Low
status: Available
requiredDataConnectors:
- connectorId: BoschAIShield
dataTypes:
- Guardian
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: |
Guardian
| where PolicyViolatedControlFeature =~ 'Gender Bias'
| where Severity =~ 'Low'
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: HostName
- identifier: NTDomain
columnName: NTDomain
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIP
eventGroupingSettings:
aggregationKind: SingleAlert
alertDetailsOverride:
alertDisplayNameFormat: Guardian- Gender Bias Policy Violation detection
alertDescriptionFormat: |
This query detects Gender Bias Policy Violation detected from the Guardian generated at {{TimeGenerated}}.\n\nPlease check the source for more information and investigate further.
alertTacticsColumnName: null
alertSeverityColumnName: Severity
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
Guardian
Stage 2: where
| where PolicyViolatedControlFeature =~ 'Gender Bias'
Stage 3: where
| where Severity =~ 'Low'
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
PolicyViolatedControlFeature | eq |
| field:"PolicyViolatedControlFeature" kind:eq value:"Gender Bias" |
Severity | eq |
| field:"Severity" kind:eq value:"Low" |
New access credential added to Application or Service Principal
#This will alert when an admin or app owner account adds a new credential to an Application or Service Principal where a verify KeyCredential was already present for the app. If a threat actor obtains access to an account with sufficient privileges and adds the alternate authentication material triggering this event, the threat actor can now authenticate as the Application or Service Principal using this credential. Additional information on OAuth Credential Grants can be found in RFC 6749 Section 4.4 or https://docs.microsoft.com/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow For further information on AuditLogs please see https://docs.microsoft.com/azure/active-directory/reports-monitoring/reference-audit-activities.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Lateral Movement |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Azure | any: Entra ID audit event (any operation) |
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] Application Granted Administrative Permission to Assign Microsoft Entra ID Roles (Kusto)
- Added Credentials to Existing Application (Sigma)
- Added Owner To Application (Sigma)
- Admin promotion after Role Management Application Permission Grant (Kusto)
- App Granted Microsoft Permissions (Sigma)
- App Granted Privileged Delegated Or App Permissions (Sigma)
- Application AppID Uri Configuration Changes (Sigma)
Rule Body
id: 79566f41-df67-4e10-a703-c38a6213afd8
name: New access credential added to Application or Service Principal
description: |
'This will alert when an admin or app owner account adds a new credential to an Application or Service Principal where a verify KeyCredential was already present for the app.
If a threat actor obtains access to an account with sufficient privileges and adds the alternate authentication material triggering this event, the threat actor can now authenticate as the Application or Service Principal using this credential.
Additional information on OAuth Credential Grants can be found in RFC 6749 Section 4.4 or https://docs.microsoft.com/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow
For further information on AuditLogs please see https://docs.microsoft.com/azure/active-directory/reports-monitoring/reference-audit-activities.'
severity: Medium
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- AuditLogs
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- DefenseEvasion
relevantTechniques:
- T1550.001
tags:
- Solorigate
- NOBELIUM
query: |
AuditLogs
| where OperationName has_any ("Add service principal", "Certificates and secrets management") // captures "Add service principal", "Add service principal credentials", and "Update application - Certificates and secrets management" events
| where Result =~ "success"
| where tostring(InitiatedBy.user.userPrincipalName) has "@" or tostring(InitiatedBy.app.displayName) has "@"
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type =~ "Application"
| extend targetDisplayName = tostring(TargetResource.displayName),
targetId = tostring(TargetResource.id),
targetType = tostring(TargetResource.type),
keyEvents = TargetResource.modifiedProperties
)
| mv-apply Property = keyEvents on
(
where Property.displayName =~ "KeyDescription"
| extend new_value_set = parse_json(tostring(Property.newValue)),
old_value_set = parse_json(tostring(Property.oldValue))
)
| where old_value_set != "[]"
| extend diff = set_difference(new_value_set, old_value_set)
| where isnotempty(diff)
| parse diff with * "KeyIdentifier=" keyIdentifier:string ",KeyType=" keyType:string ",KeyUsage=" keyUsage:string ",DisplayName=" keyDisplayName:string "]" *
| where keyUsage =~ "Verify"
| mv-apply AdditionalDetail = AdditionalDetails on
(
where AdditionalDetail.key =~ "User-Agent"
| extend UserAgent = tostring(AdditionalDetail.value)
)
| extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
| extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
| extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend InitiatingIpAddress = tostring(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))
// The below line is currently commented out but Microsoft Sentinel users can modify this query to show only Application or only Service Principal events in their environment
//| where targetType =~ "Application" // or targetType =~ "ServicePrincipal"
| project-away diff, new_value_set, old_value_set
| project-reorder TimeGenerated, OperationName, InitiatingAppName, InitiatingAppServicePrincipalId, InitiatingUserPrincipalName, InitiatingAadUserId, InitiatingIpAddress, UserAgent, targetDisplayName, targetId, targetType, keyDisplayName, keyType, keyUsage, keyIdentifier, CorrelationId, TenantId
| extend Name = tostring(split(InitiatingUserPrincipalName,'@',0)[0]), UPNSuffix = tostring(split(InitiatingUserPrincipalName,'@',1)[0])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: InitiatingUserPrincipalName
- identifier: Name
columnName: Name
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: InitiatingIpAddress
- entityType: Account
fieldMappings:
- identifier: AadUserId
columnName: InitiatingAadUserId
- entityType: Account
fieldMappings:
- identifier: AadUserId
columnName: InitiatingAppServicePrincipalId
version: 1.1.2
kind: Scheduled
Stages and Predicates
Stage 1: source
AuditLogs
Stage 2: where
| where OperationName has_any ("Add service principal", "Certificates and secrets management")
Stage 3: where
| where Result =~ "success"
Stage 4: where
| where tostring(InitiatedBy.user.userPrincipalName) has "@" or tostring(InitiatedBy.app.displayName) has "@"
Stage 5: kusto:mv-apply
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type =~ "Application"
| extend targetDisplayName = tostring(TargetResource.displayName),
targetId = tostring(TargetResource.id),
targetType = tostring(TargetResource.type),
keyEvents = TargetResource.modifiedProperties
)
Stage 6: kusto:mv-apply
| mv-apply Property = keyEvents on
(
where Property.displayName =~ "KeyDescription"
| extend new_value_set = parse_json(tostring(Property.newValue)),
old_value_set = parse_json(tostring(Property.oldValue))
)
Stage 7: where
| where old_value_set != "[]"
Stage 8: extend
| extend diff = set_difference(new_value_set, old_value_set)
Stage 9: where
| where isnotempty(diff)
Stage 10: parse
| parse diff with * "KeyIdentifier=" keyIdentifier:string ",KeyType=" keyType:string ",KeyUsage=" keyUsage:string ",DisplayName=" keyDisplayName:string "]" *
Stage 11: where
| where keyUsage =~ "Verify"
Stage 12: kusto:mv-apply
| mv-apply AdditionalDetail = AdditionalDetails on
(
where AdditionalDetail.key =~ "User-Agent"
| extend UserAgent = tostring(AdditionalDetail.value)
)
Stage 13: extend (5 consecutive steps)
| extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
| extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
| extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend InitiatingIpAddress = tostring(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))
Stage 14: project-away
| project-away diff, new_value_set, old_value_set
Stage 15: project-reorder
| project-reorder TimeGenerated, OperationName, InitiatingAppName, InitiatingAppServicePrincipalId, InitiatingUserPrincipalName, InitiatingAadUserId, InitiatingIpAddress, UserAgent, targetDisplayName, targetId, targetType, keyDisplayName, keyType, keyUsage, keyIdentifier, CorrelationId, TenantId
Stage 16: extend
| extend Name = tostring(split(InitiatingUserPrincipalName,'@',0)[0]), UPNSuffix = tostring(split(InitiatingUserPrincipalName,'@',1)[0])
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AdditionalDetail.key | eq |
| field:"AdditionalDetail.key" kind:eq value:"User-Agent" |
InitiatedBy.app.displayName | match |
| field:"InitiatedBy.app.displayName" kind:match value:"@" |
InitiatedBy.user.userPrincipalName | match |
| field:"InitiatedBy.user.userPrincipalName" kind:match value:"@" |
OperationName | match |
| field:"OperationName" kind:match |
Property.displayName | eq |
| field:"Property.displayName" kind:eq value:"KeyDescription" |
Result | eq |
| field:"Result" kind:eq value:"success" |
TargetResource.type | eq |
| field:"TargetResource.type" kind:eq value:"Application" |
diff | is_not_null | field:"diff" kind:is_not_null | |
keyUsage | eq |
| field:"keyUsage" kind:eq value:"Verify" |
old_value_set | ne |
| field:"old_value_set" kind:ne value:"[]" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
InitiatingAppName | extend |
InitiatingAppServicePrincipalId | extend |
InitiatingUserPrincipalName | extend |
InitiatingAadUserId | extend |
InitiatingIpAddress | extend |
Name | extend |
UPNSuffix | extend |
Pathlock TDnR - CUA Settings Changes
#Detects changes to SAP Central User Administration (CUA) settings, forwarded by Pathlock Threat Detection and Response. Unauthorized CUA modifications may affect centralized user management across connected SAP systems and could be used to establish persistent access.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence |
Rule Body
id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c17
name: Pathlock TDnR - CUA Settings Changes
kind: Scheduled
description: >-
Detects changes to SAP Central User Administration (CUA) settings, forwarded by Pathlock Threat
Detection and Response. Unauthorized CUA modifications may affect centralized user management
across connected SAP systems and could be used to establish persistent access.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: Pathlock_TDnR
dataTypes:
- Pathlock_TDnR_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
relevantTechniques:
- T1098
query: |
Pathlock_TDnR_CL
| where DataSource == "CHANGEDOC_USER_CUA"
| project TimeGenerated, Sysid, DataSource, Eventid, Instance, Hostname, Bname,
Tcode, Report, Area, Subid, SrcIp, DestIp, AffectedUser, LogLine,
MsgType, MsgId, MsgNo, MessageV1, MessageV2, MessageV3, MessageV4, CentralTs
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: Bname
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: Hostname
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIp
eventGroupingSettings:
aggregationKind: SingleAlert
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 5h
matchingMethod: AnyAlert
groupByEntities: []
groupByAlertDetails: []
groupByCustomDetails: []
suppressionDuration: 5h
suppressionEnabled: false
version: 1.0.0
Stages and Predicates
Stage 1: source
Pathlock_TDnR_CL
Stage 2: where
| where DataSource == "CHANGEDOC_USER_CUA"
Stage 3: project
| project TimeGenerated, Sysid, DataSource, Eventid, Instance, Hostname, Bname,
Tcode, Report, Area, Subid, SrcIp, DestIp, AffectedUser, LogLine,
MsgType, MsgId, MsgNo, MessageV1, MessageV2, MessageV3, MessageV4, CentralTs
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DataSource | eq |
| field:"DataSource" kind:eq value:"CHANGEDOC_USER_CUA" |
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 |
Possible AiTM Phishing Attempt Against Microsoft Entra ID
#Threat actors may attempt to phish users in order to hijack a users sign-in session, and skip the authentication process even if the user had enabled multifactor authentication (MFA) by stealing and replaying stolen credentials and session cookies. This detection looks for successful Microsoft Entra ID sign ins that had a high risk profile, indicating it had suspicious characteristics such as an unusual location, ISP, user agent, or use of anonymizer services. It then looks for a network connection to the IP address that made the sign in immediately before the sign in, that may indicate a user connecting to a phishing site at that IP address and having their authentication session hijacked. Ref: https://www.microsoft.com/security/blog/2022/07/12/from-cookie-theft-to-bec-attackers-use-aitm-phishing-sites-as-entry-point-to-further-financial-fraud/
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Stealth | |
| Credential Access |
Telemetry Coverage
Rules Detecting the Same Action
These rules filter on the same operation.
- [Entra ID] Suspicious Continuous OAuth Token Usage (Kusto)
- Anomalous sign-in location by user account and authenticating application (Kusto)
- Anomalous Single Factor Signin (Kusto)
- Authentications of Privileged Accounts Outside of Expected Controls (Kusto)
- Azure Portal sign in from another Azure Tenant (Kusto)
- Azure Service Principal Sign-In Followed by Arc Cluster Credential Access (Elastic)
- Azure SignIn via Legacy Authentication Protocol (Panther)
- Cisco - firewall block but success logon to Microsoft Entra ID (Kusto)
Rule Body
id: 16daa67c-b137-48dc-8eb7-76598a44791a
name: Possible AiTM Phishing Attempt Against Microsoft Entra ID
description: |
'Threat actors may attempt to phish users in order to hijack a users sign-in session, and skip the authentication process even if the user had enabled multifactor authentication (MFA) by stealing and replaying stolen credentials and session cookies.
This detection looks for successful Microsoft Entra ID sign ins that had a high risk profile, indicating it had suspicious characteristics such as an unusual location, ISP, user agent, or use of anonymizer services.
It then looks for a network connection to the IP address that made the sign in immediately before the sign in, that may indicate a user connecting to a phishing site at that IP address and having their authentication session hijacked.
Ref: https://www.microsoft.com/security/blog/2022/07/12/from-cookie-theft-to-bec-attackers-use-aitm-phishing-sites-as-entry-point-to-further-financial-fraud/'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- SigninLogs
- connectorId: Zscaler
dataTypes:
- CommonSecurityLog (Zscaler)
queryFrequency: 1h
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
- DefenseEvasion
- CredentialAccess
relevantTechniques:
- T1078.004
- T1557
- T1111
query: |
let time_threshold = 10m;
let RiskySignins = materialize (SigninLogs
| where TimeGenerated > ago(1d)
| where ResultType == 0
| where RiskLevelDuringSignIn =~ "high" or RiskLevelAggregated =~ "high"
| extend SignInTime = TimeGenerated, Name=split(UserPrincipalName, "@")[0], UPNSuffix=split(UserPrincipalName, "@")[1]);
let ips = todynamic(toscalar(RiskySignins | summarize make_list(IPAddress)));
RiskySignins
| join kind=inner (_Im_WebSession(starttime=ago(1d), ipaddr_has_any_prefix=ips, eventresult="Success", pack=True))
on $left.IPAddress == $right.DstIpAddr
| where EventStartTime < TimeGenerated
| extend TimeDelta = TimeGenerated - EventStartTime
| where TimeDelta <= time_threshold
| extend NetworkEventStartTime = EventStartTime, NetworkEventEndTime = EventEndTime
| extend SrcUsername = column_ifexists("SrcUsername", "Unknown")
| project-reorder SignInTime, UserPrincipalName, IPAddress, AppDisplayName, ClientAppUsed, DeviceDetail, LocationDetails, NetworkLocationDetails, RiskEventTypes, UserAgent, NetworkEventStartTime, NetworkEventEndTime, SrcIpAddr, DstIpAddr, DstPortNumber, Dvc, DvcHostname, SrcBytes, NetworkProtocol, SrcUsername
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: Name
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPAddress
alertDetailsOverride:
alertDisplayNameFormat: Possible AiTM Phishing Attempt Against {{UserPrincipalName}} From {{IPAddress}}
alertDescriptionFormat: |
Threat actors may attempt to phish users in order to hijack a users sign-in session, and skip the authentication process even if the user had enabled multifactor authentication (MFA) by stealing and replaying stolen credentials and session cookies.
This detection looks for successful Microsoft Entra ID sign ins (in this case from {{UserPrincipalName}}) that had a high risk profile, indicating it had suspicious characteristics such as an unusual location, ISP, user agent, or use of anonymizer services.
It then looks for a network connection to the IP address (in this case {{IPAddress}}) that made the sign in immediately before the sign in, that may indicate a user connecting to a phishing site at that IP address and having their authentication session hijacked.
Ref: https://www.microsoft.com/security/blog/2022/07/12/from-cookie-theft-to-bec-attackers-use-aitm-phishing-sites-as-entry-point-to-further-financial-fraud/
version: 1.0.4
kind: Scheduled
Stages and Predicates
Parameters
let time_threshold = 10m;
let RiskySignins is inlined into the numbered stages below.
Let Binding: ips
let ips = todynamic(toscalar(RiskySignins | summarize make_list(IPAddress)));
Stages 1 to 5 define let RiskySignins as the rule's main pipeline source. Stages 6 to 12 use it.
Stage 1: source
SigninLogs
Stage 2: where
| where TimeGenerated > ago(1d)
Stage 3: where
| where ResultType == 0
Stage 4: where
| where RiskLevelDuringSignIn =~ "high" or RiskLevelAggregated =~ "high"
Stage 5: extend
| extend SignInTime = TimeGenerated, Name=split(UserPrincipalName, "@")[0], UPNSuffix=split(UserPrincipalName, "@")[1]
Stage 6: join
RiskySignins
| join kind=inner (_Im_WebSession(starttime=ago(1d), ipaddr_has_any_prefix=ips, eventresult="Success", pack=True))
on $left.IPAddress == $right.DstIpAddr
Stage 7: where
where EventStartTime < TimeGenerated
Stage 8: extend
extend TimeDelta
Stage 9: where
where TimeDelta <= "10m"
Stage 10: extend
extend NetworkEventEndTime, NetworkEventStartTime
Stage 11: extend
extend SrcUsername
Stage 12: project-reorder
project-reorder
Indicators
These rows show field, operator, and value matches.
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Name | extend |
SignInTime | extend |
UPNSuffix | extend |
TimeDelta | extend |
NetworkEventEndTime | extend |
NetworkEventStartTime | extend |
SrcUsername | extend |
Privileged Accounts - Sign in Failure Spikes
#Identifies spike in failed sign-ins from Privileged accounts. Privileged accounts list can be based on IdentityInfo UEBA table. Spike is determined based on Time series anomaly which will look at historical baseline values. Ref : https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-privileged-accounts#things-to-monitor
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Telemetry Coverage
Rule Body
id: 34c5aff9-a8c2-4601-9654-c7e46342d03b
name: Privileged Accounts - Sign in Failure Spikes
description: |
' Identifies spike in failed sign-ins from Privileged accounts. Privileged accounts list can be based on IdentityInfo UEBA table.
Spike is determined based on Time series anomaly which will look at historical baseline values.
Ref : https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-privileged-accounts#things-to-monitor'
severity: High
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- SigninLogs
- connectorId: AzureActiveDirectory
dataTypes:
- AADNonInteractiveUserSignInLogs
- connectorId: BehaviorAnalytics
dataTypes:
- IdentityInfo
queryFrequency: 1d
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- InitialAccess
relevantTechniques:
- T1078.004
tags:
- AADSecOpsGuide
query: |
let starttime = 14d;
let timeframe = 1d;
let scorethreshold = 3;
let baselinethreshold = 5;
let aadFunc = (tableName:string){
IdentityInfo
| where TimeGenerated > ago(starttime)
| summarize arg_max(TimeGenerated, *) by AccountUPN
| mv-expand AssignedRoles
| where AssignedRoles contains 'Admin' or GroupMembership has "Admin"
| summarize Roles = make_list(AssignedRoles) by AccountUPN = tolower(AccountUPN)
| join kind=inner (
table(tableName)
| where TimeGenerated between (startofday(ago(starttime))..startofday(now()))
| where ResultType != 0
| extend UserPrincipalName = tolower(UserPrincipalName)
) on $left.AccountUPN == $right.UserPrincipalName
| extend timestamp = TimeGenerated, AccountCustomEntity = UserPrincipalName, Roles = tostring(Roles)
};
let aadSignin = aadFunc("SigninLogs");
let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");
let allSignins = union isfuzzy=true aadSignin, aadNonInt;
let TimeSeriesAlerts =
allSignins
| make-series HourlyCount=count() on TimeGenerated from startofday(ago(starttime)) to startofday(now()) step 1h by UserPrincipalName, Roles
| extend (anomalies, score, baseline) = series_decompose_anomalies(HourlyCount, scorethreshold, -1, 'linefit')
| mv-expand HourlyCount to typeof(double), TimeGenerated to typeof(datetime), anomalies to typeof(double), score to typeof(double), baseline to typeof(long)
// Filtering low count events per baselinethreshold
| where anomalies > 0 and baseline > baselinethreshold
| extend AnomalyHour = TimeGenerated
| project UserPrincipalName, Roles, AnomalyHour, TimeGenerated, HourlyCount, baseline, anomalies, score;
// Filter the alerts for specified timeframe
TimeSeriesAlerts
| where TimeGenerated > startofday(ago(timeframe))
| join kind=inner (
allSignins
| where TimeGenerated > startofday(ago(timeframe))
// create a new column and round to hour
| extend DateHour = bin(TimeGenerated, 1h)
| summarize PartialFailedSignins = count(), LatestAnomalyTime = arg_max(TimeGenerated, *) by bin(TimeGenerated, 1h), OperationName, Category, ResultType, ResultDescription, UserPrincipalName, Roles, UserDisplayName, AppDisplayName, ClientAppUsed, IPAddress, ResourceDisplayName
) on UserPrincipalName, $left.AnomalyHour == $right.DateHour
| project LatestAnomalyTime, OperationName, Category, UserPrincipalName, Roles = todynamic(Roles), UserDisplayName, ResultType, ResultDescription, AppDisplayName, ClientAppUsed, UserAgent, IPAddress, Location, AuthenticationRequirement, ConditionalAccessStatus, ResourceDisplayName, PartialFailedSignins, TotalFailedSignins = HourlyCount, baseline, anomalies, score
| extend Name = tostring(split(UserPrincipalName,'@',0)[0]), UPNSuffix = tostring(split(UserPrincipalName,'@',1)[0])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: UserPrincipalName
- identifier: Name
columnName: Name
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPAddress
version: 1.1.1
kind: Scheduled
Stages and Predicates
Parameters
let starttime = 14d;
let timeframe = 1d;
let scorethreshold = 3;
let baselinethreshold = 5;
let aadSignin = aadFunc("SigninLogs");
let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");
let allSignins and let TimeSeriesAlerts are inlined into the numbered stages below.
Let Binding: aadFunc
let aadFunc = (tableName:string){
IdentityInfo
| where TimeGenerated > ago(starttime)
| summarize arg_max(TimeGenerated, *) by AccountUPN
| mv-expand AssignedRoles
| where AssignedRoles contains 'Admin' or GroupMembership has "Admin"
| summarize Roles = make_list(AssignedRoles) by AccountUPN = tolower(AccountUPN)
| join kind=inner (
table(tableName)
| where TimeGenerated between (startofday(ago(starttime))..startofday(now()))
| where ResultType != 0
| extend UserPrincipalName = tolower(UserPrincipalName)
) on $left.AccountUPN == $right.UserPrincipalName
| extend timestamp = TimeGenerated, AccountCustomEntity = UserPrincipalName, Roles = tostring(Roles)
};
Stage 1: source
let aadSignin
Stage 2: source
let aadNonInt
Stage 3: source
let allSignins
Stage 4: source
let TimeSeriesAlerts
Stage 5: union
union of 2 branches
Stage 6: source
IdentityInfo
Stage 7: where
where TimeGenerated > ago(1209600s)
Stage 8: summarize
summarize by AccountUPN
Stage 9: mv-expand
mv-expand AssignedRoles
Stage 10: where
where (AssignedRoles contains "Admin" or GroupMembership has "Admin")
Stage 11: summarize
summarize AccountUPN, Roles by AccountUPN
Stage 12: join
join kind=inner (...) on $left.AccountUPN, $right.UserPrincipalName
Stage 13: extend
extend AccountCustomEntity, Roles, timestamp
Stage 14: source
IdentityInfo
Stage 15: where
where TimeGenerated > ago(1209600s)
Stage 16: summarize
summarize by AccountUPN
Stage 17: mv-expand
mv-expand AssignedRoles
Stage 18: where
where (AssignedRoles contains "Admin" or GroupMembership has "Admin")
Stage 19: summarize
summarize AccountUPN, Roles by AccountUPN
Stage 20: join
join kind=inner (...) on $left.AccountUPN, $right.UserPrincipalName
Stage 21: extend
extend AccountCustomEntity, Roles, timestamp
Stage 22: summarize
summarize HourlyCount by UserPrincipalName, Roles
Stage 23: extend
extend anomalies, baseline, score
Stage 24: mv-expand
mv-expand HourlyCount
Stage 25: where
where anomalies > 0 and baseline > 5
Stage 26: extend
extend AnomalyHour
Stage 27: project
project AnomalyHour, HourlyCount, Roles, TimeGenerated, UserPrincipalName, anomalies, baseline, score
Stage 28: where
where TimeGenerated > ago(86400s)
Stage 29: join
join kind=inner (allSignins) on UserPrincipalName, $left.AnomalyHour, $right.DateHour
Stage 30: project
project AppDisplayName, AuthenticationRequirement, Category, ClientAppUsed, ConditionalAccessStatus, IPAddress, LatestAnomalyTime, Location, OperationName, PartialFailedSignins, ResourceDisplayName, ResultDescription, ResultType, Roles, TotalFailedSignins, UserAgent, UserDisplayName, UserPrincipalName, anomalies, baseline, score
Stage 31: extend
extend Name, UPNSuffix
Indicators
These rows show field, operator, and value matches.
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AppDisplayName | project |
AuthenticationRequirement | project |
Category | project |
ClientAppUsed | project |
ConditionalAccessStatus | project |
IPAddress | project |
LatestAnomalyTime | project |
Location | project |
OperationName | project |
PartialFailedSignins | project |
ResourceDisplayName | project |
ResultDescription | project |
ResultType | project |
Roles | project |
TotalFailedSignins | project |
UserAgent | project |
UserDisplayName | project |
UserPrincipalName | project |
anomalies | project |
baseline | project |
score | project |
Name | extend |
UPNSuffix | extend |
Silk Typhoon Suspicious UM Service Error
#'This query looks for errors that may indicate that an attacker is attempting to exploit a vulnerability in the service. Reference: https://www.microsoft.com/security/blog/2021/03/02/hafnium-targeting-exchange-servers/'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: 0625fcce-6d52-491e-8c68-1d9b801d25b9
name: Silk Typhoon Suspicious UM Service Error
description: |
'This query looks for errors that may indicate that an attacker is attempting to exploit a vulnerability in the service.
Reference: https://www.microsoft.com/security/blog/2021/03/02/hafnium-targeting-exchange-servers/'
severity: Low
requiredDataConnectors: []
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1190
query: |
Event
| where EventLog =~ "Application"
| where Source startswith "MSExchange"
| where EventLevelName =~ "error"
| where (RenderedDescription startswith "Watson report" and RenderedDescription contains "umworkerprocess" and RenderedDescription contains "TextFormattingRunProperties") or RenderedDescription startswith "An unhandled exception occurred in a UM worker process" or RenderedDescription startswith "The Microsoft Exchange Unified Messaging service" or RenderedDescription contains "MSExchange Unified Messaging"
| where RenderedDescription !contains "System.OutOfMemoryException"
| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
| project-away DomainIndex
entityMappings:
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: Computer
- identifier: HostName
columnName: HostName
- identifier: NTDomain
columnName: HostNameDomain
version: 1.0.4
kind: Scheduled
metadata:
source:
kind: Community
author:
name: Microsoft Security Research
support:
tier: Community
categories:
domains: [ "Security - Threat Intelligence" ]
Stages and Predicates
Stage 1: source
Event
Stage 2: where
| where EventLog =~ "Application"
Stage 3: where
| where Source startswith "MSExchange"
Stage 4: where
| where EventLevelName =~ "error"
Stage 5: where
| where (RenderedDescription startswith "Watson report" and RenderedDescription contains "umworkerprocess" and RenderedDescription contains "TextFormattingRunProperties") or RenderedDescription startswith "An unhandled exception occurred in a UM worker process" or RenderedDescription startswith "The Microsoft Exchange Unified Messaging service" or RenderedDescription contains "MSExchange Unified Messaging"
Stage 6: where
| where RenderedDescription !contains "System.OutOfMemoryException"
Stage 7: extend
| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
Stage 8: extend
| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
HostNameDomain =DomainIndex != -1substring(Computer, (DomainIndex + 1))ComputerStage 9: project-away
| project-away DomainIndex
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventLevelName | eq |
| field:"EventLevelName" kind:eq value:"error" |
EventLog | eq |
| field:"EventLog" kind:eq value:"Application" |
RenderedDescription | contains |
| field:"RenderedDescription" kind:contains |
RenderedDescription | starts_with |
| field:"RenderedDescription" kind:starts_with |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
RenderedDescription | contains | System.OutOfMemoryException | excludes:RenderedDescription field:"RenderedDescription" value:"System.OutOfMemoryException" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
HostName | extend |
HostNameDomain | extend |
Silverfort - Log4Shell Incident
#'Vulnerability allows attackers to execute arbitrary code on affected systems by exploiting a flaw in the way Log4j handles log messages containing specially crafted strings'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: d6abed70-4043-46da-9304-a98f3446fa5f
name: Silverfort - Log4Shell Incident
description: |
'Vulnerability allows attackers to execute arbitrary code on affected systems by exploiting a flaw in the way Log4j handles log messages containing specially crafted strings'
severity: High
requiredDataConnectors:
- connectorId: SilverfortAma
dataTypes:
- CommonSecurityLog
tactics:
- InitialAccess
relevantTechniques:
- T1190 #Exploit Public-Facing Application
query: |-
CommonSecurityLog
| where DeviceVendor has 'Silverfort'
| where DeviceProduct has 'Admin Console'
| where DeviceEventClassID == "NewIncident"
| where Message has "Log4Shell"
| extend UserName = parse_json(replace('^""|""$', '', Message))['userName']
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: UserName
queryPeriod: 15m
queryFrequency: 15m
triggerOperator: gt
triggerThreshold: 0
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
CommonSecurityLog
Stage 2: where
| where DeviceVendor has 'Silverfort'
Stage 3: where
| where DeviceProduct has 'Admin Console'
Stage 4: where
| where DeviceEventClassID == "NewIncident"
Stage 5: where
| where Message has "Log4Shell"
Stage 6: extend
| extend UserName = parse_json(replace('^""|""$', '', Message))['userName']
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DeviceEventClassID | eq |
| field:"DeviceEventClassID" kind:eq value:"NewIncident" |
DeviceProduct | match |
| field:"DeviceProduct" kind:match value:"Admin Console" |
DeviceVendor | match |
| field:"DeviceVendor" kind:match value:"Silverfort" |
Message | match |
| field:"Message" kind:match value:"Log4Shell" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
UserName | extend |
Suspicious office child process created
#This query obtains a list of downloaded Office documents (doc, xls, etc.) by looking at files written by commonly used web browsers. It then searches for invocations of an Office program by double-clicking on these files. If these processes spawn an uncommon child process this is reported as suspicious.
Known False Positives
- Some Office plugins might cause false positives if they change the Office program behavior.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Execution |
Telemetry Coverage
Rule Body
let timeframe = 2*1d;
let browsers = dynamic(["iexplore.exe", "chrome.exe", "firefox.exe", "msedge.exe", "msedgewebview2.exe"]);
let ext = dynamic([".docm", ".xlsm", ".xls", ".doc", ".pptm", ".ppt"]);
let officeApps = dynamic(["winword.exe", "excel.exe", "powerpnt.exe"]);
let whitelist = dynamic(["MSOSYNC.exe", "splwow64.exe", "csc.exe", "outlook.exe", "AcroRd32.exe", "Acrobat.exe", "explorer.exe", "DW20.exe",
"Microsoft.Mashup.Container.Loader.exe", "Microsoft.Mashup.Container.NetFX40.exe", "WerFault.exe", "CLVIEW.exe", "wermgr.exe"]);
let whitelistedDomains = dynamic([""]);
let binPeriodForSearch = 1h;
let timeDiffFileCreateNetworkEvent = 15; // In seconds. Don't make this more than 15s because of comparison later on.
// List all filecreate events where the filename has a known Office extension which can contain macros.
let fileDownloads = materialize(
DeviceFileEvents
| where ingestion_time() >= ago(timeframe)
// We need to have FileCreated and FileRenamed here because some browsers first download the file under a different name and rename it when it's done.
// For example, the Chrome .crdownload files are all renamed to the intended name after the download has finished.
| where ActionType in~ ("FileCreated", "FileRenamed") and InitiatingProcessFileName in~ (browsers) and FileName has_any (ext)
// We need to do this to limit the search of deviceNetworkEvents. Otherwise, the dataset becomes too big to join in MDE.
| extend period=bin(Timestamp, binPeriodForSearch)
// Optimizations to keep MDE happy. Otherwise the database gets too big.
| project DeviceId, InitiatingProcessFileName, InitiatingProcessId, period, FileName, Timestamp
// The renames are meant to avoid confusions as there will be a lot of FileNames from different tables.
| project-rename DeviceFileEvents_InitiatingProcessFileName = InitiatingProcessFileName,
DeviceFileEvents_FileName = FileName,
DeviceFileEvents_Timestamp = Timestamp
);
// Now we need to find the network event that triggered the the filewrite.
// This is an approximation based on timestamp, deviceid, pid and process name.
let downloadSource = materialize(
DeviceNetworkEvents
| where ingestion_time() >= ago(timeframe)
| where DeviceId in~ ((fileDownloads | project DeviceId)) and RemotePort in (80, 443)
| extend period=bin(Timestamp, binPeriodForSearch)
// Exclude allow-listed domains. Here you want to allow-list your internal Sharepoint environment.
// This can be useful if you want to use this rule for external attacks.
// There is a trade-off, as an internal attacker might abuse your Sharepoint for malware.
| where parse_url(RemoteUrl).Host !in~ (whitelistedDomains)
| lookup kind=inner fileDownloads on DeviceId, InitiatingProcessId, $left.InitiatingProcessFileName == $right.DeviceFileEvents_InitiatingProcessFileName, period
| extend TimeDiff = datetime_diff('second', Timestamp, DeviceFileEvents_Timestamp)
// The filecreate and network event should happen within max 15 second of each other.
| where -timeDiffFileCreateNetworkEvent < TimeDiff and TimeDiff < timeDiffFileCreateNetworkEvent
// We're now only interested in the unique filenames of the downloads and the location they're possibly downloaded from.
| summarize possibelURLs=make_set(RemoteUrl) by DeviceFileEvents_FileName
);
// Final step in tying everything together.
// Find Office applications that create a child process which is in our previously generated list.
// We don't filter on devicename because we want to see all instances of this file being run.
DeviceProcessEvents
| where ingestion_time() >= ago(timeframe)
| where ActionType =~ "ProcessCreated"
| where InitiatingProcessFileName in~ (officeApps) and FileName !in~ (officeApps) and FileName !in~ (whitelist) and FileName !in~ (browsers)
| project-rename DeviceProcessEvents_InitiatingProcessCommandLine = InitiatingProcessCommandLine
| where DeviceProcessEvents_InitiatingProcessCommandLine has_any (( downloadSource | project DeviceFileEvents_FileName))
| where not(FolderPath matches regex @"C:\\Program Files \(x86\)\\TechSmith\\Camtasia Studio \d+\\TscHelp\.exe")
| where not(ProcessCommandLine matches regex @"^""?rundll32""? C:\\WINDOWS\\system32\\spool\\DRIVERS\\((x64)|(x86))\\\d\\hpmsn\d+.((dll)|(DLL)),")
// Verclsid cannot be filtered as it's a LOLBIN which allows creating arbitrary COM objects.
// We can only filter the execution of very specific COM objects.
// Embedded Outlook item in an Office file.
//00020D0B-0000-0000-C000-000000000046 == Outlook 97-2003 Object. 00000112-0000-0000-C000-000000000046 == IOLEInterface 0x5 == CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER
| where not(ProcessCommandLine matches regex @"^""?verclsid\.exe""? \/S \/C \{00020D0B-0000-0000-C000-000000000046\} \/I \{00000112-0000-0000-C000-000000000046\} \/X 0x5$")
// Embedded Adobe document in Office file.
| where not(ProcessCommandLine matches regex @"^""?verclsid\.exe""? \/S \/C \{B801CA65-A1FC-11D0-85AD-444553540000\} \/I \{00000112-0000-0000-C000-000000000046\} \/X 0x5$")
// Embedded XML file in Office file.
| where not(ProcessCommandLine matches regex @"^""?verclsid\.exe""? \/S \/C \{48123BC4-99D9-11D1-A6B3-00C04FD91555\} \/I \{00000112-0000-0000-C000-000000000046\} \/X 0x5$")
// Bitmap image.
| where not(ProcessCommandLine matches regex @"^""?verclsid\.exe""? \/S \/C \{D3E34B21-9D75-101A-8C3D-00AA001A1652\} \/I \{00000112-0000-0000-C000-000000000046\} \/X 0x5$")
// ZIP folder.
| where not(ProcessCommandLine matches regex @"^""?verclsid\.exe""? \/S \/C \{E88DCCE0-B7B3-11D1-A9F0-00AA0060FA31\} \/I \{00000112-0000-0000-C000-000000000046\} \/X 0x5$")
// HTML document.
| where not(ProcessCommandLine matches regex @"^""?verclsid\.exe""? \/S \/C \{25336920-03F9-11CF-8FD0-00AA00686F13\} \/I \{00000112-0000-0000-C000-000000000046\} \/X 0x5$")
// FileProfile is case-sensitive and works on lower-case hashes.
| extend SHA1=tolower(SHA1)
| invoke FileProfile(SHA1, 1000)
| where not(FolderPath startswith @"C:\Windows\System32\spool\drivers\" and IsCertificateValid and IsRootSignerMicrosoft) // All drivers signed by Microsoft are trusted.
// Begin environment-specific filter.
// End environment-specific filter.
Stages and Predicates
Parameters
let timeframe = 2*1d;
let browsers = dynamic(["iexplore.exe", "chrome.exe", "firefox.exe", "msedge.exe", "msedgewebview2.exe"]);
let ext = dynamic([".docm", ".xlsm", ".xls", ".doc", ".pptm", ".ppt"]);
let officeApps = dynamic(["winword.exe", "excel.exe", "powerpnt.exe"]);
let whitelistedDomains = dynamic([""]);
let binPeriodForSearch = 1h;
let timeDiffFileCreateNetworkEvent = 15;
Let Binding: whitelist
let whitelist = dynamic(["MSOSYNC.exe", "splwow64.exe", "csc.exe", "outlook.exe", "AcroRd32.exe", "Acrobat.exe", "explorer.exe", "DW20.exe",
"Microsoft.Mashup.Container.Loader.exe", "Microsoft.Mashup.Container.NetFX40.exe", "WerFault.exe", "CLVIEW.exe", "wermgr.exe"]);
Let Binding: fileDownloads
let fileDownloads = materialize(
DeviceFileEvents
| where ingestion_time() >= ago(timeframe)
| where ActionType in~ ("FileCreated", "FileRenamed") and InitiatingProcessFileName in~ (browsers) and FileName has_any (ext)
| extend period=bin(Timestamp, binPeriodForSearch)
| project DeviceId, InitiatingProcessFileName, InitiatingProcessId, period, FileName, Timestamp
| project-rename DeviceFileEvents_InitiatingProcessFileName = InitiatingProcessFileName,
DeviceFileEvents_FileName = FileName,
DeviceFileEvents_Timestamp = Timestamp
);
Let Binding: downloadSource
let downloadSource = materialize(
DeviceNetworkEvents
| where ingestion_time() >= ago(timeframe)
| where DeviceId in~ ((fileDownloads | project DeviceId)) and RemotePort in (80, 443)
| extend period=bin(Timestamp, binPeriodForSearch)
| where parse_url(RemoteUrl).Host !in~ (whitelistedDomains)
| lookup kind=inner fileDownloads on DeviceId, InitiatingProcessId, $left.InitiatingProcessFileName == $right.DeviceFileEvents_InitiatingProcessFileName, period
| extend TimeDiff = datetime_diff('second', Timestamp, DeviceFileEvents_Timestamp)
| where -timeDiffFileCreateNetworkEvent < TimeDiff and TimeDiff < timeDiffFileCreateNetworkEvent
| summarize possibelURLs=make_set(RemoteUrl) by DeviceFileEvents_FileName
);
Stage 1: source
let fileDownloads
Stage 2: source
let downloadSource
Stage 3: source
DeviceProcessEvents
Stage 4: where
where ...
Stage 5: where
where ActionType =~ "ProcessCreated"
Stage 6: where
where not (FileName in~ ("AcroRd32.exe", "Acrobat.exe", "CLVIEW.exe", "DW20.exe", "MSOSYNC.exe", "Microsoft.Mashup.Container.Loader.exe", "Microsoft.Mashup.Container.NetFX40.exe", "WerFault.exe", "csc.exe", "explorer.exe", "outlook.exe", "splwow64.exe", "wermgr.exe")) and not (FileName in~ ("chrome.exe", "firefox.exe", "iexplore.exe", "msedge.exe", "msedgewebview2.exe")) and not (FileName in~ ("excel.exe", "powerpnt.exe", "winword.exe")) and InitiatingProcessFileName in~ ("excel.exe", "powerpnt.exe", "winword.exe")
Stage 7: project-rename
project-rename
Stage 8: where
where /* macro: (DeviceProcessEvents_InitiatingProcessCommandLine has_any (downloadSource | project)) */
Stage 9: where
where not (FolderPath matches regex @"C:\\Program Files \(x86\)\\TechSmith\\Camtasia Studio \d+\\TscHelp\.exe")
Stage 10: where
where not (ProcessCommandLine matches regex @"^""?rundll32""? C:\\WINDOWS\\system32\\spool\\DRIVERS\\((x64)|(x86))\\\d\\hpmsn\d+.((dll)|(DLL)),")
Stage 11: where
where not (ProcessCommandLine matches regex @"^""?verclsid\.exe""? \/S \/C \{00020D0B-0000-0000-C000-000000000046\} \/I \{00000112-0000-0000-C000-000000000046\} \/X 0x5$")
Stage 12: where
where not (ProcessCommandLine matches regex @"^""?verclsid\.exe""? \/S \/C \{B801CA65-A1FC-11D0-85AD-444553540000\} \/I \{00000112-0000-0000-C000-000000000046\} \/X 0x5$")
Stage 13: where
where not (ProcessCommandLine matches regex @"^""?verclsid\.exe""? \/S \/C \{48123BC4-99D9-11D1-A6B3-00C04FD91555\} \/I \{00000112-0000-0000-C000-000000000046\} \/X 0x5$")
Stage 14: where
where not (ProcessCommandLine matches regex @"^""?verclsid\.exe""? \/S \/C \{D3E34B21-9D75-101A-8C3D-00AA001A1652\} \/I \{00000112-0000-0000-C000-000000000046\} \/X 0x5$")
Stage 15: where
where not (ProcessCommandLine matches regex @"^""?verclsid\.exe""? \/S \/C \{E88DCCE0-B7B3-11D1-A9F0-00AA0060FA31\} \/I \{00000112-0000-0000-C000-000000000046\} \/X 0x5$")
Stage 16: where
where not (ProcessCommandLine matches regex @"^""?verclsid\.exe""? \/S \/C \{25336920-03F9-11CF-8FD0-00AA00686F13\} \/I \{00000112-0000-0000-C000-000000000046\} \/X 0x5$")
Stage 17: extend
extend SHA1
Stage 18: invoke
invoke
Stage 19: where
where not ((FolderPath startswith @"C:\Windows\System32\spool\drivers\" and IsCertificateValid == true and IsRootSignerMicrosoft == true))
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ActionType | eq |
| field:"ActionType" kind:eq value:"ProcessCreated" |
InitiatingProcessFileName | in |
| field:"InitiatingProcessFileName" kind:in |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
FileName | in | AcroRd32.exe, Acrobat.exe, CLVIEW.exe, DW20.exe, MSOSYNC.exe, Microsoft.Mashup.Container.Loader.exe, Microsoft.Mashup.Container.NetFX40.exe, WerFault.exe, csc.exe, explorer.exe, outlook.exe, splwow64.exe, wermgr.exe | excludes:FileName |
FileName | in | chrome.exe, firefox.exe, iexplore.exe, msedge.exe, msedgewebview2.exe | excludes:FileName |
FileName | in | excel.exe, powerpnt.exe, winword.exe | excludes:FileName field:"FileName" value:"excel.exe" field:"FileName" value:"powerpnt.exe" field:"FileName" value:"winword.exe" |
FolderPath | regex_match | C:\Program Files (x86)\TechSmith\Camtasia Studio \d+\TscHelp.exe | excludes:FolderPath field:"FolderPath" value:"C:\Program Files (x86)\TechSmith\Camtasia Studio \d+\TscHelp.exe" |
ProcessCommandLine | regex_match | ^"?rundll32"? C:\WINDOWS\system32\spool\DRIVERS\((x64)|(x86))\\d\hpmsn\d+.((dll)|(DLL)), | excludes:ProcessCommandLine |
ProcessCommandLine | regex_match | ^"?verclsid.exe"? \/S \/C {00020D0B-0000-0000-C000-000000000046} \/I {00000112-0000-0000-C000-000000000046} \/X 0x5$ | excludes:ProcessCommandLine |
ProcessCommandLine | regex_match | ^"?verclsid.exe"? \/S \/C {B801CA65-A1FC-11D0-85AD-444553540000} \/I {00000112-0000-0000-C000-000000000046} \/X 0x5$ | excludes:ProcessCommandLine |
ProcessCommandLine | regex_match | ^"?verclsid.exe"? \/S \/C {48123BC4-99D9-11D1-A6B3-00C04FD91555} \/I {00000112-0000-0000-C000-000000000046} \/X 0x5$ | excludes:ProcessCommandLine |
ProcessCommandLine | regex_match | ^"?verclsid.exe"? \/S \/C {D3E34B21-9D75-101A-8C3D-00AA001A1652} \/I {00000112-0000-0000-C000-000000000046} \/X 0x5$ | excludes:ProcessCommandLine |
ProcessCommandLine | regex_match | ^"?verclsid.exe"? \/S \/C {E88DCCE0-B7B3-11D1-A9F0-00AA0060FA31} \/I {00000112-0000-0000-C000-000000000046} \/X 0x5$ | excludes:ProcessCommandLine |
ProcessCommandLine | regex_match | ^"?verclsid.exe"? \/S \/C {25336920-03F9-11CF-8FD0-00AA00686F13} \/I {00000112-0000-0000-C000-000000000046} \/X 0x5$ | excludes:ProcessCommandLine |
FolderPath | starts_with | C:\Windows\System32\spool\drivers\ | excludes:FolderPath field:"FolderPath" value:"C:\Windows\System32\spool\drivers\" |
IsCertificateValid | eq | true | excludes:IsCertificateValid field:"IsCertificateValid" value:"true" |
IsRootSignerMicrosoft | eq | true | excludes:IsRootSignerMicrosoft field:"IsRootSignerMicrosoft" value:"true" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DeviceProcessEvents_InitiatingProcessCommandLine | project-rename |
SHA1 | extend |
TI map Domain entity to Syslog
#Identifies a match in Syslog table from any Domain IOC from TI
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control |
Rule Body
id: 532f62c1-fba6-4baa-bbb6-4a32a4ef32fa
name: TI map Domain entity to Syslog
description: |
Identifies a match in Syslog table from any Domain IOC from TI
severity: Medium
requiredDataConnectors:
- connectorId: Syslog
dataTypes:
- Syslog
- connectorId: ThreatIntelligence
dataTypes:
- ThreatIntelligenceIndicator
- connectorId: ThreatIntelligenceTaxii
dataTypes:
- ThreatIntelligenceIndicator
- connectorId: MicrosoftDefenderThreatIntelligence
dataTypes:
- ThreatIntelligenceIndicator
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- CommandAndControl
relevantTechniques:
- T1071
query: |
let dt_lookBack = 1h; // Define the time range to look back for syslog data (1 hour)
let ioc_lookBack = 14d; // Define the time range to look back for threat intelligence indicators (14 days)
// Create a list of top-level domains (TLDs) from the threat feed for later validation
let list_tlds = ThreatIntelligenceIndicator
| where isnotempty(DomainName)
| where TimeGenerated > ago(ioc_lookBack)
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
| where Active == true and ExpirationDateTime > now()
| extend parts = split(DomainName, '.')
| extend tld = parts[(array_length(parts)-1)]
| summarize count() by tostring(tld)
| summarize make_list(tld);
// Fetch the latest active domain indicators from the threat intelligence data within the specified time range
let Domain_Indicators = ThreatIntelligenceIndicator
| where isnotempty(DomainName)
| where TimeGenerated >= ago(ioc_lookBack)
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
| where Active == true and ExpirationDateTime > now()
| extend TI_DomainEntity = DomainName;
// Join the threat intelligence indicators with syslog data on matching domain entities
Domain_Indicators
| join kind=innerunique (
Syslog
| where TimeGenerated > ago(dt_lookBack)
// Extract domain patterns from syslog messages
| extend domain = extract("(([a-z0-9]+(-[a-z0-9]+)*\\.)+[a-z]{2,})",1, tolower(SyslogMessage))
| where isnotempty(domain)
| extend parts = split(domain, '.')
// Split out the top-level domain (TLD)
| extend tld = parts[(array_length(parts)-1)]
// Validate parsed domain by checking if the TLD is in the list of TLDs in our threat feed
| where tld in~ (list_tlds)
| extend Syslog_TimeGenerated = TimeGenerated
) on $left.TI_DomainEntity==$right.domain
| where Syslog_TimeGenerated < ExpirationDateTime
// Retrieve the latest syslog timestamp for each indicator and domain combination
| summarize Syslog_TimeGenerated = arg_max(Syslog_TimeGenerated, *) by IndicatorId, domain
// Select the desired columns for the final result set
| project Syslog_TimeGenerated, Description, ActivityGroupNames, IndicatorId, ThreatType, ExpirationDateTime, ConfidenceScore, SyslogMessage, Computer, ProcessName, domain, HostIP, Url, Type, TI_DomainEntity
// Extract the hostname from the Computer field
| extend HostName = tostring(split(Computer, '.', 0)[0])
// Extract the DNS domain from the Computer field
| extend DnsDomain = tostring(strcat_array(array_slice(split(Computer, '.'), 1, -1), '.'))
// Assign the Syslog_TimeGenerated value to the timestamp field
| extend timestamp = Syslog_TimeGenerated
entityMappings:
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: Computer
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: DnsDomain
- entityType: IP
fieldMappings:
- identifier: Address
columnName: HostIP
- entityType: URL
fieldMappings:
- identifier: Url
columnName: Url
version: 1.4.3
kind: Scheduled
Stages and Predicates
Parameters
let dt_lookBack = 1h;
let ioc_lookBack = 14d;
let Domain_Indicators is inlined into the numbered stages below.
Let Binding: list_tlds
let list_tlds = ThreatIntelligenceIndicator
| where isnotempty(DomainName)
| where TimeGenerated > ago(ioc_lookBack)
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
| where Active == true and ExpirationDateTime > now()
| extend parts = split(DomainName, '.')
| extend tld = parts[(array_length(parts)-1)]
| summarize count() by tostring(tld)
| summarize make_list(tld);
Stages 1 to 6 define let Domain_Indicators as the rule's main pipeline source. Stages 7 to 11 use it.
Stage 1: source
ThreatIntelligenceIndicator
Stage 2: where
| where isnotempty(DomainName)
Stage 3: where
| where TimeGenerated >= ago(ioc_lookBack)
Stage 4: summarize
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
Stage 5: where
| where Active == true and ExpirationDateTime > now()
Stage 6: extend
| extend TI_DomainEntity = DomainName
Stage 7: join
Domain_Indicators
| join kind=innerunique (
Syslog
| where TimeGenerated > ago(dt_lookBack)
| extend domain = extract("(([a-z0-9]+(-[a-z0-9]+)*\\.)+[a-z]{2,})",1, tolower(SyslogMessage))
| where isnotempty(domain)
| extend parts = split(domain, '.')
| extend tld = parts[(array_length(parts)-1)]
| where tld in~ (list_tlds)
| extend Syslog_TimeGenerated = TimeGenerated
) on $left.TI_DomainEntity==$right.domain
Stage 8: where
where Syslog_TimeGenerated < ExpirationDateTime
Stage 9: summarize
summarize Syslog_TimeGenerated by IndicatorId, domain
Stage 10: project
project ActivityGroupNames, Computer, ConfidenceScore, Description, ExpirationDateTime, HostIP, IndicatorId, ProcessName, SyslogMessage, Syslog_TimeGenerated, TI_DomainEntity, ThreatType, Type, Url, domain
Stage 11: extend (3 consecutive steps)
extend DnsDomain, HostName, timestamp
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Active | eq |
| field:"Active" kind:eq value:"true" |
DomainName | is_not_null | field:"DomainName" kind:is_not_null | |
domain | is_not_null | field:"domain" kind:is_not_null | |
tld | in |
| field:"tld" kind:in value:"list_tlds" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActivityGroupNames | project |
Computer | project |
ConfidenceScore | project |
Description | project |
ExpirationDateTime | project |
HostIP | project |
IndicatorId | project |
ProcessName | project |
SyslogMessage | project |
Syslog_TimeGenerated | project |
TI_DomainEntity | project |
ThreatType | project |
Type | project |
Url | project |
domain | project |
HostName | extend |
DnsDomain | extend |
timestamp | extend |
UniFi Site Manager: Multiple Devices Offline
#Identifies when multiple UniFi devices go offline simultaneously, typically signaling a power outage, upstream connectivity failure, or infrastructure issue requiring investigation.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Impact |
Rule Body
id: 9283b576-5350-fca1-3979-dacb6acd1d16
name: "UniFi Site Manager: Multiple Devices Offline"
description: |
Identifies when multiple UniFi devices go offline simultaneously, typically signaling a power outage, upstream connectivity failure, or infrastructure issue requiring investigation.
severity: High
status: Available
requiredDataConnectors:
- connectorId: UniFiSiteManagerConnectorDefinition
dataTypes:
- Unifi_SiteManager_Devices_CL
queryFrequency: 15m
queryPeriod: 30m
triggerOperator: gt
triggerThreshold: 0
tactics:
- Impact
relevantTechniques:
- T1489
- T1499
subTechniques:
- T1499.002
query: |
let prev_offline_ids = Unifi_SiteManager_Devices_CL
| where TimeGenerated between (ago(45m) .. ago(15m))
| summarize arg_max(TimeGenerated, *) by Id
| where Status == "offline"
| distinct Id;
Unifi_SiteManager_Devices_CL
| where TimeGenerated > ago(15m)
| summarize arg_max(TimeGenerated, *) by Id
| where Status == "offline"
| extend IsNew = Id !in (prev_offline_ids)
| summarize
OfflineDeviceCount = count(),
NewlyOfflineCount = countif(IsNew),
OfflineDevices = make_list(coalesce(Name, Id)),
NewlyOfflineDevices = make_list_if(coalesce(Name, Id), IsNew),
ProductLines = make_set(ProductLine)
| where OfflineDeviceCount >= 3
| where NewlyOfflineCount > 0
| extend
TimeGenerated = now(),
OfflineDeviceList = strcat_array(OfflineDevices, ", "),
NewlyOfflineList = strcat_array(NewlyOfflineDevices, ", "),
AffectedProductLines = strcat_array(ProductLines, ", ")
| extend Activity = strcat(NewlyOfflineCount, ' device(s) newly offline (', OfflineDeviceCount, ' total offline): ', NewlyOfflineList)
| project
TimeGenerated,
OfflineDeviceCount,
NewlyOfflineCount,
OfflineDeviceList,
NewlyOfflineList,
AffectedProductLines,
Activity
entityMappings:
- entityType: CloudApplication
fieldMappings:
- identifier: Name
columnName: AffectedProductLines
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: true
lookbackDuration: PT4H
matchingMethod: AllEntities
kind: Scheduled
version: 1.0.3
Stages and Predicates
Let Binding: prev_offline_ids
let prev_offline_ids = Unifi_SiteManager_Devices_CL
| where TimeGenerated between (ago(45m) .. ago(15m))
| summarize arg_max(TimeGenerated, *) by Id
| where Status == "offline"
| distinct Id;
Stage 1: source
Unifi_SiteManager_Devices_CL
Stage 2: where
| where TimeGenerated > ago(15m)
Stage 3: summarize
| summarize arg_max(TimeGenerated, *) by Id
Stage 4: where
| where Status == "offline"
Stage 5: extend
| extend IsNew = Id !in (prev_offline_ids)
Stage 6: summarize
| summarize
OfflineDeviceCount = count(),
NewlyOfflineCount = countif(IsNew),
OfflineDevices = make_list(coalesce(Name, Id)),
NewlyOfflineDevices = make_list_if(coalesce(Name, Id), IsNew),
ProductLines = make_set(ProductLine)
Stage 7: where
| where OfflineDeviceCount >= 3
Stage 8: where
| where NewlyOfflineCount > 0
Stage 9: extend
| extend
TimeGenerated = now(),
OfflineDeviceList = strcat_array(OfflineDevices, ", "),
NewlyOfflineList = strcat_array(NewlyOfflineDevices, ", "),
AffectedProductLines = strcat_array(ProductLines, ", ")
Stage 10: extend
| extend Activity = strcat(NewlyOfflineCount, ' device(s) newly offline (', OfflineDeviceCount, ' total offline): ', NewlyOfflineList)
Stage 11: project
| project
TimeGenerated,
OfflineDeviceCount,
NewlyOfflineCount,
OfflineDeviceList,
NewlyOfflineList,
AffectedProductLines,
Activity
Indicators
These rows show field, operator, and value matches.
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Activity | project |
AffectedProductLines | project |
NewlyOfflineCount | project |
NewlyOfflineList | project |
OfflineDeviceCount | project |
OfflineDeviceList | project |
TimeGenerated | project |