Detection rules › Kusto

Kusto rules: 1x

Account Creation

#
Status
available
Severity
medium
Time window
1h
Group by
CreatedOnLocalMachine, CreatedUser, DeviceId, DeviceName, FileName, InitiatingProcessCommandLine, InitiatingProcessFileName, ProcessCommandLine, ProcessId
Source
github.com/Azure/Azure-Sentinel

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

TacticTechniques
Persistence

Telemetry Coverage

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 =
ifDeviceName has "."substring(DeviceName, 0, indexof(DeviceName, '.'))
elseDeviceName

Stage 10: extend

| extend DnsDomain = iff(DeviceName has '.', substring(DeviceName, indexof(DeviceName, '.') + 1), "")
DnsDomain =
ifDeviceName has "."substring(DeviceName, (indexof(DeviceName, '.') + 1))
else""

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
CreatedOnLocalMachineeq
  • 0
field:"CommandLine" kind:eq value:"0"
CreatedUseris_not_null
  • (no value, null check)
field:"CreatedUser" kind:is_not_null
FileNamein
  • net.exe corpus 5 (splunk 3, elastic 1, kusto 1)
  • net1.exe corpus 5 (splunk 4, kusto 1)
field:"process_basename" kind:in

Exclusions

The rule actively suppresses these predicates.

Output Fields

These fields are emitted when the rule matches.

FieldSource
CreatedOnLocalMachinesummarize
CreatedUsersummarize
DeviceIdsummarize
DeviceNamesummarize
FileNamesummarize
InitiatingProcessCommandLinesummarize
InitiatingProcessFileNamesummarize
MachineCountsummarize
ProcessCommandLinesummarize
ProcessIdsummarize
HostNameextend
DnsDomainextend

AIShield - Tabular classification AI Model extraction high suspicious vulnerability detection

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

'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.

Output Fields

These fields are emitted when the rule matches.

FieldSource
HostNameextend
NTDomainextend

AWSCloudTrail - Creation of CRUD KMS policy and then privilege escalation

#
Status
available
Severity
medium
Time window
1d
Group by
AccountName, AccountUPNSuffix, EventName, EventSource, PolicyName, RecipientAccountId, SourceIpAddress, UserIdentityArn, UserIdentityType
Source
github.com/Azure/Azure-Sentinel

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

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

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 used in Stage 15

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.

FieldKindValuesSearch
Actioncontains
  • kms:Create
  • kms:Delete
  • kms:Describe
  • kms:Disable
  • kms:Enable
  • kms:Get
field:"Action" kind:contains
Effecteq
  • Allow
field:"Effect" kind:eq value:"Allow"
ErrorCodeis_null
  • (no value, null check)
field:"ErrorCode" kind:is_null
ErrorMessageis_null
  • (no value, null check)
field:"ErrorMessage" kind:is_null
EventNamein
  • AttachGroupPolicy corpus 17 (kusto 15, panther 2)
  • AttachGroupPolicy
  • AttachRolePolicy corpus 17 (kusto 15, panther 2)
  • AttachRolePolicy
  • AttachUserPolicy corpus 17 (kusto 15, panther 2)
  • AttachUserPolicy
  • CreatePolicy corpus 14 (kusto 12, panther 2)
  • CreatePolicy
  • CreatePolicyVersion corpus 14 (kusto 12, panther 2)
  • CreatePolicyVersion
field:"aws::eventName" kind:in
Resourceeq
  • *
field:"Resource" kind:eq value:"*"

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountNamesummarize
AccountUPNSuffixsummarize
EventNamesummarize
EventSourcesummarize
PolicyNamesummarize
RecipientAccountIdsummarize
SourceIpAddresssummarize
UserIdentityArnsummarize
UserIdentityTypesummarize

Azure VM Run Command operations executing a unique PowerShell script

#
Severity
medium
Time window
1d
Group by
$left.FileName, $right.PowershellFileName, Authorization, Caller, CorrelationId, ScriptFingerprintHash, VirtualMachineName
Author
Microsoft Security Research
Source
github.com/Azure/Azure-Sentinel

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

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 used in Stages 2, 18

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.

Output Fields

These fields are emitted when the rule matches.

FieldSource
Callerproject
CallerIpAddressproject
CallerNameproject
CallerUPNSuffixproject
EndTimeproject
PowershellFileNameproject
PowershellFileSizeproject
PowershellScriptCommandsproject
Prevalenceproject
Scopeproject
ScriptFingerprintHashproject
StartTimeproject
VirtualMachineNameproject
timestampproject

Bitsadmin Activity

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

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

