Detection rules › Kusto
Kusto rules: 1x
Account Creation
#User accounts may be created to achieve persistence on a machine. Read more here: https://attack.mitre.org/wiki/Technique/T1136. Tags: #CreateAccount. Query #1: Query for users being created using "net user" command. "net user" commands are noisy, so needs to be joined with another signal -. E.g. in this example we look for use of uncommon & undocumented commandline switches (e.g. /ad instead of /add).
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Microsoft Defender for Endpoint | DeviceProcessEvents action any: Process activity |
Rule Body
id: 450f4e56-5bba-4070-b9d9-9204ba9d777d
name: Account Creation
description: |
User accounts may be created to achieve persistence on a machine.
Read more here: https://attack.mitre.org/wiki/Technique/T1136.
Tags: #CreateAccount.
Query #1: Query for users being created using "net user" command.
"net user" commands are noisy, so needs to be joined with another signal -.
E.g. in this example we look for use of uncommon & undocumented commandline switches (e.g. /ad instead of /add).
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: MicrosoftThreatProtection
dataTypes:
- DeviceProcessEvents
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
relevantTechniques:
- T1136
query: |
DeviceProcessEvents
// Pro-tip:
// There are many different ways to run a process from a file - e.g. by using full path, env. variables, ~1 annotation, more...
// So, to find executions of a known filename, better filter on the filename (and possibly on folder path) than on the commandline.
| where FileName in~ ("net.exe", "net1.exe")
// Parse the user name from the commandline.
// To have case-insensitive parsing use the i flag, to have non-greedy match (e.g. CreatedUser as short as possible), specify U flag:
// "kind=regex flags=i"
| parse kind=regex flags=iU ProcessCommandLine with * "user " CreatedUser " " * "/ad"
// Filter rows where user could not be parsed - e.g. because it was not a user command, or the /add commandline switch was not specified.
| where isnotempty(CreatedUser)
// Every net.exe executed will run net1.exe with the same commandline.
// in this where clause we remove such rows, as they duplicate the number of results we have without adding any value.
| where not (FileName =~ "net1.exe" and InitiatingProcessFileName =~ "net.exe" and replace("net", "net1", InitiatingProcessCommandLine) =~ ProcessCommandLine)
// If /domain is specified, so the user is created on the domain controller.
// Also, any prefix that's longer than 1 char will also do the same, e.g. /do, /dom, /doma, ....
| extend CreatedOnLocalMachine=(ProcessCommandLine !contains "/do")
| where ProcessCommandLine !contains "/add" or (CreatedOnLocalMachine == 0 and ProcessCommandLine !contains "/domain")
| summarize MachineCount=dcount(DeviceName) by CreatedUser, CreatedOnLocalMachine, InitiatingProcessFileName, FileName, ProcessId, ProcessCommandLine, InitiatingProcessCommandLine, DeviceId, DeviceName
| extend HostName = iff(DeviceName has '.', substring(DeviceName, 0, indexof(DeviceName, '.')), DeviceName)
| extend DnsDomain = iff(DeviceName has '.', substring(DeviceName, indexof(DeviceName, '.') + 1), "")
entityMappings:
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: DeviceName
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: DnsDomain
- entityType: Process
fieldMappings:
- identifier: ProcessId
columnName: ProcessId
- identifier: CommandLine
columnName: ProcessCommandLine
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
DeviceProcessEvents
Stage 2: where
| where FileName in~ ("net.exe", "net1.exe")
Stage 3: parse
| parse kind=regex flags=iU ProcessCommandLine with * "user " CreatedUser " " * "/ad"
Stage 4: where
| where isnotempty(CreatedUser)
Stage 5: where
| where not (FileName =~ "net1.exe" and InitiatingProcessFileName =~ "net.exe" and replace("net", "net1", InitiatingProcessCommandLine) =~ ProcessCommandLine)
Stage 6: extend
| extend CreatedOnLocalMachine=(ProcessCommandLine !contains "/do")
Stage 7: where
| where ProcessCommandLine !contains "/add" or (CreatedOnLocalMachine == 0 and ProcessCommandLine !contains "/domain")
Stage 8: summarize
| summarize MachineCount=dcount(DeviceName) by CreatedUser, CreatedOnLocalMachine, InitiatingProcessFileName, FileName, ProcessId, ProcessCommandLine, InitiatingProcessCommandLine, DeviceId, DeviceName
Stage 9: extend
| extend HostName = iff(DeviceName has '.', substring(DeviceName, 0, indexof(DeviceName, '.')), DeviceName)
HostName =DeviceName has "."substring(DeviceName, 0, indexof(DeviceName, '.'))DeviceNameStage 10: extend
| extend DnsDomain = iff(DeviceName has '.', substring(DeviceName, indexof(DeviceName, '.') + 1), "")
DnsDomain =DeviceName has "."substring(DeviceName, (indexof(DeviceName, '.') + 1))""Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
CreatedOnLocalMachine | eq |
| field:"CommandLine" kind:eq value:"0" |
CreatedUser | is_not_null | field:"CreatedUser" kind:is_not_null | |
FileName | in |
| field:"process_basename" kind:in |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
FileName | eq | net1.exe | excludes:FileName field:"FileName" value:"net1.exe" |
InitiatingProcessCommandLine | cross_field_compare | ProcessCommandLine | excludes:InitiatingProcessCommandLine field:"InitiatingProcessCommandLine" value:"ProcessCommandLine" |
InitiatingProcessFileName | eq | net.exe | excludes:InitiatingProcessFileName field:"InitiatingProcessFileName" value:"net.exe" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
CreatedOnLocalMachine | summarize |
CreatedUser | summarize |
DeviceId | summarize |
DeviceName | summarize |
FileName | summarize |
InitiatingProcessCommandLine | summarize |
InitiatingProcessFileName | summarize |
MachineCount | summarize |
ProcessCommandLine | summarize |
ProcessId | summarize |
HostName | extend |
DnsDomain | extend |
AIShield - Tabular classification AI Model extraction high suspicious vulnerability detection
#'This alert creates an incident when Tabular classification AI Model extraction high suspicious, high severity vulnerability detected from the AIShield.'
Rule Body
id: 8d03e3ff-18eb-497c-a6cb-1c35ccdb0ed3
name: AIShield - Tabular classification AI Model extraction high suspicious vulnerability detection
description: |
'This alert creates an incident when Tabular classification AI Model extraction high suspicious, high severity vulnerability detected from the AIShield.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: BoschAIShield
dataTypes:
- AIShield
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: |
AIShield
| where Message has 'Tabular Classification AI Model Extraction Attack Identified'
| where Severity =~ 'High'
| where SuspiciousLevel =~ 'high suspicious attack'
| extend NTDomain = tostring(split(Computer, '\\')[0]), HostName = tostring(split(Computer, '.')[0])
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: HostName
- identifier: NTDomain
columnName: NTDomain
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceName
eventGroupingSettings:
aggregationKind: SingleAlert
alertDetailsOverride:
alertDisplayNameFormat: AIShield - Tabular Classification AI Model extraction high suspicious vulnerability detected.
alertDescriptionFormat: |
This query detects Tabular Classification AI Model extraction high suspicious, high severity alert from AIShield generated at {{TimeGenerated}}.\n\nPlease check the source for more information and investigate further.
alertTacticsColumnName: null
alertSeverityColumnName: Severity
version: 1.0.4
kind: Scheduled
Stages and Predicates
Stage 1: source
AIShield
Stage 2: where
| where Message has 'Tabular Classification AI Model Extraction Attack Identified'
Stage 3: where
| where Severity =~ 'High'
Stage 4: where
| where SuspiciousLevel =~ 'high suspicious attack'
Stage 5: extend
| extend NTDomain = tostring(split(Computer, '\\')[0]), HostName = tostring(split(Computer, '.')[0])
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Message | match |
| field:"Message" kind:match value:"Tabular Classification AI Model Extraction Attack Identified" |
Severity | eq |
| field:"Severity" kind:eq value:"High" |
SuspiciousLevel | eq |
| field:"SuspiciousLevel" kind:eq value:"high suspicious attack" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
HostName | extend |
NTDomain | extend |
AWSCloudTrail - Creation of CRUD KMS policy and then privilege escalation
#Identifies creation of IAM policies that grant permissions for broad KMS CRUD actions, followed by attachment activity to a principal. This sequence can be used to expand access to encryption keys or disable protections and should be investigated as possible privilege escalation.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| Privilege Escalation |
Telemetry Coverage
Rules Detecting the Same Action
These rules filter on the same operation.
- ASL AWS Create Policy Version to allow all resources (Splunk)
- AWS IAM CompromisedKeyQuarantine Policy Attached to User (Elastic)
- AWS Lateral Movement from Kubernetes SA via AssumeRoleWithWebIdentity (Elastic)
- AWSCloudTrail - CloudFormation policy created then used for privilege escalation (Kusto)
- AWSCloudTrail - Created CRUD S3 policy and then privilege escalation (Kusto)
- AWSCloudTrail - Creation of CRUD DynamoDB policy and then privilege escalation (Kusto)
- AWSCloudTrail - Creation of CRUD Lambda policy and then privilege escalation (Kusto)
- AWSCloudTrail - Creation of DataPipeline policy and then privilege escalation (Kusto)
Rule Body
id: 8e15998e-1e32-4b6d-abd1-e8482e8f3def
name: AWSCloudTrail - Creation of CRUD KMS policy and then privilege escalation
description: |
Identifies creation of IAM policies that grant permissions for broad KMS CRUD actions, followed by attachment activity to a principal. This sequence can be used to expand access to encryption keys or
disable protections and should be investigated as possible privilege escalation.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: AWS
dataTypes:
- AWSCloudTrail
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
- PrivilegeEscalation
- Persistence
relevantTechniques:
- T1484
- T1098.003
query: |
let EventNameList = dynamic(["AttachUserPolicy","AttachRolePolicy","AttachGroupPolicy"]);
let createPolicy = dynamic(["CreatePolicy", "CreatePolicyVersion"]);
let timeframe = 1d;
let lookback = 14d;
// Creating Master table with all the events to use with materialize for better performance
let EventInfo = AWSCloudTrail
| where TimeGenerated >= ago(lookback)
| where EventName in (EventNameList) or EventName in (createPolicy)
| extend UserIdentityArn = iif(isempty(UserIdentityArn), tostring(parse_json(Resources)[0].ARN), UserIdentityArn)
| extend UserName = tostring(split(UserIdentityArn, '/')[-1])
| extend AccountName = case( UserIdentityPrincipalid == "Anonymous", "Anonymous", isempty(UserIdentityUserName), UserName, UserIdentityUserName)
| extend AccountName = iif(AccountName contains "@", tostring(split(AccountName, '@', 0)[0]), AccountName),
AccountUPNSuffix = iif(AccountName contains "@", tostring(split(AccountName, '@', 1)[0]), "");
//Checking for Policy creation event with Full Admin Privileges since lookback period.
let FullAdminPolicyEvents = materialize( EventInfo
| where TimeGenerated >= ago(lookback)
| where EventName in (createPolicy)
| extend PolicyName = tostring(parse_json(RequestParameters).policyName)
| extend Statement = parse_json(tostring((parse_json(RequestParameters).policyDocument))).Statement
| mvexpand Statement
| extend Action = parse_json(Statement).Action , Effect = tostring(parse_json(Statement).Effect), Resource = tostring(parse_json(Statement).Resource), Condition = tostring(parse_json(Statement).Condition)
| extend Action = tostring(Action)
| where Effect =~ "Allow" and (Action contains "kms:Create" and (Action contains "kms:Get" or Action contains "kms:Describe") and (Action contains "kms:Disable" or Action contains "kms:Enable") and Action contains "kms:Delete") and Resource == "*" and Condition == ""
| distinct TimeGenerated, EventName, PolicyName, SourceIpAddress, UserIdentityArn, RecipientAccountId, AccountName, AccountUPNSuffix
| project-rename StartTime = TimeGenerated );
let PolicyAttach = materialize( EventInfo
| where TimeGenerated >= ago(timeframe)
| where EventName in (EventNameList) and isempty(ErrorCode) and isempty(ErrorMessage)
| extend PolicyName = tostring(split(tostring(parse_json(RequestParameters).policyArn),"/")[1])
| summarize AttachEventCount=count(), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by EventSource, EventName, UserIdentityType , UserIdentityArn, SourceIpAddress, RecipientAccountId, AccountName, AccountUPNSuffix, PolicyName
| extend AttachEvent = pack("StartTime", StartTime, "EndTime", EndTime, "EventName", EventName, "UserIdentityType", UserIdentityType, "SourceIpAddress", SourceIpAddress, "AccountName", AccountName, "AccountUPNSuffix", AccountUPNSuffix, "RecipientAccountId", RecipientAccountId, "UserIdentityArn", UserIdentityArn)
| project EventSource, PolicyName, AttachEvent, RecipientAccountId, AccountName, AccountUPNSuffix, AttachEventCount
);
// Joining the list of PolicyNames and checking if it has been attached to any Roles/Users/Groups.
// These Roles/Users/Groups will be Privileged and can be used by adversaries as pivot point for privilege escalation via multiple ways.
FullAdminPolicyEvents
| join kind=leftouter
(
PolicyAttach
)
on PolicyName
| project-away PolicyName1
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: AccountUPNSuffix
- identifier: CloudAppAccountId
columnName: RecipientAccountId
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIpAddress
customDetails:
PolicyName: PolicyName
EventName: EventName
RecipientAccountId: RecipientAccountId
UserIdentityArn: UserIdentityArn
alertDetailsOverride:
alertDisplayNameFormat: 'AWS KMS policy {{PolicyName}} created and attached by {{AccountName}}'
alertDescriptionFormat: 'Detected {{EventName}} for policy {{PolicyName}} in account {{RecipientAccountId}}.'
version: 1.0.3
kind: Scheduled
Stages and Predicates
Parameters
let EventNameList = dynamic(["AttachUserPolicy","AttachRolePolicy","AttachGroupPolicy"]);
let createPolicy = dynamic(["CreatePolicy", "CreatePolicyVersion"]);
let timeframe = 1d;
let lookback = 14d;
let EventInfo and let FullAdminPolicyEvents are inlined into the numbered stages below.
Let Binding: PolicyAttach
let PolicyAttach = materialize( EventInfo
| where TimeGenerated >= ago(timeframe)
| where EventName in (EventNameList) and isempty(ErrorCode) and isempty(ErrorMessage)
| extend PolicyName = tostring(split(tostring(parse_json(RequestParameters).policyArn),"/")[1])
| summarize AttachEventCount=count(), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by EventSource, EventName, UserIdentityType , UserIdentityArn, SourceIpAddress, RecipientAccountId, AccountName, AccountUPNSuffix, PolicyName
| extend AttachEvent = pack("StartTime", StartTime, "EndTime", EndTime, "EventName", EventName, "UserIdentityType", UserIdentityType, "SourceIpAddress", SourceIpAddress, "AccountName", AccountName, "AccountUPNSuffix", AccountUPNSuffix, "RecipientAccountId", RecipientAccountId, "UserIdentityArn", UserIdentityArn)
| project EventSource, PolicyName, AttachEvent, RecipientAccountId, AccountName, AccountUPNSuffix, AttachEventCount
);
Stages 1 to 14 define let FullAdminPolicyEvents as the rule's main pipeline source. Stages 15 to 17 use it.
Stage 1: source
AWSCloudTrail
Stage 2: where
| where TimeGenerated >= ago(lookback)
Stage 3: where
| where EventName in (EventNameList) or EventName in (createPolicy)
Stage 4: extend (4 consecutive steps)
| extend UserIdentityArn = iif(isempty(UserIdentityArn), tostring(parse_json(Resources)[0].ARN), UserIdentityArn)
| extend UserName = tostring(split(UserIdentityArn, '/')[-1])
| extend AccountName = case( UserIdentityPrincipalid == "Anonymous", "Anonymous", isempty(UserIdentityUserName), UserName, UserIdentityUserName)
| extend AccountName = iif(AccountName contains "@", tostring(split(AccountName, '@', 0)[0]), AccountName),
AccountUPNSuffix = iif(AccountName contains "@", tostring(split(AccountName, '@', 1)[0]), "")
Stage 5: where
| where TimeGenerated >= ago(lookback)
Stage 6: where
| where EventName in (createPolicy)
Stage 7: extend
| extend PolicyName = tostring(parse_json(RequestParameters).policyName)
Stage 8: extend
| extend Statement = parse_json(tostring((parse_json(RequestParameters).policyDocument))).Statement
Stage 9: mv-expand
| mvexpand Statement
Stage 10: extend
| extend Action = parse_json(Statement).Action , Effect = tostring(parse_json(Statement).Effect), Resource = tostring(parse_json(Statement).Resource), Condition = tostring(parse_json(Statement).Condition)
Stage 11: extend
| extend Action = tostring(Action)
Stage 12: where
| where Effect =~ "Allow" and (Action contains "kms:Create" and (Action contains "kms:Get" or Action contains "kms:Describe") and (Action contains "kms:Disable" or Action contains "kms:Enable") and Action contains "kms:Delete") and Resource == "*" and Condition == ""
Stage 13: distinct
| distinct TimeGenerated, EventName, PolicyName, SourceIpAddress, UserIdentityArn, RecipientAccountId, AccountName, AccountUPNSuffix
Stage 14: project-rename
| project-rename StartTime = TimeGenerated
Stage 15: join
FullAdminPolicyEvents
| join kind=leftouter
(
PolicyAttach
)
on PolicyName
Stage 16: project-away
project-away PolicyName1
Stage 17: summarize aggregation inside the join branch
summarize by EventSource, EventName, UserIdentityType, UserIdentityArn, SourceIpAddress, RecipientAccountId, AccountName, AccountUPNSuffix, PolicyName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Action | contains |
| field:"Action" kind:contains |
Effect | eq |
| field:"Effect" kind:eq value:"Allow" |
ErrorCode | is_null | field:"ErrorCode" kind:is_null | |
ErrorMessage | is_null | field:"ErrorMessage" kind:is_null | |
EventName | in |
| field:"aws::eventName" kind:in |
Resource | eq |
| field:"Resource" kind:eq value:"*" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountName | summarize |
AccountUPNSuffix | summarize |
EventName | summarize |
EventSource | summarize |
PolicyName | summarize |
RecipientAccountId | summarize |
SourceIpAddress | summarize |
UserIdentityArn | summarize |
UserIdentityType | summarize |
Azure VM Run Command operations executing a unique PowerShell script
#Identifies when Azure Run command is used to execute a PowerShell script on a VM that is unique. The uniqueness of the PowerShell script is determined by taking a combined hash of the cmdLets it imports and the file size of the PowerShell script. Alerts from this detection indicate a unique PowerShell was executed in your environment.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Execution | |
| Lateral Movement |
Telemetry Coverage
Rule Body
id: 5239248b-abfb-4c6a-8177-b104ade5db56
name: Azure VM Run Command operations executing a unique PowerShell script
description: |
'Identifies when Azure Run command is used to execute a PowerShell script on a VM that is unique.
The uniqueness of the PowerShell script is determined by taking a combined hash of the cmdLets it imports and the file size of the PowerShell script. Alerts from this detection indicate a unique PowerShell was executed in your environment.'
severity: Medium
requiredDataConnectors:
- connectorId: AzureActivity
dataTypes:
- AzureActivity
- connectorId: MicrosoftThreatProtection
dataTypes:
- DeviceFileEvents
- DeviceEvents
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- LateralMovement
- Execution
relevantTechniques:
- T1570
- T1059.001
query: |
let RunCommandData = materialize ( AzureActivity
// Isolate run command actions
| where OperationNameValue =~ "Microsoft.Compute/virtualMachines/runCommand/action"
// Confirm that the operation impacted a virtual machine
| where Authorization has "virtualMachines"
// Each runcommand operation consists of three events when successful, StartTimeed, Accepted (or Rejected), Successful (or Failed).
| summarize StartTime=min(TimeGenerated), EndTime=max(TimeGenerated), max(CallerIpAddress), make_list(ActivityStatusValue) by CorrelationId, Authorization, Caller
// Limit to Run Command executions that Succeeded
| where list_ActivityStatusValue has_any ("Succeeded", "Success")
// Extract data from the Authorization field, allowing us to later extract the Caller (UPN) and CallerIpAddress
| extend Authorization_d = parse_json(Authorization)
| extend Scope = Authorization_d.scope
| extend Scope_s = split(Scope, "/")
| extend Subscription = tostring(Scope_s[2])
| extend VirtualMachineName = tostring(Scope_s[-1])
| project StartTime, EndTime, Subscription, VirtualMachineName, CorrelationId, Caller, CallerIpAddress=max_CallerIpAddress, Scope
| join kind=leftouter (
DeviceFileEvents
| where InitiatingProcessFileName == "RunCommandExtension.exe"
| extend VirtualMachineName = tostring(split(DeviceName, ".")[0])
| project VirtualMachineName, PowershellFileCreatedTimestamp=TimeGenerated, FileName, FileSize, InitiatingProcessAccountName, InitiatingProcessAccountDomain, InitiatingProcessFolderPath, InitiatingProcessId
) on VirtualMachineName
// We need to filter by time sadly, this is the only way to link events
| where PowershellFileCreatedTimestamp between (StartTime .. EndTime)
| project StartTime, EndTime, PowershellFileCreatedTimestamp, VirtualMachineName, Caller, CallerIpAddress, FileName, FileSize, InitiatingProcessId, InitiatingProcessAccountDomain, InitiatingProcessFolderPath, Scope
| join kind=inner(
DeviceEvents
| extend VirtualMachineName = tostring(split(DeviceName, ".")[0])
| where InitiatingProcessCommandLine has "-File"
// Extract the script name based on the structure used by the RunCommand extension
| extend PowershellFileName = extract(@"\-File\s(script[0-9]{1,9}\.ps1)", 1, InitiatingProcessCommandLine)
// Discard results that didn't successfully extract, these are not run command related
| where isnotempty(PowershellFileName)
| extend PSCommand = tostring(parse_json(AdditionalFields).Command)
// The first execution of PowerShell will be the RunCommand script itself, we can discard this as it will break our hash later
| where PSCommand != PowershellFileName
// Now we normalise the cmdlets, we're aiming to hash them to find scripts using rare combinations
| extend PSCommand = toupper(PSCommand)
| order by PSCommand asc
| summarize PowershellExecStartTime=min(TimeGenerated), PowershellExecEnd=max(TimeGenerated), make_list(PSCommand) by PowershellFileName, InitiatingProcessCommandLine
) on $left.FileName == $right.PowershellFileName
| project StartTime, EndTime, PowershellFileCreatedTimestamp, PowershellExecStartTime, PowershellExecEnd, PowershellFileName, PowershellScriptCommands=list_PSCommand, Caller, CallerIpAddress, InitiatingProcessCommandLine, PowershellFileSize=FileSize, VirtualMachineName, Scope
| order by StartTime asc
// We generate the hash based on the cmdlets called and the size of the powershell script
| extend TempFingerprintString = strcat(PowershellScriptCommands, PowershellFileSize)
| extend ScriptFingerprintHash = hash_sha256(tostring(PowershellScriptCommands)));
let totals = toscalar (RunCommandData
| summarize count());
let hashTotals = RunCommandData
| summarize HashCount=count() by ScriptFingerprintHash;
RunCommandData
| join kind=leftouter (
hashTotals
) on ScriptFingerprintHash
// Calculate prevalence, while we don't need this, it may be useful for responders to know how rare this script is in relation to normal activity
| extend Prevalence = toreal(HashCount) / toreal(totals) * 100
// Where the hash was only ever seen once.
| where HashCount == 1
| extend timestamp = StartTime
| extend CallerName = tostring(split(Caller, "@")[0]), CallerUPNSuffix = tostring(split(Caller, "@")[1])
| project timestamp, StartTime, EndTime, PowershellFileName, VirtualMachineName, Caller, CallerName, CallerUPNSuffix, CallerIpAddress, PowershellScriptCommands, PowershellFileSize, ScriptFingerprintHash, Prevalence, Scope
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: Caller
- identifier: Name
columnName: CallerName
- identifier: UPNSuffix
columnName: CallerUPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: CallerIpAddress
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: VirtualMachineName
- identifier: AzureID
columnName: Scope
version: 1.0.8
kind: Scheduled
metadata:
source:
kind: Community
author:
name: Microsoft Security Research
support:
tier: Community
categories:
domains: [ "Security - Others", "Identity" ]
Stages and Predicates
let RunCommandData is inlined into the numbered stages below.
Let Binding: totals
let totals = toscalar (RunCommandData
| summarize count());
Let Binding: hashTotals
let hashTotals = RunCommandData
| summarize HashCount=count() by ScriptFingerprintHash;
Stage 1: source
let RunCommandData
Stage 2: source
let hashTotals
Stage 3: source
AzureActivity
Stage 4: where
where OperationNameValue =~ "Microsoft.Compute/virtualMachines/runCommand/action"
Stage 5: where
where Authorization has "virtualMachines"
Stage 6: summarize
summarize EndTime, StartTime by CorrelationId, Authorization, Caller
Stage 7: where
where (list_ActivityStatusValue has "Succeeded" or list_ActivityStatusValue has "Success")
Stage 8: extend (5 consecutive steps)
extend Authorization_d, Scope, Scope_s, Subscription, VirtualMachineName
Stage 9: project
project Caller, CallerIpAddress, CorrelationId, EndTime, Scope, StartTime, Subscription, VirtualMachineName
Stage 10: join
join kind=leftouter (DeviceFileEvents) on VirtualMachineName
Stage 11: where
where PowershellFileCreatedTimestamp >= "StartTime" and PowershellFileCreatedTimestamp <= "EndTime"
Stage 12: project
project Caller, CallerIpAddress, EndTime, FileName, FileSize, InitiatingProcessAccountDomain, InitiatingProcessFolderPath, InitiatingProcessId, PowershellFileCreatedTimestamp, Scope, StartTime, VirtualMachineName
Stage 13: join
join kind=inner (DeviceEvents) on $left.FileName, $right.PowershellFileName
Stage 14: project
project Caller, CallerIpAddress, EndTime, InitiatingProcessCommandLine, PowershellExecEnd, PowershellExecStartTime, PowershellFileCreatedTimestamp, PowershellFileName, PowershellFileSize, PowershellScriptCommands, Scope, StartTime, VirtualMachineName
Stage 15: sort
sort by StartTime
Stage 16: extend
extend TempFingerprintString
Stage 17: extend
extend ScriptFingerprintHash
Stage 18: join
join kind=leftouter (hashTotals) on ScriptFingerprintHash
Stage 19: extend
extend Prevalence
Stage 20: where
where HashCount == 1
Stage 21: extend
extend timestamp
Stage 22: extend
extend CallerName, CallerUPNSuffix
Stage 23: project
project Caller, CallerIpAddress, CallerName, CallerUPNSuffix, EndTime, PowershellFileName, PowershellFileSize, PowershellScriptCommands, Prevalence, Scope, ScriptFingerprintHash, StartTime, VirtualMachineName, timestamp
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Authorization | match |
| field:"Authorization" kind:match value:"virtualMachines" |
HashCount | eq |
| field:"HashCount" kind:eq value:"1" |
InitiatingProcessCommandLine | match |
| field:"InitiatingProcessCommandLine" kind:match value:"-File" |
InitiatingProcessFileName | eq |
| field:"InitiatingProcessFileName" kind:eq value:"RunCommandExtension.exe" |
OperationNameValue | eq |
| field:"OperationNameValue" kind:eq value:"Microsoft.Compute/virtualMachines/runCommand/action" |
PowershellFileCreatedTimestamp | ge |
| field:"PowershellFileCreatedTimestamp" kind:ge value:"StartTime" |
PowershellFileCreatedTimestamp | le |
| field:"PowershellFileCreatedTimestamp" kind:le value:"EndTime" |
PowershellFileName | is_not_null | field:"PowershellFileName" kind:is_not_null | |
list_ActivityStatusValue | match |
| field:"list_ActivityStatusValue" kind:match |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Caller | project |
CallerIpAddress | project |
CallerName | project |
CallerUPNSuffix | project |
EndTime | project |
PowershellFileName | project |
PowershellFileSize | project |
PowershellScriptCommands | project |
Prevalence | project |
Scope | project |
ScriptFingerprintHash | project |
StartTime | project |
VirtualMachineName | project |
timestamp | project |
Bitsadmin Activity
#Background Intelligent Transfer Service (BITS) is a way to reliably download files from webservers or SMB servers. This service is commonly used for legitimate purposes, but can also be used as part of a malware downloader. Additionally, bitsadmin can be used to upload files and therefore can be used for data exfiltration. This query will identify use of bitsadmin.exe for either purpose and will identify directionality file transfer directionality.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| Command & Control | |
| Exfiltration |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Microsoft Defender for Endpoint | DeviceProcessEvents action any: Process activity |
Rule Body
id: 2a1dc4c2-a8d6-4a0e-8539-9b971c851195
name: Bitsadmin Activity
description: |
Background Intelligent Transfer Service (BITS) is a way to reliably download files from webservers or SMB servers.
This service is commonly used for legitimate purposes, but can also be used as part of a malware downloader.
Additionally, bitsadmin can be used to upload files and therefore can be used for data exfiltration. This
query will identify use of bitsadmin.exe for either purpose and will identify directionality file transfer
directionality.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: MicrosoftThreatProtection
dataTypes:
- DeviceProcessEvents
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
- CommandAndControl
- Exfiltration
relevantTechniques:
- T1197
- T1105
- T1048
query: |
DeviceProcessEvents
| where
(FileName =~ "bitsadmin.exe" or column_ifexists('ProcessVersionInfoOriginalFileName','ColumnNotAvailable') =~ 'bitsadmin.exe')
and ProcessCommandLine has_any ('/Transfer','/AddFile', '/AddFileSet','/AddFileWithRanges')
| extend
ParsedCommandLine = parse_command_line(ProcessCommandLine,'windows')
| extend
RemoteUrl = tostring(ParsedCommandLine[-2]),
LocalFile= tostring(ParsedCommandLine[-1]),
Direction = iff(ProcessCommandLine has "/Upload", 'Upload', 'Download')
| project-reorder
TimeGenerated,
DeviceId,
DeviceName,
Direction,
RemoteUrl,
LocalFile,
InitiatingProcessFolderPath,
InitiatingProcessAccountDomain,
InitiatingProcessAccountName,
InitiatingProcessSHA256,
ProcessId,
ProcessCommandLine
| extend HostName = iff(DeviceName has '.', substring(DeviceName, 0, indexof(DeviceName, '.')), DeviceName)
| extend DnsDomain = iff(DeviceName has '.', substring(DeviceName, indexof(DeviceName, '.') + 1), "")
entityMappings:
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: DeviceName
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: DnsDomain
- entityType: Process
fieldMappings:
- identifier: ProcessId
columnName: ProcessId
- identifier: CommandLine
columnName: ProcessCommandLine
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
DeviceProcessEvents
Stage 2: where
| where
(FileName =~ "bitsadmin.exe" or column_ifexists('ProcessVersionInfoOriginalFileName','ColumnNotAvailable') =~ 'bitsadmin.exe')
and ProcessCommandLine has_any ('/Transfer','/AddFile', '/AddFileSet','/AddFileWithRanges')
Stage 3: extend
| extend
ParsedCommandLine = parse_command_line(ProcessCommandLine,'windows')
Stage 4: extend
| extend
RemoteUrl = tostring(ParsedCommandLine[-2]),
LocalFile= tostring(ParsedCommandLine[-1]),
Direction = iff(ProcessCommandLine has "/Upload", 'Upload', 'Download')
Direction =ProcessCommandLine has "/Upload"'Upload''Download'Stage 5: project-reorder
| project-reorder
TimeGenerated,
DeviceId,
DeviceName,
Direction,
RemoteUrl,
LocalFile,
InitiatingProcessFolderPath,
InitiatingProcessAccountDomain,
InitiatingProcessAccountName,
InitiatingProcessSHA256,
ProcessId,
ProcessCommandLine
Stage 6: extend
| extend HostName = iff(DeviceName has '.', substring(DeviceName, 0, indexof(DeviceName, '.')), DeviceName)
HostName =DeviceName has "."substring(DeviceName, 0, indexof(DeviceName, '.'))DeviceNameStage 7: extend
| extend DnsDomain = iff(DeviceName has '.', substring(DeviceName, indexof(DeviceName, '.') + 1), "")
DnsDomain =DeviceName has "."substring(DeviceName, (indexof(DeviceName, '.') + 1))""Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
FileName | eq |
| field:"process_basename" kind:eq value:"bitsadmin.exe" |
ProcessCommandLine | match |
| field:"CommandLine" kind:match |
ProcessVersionInfoOriginalFileName | eq |
| field:"OriginalFileName" kind:eq value:"bitsadmin.exe" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ParsedCommandLine | extend |
Direction | extend |
LocalFile | extend |
RemoteUrl | extend |
HostName | extend |
DnsDomain | extend |
BloodHound Attack Path Finding - Non-Tier Zero Entra User Synced to Tier Zero AD User
#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 Entra User Synced to Tier Zero AD User
Rule Body
id: 94e4114f-acd5-48d9-8b5e-07d08b809541
name: BloodHound Attack Path Finding - Non-Tier Zero Entra User Synced to Tier Zero
AD User
description: BloodHound Attack Path Finding - Non-Tier Zero Entra User Synced to Tier
Zero AD User
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 == "T0SyncedToADUser"'
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 == "T0SyncedToADUser"
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Finding | eq |
| field:"Finding" kind:eq value:"T0SyncedToADUser" |
Cisco Cloud Security - Crypto Miner User-Agent Detected
#'Detects suspicious user agent strings used by crypto miners in proxy logs.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control | |
| Exfiltration | |
| Impact |
Rule Body
id: b619d1f1-7f39-4c7e-bf9e-afbb46457997
name: Cisco Cloud Security - Crypto Miner User-Agent Detected
description: |
'Detects suspicious user agent strings used by crypto miners in proxy logs.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: CiscoUmbrellaDataConnector
dataTypes:
- Cisco_Umbrella_proxy_CL
queryFrequency: 15m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
tactics:
- Impact
- CommandAndControl
- Exfiltration
relevantTechniques:
- T1496
- T1071.001
- T1041
query: |
Cisco_Umbrella
| where EventType == "proxylogs"
| where HttpUserAgentOriginal contains "XMRig" or HttpUserAgentOriginal contains "ccminer"
| extend Message = "Crypto Miner User Agent"
| project TimeGenerated, Message, SrcIpAddr, DstIpAddr, UrlOriginal, HttpUserAgentOriginal
entityMappings:
- entityType: URL
fieldMappings:
- identifier: Url
columnName: UrlOriginal
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIpAddr
version: 1.1.3
kind: Scheduled
Stages and Predicates
Stage 1: source
Cisco_Umbrella
Stage 2: where
| where EventType == "proxylogs"
Stage 3: where
| where HttpUserAgentOriginal contains "XMRig" or HttpUserAgentOriginal contains "ccminer"
Stage 4: extend
| extend Message = "Crypto Miner User Agent"
Stage 5: project
| project TimeGenerated, Message, SrcIpAddr, DstIpAddr, UrlOriginal, HttpUserAgentOriginal
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventType | eq |
| field:"EventType" kind:eq value:"proxylogs" |
HttpUserAgentOriginal | contains |
| field:"HttpUserAgentOriginal" kind:contains |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DstIpAddr | project |
HttpUserAgentOriginal | project |
Message | project |
SrcIpAddr | project |
TimeGenerated | project |
UrlOriginal | project |
Cloudflare - Client request from country in blocklist
#'Detects requests from countries which are in blocklist.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: 40554544-6e4a-4413-8d14-bf2de939c5d9
name: Cloudflare - Client request from country in blocklist
description: |
'Detects requests from countries which are in blocklist.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: CloudflareDataConnector
dataTypes:
- Cloudflare
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1190
- T1133
query: |
let bl_countries = dynamic(['cn', 'hk']);
Cloudflare
| where SrcGeoCountry in~ (bl_countries)
| extend CompleteUrl = strcat(HttpRequestHeaderHost,ClientRequestPath)
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIpAddr
- entityType: URL
fieldMappings:
- identifier: Url
columnName: CompleteUrl
version: 1.0.1
kind: Scheduled
Stages and Predicates
Parameters
let bl_countries = dynamic(['cn', 'hk']);
Stage 1: source
Cloudflare
Stage 2: where
| where SrcGeoCountry in~ (bl_countries)
Stage 3: extend
| extend CompleteUrl = strcat(HttpRequestHeaderHost,ClientRequestPath)
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
SrcGeoCountry | in |
| field:"SrcGeoCountry" kind:in |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
CompleteUrl | extend |
Copilot - File Uploads Disabled
#'Detects when file uploads are disabled in Copilot. Attackers often disable logging or file upload capabilities to avoid evidence collection and cover their tracks. This rule identifies potential data exfiltration cover-up scenarios where security controls are being disabled.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Defense Impairment |
Rule Body
id: c3d4e5f6-a7b8-49c0-d1e2-f3a4b5c6d7e8
name: Copilot - File Uploads Disabled
description: |
'Detects when file uploads are disabled in Copilot. Attackers often disable logging or file upload capabilities to avoid evidence collection and cover their tracks.
This rule identifies potential data exfiltration cover-up scenarios where security controls are being disabled.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: MicrosoftCopilot
dataTypes:
- CopilotActivity
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
relevantTechniques:
- T1562.001
query: |
CopilotActivity
| where RecordType == "DisableCopilotPlugin"
| extend Resource = parse_json(LLMEventData).Resource[0]
| extend Property = tostring(Resource.Property)
| extend Old = tostring(Resource.OriginalValue)
| extend New = tostring(Resource.NewValue)
| where Property == "FileUploads" and Old == "Enabled" and New == "Disabled"
| project TimeGenerated, ActorName, SrcIpAddr, Property, Old, New
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: ActorName
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIpAddr
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
CopilotActivity
Stage 2: where
| where RecordType == "DisableCopilotPlugin"
Stage 3: extend (4 consecutive steps)
| extend Resource = parse_json(LLMEventData).Resource[0]
| extend Property = tostring(Resource.Property)
| extend Old = tostring(Resource.OriginalValue)
| extend New = tostring(Resource.NewValue)
Stage 4: where
| where Property == "FileUploads" and Old == "Enabled" and New == "Disabled"
Stage 5: project
| project TimeGenerated, ActorName, SrcIpAddr, Property, Old, New
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
New | eq |
| field:"New" kind:eq value:"Disabled" |
Old | eq |
| field:"Old" kind:eq value:"Enabled" |
Property | eq |
| field:"Property" kind:eq value:"FileUploads" |
RecordType | eq |
| field:"RecordType" kind:eq value:"DisableCopilotPlugin" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActorName | project |
New | project |
Old | project |
Property | project |
SrcIpAddr | project |
TimeGenerated | project |
CTE - Security Rule Changed (Created/Updated/Deleted)
#A URL blocking/enforcement rule was created, updated, or deleted (potential policy tampering). Deduplicated per rule/day to avoid re-alerting on replayed audit events for the same change.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Defense Impairment |
Rule Body
id: cf7ad1f5-7325-4bda-83d4-957ec943c457
name: CTE - Security Rule Changed (Created/Updated/Deleted)
description: |
A URL blocking/enforcement rule was created, updated, or deleted (potential policy tampering). Deduplicated per rule/day to avoid re-alerting on replayed audit events for the same change.
severity: Medium
requiredDataConnectors:
- connectorId: cybereinforce_cte
dataTypes:
- CybereinforceCTE_CL
queryFrequency: 30m
queryPeriod: 30m
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
relevantTechniques:
- T1562
query: |
CybereinforceCTE_CL
| where EventType in ("rule.created", "rule.updated", "rule.deleted")
| extend d = parse_json(Details)
| extend
AccountName = tostring(split(Actor, "@")[0]),
UPNSuffix = tostring(split(Actor, "@")[1]),
AppName = "Cybereinforce",
RuleId = tostring(d.ruleId),
Pattern = tostring(d.pattern)
| summarize EventCount=count(), FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated) by bin(TimeGenerated, 1d), EventType, RuleId, Actor, AccountName, UPNSuffix, AppName, Pattern, TenantId
| project TimeGenerated=LastSeen, TenantId, Actor, AccountName, UPNSuffix, AppName, EventType, RuleId, Pattern, EventCount, FirstSeen, LastSeen
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: CloudApplication
fieldMappings:
- identifier: Name
columnName: AppName
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
CybereinforceCTE_CL
Stage 2: where
| where EventType in ("rule.created", "rule.updated", "rule.deleted")
Stage 3: extend
| extend d = parse_json(Details)
Stage 4: extend
| extend
AccountName = tostring(split(Actor, "@")[0]),
UPNSuffix = tostring(split(Actor, "@")[1]),
AppName = "Cybereinforce",
RuleId = tostring(d.ruleId),
Pattern = tostring(d.pattern)
Stage 5: summarize
| summarize EventCount=count(), FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated) by bin(TimeGenerated, 1d), EventType, RuleId, Actor, AccountName, UPNSuffix, AppName, Pattern, TenantId
Stage 6: project
| project TimeGenerated=LastSeen, TenantId, Actor, AccountName, UPNSuffix, AppName, EventType, RuleId, Pattern, EventCount, FirstSeen, LastSeen
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventType | in |
| field:"EventType" kind:in |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountName | project |
Actor | project |
AppName | project |
EventCount | project |
EventType | project |
FirstSeen | project |
LastSeen | project |
Pattern | project |
RuleId | project |
TenantId | project |
TimeGenerated | project |
UPNSuffix | project |
Cyble Vision Alerts New Vulnerability Detected
#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.
'A newly detected CVE has been associated with a monitored keyword or asset. This may indicate exposure to newly published or exploited vulnerabilities.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: e52f36dd-7d4f-4aa8-a095-3b6fa2b28b8d
name: Cyble Vision Alerts New Vulnerability Detected
description: |
'A newly detected CVE has been associated with a monitored keyword or asset. This may indicate exposure to newly published or exploited vulnerabilities.'
severity: Low
status: Available
requiredDataConnectors:
- connectorId: CybleVisionAlerts
dataTypes:
- CybleVisionAlerts_CL
enabled: true
queryfrequency: 30m
queryPeriod: 30m
triggerOperator: GreaterThan
triggerThreshold: 0
eventGroupingSettings:
aggregationKind: AlertPerResult
tactics:
- InitialAccess
relevantTechniques:
- T1190
query: |
Alerts_new_vulnerability
| where Service == "new_vulnerability"
| extend MappedSeverity = Severity
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
severityColumnName: MappedSeverity
alertDetailsOverride:
alertDisplayNameFormat: 'New Vulnerability Identified {{NV_CVE}}'
description: |
A new vulnerability ({{NV_CVE}}) has been identified for one of the monitored keywords or assets. This CVE may pose risks depending on exposure and exploitability. Review CVE details and assess potential impact on your environment.
customDetails:
MappedSeverity: Severity
Status: Status
AlertID: AlertID
Service: Service
NV_CVE: NV_CVE
entityMappings: null
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
Alerts_new_vulnerability
Stage 2: where
| where Service == "new_vulnerability"
Stage 3: extend
| extend MappedSeverity = Severity
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Service | eq |
| field:"Service" kind:eq value:"new_vulnerability" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
MappedSeverity | extend |
Cyble Vision Alerts Suspicious Domain
#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 generates Cyble Vision Alerts for Service - Suspicious Domain severity LOW'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Reconnaissance |
Rule Body
id: c56fcb78-b708-4a92-bad4-d50b1e15c42c
name: Cyble Vision Alerts Suspicious Domain
description: |
'This Rule generates Cyble Vision Alerts for Service - Suspicious Domain severity LOW'
severity: Low
status: Available
requiredDataConnectors:
- connectorId: CybleVisionAlerts
dataTypes:
- CybleVisionAlerts_CL
enabled: true
queryFrequency: 30m
queryPeriod: 30m
triggerOperator: GreaterThan
triggerThreshold: 0
suppressionDuration: PT5H
eventGroupingSettings:
aggregationKind: AlertPerResult
tactics:
- Reconnaissance
relevantTechniques:
- T1595
subTechniques: []
query: |
Alerts_suspicious_domains
| where Service == "suspicious_domain"
| extend MappedSeverity = Severity
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
alertDetailsOverride:
alertDisplayNameFormat: Suspicious Domain Detected {{Domain}} {{Status}}
alertDescriptionFormat: |
Suspicious Domain detected for {{Domain}} with {{Status}}
alertDynamicProperties: []
customDetails:
MappedSeverity: Severity
Status: Status
AlertID: AlertID
Service: Service
Domain: Domain
Keyword: KeywordName
Score: SD_Score
LiveStatus: SD_IsLive
RegistrationDate: SD_RegistrationDate
LastLiveCheck: SD_LastLiveCheck
Screenshot: SD_ScreenshotURL
A_Records: A_Records
NS_Records: NS_Records
MX_Records: MX_Records
entityMappings:
- entityType: DNS
fieldMappings:
- identifier: DomainName
columnName: Domain
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
Alerts_suspicious_domains
Stage 2: where
| where Service == "suspicious_domain"
Stage 3: extend
| extend MappedSeverity = Severity
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Service | eq |
| field:"Service" kind:eq value:"suspicious_domain" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
MappedSeverity | extend |
CYFIRMA - Brand Intelligence - Domain Impersonation 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 analytics rule detects high-risk domain impersonation activity, where newly registered or existing domains closely resemble the legitimate brand name or organizational assets. These suspicious domains may use typosquatting, homoglyphs, or brand keywords to mislead users, steal credentials, or host phishing/malicious content. The domains are identified through CYFIRMA's external threat intelligence feeds and flagged due to potential misuse in impersonation, fraud, or social engineering attacks targeting employees, customers, or partners. Early detection of these domains enables proactive mitigation measures such as domain takedown, DNS blocking, and awareness campaigns."
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Resource Development | |
| Initial Access | |
| Command & Control |
Rule Body
id: 8f97ddbe-ab66-4f6c-b675-73b5eeb07259
name: CYFIRMA - Brand Intelligence - Domain Impersonation Medium Rule
description: |
"This analytics rule detects high-risk domain impersonation activity, where newly registered or existing domains closely resemble the legitimate brand name or organizational assets.
These suspicious domains may use typosquatting, homoglyphs, or brand keywords to mislead users, steal credentials, or host phishing/malicious content.
The domains are identified through CYFIRMA's external threat intelligence feeds and flagged due to potential misuse in impersonation, fraud, or social engineering attacks targeting employees, customers, or partners.
Early detection of these domains enables proactive mitigation measures such as domain takedown, DNS blocking, and awareness campaigns."
version: 1.0.1
kind: Scheduled
severity: Medium
requiredDataConnectors:
- connectorId: CyfirmaBrandIntelligenceAlertsDC
dataTypes:
- CyfirmaBIDomainITAssetAlerts_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- ResourceDevelopment
- InitialAccess
- CommandAndControl
relevantTechniques:
- T1583.001
- T1586.002
- T1566.002
- T1566.001
- T1071.003
- T1071.001
query: |
// Medium severity Brand Intelligence - Domain Impersonation
let timeFrame = 5m;
CyfirmaBIDomainITAssetAlerts_CL
| where severity == 'High' and TimeGenerated between (ago(timeFrame) .. now())
| extend
Description=description,
FirstSeen=first_seen,
LastSeen=last_seen,
RiskScore=risk_score,
AlertUID=alert_uid,
UID=uid,
Domain=domain,
DRDomain=dr_domain,
DRSubDomain=dr_sub_domain,
DomainSquat=signature,
HostProvider=host_provider,
RegisteredDate=registered_date,
CreatedDate=created_date,
ThreatActor=suspected_threat_actor,
ProviderName='CYFIRMA',
ProductName='DeCYFIR/DeTCT'
| project
TimeGenerated,
Description,
RiskScore,
FirstSeen,
LastSeen,
AlertUID,
UID,
Domain,
DRDomain,
DRSubDomain,
DomainSquat,
HostProvider,
RegisteredDate,
CreatedDate,
ThreatActor,
ProductName,
ProviderName
entityMappings:
- entityType: DNS
fieldMappings:
- identifier: DomainName
columnName: Domain
- entityType: DNS
fieldMappings:
- identifier: DomainName
columnName: DomainSquat
customDetails:
TimeGenerated: TimeGenerated
RiskScore: RiskScore
DomainSquat: DomainSquat
Domain: Domain
Description: Description
DRDomain: DRDomain
DRSubDomain: DRSubDomain
AlertUID: AlertUID
UID: UID
ThreatActor: ThreatActor
FirstSeen: FirstSeen
LastSeen: LastSeen
CreatedDate: CreatedDate
RegisteredDate: RegisteredDate
HostProvider: HostProvider
alertDetailsOverride:
alertDisplayNameFormat: "CYFIRMA - Medium Severity Alert - Malicious Domain Impersonation of Corporate Brand - Domain Squat: {{DomainSquat}} "
alertDescriptionFormat: "{{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
CyfirmaBIDomainITAssetAlerts_CL
Stage 2: where
| where severity == 'High' and TimeGenerated between (ago(timeFrame) .. now())
Stage 3: extend
| extend
Description=description,
FirstSeen=first_seen,
LastSeen=last_seen,
RiskScore=risk_score,
AlertUID=alert_uid,
UID=uid,
Domain=domain,
DRDomain=dr_domain,
DRSubDomain=dr_sub_domain,
DomainSquat=signature,
HostProvider=host_provider,
RegisteredDate=registered_date,
CreatedDate=created_date,
ThreatActor=suspected_threat_actor,
ProviderName='CYFIRMA',
ProductName='DeCYFIR/DeTCT'
Stage 4: project
| project
TimeGenerated,
Description,
RiskScore,
FirstSeen,
LastSeen,
AlertUID,
UID,
Domain,
DRDomain,
DRSubDomain,
DomainSquat,
HostProvider,
RegisteredDate,
CreatedDate,
ThreatActor,
ProductName,
ProviderName
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 |
CreatedDate | project |
DRDomain | project |
DRSubDomain | project |
Description | project |
Domain | project |
DomainSquat | project |
FirstSeen | project |
HostProvider | project |
LastSeen | project |
ProductName | project |
ProviderName | project |
RegisteredDate | project |
RiskScore | project |
ThreatActor | project |
TimeGenerated | project |
UID | project |
CYFIRMA - Data Breach and Web Monitoring - Phishing Campaign Detection 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.
"Detects phishing campaigns targeting enterprise domains, as identified through CYFIRMA's Data Breach and Dark Web Monitoring. These alerts may include malicious URLs used for credential harvesting, domain impersonation, or social engineering. Immediate triage and takedown actions are recommended."
MITRE ATT&CK Coverage
Rule Body
id: 00c7b41c-ddeb-4c49-acd7-2f7897e27fb4
name: CYFIRMA - Data Breach and Web Monitoring - Phishing Campaign Detection Rule
description: |
"Detects phishing campaigns targeting enterprise domains, as identified through CYFIRMA's Data Breach and Dark Web Monitoring.
These alerts may include malicious URLs used for credential harvesting, domain impersonation, or social engineering.
Immediate triage and takedown actions are recommended."
version: 1.0.1
kind: Scheduled
severity: Medium
requiredDataConnectors:
- connectorId: CyfirmaDigitalRiskAlertsConnector
dataTypes:
- CyfirmaDBWMPhishingAlerts_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- InitialAccess
- Exfiltration
relevantTechniques:
- T1566.001
- T1566.002
- T1566.003
query: |
// Medium severity - Data Breach and Web Monitoring - Phishing Campaign Detection
let timeFrame = 5m;
CyfirmaDBWMPhishingAlerts_CL
| where severity == 'High' and TimeGenerated between (ago(timeFrame) .. now())
| extend
Description=description,
FirstSeen=first_seen,
LastSeen=last_seen,
RiskScore=risk_score,
AlertUID=alert_uid,
UID=uid,
AssetType=asset_type,
AssetValue=signature,
Source=source,
Impact='',
Recommendation='',
ProviderName='CYFIRMA',
ProductName='DeCYFIR/DeTCT',
AlertTitle=Alert_title
| project
TimeGenerated,
Description,
RiskScore,
FirstSeen,
LastSeen,
AlertUID,
UID,
AssetType,
AssetValue,
Source,
Impact,
Recommendation,
ProductName,
ProviderName,
AlertTitle
customDetails:
AssetType: AssetType
AssetValue: AssetValue
RiskScore: RiskScore
Source: Source
Impact: Impact
Description: Description
Recommendation: Recommendation
FirstSeen: FirstSeen
LastSeen: LastSeen
AlertUID: AlertUID
UID: UID
TimeGenerated: TimeGenerated
alertDetailsOverride:
alertDisplayNameFormat: "CYFIRMA - Medium Severity Alert: Phishing Campaign Detection - {{AlertTitle}} "
alertDescriptionFormat: "{{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
CyfirmaDBWMPhishingAlerts_CL
Stage 2: where
| where severity == 'High' and TimeGenerated between (ago(timeFrame) .. now())
Stage 3: extend
| extend
Description=description,
FirstSeen=first_seen,
LastSeen=last_seen,
RiskScore=risk_score,
AlertUID=alert_uid,
UID=uid,
AssetType=asset_type,
AssetValue=signature,
Source=source,
Impact='',
Recommendation='',
ProviderName='CYFIRMA',
ProductName='DeCYFIR/DeTCT',
AlertTitle=Alert_title
Stage 4: project
| project
TimeGenerated,
Description,
RiskScore,
FirstSeen,
LastSeen,
AlertUID,
UID,
AssetType,
AssetValue,
Source,
Impact,
Recommendation,
ProductName,
ProviderName,
AlertTitle
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 |
|---|---|
AlertTitle | project |
AlertUID | project |
AssetType | project |
AssetValue | project |
Description | project |
FirstSeen | project |
Impact | project |
LastSeen | project |
ProductName | project |
ProviderName | project |
Recommendation | project |
RiskScore | project |
Source | project |
TimeGenerated | project |
UID | project |
Failover Plan Settings Updated
#Detects when failover plan settings are updated in Veeam Backup & Replication. This might indicate configuration changes that require review.
Rule Body
id: a053e7ba-e60c-482a-bbd8-89a25c53d905
name: Failover Plan Settings Updated
description: Detects when failover plan settings are updated in Veeam Backup & Replication.
This might indicate configuration changes that require review.
severity: Informational
status: Available
requiredDataConnectors:
- connectorId: Syslog
dataTypes:
- Syslog
- connectorId: SyslogAma
dataTypes:
- Syslog
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
aggregationKind: AlertPerResult
tactics: []
relevantTechniques: []
query: "Veeam_GetSecurityEvents\n| where instanceId == 26000\n| project\n Date\
\ = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),\n DataSource = original_host,\n\
\ EventId = instanceId,\n [\"User Name\"] = user,\n MessageDetails = Description,\n\
\ Severity = SeverityDescription"
version: 1.0.1
kind: Scheduled
customDetails:
Date: Date
VbrHostName: DataSource
EventId: EventId
MessageDetails: MessageDetails
Severity: Severity
Stages and Predicates
Stage 1: source
Veeam_GetSecurityEvents
Stage 2: where
| where instanceId == 26000
Stage 3: project
| project
Date = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),
DataSource = original_host,
EventId = instanceId,
["User Name"] = user,
MessageDetails = Description,
Severity = SeverityDescription
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
instanceId | eq |
| field:"instanceId" kind:eq value:"26000" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DataSource | project |
Date | project |
EventId | project |
MessageDetails | project |
Severity | project |
User Name | project |
Guardian- Input Rate Limiter Policy Violation Detection
#'This alert creates an incident when Input Rate Limiter Policy Violation detected from the Guardian.'
Rule Body
id: 5d736bd5-b8ff-493c-bfbb-19674903fced
name: Guardian- Input Rate Limiter Policy Violation Detection
description: |
'This alert creates an incident when Input Rate Limiter Policy Violation detected from the Guardian.'
severity: Informational
status: Available
requiredDataConnectors:
- connectorId: BoschAIShield
dataTypes:
- Guardian
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: |
Guardian
| where PolicyViolatedControlFeature =~ 'Input Rate Limiter'
| where Severity =~ 'Informational'
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- Input Rate Limiter Policy Violation detection
alertDescriptionFormat: |
This query detects Input Rate Limiter 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 =~ 'Input Rate Limiter'
Stage 3: where
| where Severity =~ 'Informational'
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
PolicyViolatedControlFeature | eq |
| field:"PolicyViolatedControlFeature" kind:eq value:"Input Rate Limiter" |
Severity | eq |
| field:"Severity" kind:eq value:"Informational" |
M2131_EventLogManagementPostureChanged_EL2
#'This alert is desinged to monitor Azure policies aligned with the Maturity Model for Event Log Management (M-21-31) standard. The alert triggers when EL2 policy compliance falls below 70% within a 1 week timeframe.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Discovery |
Rule Body
id: e1bb07c4-066b-4069-9b8e-f5275c592b6d
name: M2131_EventLogManagementPostureChanged_EL2
description: |
'This alert is desinged to monitor Azure policies aligned with the Maturity Model for Event Log Management (M-21-31) standard. The alert triggers when EL2 policy compliance falls below 70% within a 1 week timeframe.'
severity: Medium
status: Available
requiredDataConnectors: []
queryFrequency: 7d
queryPeriod: 7d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Discovery
relevantTechniques:
- T1082
query: |
SecurityRecommendation
| where RecommendationDisplayName <> ""
| extend MaturityLevel=iff(RecommendationDisplayName has_any("agent","extension","retention","logs encryption","collect","retained","log profile","CloudTrail","metric","AWS","GCP","DNS","Auditing","Flow","logging","usage"), "Event Logging (EL0)",
iff(RecommendationDisplayName has_any("container registries","logic apps","Enhanced monitoring","IoT Hub","Event Hub","App Service", "Kubernetes","updates","email", "automation", "adaptive"), "Advanced Event Logging (EL3)",
iff(RecommendationDisplayName has_any("signatures","CMK","CMEK","double encryption","managed key","KMS","container","Watcher"), "Intermediate Event Logging (EL2)",
iff(RecommendationDisplayName has_any("Exploit Guard","endpoint protection","Antimalware","health","VPC","Defender","Vulnerabilities","vulnerability","diagnostic","Key","activity log alert",""), "Basic Event Logging (EL1)","Other"))))
| summarize arg_max(TimeGenerated, *) by RecommendationDisplayName, AssessedResourceId, MaturityLevel
| summarize Failed = countif(RecommendationState == "Unhealthy"), Passed = countif(RecommendationState == "Healthy"), Total = countif(RecommendationState == "Healthy" or RecommendationState == "Unhealthy") by MaturityLevel
| extend PassedControls = (Passed/todouble(Total))*100
| extend RemediationLink = strcat('https://portal.azure.com/#blade/Microsoft_Azure_Security/SecurityMenuBlade/22')
| project MaturityLevel, Total, PassedControls, Passed, Failed, RemediationLink, LastObserved=now()
| where MaturityLevel <> ''
| where MaturityLevel == "Intermediate Event Logging (EL2)"
| where PassedControls < 70
//Adjust Either FailedRatePercentage or PasedRatePercentage Thresholds within Organizational Needs
| sort by PassedControls desc
| extend URLCustomEntity = RemediationLink
entityMappings:
- entityType: URL
fieldMappings:
- identifier: Url
columnName: URLCustomEntity
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
SecurityRecommendation
Stage 2: where
| where RecommendationDisplayName <> ""
Stage 3: extend
| extend MaturityLevel=iff(RecommendationDisplayName has_any("agent","extension","retention","logs encryption","collect","retained","log profile","CloudTrail","metric","AWS","GCP","DNS","Auditing","Flow","logging","usage"), "Event Logging (EL0)",
iff(RecommendationDisplayName has_any("container registries","logic apps","Enhanced monitoring","IoT Hub","Event Hub","App Service", "Kubernetes","updates","email", "automation", "adaptive"), "Advanced Event Logging (EL3)",
iff(RecommendationDisplayName has_any("signatures","CMK","CMEK","double encryption","managed key","KMS","container","Watcher"), "Intermediate Event Logging (EL2)",
iff(RecommendationDisplayName has_any("Exploit Guard","endpoint protection","Antimalware","health","VPC","Defender","Vulnerabilities","vulnerability","diagnostic","Key","activity log alert",""), "Basic Event Logging (EL1)","Other"))))
MaturityLevel =(RecommendationDisplayName has "agent" or RecommendationDisplayName has "extension" or RecommendationDisplayName has "retention" or RecommendationDisplayName has "logs encryption" or RecommendationDisplayName has "collect" or RecommendationDisplayName has "retained" or RecommendationDisplayName has "log profile" or RecommendationDisplayName has "CloudTrail" or RecommendationDisplayName has "metric" or RecommendationDisplayName has "AWS" or RecommendationDisplayName has "GCP" or RecommendationDisplayName has "DNS" or RecommendationDisplayName has "Auditing" or RecommendationDisplayName has "Flow" or RecommendationDisplayName has "logging" or RecommendationDisplayName has "usage")"Event Logging (EL0)"iff((RecommendationDisplayName has_any ("container registries", "logic apps", "Enhanced monitoring", "IoT Hub", "Event Hub", "App Service", "Kubernetes", "updates", "email", "automation", "adaptive")), "Advanced Event Logging (EL3)", iff((RecommendationDisplayName has_any ("signatures", "CMK", "CMEK", "double encryption", "managed key", "KMS", "container", "Watcher")), "Intermediate Event Logging (EL2)", iff((RecommendationDisplayName has_any ("Exploit Guard", "endpoint protection", "Antimalware", "health", "VPC", "Defender", "Vulnerabilities", "vulnerability", "diagnostic", "Key", "activity log alert", "")), "Basic Event Logging (EL1)", "Other")))Stage 4: summarize
| summarize arg_max(TimeGenerated, *) by RecommendationDisplayName, AssessedResourceId, MaturityLevel
Stage 5: summarize
| summarize Failed = countif(RecommendationState == "Unhealthy"), Passed = countif(RecommendationState == "Healthy"), Total = countif(RecommendationState == "Healthy" or RecommendationState == "Unhealthy") by MaturityLevel
Stage 6: extend
| extend PassedControls = (Passed/todouble(Total))*100
Stage 7: extend
| extend RemediationLink = strcat('https://portal.azure.com/#blade/Microsoft_Azure_Security/SecurityMenuBlade/22')
Stage 8: project
| project MaturityLevel, Total, PassedControls, Passed, Failed, RemediationLink, LastObserved=now()
Stage 9: where
| where MaturityLevel <> ''
Stage 10: where
| where MaturityLevel == "Intermediate Event Logging (EL2)"
Stage 11: where
| where PassedControls < 70
Stage 12: sort
| sort by PassedControls desc
Stage 13: extend
| extend URLCustomEntity = RemediationLink
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
MaturityLevel | eq |
| field:"MaturityLevel" kind:eq value:"Intermediate Event Logging (EL2)" |
PassedControls | lt |
| field:"PassedControls" kind:lt value:"70" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Failed | project |
LastObserved | project |
MaturityLevel | project |
Passed | project |
PassedControls | project |
RemediationLink | project |
Total | project |
URLCustomEntity | extend |
Mail.Read Permissions Granted to Application
#This query look for applications that have been granted (Delegated or App/Role) permissions to Read Mail (Permissions field has Mail.Read) and subsequently has been consented to. This can help identify applications that have been abused to gain access to mailboxes.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence |
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: 2560515c-07d1-434e-87fb-ebe3af267760
name: Mail.Read Permissions Granted to Application
description: |
'This query look for applications that have been granted (Delegated or App/Role) permissions to Read Mail (Permissions field has Mail.Read) and subsequently has been consented to. This can help identify applications that have been abused to gain access to mailboxes.'
severity: Medium
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- AuditLogs
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- Persistence
relevantTechniques:
- T1098
tags:
- Solorigate
- NOBELIUM
query: |
AuditLogs
| where Category =~ "ApplicationManagement"
| where ActivityDisplayName has_any ("Add delegated permission grant","Add app role assignment to service principal")
| where Result =~ "success"
| where tostring(InitiatedBy.user.userPrincipalName) has "@" or tostring(InitiatedBy.app.displayName) has "@"
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type =~ "ServicePrincipal" and array_length(TargetResource.modifiedProperties) > 0 and isnotnull(TargetResource.displayName)
| extend props = TargetResource.modifiedProperties,
Type = tostring(TargetResource.type),
PermissionsAddedTo = tostring(TargetResource.displayName)
)
| mv-apply Property = props on
(
where Property.displayName =~ "DelegatedPermissionGrant.Scope"
| extend DisplayName = tostring(Property.displayName), Permissions = trim('"',tostring(Property.newValue))
)
| where Permissions has_any ("Mail.Read", "Mail.ReadWrite")
| mv-apply AdditionalDetail = AdditionalDetails on
(
where AdditionalDetail.key =~ "User-Agent"
| extend InitiatingUserAgent = 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))
| project-away props, TargetResource, AdditionalDetail, Property
| join kind=leftouter(
AuditLogs
| where ActivityDisplayName has "Consent to application"
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type =~ "ServicePrincipal"
| extend AppName = tostring(TargetResource.displayName),
AppId = tostring(TargetResource.id)
)
| project AppName, AppId, CorrelationId) on CorrelationId
| project-away CorrelationId1
| project-reorder TimeGenerated, OperationName, InitiatingUserPrincipalName, InitiatingAadUserId, InitiatingAppName, InitiatingAppServicePrincipalId, InitiatingIpAddress, InitiatingUserAgent, PermissionsAddedTo, Permissions, AppName, AppId, CorrelationId
| 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: Account
fieldMappings:
- identifier: AadUserId
columnName: InitiatingAadUserId
- entityType: Account
fieldMappings:
- identifier: AadUserId
columnName: InitiatingAppServicePrincipalId
- entityType: IP
fieldMappings:
- identifier: Address
columnName: InitiatingIpAddress
version: 1.0.4
kind: Scheduled
Stages and Predicates
Stage 1: source
AuditLogs
Stage 2: where
| where Category =~ "ApplicationManagement"
Stage 3: where
| where ActivityDisplayName has_any ("Add delegated permission grant","Add app role assignment to service principal")
Stage 4: where
| where Result =~ "success"
Stage 5: where
| where tostring(InitiatedBy.user.userPrincipalName) has "@" or tostring(InitiatedBy.app.displayName) has "@"
Stage 6: kusto:mv-apply
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type =~ "ServicePrincipal" and array_length(TargetResource.modifiedProperties) > 0 and isnotnull(TargetResource.displayName)
| extend props = TargetResource.modifiedProperties,
Type = tostring(TargetResource.type),
PermissionsAddedTo = tostring(TargetResource.displayName)
)
Stage 7: kusto:mv-apply
| mv-apply Property = props on
(
where Property.displayName =~ "DelegatedPermissionGrant.Scope"
| extend DisplayName = tostring(Property.displayName), Permissions = trim('"',tostring(Property.newValue))
)
Stage 8: where
| where Permissions has_any ("Mail.Read", "Mail.ReadWrite")
Stage 9: kusto:mv-apply
| mv-apply AdditionalDetail = AdditionalDetails on
(
where AdditionalDetail.key =~ "User-Agent"
| extend InitiatingUserAgent = tostring(AdditionalDetail.value)
)
Stage 10: 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 11: project-away
| project-away props, TargetResource, AdditionalDetail, Property
Stage 12: join
| join kind=leftouter(
AuditLogs
| where ActivityDisplayName has "Consent to application"
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type =~ "ServicePrincipal"
| extend AppName = tostring(TargetResource.displayName),
AppId = tostring(TargetResource.id)
)
| project AppName, AppId, CorrelationId) on CorrelationId
Stage 13: project-away
project-away CorrelationId1
Stage 14: project-reorder
project-reorder
Stage 15: 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 |
|---|---|
InitiatingAppName | extend |
InitiatingAppServicePrincipalId | extend |
InitiatingUserPrincipalName | extend |
InitiatingAadUserId | extend |
InitiatingIpAddress | extend |
Name | extend |
UPNSuffix | extend |
NRT Multiple users email forwarded to same destination
#'Identifies when multiple (more than one) users mailboxes are configured to forward to the same destination. This could be an attacker-controlled destination mailbox configured to collect mail from multiple compromised user accounts.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Collection | |
| Exfiltration |
Rules Detecting the Same Action
These rules filter on the same operation.
Rule Body
id: 3b05727d-a8d1-477d-bbdd-d957da96ac7b
name: NRT Multiple users email forwarded to same destination
description: |
'Identifies when multiple (more than one) users mailboxes are configured to forward to the same destination.
This could be an attacker-controlled destination mailbox configured to collect mail from multiple compromised user accounts.'
severity: Medium
requiredDataConnectors:
- connectorId: Office365
dataTypes:
- OfficeActivity
tactics:
- Collection
- Exfiltration
relevantTechniques:
- T1114
- T1020
query: |
OfficeActivity
| where OfficeWorkload =~ "Exchange"
| where Parameters has_any ("ForwardTo", "RedirectTo", "ForwardingSmtpAddress")
| mv-apply DynamicParameters = todynamic(Parameters) on (summarize ParsedParameters = make_bag(pack(tostring(DynamicParameters.Name), DynamicParameters.Value)))
| evaluate bag_unpack(ParsedParameters, columnsConflict='replace_source')
| extend DestinationMailAddress = tolower(case(
isnotempty(column_ifexists("ForwardTo", "")), column_ifexists("ForwardTo", ""),
isnotempty(column_ifexists("RedirectTo", "")), column_ifexists("RedirectTo", ""),
isnotempty(column_ifexists("ForwardingSmtpAddress", "")), trim_start(@"smtp:", column_ifexists("ForwardingSmtpAddress", "")),
""))
| where isnotempty(DestinationMailAddress)
| mv-expand split(DestinationMailAddress, ";")
| extend ClientIPValues = extract_all(@'\[?(::ffff:)?(?P<IPAddress>(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P<Port>\d+))?', dynamic(["IPAddress", "Port"]), ClientIP)[0]
| extend ClientIP = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1])
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), DistinctUserCount = dcount(UserId), UserId = make_set(UserId, 250), Ports = make_set(Port, 250), EventCount = count() by tostring(DestinationMailAddress), ClientIP
| where DistinctUserCount > 1
| mv-expand UserId to typeof(string)
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: UserId
- entityType: IP
fieldMappings:
- identifier: Address
columnName: ClientIP
version: 1.0.3
kind: NRT
metadata:
source:
kind: Community
author:
name: Pete Bryan
support:
tier: Community
categories:
domains: [ "Security - Threat Protection" ]
Stages and Predicates
Stage 1: source
OfficeActivity
Stage 2: where
| where OfficeWorkload =~ "Exchange"
Stage 3: where
| where Parameters has_any ("ForwardTo", "RedirectTo", "ForwardingSmtpAddress")
Stage 4: kusto:mv-apply
| mv-apply DynamicParameters = todynamic(Parameters) on (summarize ParsedParameters = make_bag(pack(tostring(DynamicParameters.Name), DynamicParameters.Value)))
Stage 5: evaluate
| evaluate bag_unpack(ParsedParameters, columnsConflict='replace_source')
Stage 6: extend
| extend DestinationMailAddress = tolower(case(
isnotempty(column_ifexists("ForwardTo", "")), column_ifexists("ForwardTo", ""),
isnotempty(column_ifexists("RedirectTo", "")), column_ifexists("RedirectTo", ""),
isnotempty(column_ifexists("ForwardingSmtpAddress", "")), trim_start(@"smtp:", column_ifexists("ForwardingSmtpAddress", "")),
""))
Stage 7: where
| where isnotempty(DestinationMailAddress)
Stage 8: mv-expand
| mv-expand split(DestinationMailAddress, ";")
Stage 9: extend
| extend ClientIPValues = extract_all(@'\[?(::ffff:)?(?P<IPAddress>(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P<Port>\d+))?', dynamic(["IPAddress", "Port"]), ClientIP)[0]
Stage 10: extend
| extend ClientIP = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1])
Stage 11: summarize
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), DistinctUserCount = dcount(UserId), UserId = make_set(UserId, 250), Ports = make_set(Port, 250), EventCount = count() by tostring(DestinationMailAddress), ClientIP
Stage 12: where
| where DistinctUserCount > 1
Stage 13: mv-expand
| mv-expand UserId to typeof(string)
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DestinationMailAddress | is_not_null | field:"DestinationMailAddress" kind:is_not_null | |
DistinctUserCount | gt |
| field:"DistinctUserCount" kind:gt value:"1" |
OfficeWorkload | eq |
| field:"m365::Workload" kind:eq value:"Exchange" |
Parameters | match |
| field:"m365::Parameters" kind:match |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ClientIP | summarize |
DistinctUserCount | summarize |
EndTime | summarize |
EventCount | summarize |
Ports | summarize |
StartTime | summarize |
UserId | summarize |
Oracle - Private IP in URL
#'Detects requests to unusual URL'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: 153ce6d8-5740-11ec-bf63-0242ac130002
name: Oracle - Private IP in URL
description: |
'Detects requests to unusual URL'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: CustomLogsAma
dataTypes:
- OracleWebLogicServer_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1190
- T1133
query: |
OracleWebLogicServerEvent
| where isnotempty(UrlOriginal)
| where UrlOriginal matches regex @'(10\.\d{1,3}\.\d{1,3}\.\d{1,3})|(172\.1[6-9]\.\d{1,3}\.\d{1,3})|(172\.2[0-9]\.\d{1,3}\.\d{1,3})|(172\.3[0-1]\.\d{1,3}\.\d{1,3})|(192\.168\.\d{1,3}\.\d{1,3})'
| extend IPCustomEntity = SrcIpAddr, UrlCustomEntity = UrlOriginal
entityMappings:
- entityType: URL
fieldMappings:
- identifier: Url
columnName: UrlCustomEntity
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPCustomEntity
version: 1.0.3
kind: Scheduled
Stages and Predicates
Stage 1: source
OracleWebLogicServerEvent
Stage 2: where
| where isnotempty(UrlOriginal)
Stage 3: where
| where UrlOriginal matches regex @'(10\.\d{1,3}\.\d{1,3}\.\d{1,3})|(172\.1[6-9]\.\d{1,3}\.\d{1,3})|(172\.2[0-9]\.\d{1,3}\.\d{1,3})|(172\.3[0-1]\.\d{1,3}\.\d{1,3})|(192\.168\.\d{1,3}\.\d{1,3})'
Stage 4: extend
| extend IPCustomEntity = SrcIpAddr, UrlCustomEntity = UrlOriginal
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
UrlOriginal | is_not_null | field:"UrlOriginal" kind:is_not_null | |
UrlOriginal | regex_match |
| field:"UrlOriginal" kind:regex_match |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
IPCustomEntity | extend |
UrlCustomEntity | extend |
Palo Alto Prisma Cloud - Network ACL allow ingress traffic to server administration ports
#'Detects Network ACLs allow ingress traffic to server administration ports.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: df89f4bf-720e-41c5-a209-15e41e400d35
name: Palo Alto Prisma Cloud - Network ACL allow ingress traffic to server administration ports
description: |
'Detects Network ACLs allow ingress traffic to server administration ports.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: PaloAltoPrismaCloud
dataTypes:
- PaloAltoPrismaCloud
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1133
query: |
PaloAltoPrismaCloud
| where Reason =~ 'NEW_ALERT'
| where Status =~ 'open'
| where AlertMessage has 'Network ACLs allow ingress traffic to server administration ports'
| extend AccountCustomEntity = UserName
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountCustomEntity
version: 1.0.1
kind: Scheduled
Stages and Predicates
Stage 1: source
PaloAltoPrismaCloud
Stage 2: where
| where Reason =~ 'NEW_ALERT'
Stage 3: where
| where Status =~ 'open'
Stage 4: where
| where AlertMessage has 'Network ACLs allow ingress traffic to server administration ports'
Stage 5: extend
| extend AccountCustomEntity = UserName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AlertMessage | match |
| field:"AlertMessage" kind:match value:"Network ACLs allow ingress traffic to server administration ports" |
Reason | eq |
| field:"Reason" kind:eq value:"NEW_ALERT" |
Status | eq |
| field:"Status" kind:eq value:"open" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountCustomEntity | extend |
Password Spraying
#This query detects a password spraying attack, where a single machine has performed a large number of failed login attempts, with a large number of different accounts. For each account, the attacker uses just a few attempts to prevent account lockout. This query uses the DeviceLogonEvents per machine to detect a password spraying attacks. The machine against which the password spraying is performed (can be DC, a server or even an endpoint) needs to be enrolled in Microsoft Defender for Endpoint.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
Telemetry Coverage
Rule Body
id: e00f72ab-fea1-4a31-9ecc-eea6397cd38d
name: Password Spraying
description: |
This query detects a password spraying attack, where a single machine has performed a large number of failed login attempts, with a large number of different accounts.
For each account, the attacker uses just a few attempts to prevent account lockout. This query uses the DeviceLogonEvents per machine to detect a password spraying attacks.
The machine against which the password spraying is performed (can be DC, a server or even an endpoint) needs to be enrolled in Microsoft Defender for Endpoint.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: MicrosoftThreatProtection
dataTypes:
- DeviceLogonEvents
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
relevantTechniques:
- T1110.003
query: |
let thresholdForUniqueFailedAccounts = 20;
let upperBoundOfFailedLogonsPerAccount = 10;
let ratioSuccessFailedLogons = 0.5;
let timeframe = 1d;
DeviceLogonEvents
| where Timestamp >= ago(timeframe)
| where LogonType != "Unlock" and ActionType in ("LogonSuccess", "LogonFailed")
| where not(isempty( RemoteIP) and isempty( RemoteDeviceName))
| extend LocalLogon=parse_json(AdditionalFields)
| where RemoteIPType != "Loopback"
| summarize SuccessLogonCount = countif(ActionType == "LogonSuccess"), FailedLogonCount = countif(ActionType == "LogonFailed"),
UniqueAccountFailedLogons=dcountif(AccountName, ActionType == "LogonFailed"), FirstFailed=minif(Timestamp, ActionType == "LogonFailed"),
LastFailed=maxif(Timestamp, ActionType == "LogonFailed"), LastTimestamp=arg_max(Timestamp, tostring(ReportId)) by RemoteIP, DeviceName // RemoteIP is the source of the logon attempt.
| project-rename IPAddress=RemoteIP
| where UniqueAccountFailedLogons > thresholdForUniqueFailedAccounts and SuccessLogonCount*ratioSuccessFailedLogons < FailedLogonCount and UniqueAccountFailedLogons*upperBoundOfFailedLogonsPerAccount > FailedLogonCount
entityMappings:
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: DeviceName
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPAddress
version: 1.0.1
kind: Scheduled
Stages and Predicates
Parameters
let thresholdForUniqueFailedAccounts = 20;
let upperBoundOfFailedLogonsPerAccount = 10;
let ratioSuccessFailedLogons = 0.5;
let timeframe = 1d;
Stage 1: source
DeviceLogonEvents
Stage 2: where
| where Timestamp >= ago(timeframe)
Stage 3: where
| where LogonType != "Unlock" and ActionType in ("LogonSuccess", "LogonFailed")
Stage 4: where
| where not(isempty( RemoteIP) and isempty( RemoteDeviceName))
Stage 5: extend
| extend LocalLogon=parse_json(AdditionalFields)
Stage 6: where
| where RemoteIPType != "Loopback"
Stage 7: summarize
| summarize SuccessLogonCount = countif(ActionType == "LogonSuccess"), FailedLogonCount = countif(ActionType == "LogonFailed"),
UniqueAccountFailedLogons=dcountif(AccountName, ActionType == "LogonFailed"), FirstFailed=minif(Timestamp, ActionType == "LogonFailed"),
LastFailed=maxif(Timestamp, ActionType == "LogonFailed"), LastTimestamp=arg_max(Timestamp, tostring(ReportId)) by RemoteIP, DeviceName
Stage 8: project-rename
| project-rename IPAddress=RemoteIP
Stage 9: where
| where UniqueAccountFailedLogons > thresholdForUniqueFailedAccounts and SuccessLogonCount*ratioSuccessFailedLogons < FailedLogonCount and UniqueAccountFailedLogons*upperBoundOfFailedLogonsPerAccount > FailedLogonCount
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ActionType | in |
| field:"ActionType" kind:in |
LogonType | ne |
| field:"LogonType" kind:ne value:"Unlock" |
RemoteIPType | ne |
| field:"RemoteIPType" kind:ne value:"Loopback" |
UniqueAccountFailedLogons | gt |
| field:"UniqueAccountFailedLogons" kind:gt value:"20" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
RemoteDeviceName | is_null | excludes:RemoteDeviceName | |
RemoteIP | is_null | excludes:RemoteIP |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DeviceName | summarize |
FailedLogonCount | summarize |
FirstFailed | summarize |
LastFailed | summarize |
LastTimestamp | summarize |
RemoteIP | summarize |
SuccessLogonCount | summarize |
UniqueAccountFailedLogons | summarize |
IPAddress | project-rename |
Potential Password Spray Attack
#'This query searches for failed attempts to log in from more than 15 various users within a 5 minutes timeframe from the same source. This is a potential indication of a password spray attack.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
Rule Body
id: 64d16e62-1a17-4a35-9ea7-2b9fe6f07118
name: Potential Password Spray Attack
description: |
'This query searches for failed attempts to log in from more than 15 various users within a 5 minutes timeframe from the same source. This is a potential indication of a password spray attack.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: SalesforceServiceCloudCCPDefinition
dataTypes:
- SalesforceServiceCloud
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
relevantTechniques:
- T1110
query: |
let FailureThreshold = 15;
SalesforceServiceCloud
| where EventType =~ 'Login' and LoginStatus != 'LOGIN_NO_ERROR'
| where LoginStatus in~ ('LOGIN_ERROR_INVALID_PASSWORD', 'LOGIN_ERROR_SSO_PWD_INVALID')
| extend TimestampDerived = todatetime(TimestampDerived)
| summarize UserCount=dcount(UserId), Users = make_set(UserId,100) by ClientIp, bin(TimestampDerived, 5m)
| where UserCount > FailureThreshold
customDetails:
Users: Users
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: ClientIp
version: 1.0.4
kind: Scheduled
Stages and Predicates
Parameters
let FailureThreshold = 15;
Stage 1: source
SalesforceServiceCloud
Stage 2: where
| where EventType =~ 'Login' and LoginStatus != 'LOGIN_NO_ERROR'
Stage 3: where
| where LoginStatus in~ ('LOGIN_ERROR_INVALID_PASSWORD', 'LOGIN_ERROR_SSO_PWD_INVALID')
Stage 4: extend
| extend TimestampDerived = todatetime(TimestampDerived)
Stage 5: summarize
| summarize UserCount=dcount(UserId), Users = make_set(UserId,100) by ClientIp, bin(TimestampDerived, 5m)
Stage 6: where
| where UserCount > FailureThreshold
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventType | eq |
| field:"EventType" kind:eq value:"Login" |
LoginStatus | in |
| field:"LoginStatus" kind:in |
LoginStatus | ne |
| field:"LoginStatus" kind:ne value:"LOGIN_NO_ERROR" |
UserCount | gt |
| field:"UserCount" kind:gt value:"15" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ClientIp | summarize |
UserCount | summarize |
Users | summarize |
Power Apps - Bulk sharing of Power Apps to newly created guest users
#Identifies unusual bulk sharing, based on a predefined threshold in the query, of Power Apps to newly created Microsoft Entra guest users.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Resource Development | |
| Initial Access | |
| Lateral Movement |
Telemetry Coverage
Rules Detecting the Same Action
These rules filter on the same operation.
- [Entra ID] Authentication Method Changed for Privileged Account (Kusto)
- Account Created and Deleted in Short Timeframe (Kusto)
- Account Created And Deleted Within A Close Time Frame (Sigma)
- Account created from non-approved sources (Kusto)
- Account created or deleted by non-approved user (Kusto)
- Addition of a Temporary Access Pass to a Privileged Account (Kusto)
- Authentication Method Changed for Privileged Account (Kusto)
- Authentication Methods Changed for Privileged Account (Kusto)
Rule Body
id: 943acfa0-9285-4eb0-a9c0-42e36177ef19
kind: Scheduled
name: Power Apps - Bulk sharing of Power Apps to newly created guest users
description: Identifies unusual bulk sharing, based on a predefined threshold in the
query, of Power Apps to newly created Microsoft Entra guest users.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: PowerPlatformAdmin
dataTypes:
- PowerPlatformAdminActivity
- connectorId: AzureActiveDirectory
dataTypes:
- AuditLogs
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- ResourceDevelopment
- InitialAccess
- LateralMovement
relevantTechniques:
- T1587
- T1566
- T1534
query: |
////////////
// threshold = If the number of unique accounts that a power app is shared with is greater than
// threshold than it'll trigger an alert. A threshold of 5 is good to start with.
// However, if this is giving too many false positives, please adjust the threshold.
////////////
let threshold = 5;
////////////
// Please replace the allowed_domains with a list of domains of your partners/sibling orgs
// with whom you generally share power apps with. This will allow us to filter
// legitimate bulk sharing attempts. Avoid using domains such as gmail, outlook, etc.
///////////
let allowed_domains = pack_array("contoso.com");
let query_frequency = 1h;
let query_lookback = 14d;
PowerPlatformAdminActivity
| where TimeGenerated >= ago(query_frequency)
| where EventOriginalType == "PowerAppPermissionEdited"
| extend Properties = tostring(PropertyCollection)
| extend AppId = extract(@'"powerplatform.analytics.resource.power_app.id","Value":"([^"]+)"', 1, Properties)
| extend AppId = tolower(replace_string(AppId, '/providers/Microsoft.PowerApps/apps/', ''))
| extend TargetPrincipalId = extract(@'"targetuser.id","Value":"([^"]+)"', 1, Properties)
| join kind=leftouter (
AuditLogs
| where ActivityDateTime >= ago(query_lookback)
| where SourceSystem =~ "Azure AD" and OperationName == "Invite external user"
| where Result =~ "success"
| extend InvitedOrgEmail = tostring(parse_json(AdditionalDetails[5])['value'])
| extend InvitedOrgDomain = tostring(split(InvitedOrgEmail, "@")[1])
| where not(InvitedOrgDomain has_any(allowed_domains))
| extend
InvitedById = tostring(parse_json(InitiatedBy)['user']['id']),
InvitedByUPN = tostring(parse_json(InitiatedBy)['user']['userPrincipalName']),
InvitedEmail = tostring(parse_json(TargetResources[0])['userPrincipalName']),
InvitedId = tostring(parse_json(TargetResources[0])['id'])
| summarize by InvitedById, InvitedByUPN, InvitedEmail, InvitedId, InvitedOrgDomain)
on $left.TargetPrincipalId == $right.InvitedId
| where isnotempty(InvitedId)
| summarize
StartTime = min(TimeGenerated),
EndTime = max(TimeGenerated),
TargetedUsersCount=dcount(TargetPrincipalId),
TargetedObjectIds = make_set(TargetPrincipalId, 1000),
InvitedDomains = make_set(InvitedOrgDomain, 1000),
InvitedEmailAddresses = make_set(InvitedEmail, 1000)
by AppId, InvitedById, InvitedByUPN
| extend
PowerAppsEntityId = 27593,
AccountName = tostring(split(InvitedByUPN, '@')[0]),
UPNSuffix = tostring(split(InvitedByUPN, '@')[1])
| project
StartTime,
EndTime,
InvitedByUPN,
InvitedById,
InvitedDomains,
InvitedEmailAddresses,
TargetedUsersCount,
TargetedObjectIds,
AppId,
PowerAppsEntityId,
AccountName,
UPNSuffix
eventGroupingSettings:
aggregationKind: SingleAlert
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: CloudApplication
fieldMappings:
- identifier: AppId
columnName: PowerAppsEntityId
- identifier: InstanceName
columnName: AppId
alertDetailsOverride:
alertDisplayNameFormat: Power Apps - app shared with recently created external guest
accounts
alertDescriptionFormat: '{{InvitedByUPN}} shared an app with {{TargetedUsersCount}}
recently added guest user accounts that are not on the list of allowed partner
domains. List of domain s {{InvitedDomains}}'
customDetails:
PowerAppsApp: AppId
version: 3.2.0
Stages and Predicates
Parameters
let threshold = 5;
let allowed_domains = pack_array("contoso.com");
let query_frequency = 1h;
let query_lookback = 14d;
Stage 1: source
PowerPlatformAdminActivity
Stage 2: where
| where TimeGenerated >= ago(query_frequency)
Stage 3: where
| where EventOriginalType == "PowerAppPermissionEdited"
Stage 4: extend (4 consecutive steps)
| extend Properties = tostring(PropertyCollection)
| extend AppId = extract(@'"powerplatform.analytics.resource.power_app.id","Value":"([^"]+)"', 1, Properties)
| extend AppId = tolower(replace_string(AppId, '/providers/Microsoft.PowerApps/apps/', ''))
| extend TargetPrincipalId = extract(@'"targetuser.id","Value":"([^"]+)"', 1, Properties)
Stage 5: join
| join kind=leftouter (
AuditLogs
| where ActivityDateTime >= ago(query_lookback)
| where SourceSystem =~ "Azure AD" and OperationName == "Invite external user"
| where Result =~ "success"
| extend InvitedOrgEmail = tostring(parse_json(AdditionalDetails[5])['value'])
| extend InvitedOrgDomain = tostring(split(InvitedOrgEmail, "@")[1])
| where not(InvitedOrgDomain has_any(allowed_domains))
| extend
InvitedById = tostring(parse_json(InitiatedBy)['user']['id']),
InvitedByUPN = tostring(parse_json(InitiatedBy)['user']['userPrincipalName']),
InvitedEmail = tostring(parse_json(TargetResources[0])['userPrincipalName']),
InvitedId = tostring(parse_json(TargetResources[0])['id'])
| summarize by InvitedById, InvitedByUPN, InvitedEmail, InvitedId, InvitedOrgDomain)
on $left.TargetPrincipalId == $right.InvitedId
Stage 6: where
where isnotempty(InvitedId)
Stage 7: summarize
summarize EndTime, InvitedDomains, InvitedEmailAddresses, StartTime, TargetedObjectIds, TargetedUsersCount by AppId, InvitedById, InvitedByUPN
Stage 8: extend
extend AccountName, PowerAppsEntityId, UPNSuffix
Stage 9: project
project AccountName, AppId, EndTime, InvitedById, InvitedByUPN, InvitedDomains, InvitedEmailAddresses, PowerAppsEntityId, StartTime, TargetedObjectIds, TargetedUsersCount, UPNSuffix
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventOriginalType | eq |
| field:"EventOriginalType" kind:eq value:"PowerAppPermissionEdited" |
InvitedId | is_not_null | field:"InvitedId" kind:is_not_null | |
OperationName | eq |
| field:"OperationName" kind:eq value:"Invite external user" |
Result | eq |
| field:"Result" kind:eq value:"success" |
SourceSystem | eq |
| field:"SourceSystem" kind:eq value:"Azure AD" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
InvitedOrgDomain | match | allowed_domains | excludes:InvitedOrgDomain field:"InvitedOrgDomain" value:"allowed_domains" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountName | project |
AppId | project |
EndTime | project |
InvitedById | project |
InvitedByUPN | project |
InvitedDomains | project |
InvitedEmailAddresses | project |
PowerAppsEntityId | project |
StartTime | project |
TargetedObjectIds | project |
TargetedUsersCount | project |
UPNSuffix | project |
TI Map URL Entity to AuditLogs
#This query identifies any URL indicators of compromise (IOCs) from threat intelligence (TI) by searching for matches in AuditLogs.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Azure | any: Entra ID audit event (any operation) |
Rule Body
id: 712fab52-2a7d-401e-a08c-ff939cc7c25e
name: TI Map URL Entity to AuditLogs
description: |
'This query identifies any URL indicators of compromise (IOCs) from threat intelligence (TI) by searching for matches in AuditLogs.'
severity: Medium
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- AuditLogs
- 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;
let ioc_lookBack = 14d;
let AuditEvents = materialize(AuditLogs
| where TimeGenerated >= ago(dt_lookBack)
// Extract the URL that is contained within the JSON data
| extend Url = extract("(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\\(\\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)", 1,tostring(TargetResources))
| where isnotempty(Url)
| extend userPrincipalName = tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName)
| extend TargetResourceDisplayName = tostring(TargetResources[0].displayName)
| extend Audit_TimeGenerated = TimeGenerated);
let AuditUrls = AuditEvents | distinct Url = tolower(Url) | summarize make_list(Url);
ThreatIntelligenceIndicator
| where isnotempty(Url)
| where TimeGenerated >= ago(ioc_lookBack)
| where tolower(Url) in (AuditUrls)
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
| where Active == true and ExpirationDateTime > now()
| where Description !contains_cs "State: inactive;" and Description !contains_cs "State: falsepos;"
// using innerunique to keep perf fast and result set low, we only need one match to indicate potential malicious activity that needs to be investigated
| join kind=innerunique (AuditEvents) on Url
| where Audit_TimeGenerated < ExpirationDateTime
| summarize Audit_TimeGenerated = arg_max(Audit_TimeGenerated, *) by IndicatorId, Url
| project Audit_TimeGenerated, Description, ActivityGroupNames, IndicatorId, ThreatType, ExpirationDateTime, ConfidenceScore,
OperationName, Identity, userPrincipalName, TargetResourceDisplayName, Url
| extend AccountName = tostring(split(userPrincipalName, "@")[0]), AccountUPNSuffix = tostring(split(userPrincipalName, "@")[1])
| extend HostName = tostring(split(TargetResourceDisplayName, ".")[0]), DomainIndex = toint(indexof(TargetResourceDisplayName, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(TargetResourceDisplayName, DomainIndex + 1), TargetResourceDisplayName)
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: userPrincipalName
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: AccountUPNSuffix
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: TargetResourceDisplayName
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: HostNameDomain
- entityType: URL
fieldMappings:
- identifier: Url
columnName: Url
version: 1.2.8
kind: Scheduled
Stages and Predicates
Parameters
let dt_lookBack = 1h;
let ioc_lookBack = 14d;
Let Binding: AuditEvents
let AuditEvents = materialize(AuditLogs
| where TimeGenerated >= ago(dt_lookBack)
| extend Url = extract("(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\\(\\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)", 1,tostring(TargetResources))
| where isnotempty(Url)
| extend userPrincipalName = tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName)
| extend TargetResourceDisplayName = tostring(TargetResources[0].displayName)
| extend Audit_TimeGenerated = TimeGenerated);
Let Binding: AuditUrls
let AuditUrls = AuditEvents | distinct Url = tolower(Url) | summarize make_list(Url);
Stage 1: source
ThreatIntelligenceIndicator
Stage 2: where
| where isnotempty(Url)
Stage 3: where
| where TimeGenerated >= ago(ioc_lookBack)
Stage 4: where
| where tolower(Url) in (AuditUrls)
Stage 5: summarize
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
Stage 6: where
| where Active == true and ExpirationDateTime > now()
Stage 7: where
| where Description !contains_cs "State: inactive;" and Description !contains_cs "State: falsepos;"
Stage 8: join
| join kind=innerunique (AuditEvents) on Url
Stage 9: where
where Audit_TimeGenerated < ExpirationDateTime
Stage 10: summarize
summarize Audit_TimeGenerated by IndicatorId, Url
Stage 11: project
project ActivityGroupNames, Audit_TimeGenerated, ConfidenceScore, Description, ExpirationDateTime, Identity, IndicatorId, OperationName, TargetResourceDisplayName, ThreatType, Url, userPrincipalName
Stage 12: extend (3 consecutive steps)
extend AccountName, AccountUPNSuffix, DomainIndex, HostName, HostNameDomain
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Active | eq |
| field:"Active" kind:eq value:"true" |
Url | in |
| field:"Url" kind:in value:"AuditUrls" |
Url | is_not_null | field:"Url" kind:is_not_null |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
Description | contains | State: falsepos; | excludes:Description field:"Description" value:"State: falsepos;" |
Description | contains | State: inactive; | excludes:Description field:"Description" value:"State: inactive;" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActivityGroupNames | project |
Audit_TimeGenerated | project |
ConfidenceScore | project |
Description | project |
ExpirationDateTime | project |
Identity | project |
IndicatorId | project |
OperationName | project |
TargetResourceDisplayName | project |
ThreatType | project |
Url | project |
userPrincipalName | project |
AccountName | extend |
AccountUPNSuffix | extend |
DomainIndex | extend |
HostName | extend |
HostNameDomain | extend |
Trend Micro CAS - Possible phishing mail
#'Detects possible phishing mail.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: 9e7b3811-d743-479c-a296-635410562429
name: Trend Micro CAS - Possible phishing mail
description: |
'Detects possible phishing mail.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: TrendMicroCAS
dataTypes:
- TrendMicroCAS
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1566
query: |
let threshold = 5;
TrendMicroCAS
| where EventCategoryType in~ ('exchange', 'gmail', 'exchangeserver')
| where isnotempty(SrcFileName)
| where isnotempty(SecurityRiskName)
| summarize r_users = makeset(DstUserName) by SrcFileName, bin(TimeGenerated, 30m)
| where array_length(r_users) > threshold
| extend AccountCustomEntity = r_users
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountCustomEntity
version: 1.0.0
kind: Scheduled
Stages and Predicates
Parameters
let threshold = 5;
Stage 1: source
TrendMicroCAS
Stage 2: where
| where EventCategoryType in~ ('exchange', 'gmail', 'exchangeserver')
Stage 3: where
| where isnotempty(SrcFileName)
Stage 4: where
| where isnotempty(SecurityRiskName)
Stage 5: summarize
| summarize r_users = makeset(DstUserName) by SrcFileName, bin(TimeGenerated, 30m)
Stage 6: where
| where array_length(r_users) > threshold
Stage 7: extend
| extend AccountCustomEntity = r_users
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventCategoryType | in |
| field:"EventCategoryType" kind:in |
SecurityRiskName | is_not_null | field:"SecurityRiskName" kind:is_not_null | |
SrcFileName | is_not_null | field:"SrcFileName" kind:is_not_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
SrcFileName | summarize |
r_users | summarize |
AccountCustomEntity | extend |
WinRM Plugin Lateral Movement
#This query detects loading of malicious WinRM plugins. These plugins can be used for lateral movement. This tradecraft has been researched and published by Arnau Ortega at FalconForce. Refer to the references for the blog post describing the full attack chain. This detection looks at low-prevalence DLLs being loaded into the WinRM host process. To minimize false-positives, the detection looks for files that are written to disk in the last 30 days, prior to being loaded into the WinRM host process as DLL. Such DLLs are likely WinRM plugins that are being loaded. Since the use of WinRM plugins is extremely scarce in real environments, we assume that any such DLL is malicious and warrants an investigation.
Known False Positives
- None expected.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Lateral Movement |
Telemetry Coverage
Rule Body
let timeframe = 2*1d;
let default_global_prevalence = 0;
let lookback = 30d;
let PotentialPlugins = materialize(
DeviceImageLoadEvents
| where ingestion_time() >= ago(timeframe)
| where InitiatingProcessFileName =~ "wsmprovhost.exe"
| where FolderPath !startswith @"C:\windows\assembly\nativeimages_" // Excluding .NET GAC as these are irrelevant for WinRM plugins and generate false positives.
| invoke FileProfile(SHA1, 1000)
| where ProfileAvailability !~ "Error"
| where coalesce(GlobalPrevalence, default_global_prevalence) < 100
| extend FolderPath=tolower(FolderPath)
);
let PotentialWrites = (
DeviceFileEvents
| where Timestamp >= ago(lookback)
| where ActionType in~ ("FileCreated", "FileRenamed", "FileModified")
| where SHA1 in~ ((PotentialPlugins | project SHA1))
| extend FolderPath=tolower(FolderPath)
);
PotentialPlugins
| join kind=inner PotentialWrites on SHA1, DeviceId, FolderPath
// Begin environment-specific filter.
// End environment-specific filter.
Stages and Predicates
Parameters
let timeframe = 2*1d;
let default_global_prevalence = 0;
let lookback = 30d;
let PotentialPlugins is inlined into the numbered stages below.
Let Binding: PotentialWrites
let PotentialWrites = (
DeviceFileEvents
| where Timestamp >= ago(lookback)
| where ActionType in~ ("FileCreated", "FileRenamed", "FileModified")
| where SHA1 in~ ((PotentialPlugins | project SHA1))
| extend FolderPath=tolower(FolderPath)
);
Stage 1: source
let PotentialPlugins
Stage 2: source
let PotentialWrites
Stage 3: source
DeviceImageLoadEvents
Stage 4: where
where ...
Stage 5: where
where InitiatingProcessFileName =~ "wsmprovhost.exe"
Stage 6: where
where not (FolderPath startswith @"C:\windows\assembly\nativeimages_")
Stage 7: invoke
invoke
Stage 8: where
where ProfileAvailability !~ "Error"
Stage 9: where
where GlobalPrevalence < 100
Stage 10: extend
extend FolderPath
Stage 11: join
join kind=inner (PotentialWrites) on SHA1, DeviceId, FolderPath
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ActionType | in |
| field:"ActionType" kind:in |
GlobalPrevalence | lt |
| field:"GlobalPrevalence" kind:lt value:"100" |
InitiatingProcessFileName | eq |
| field:"InitiatingProcessFileName" kind:eq value:"wsmprovhost.exe" |
ProfileAvailability | ne |
| field:"ProfileAvailability" kind:ne value:"Error" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
FolderPath | starts_with | C:\windows\assembly\nativeimages_ | excludes:FolderPath field:"FolderPath" value:"C:\windows\assembly\nativeimages_" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
FolderPath | extend |