Telemetry Coverage

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 =
ifProcessCommandLine has "/Upload"'Upload'
else'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 =
ifDeviceName has "."substring(DeviceName, 0, indexof(DeviceName, '.'))
elseDeviceName

Stage 7: extend

| extend DnsDomain = iff(DeviceName has '.', substring(DeviceName, indexof(DeviceName, '.') + 1), "")
DnsDomain =
ifDeviceName has "."substring(DeviceName, (indexof(DeviceName, '.') + 1))
else""

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
FileNameeq
  • bitsadmin.exe corpus 25 (elastic 22, splunk 2, kusto 1)
field:"process_basename" kind:eq value:"bitsadmin.exe"
ProcessCommandLinematch
  • /AddFile transforms: term
  • /AddFileSet transforms: term
  • /AddFileWithRanges transforms: term
  • /Transfer transforms: term
field:"CommandLine" kind:match
ProcessVersionInfoOriginalFileNameeq
  • bitsadmin.exe transforms: column_ifexists
field:"OriginalFileName" kind:eq value:"bitsadmin.exe"

Output Fields

These fields are emitted when the rule matches.

FieldSource
ParsedCommandLineextend
Directionextend
LocalFileextend
RemoteUrlextend
HostNameextend
DnsDomainextend

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.

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

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.

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

Cisco Cloud Security - Crypto Miner User-Agent Detected

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

'Detects suspicious user agent strings used by crypto miners in proxy logs.'

MITRE ATT&CK Coverage

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.

Output Fields

These fields are emitted when the rule matches.

FieldSource
DstIpAddrproject
HttpUserAgentOriginalproject
Messageproject
SrcIpAddrproject
TimeGeneratedproject
UrlOriginalproject

Cloudflare - Client request from country in blocklist

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

'Detects requests from countries which are in blocklist.'

MITRE ATT&CK Coverage

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.

Output Fields

These fields are emitted when the rule matches.

FieldSource
CompleteUrlextend

Copilot - File Uploads Disabled

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

'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

TacticTechniques
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.

Output Fields

These fields are emitted when the rule matches.

FieldSource
ActorNameproject
Newproject
Oldproject
Propertyproject
SrcIpAddrproject
TimeGeneratedproject

CTE - Security Rule Changed (Created/Updated/Deleted)

#
Severity
medium
Time window
1d
Group by
AccountName, Actor, AppName, EventType, Pattern, RuleId, TenantId, UPNSuffix
Source
github.com/Azure/Azure-Sentinel

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

TacticTechniques
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.

FieldKindValuesSearch
EventTypein
  • rule.created
  • rule.deleted
  • rule.updated
field:"EventType" kind:in

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountNameproject
Actorproject
AppNameproject
EventCountproject
EventTypeproject
FirstSeenproject
LastSeenproject
Patternproject
RuleIdproject
TenantIdproject
TimeGeneratedproject
UPNSuffixproject

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.

Status
available
Severity
low
Time window
30m
Source
github.com/Azure/Azure-Sentinel

'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

TacticTechniques
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.

FieldKindValuesSearch
Serviceeq
  • new_vulnerability
field:"Service" kind:eq value:"new_vulnerability"

Output Fields

These fields are emitted when the rule matches.

FieldSource
MappedSeverityextend

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.

Status
available
Severity
low
Time window
30m
Source
github.com/Azure/Azure-Sentinel

'This Rule generates Cyble Vision Alerts for Service - Suspicious Domain severity LOW'

MITRE ATT&CK Coverage

TacticTechniques
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.

FieldKindValuesSearch
Serviceeq
  • suspicious_domain
field:"Service" kind:eq value:"suspicious_domain"

Output Fields

These fields are emitted when the rule matches.

FieldSource
MappedSeverityextend

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.

Status
available
Severity
medium
Time window
5m
Source
github.com/Azure/Azure-Sentinel

"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

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.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AlertUIDproject
CreatedDateproject
DRDomainproject
DRSubDomainproject
Descriptionproject
Domainproject
DomainSquatproject
FirstSeenproject
HostProviderproject
LastSeenproject
ProductNameproject
ProviderNameproject
RegisteredDateproject
RiskScoreproject
ThreatActorproject
TimeGeneratedproject
UIDproject

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.

Status
available
Severity
medium
Time window
5m
Source
github.com/Azure/Azure-Sentinel

"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.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AlertTitleproject
AlertUIDproject
AssetTypeproject
AssetValueproject
Descriptionproject
FirstSeenproject
Impactproject
LastSeenproject
ProductNameproject
ProviderNameproject
Recommendationproject
RiskScoreproject
Sourceproject
TimeGeneratedproject
UIDproject

Failover Plan Settings Updated

#
Status
available
Severity
informational
Time window
1d
Source
github.com/Azure/Azure-Sentinel

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.

Output Fields

These fields are emitted when the rule matches.

FieldSource
DataSourceproject
Dateproject
EventIdproject
MessageDetailsproject
Severityproject
User Nameproject

Guardian- Input Rate Limiter Policy Violation Detection

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

'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.

M2131_EventLogManagementPostureChanged_EL2

#
Status
available
Severity
medium
Time window
7d
Group by
AssessedResourceId, MaturityLevel, RecommendationDisplayName
Source
github.com/Azure/Azure-Sentinel

'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

TacticTechniques
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 =
if(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)"
elseiff((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.

Output Fields

These fields are emitted when the rule matches.

FieldSource
Failedproject
LastObservedproject
MaturityLevelproject
Passedproject
PassedControlsproject
RemediationLinkproject
Totalproject
URLCustomEntityextend

Mail.Read Permissions Granted to Application

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

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

TacticTechniques
Persistence

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

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.

FieldKindValuesSearch
ActivityDisplayNamematch
  • Add app role assignment to service principal transforms: term
  • Add delegated permission grant transforms: term
  • Consent to application transforms: term
field:"azure_ad::activity_display_name" kind:match
AdditionalDetail.keyeq
  • User-Agent transforms: kusto_dynamic_path
field:"AdditionalDetail.key" kind:eq value:"User-Agent"
Categoryeq
  • ApplicationManagement corpus 13 (kusto 13)
field:"Category" kind:eq value:"ApplicationManagement"
InitiatedBy.app.displayNamematch
  • @ transforms: tostring, term, kusto_dynamic_path
field:"InitiatedBy.app.displayName" kind:match value:"@"
InitiatedBy.user.userPrincipalNamematch
  • @ transforms: tostring, term, kusto_dynamic_path
field:"InitiatedBy.user.userPrincipalName" kind:match value:"@"
Permissionsmatch
  • Mail.Read transforms: term
  • Mail.ReadWrite transforms: term
field:"Permissions" kind:match
Property.displayNameeq
  • DelegatedPermissionGrant.Scope transforms: kusto_dynamic_path
field:"Property.displayName" kind:eq value:"DelegatedPermissionGrant.Scope"
Resulteq
  • success corpus 23 (kusto 23)
field:"Result" kind:eq value:"success"
TargetResource.displayNameis_not_null
  • (no value, null check)
field:"TargetResource.displayName" kind:is_not_null
TargetResource.typeeq
  • ServicePrincipal transforms: kusto_dynamic_path
field:"TargetResource.type" kind:eq value:"ServicePrincipal"

Output Fields

These fields are emitted when the rule matches.

FieldSource
InitiatingAppNameextend
InitiatingAppServicePrincipalIdextend
InitiatingUserPrincipalNameextend
InitiatingAadUserIdextend
InitiatingIpAddressextend
Nameextend
UPNSuffixextend

NRT Multiple users email forwarded to same destination

#
Severity
medium
Group by
ClientIP
Author
Pete Bryan
Source
github.com/Azure/Azure-Sentinel

'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

TacticTechniques
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.

FieldKindValuesSearch
DestinationMailAddressis_not_null
  • (no value, null check)
field:"DestinationMailAddress" kind:is_not_null
DistinctUserCountgt
  • 1 transforms: number
field:"DistinctUserCount" kind:gt value:"1"
OfficeWorkloadeq
  • Exchange
field:"m365::Workload" kind:eq value:"Exchange"
Parametersmatch
  • ForwardTo transforms: term
  • ForwardingSmtpAddress transforms: term
  • RedirectTo transforms: term
field:"m365::Parameters" kind:match

Output Fields

These fields are emitted when the rule matches.

FieldSource
ClientIPsummarize
DistinctUserCountsummarize
EndTimesummarize
EventCountsummarize
Portssummarize
StartTimesummarize
UserIdsummarize

Oracle - Private IP in URL

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

'Detects requests to unusual URL'

MITRE ATT&CK Coverage

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.

FieldKindValuesSearch
UrlOriginalis_not_null
  • (no value, null check)
field:"UrlOriginal" kind:is_not_null
UrlOriginalregex_match
    • (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})
field:"UrlOriginal" kind:regex_match

Output Fields

These fields are emitted when the rule matches.

FieldSource
IPCustomEntityextend
UrlCustomEntityextend

Palo Alto Prisma Cloud - Network ACL allow ingress traffic to server administration ports

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

'Detects Network ACLs allow ingress traffic to server administration ports.'

MITRE ATT&CK Coverage

TacticTechniques
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.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountCustomEntityextend

Password Spraying

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

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

TacticTechniques
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.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
RemoteDeviceNameis_null(no value, null check)excludes:RemoteDeviceName
RemoteIPis_null(no value, null check)excludes:RemoteIP

Output Fields

These fields are emitted when the rule matches.

FieldSource
DeviceNamesummarize
FailedLogonCountsummarize
FirstFailedsummarize
LastFailedsummarize
LastTimestampsummarize
RemoteIPsummarize
SuccessLogonCountsummarize
UniqueAccountFailedLogonssummarize
IPAddressproject-rename

Potential Password Spray Attack

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

'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

TacticTechniques
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.

Output Fields

These fields are emitted when the rule matches.

FieldSource
ClientIpsummarize
UserCountsummarize
Userssummarize

Power Apps - Bulk sharing of Power Apps to newly created guest users

#
Status
available
Severity
medium
Time window
14d
Group by
$left.TargetPrincipalId, $right.InvitedId, AppId, InvitedById, InvitedByUPN
Source
github.com/Azure/Azure-Sentinel

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

TacticTechniques
Resource Development
Initial Access
Lateral Movement

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

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.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
InvitedOrgDomainmatchallowed_domainsexcludes:InvitedOrgDomain field:"InvitedOrgDomain" value:"allowed_domains"

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountNameproject
AppIdproject
EndTimeproject
InvitedByIdproject
InvitedByUPNproject
InvitedDomainsproject
InvitedEmailAddressesproject
PowerAppsEntityIdproject
StartTimeproject
TargetedObjectIdsproject
TargetedUsersCountproject
UPNSuffixproject

TI Map URL Entity to AuditLogs

#
Severity
medium
Time window
14d
Group by
IndicatorId, Url
Source
github.com/Azure/Azure-Sentinel

This query identifies any URL indicators of compromise (IOCs) from threat intelligence (TI) by searching for matches in AuditLogs.

MITRE ATT&CK Coverage

TacticTechniques
Command & Control

Telemetry Coverage

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 used in Stage 8

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 used in Stage 4

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.

FieldKindValuesSearch
Activeeq
  • true corpus 60 (kusto 60)
field:"Active" kind:eq value:"true"
Urlin
  • AuditUrls transforms: tolower
field:"Url" kind:in value:"AuditUrls"
Urlis_not_null
  • (no value, null check)
field:"Url" kind:is_not_null

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
DescriptioncontainsState: falsepos;excludes:Description field:"Description" value:"State: falsepos;"
DescriptioncontainsState: inactive;excludes:Description field:"Description" value:"State: inactive;"

Output Fields

These fields are emitted when the rule matches.

FieldSource
ActivityGroupNamesproject
Audit_TimeGeneratedproject
ConfidenceScoreproject
Descriptionproject
ExpirationDateTimeproject
Identityproject
IndicatorIdproject
OperationNameproject
TargetResourceDisplayNameproject
ThreatTypeproject
Urlproject
userPrincipalNameproject
AccountNameextend
AccountUPNSuffixextend
DomainIndexextend
HostNameextend
HostNameDomainextend

Trend Micro CAS - Possible phishing mail

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

'Detects possible phishing mail.'

MITRE ATT&CK Coverage

TacticTechniques
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.

FieldKindValuesSearch
EventCategoryTypein
  • exchange corpus 2 (kusto 2)
  • exchangeserver corpus 2 (kusto 2)
  • gmail corpus 2 (kusto 2)
field:"EventCategoryType" kind:in
SecurityRiskNameis_not_null
  • (no value, null check)
field:"SecurityRiskName" kind:is_not_null
SrcFileNameis_not_null
  • (no value, null check)
field:"SrcFileName" kind:is_not_null

Output Fields

These fields are emitted when the rule matches.

FieldSource
SrcFileNamesummarize
r_userssummarize
AccountCustomEntityextend

WinRM Plugin Lateral Movement

#
Group by
DeviceId, FolderPath, SHA1
Author
FalconForce
Source
github.com/FalconForceTeam/FalconFriday

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

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 used in Stages 2, 11

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.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
FolderPathstarts_withC:\windows\assembly\nativeimages_excludes:FolderPath field:"FolderPath" value:"C:\windows\assembly\nativeimages_"

Output Fields

These fields are emitted when the rule matches.

FieldSource
FolderPathextend

Back to all Kusto rules