Detection rules › Kusto

Kusto rules: 0a

API - Invalid host access

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

'42Crunch API protection against invalid host access'

MITRE ATT&CK Coverage

TacticTechniques
Reconnaissance

Rule Body

id: 28500be7-cfcf-40e1-bad4-bc524e9283e2
name: API - Invalid host access
description: |
  '42Crunch API protection against invalid host access'
severity: Low
requiredDataConnectors:
  - connectorId: 42CrunchAPIProtection
    dataTypes:
      - FortyTwoCrunchAPIProtection
  - connectorId: FortyTwoCrunchAPIProtection
    dataTypes:
      - FortyTwoCrunchAPIProtection
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
  aggregationKind: SingleAlert
status: Available
tactics:
  - Reconnaissance
relevantTechniques:
  - T1592 
query: |
  FortyTwoCrunchAPIProtection
  | where TimeGenerated >= ago(5m) 
  | where ErrorMessage has "hostpath_mapping" 
  | project-away NonBlockingMode, SourcePort, DestinationPort, Query, ApiId, ResponseHeader, RequestHeader, Errors, EventType, Uuid 
  | sort by TimeGenerated desc 
customDetails:
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIp
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: Hostname
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: InstanceName
version: 3.0.1
kind: Scheduled

Stages and Predicates

Stage 1: source

FortyTwoCrunchAPIProtection

Stage 2: where

| where TimeGenerated >= ago(5m)

Stage 3: where

| where ErrorMessage has "hostpath_mapping"

Stage 4: project-away

| project-away NonBlockingMode, SourcePort, DestinationPort, Query, ApiId, ResponseHeader, RequestHeader, Errors, EventType, Uuid

Stage 5: sort

| sort by TimeGenerated desc

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
ErrorMessagematch
  • hostpath_mapping transforms: term
field:"aws::errorMessage" kind:match value:"hostpath_mapping"

Attempts to sign in to disabled accounts

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

Identifies failed attempts to sign in to disabled accounts across multiple Azure Applications. Default threshold for Azure Applications attempted to sign in to is 3. References: https://docs.microsoft.com/azure/active-directory/reports-monitoring/reference-sign-ins-error-codes 50057 - User account is disabled. The account has been disabled by an administrator.

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Telemetry Coverage

Rule Body

id: 75ea5c39-93e5-489b-b1e1-68fa6c9d2d04
name: Attempts to sign in to disabled accounts
description: |
  'Identifies failed attempts to sign in to disabled accounts across multiple Azure Applications.
  Default threshold for Azure Applications attempted to sign in to is 3.
  References: https://docs.microsoft.com/azure/active-directory/reports-monitoring/reference-sign-ins-error-codes
  50057 - User account is disabled. The account has been disabled by an administrator.'
severity: Medium
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - SigninLogs
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AADNonInteractiveUserSignInLogs
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - InitialAccess
relevantTechniques:
  - T1078
query: |
  let threshold = 3;
  let aadFunc = (tableName:string){
  table(tableName)
  | where ResultType == "50057"
  | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), applicationCount = dcount(AppDisplayName),
  applicationSet = make_set(AppDisplayName), count() by UserPrincipalName, IPAddress, Type
  | where applicationCount >= threshold
  | extend Name = tostring(split(UserPrincipalName,'@',0)[0]), UPNSuffix = tostring(split(UserPrincipalName,'@',1)[0])
  };
  let aadSignin = aadFunc("SigninLogs");
  let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");
  union isfuzzy=true aadSignin, aadNonInt
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: UserPrincipalName
      - identifier: Name
        columnName: Name
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPAddress
version: 1.0.4
kind: Scheduled

Stages and Predicates

Parameters

let threshold = 3;
let aadSignin = aadFunc("SigninLogs");
let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");

Let Binding: aadFunc

let aadFunc = (tableName:string){
table(tableName)
| where ResultType == "50057"
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), applicationCount = dcount(AppDisplayName),
applicationSet = make_set(AppDisplayName), count() by UserPrincipalName, IPAddress, Type
| where applicationCount >= threshold
| extend Name = tostring(split(UserPrincipalName,'@',0)[0]), UPNSuffix = tostring(split(UserPrincipalName,'@',1)[0])
};

Union isfuzzy=true (2 Sources)

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

Leg 1: aadSignin

Leg 2: aadNonInt

Authentication Methods Changed for Privileged Account

#
Status
available
Severity
high
Time window
14d
Group by
InitiatingAadUserId, InitiatingAppName, InitiatingAppServicePrincipalId, InitiatingIpAddress, InitiatingUserPrincipalName, Result, TargetUserPrincipalName
Source
github.com/Azure/Azure-Sentinel

Identifies authentication methods being changed for a privileged account. This could be an indication of an attacker adding an auth method to the account so they can have continued access. Ref : https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-privileged-accounts#things-to-monitor-1

MITRE ATT&CK Coverage

TacticTechniques
Persistence

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: 694c91ee-d606-4ba9-928e-405a2dd0ff0f
name: Authentication Methods Changed for Privileged Account
description: |
  'Identifies authentication methods being changed for a privileged account. This could be an indication of an attacker adding an auth method to the account so they can have continued access.
  Ref : https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-privileged-accounts#things-to-monitor-1'
severity: High
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AuditLogs
  - connectorId: BehaviorAnalytics
    dataTypes:
      - IdentityInfo
queryFrequency: 2h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - Persistence
relevantTechniques:
  - T1098
tags:
  - AADSecOpsGuide
query: |
  let queryperiod = 14d;
  let queryfrequency = 2h;
  let security_info_actions = dynamic(["User registered security info", "User changed default security info", "User deleted security info", "Admin updated security info", "User reviewed security info", "Admin deleted security info", "Admin registered security info"]);
  let VIPUsers = (
      IdentityInfo
      | where TimeGenerated > ago(queryperiod)
      | mv-expand AssignedRoles
      | where AssignedRoles contains 'Admin'
      | summarize by AccountUPN);
  AuditLogs
  | where TimeGenerated > ago(queryfrequency)
  | where Category =~ "UserManagement"
  | where ActivityDisplayName in (security_info_actions)
  | 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))
  | mv-apply TargetResource = TargetResources on 
    (
        where TargetResource.type =~ "User"
        | extend TargetUserPrincipalName = tostring(TargetResource.userPrincipalName)
    )
  | where TargetUserPrincipalName in~ (VIPUsers)
  // Uncomment the line below if you are experiencing high volumes of Target entities. If this is uncommented, the Target column will not be mapped to an entity.
  //| summarize Start=min(TimeGenerated), End=max(TimeGenerated), Actions = make_set(ResultReason, MaxSize=8), Targets=make_set(Target, MaxSize=256) by Initiator, IP, Result
  // Comment out this line below, if line above is used.
  | summarize Start=min(TimeGenerated), End=max(TimeGenerated), Actions = make_set(ResultReason, MaxSize=8) by InitiatingAppName, InitiatingAppServicePrincipalId, 
  InitiatingUserPrincipalName, InitiatingAadUserId, InitiatingIpAddress, TargetUserPrincipalName, Result
  | extend InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1]), 
  TargetName = iff(tostring(TargetUserPrincipalName) has "[", "", tostring(split(TargetUserPrincipalName,'@',0)[0])), TargetUPNSuffix = iff(tostring(TargetUserPrincipalName) has "[", "", tostring(split(TargetUserPrincipalName,'@',1)[0]))
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: InitiatingAppName
      - identifier: AadUserId
        columnName: InitiatingAppServicePrincipalId
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: InitiatingUserPrincipalName
      - identifier: Name
        columnName: InitiatingAccountName
      - identifier: UPNSuffix
        columnName: InitiatingAccountUPNSuffix
  - entityType: Account
    fieldMappings:
      - identifier: AadUserId
        columnName: InitiatingAadUserId
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: InitiatingIpAddress
version: 1.1.1
kind: Scheduled

Stages and Predicates

Parameters

let queryperiod = 14d;
let queryfrequency = 2h;

Let Binding: security_info_actions

let security_info_actions = dynamic(["User registered security info", "User changed default security info", "User deleted security info", "Admin updated security info", "User reviewed security info", "Admin deleted security info", "Admin registered security info"]);

Let Binding: VIPUsers used in Stages 1, 8

let VIPUsers = (
    IdentityInfo
    | where TimeGenerated > ago(queryperiod)
    | mv-expand AssignedRoles
    | where AssignedRoles contains 'Admin'
    | summarize by AccountUPN);

Stage 1: source

let VIPUsers

Stage 2: source

AuditLogs

Stage 3: where

where TimeGenerated > ago(7200s)

Stage 4: where

where Category =~ "UserManagement"

Stage 5: where

where ActivityDisplayName in ("Admin deleted security info", "Admin registered security info", "Admin updated security info", "User changed default security info", "User deleted security info", "User registered security info", "User reviewed security info")

Stage 6: extend (5 consecutive steps)

extend InitiatingAadUserId, InitiatingAppName, InitiatingAppServicePrincipalId, InitiatingIpAddress, InitiatingUserPrincipalName

Stage 7: kusto:mv-apply

kusto:mv-apply TargetResource.type =~ "User"

Stage 8: where

where TargetUserPrincipalName =~ "VIPUsers"

Stage 9: summarize

summarize Actions, End, Start by InitiatingAppName, InitiatingAppServicePrincipalId, InitiatingUserPrincipalName, InitiatingAadUserId, InitiatingIpAddress, TargetUserPrincipalName, Result

Stage 10: extend

extend InitiatingAccountName, InitiatingAccountUPNSuffix, TargetName, TargetUPNSuffix
TargetName =
ifTargetUserPrincipalName has "["""
elsetostring(split(TargetUserPrincipalName, '@', 0)[0])
TargetUPNSuffix =
ifTargetUserPrincipalName has "["""
elsetostring(split(TargetUserPrincipalName, '@', 1)[0])

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
ActivityDisplayNamein
  • Admin deleted security info
  • Admin registered security info
  • Admin updated security info
  • User changed default security info
  • User deleted security info
  • User registered security info
  • User reviewed security info
field:"ActivityDisplayName" kind:in
Categoryeq
  • UserManagement
field:"Category" kind:eq value:"UserManagement"
TargetResource.typeeq
  • User transforms: kusto_dynamic_path
field:"TargetResource.type" kind:eq value:"User"
TargetUserPrincipalNamein
  • VIPUsers
field:"TargetUserPrincipalName" kind:in value:"VIPUsers"

Output Fields

These fields are emitted when the rule matches.

FieldSource
Actionssummarize
Endsummarize
InitiatingAadUserIdsummarize
InitiatingAppNamesummarize
InitiatingAppServicePrincipalIdsummarize
InitiatingIpAddresssummarize
InitiatingUserPrincipalNamesummarize
Resultsummarize
Startsummarize
TargetUserPrincipalNamesummarize
InitiatingAccountNameextend
InitiatingAccountUPNSuffixextend
TargetNameextend
TargetUPNSuffixextend

AV detections related to Tarrask malware

#
Status
available
Severity
high
Time window
1d
Group by
$left.DeviceName, $right.CompromisedEntity, AlertSeverity, CompromisedEntity, Description, DeviceId, DisplayName, Entities, ProductName, PublicIP, TenantId, ThreatFamilyName, ThreatName
Source
github.com/Azure/Azure-Sentinel

This query looks for Microsoft Defender AV detections related to Tarrask malware. In Microsoft Sentinel, the SecurityAlerts table includes only the Device Name of the affected device, this query joins the DeviceInfo table to clearly connect other information such as Device group, ip, logged-on users etc. This would allow the Microsoft Sentinel analyst to have more context related to the alert, if available. Reference: https://www.microsoft.com/security/blog/2022/04/12/tarrask-malware-uses-scheduled-tasks-for-defense-evasion/

MITRE ATT&CK Coverage

TacticTechniques
Persistence

Telemetry Coverage

Rule Body

id: 1785d372-b9fe-4283-96a6-3a1d83cabfd1
name: AV detections related to Tarrask malware
description: |
  'This query looks for Microsoft Defender AV detections related to Tarrask malware. In Microsoft Sentinel, the SecurityAlerts table includes only the Device Name of the affected device, this query joins the DeviceInfo table to clearly connect other information such as Device group, ip, logged-on users etc. 
   This would allow the Microsoft Sentinel analyst to have more context related to the alert, if available.
   Reference: https://www.microsoft.com/security/blog/2022/04/12/tarrask-malware-uses-scheduled-tasks-for-defense-evasion/'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - SecurityAlert
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Persistence
relevantTechniques:
  - T1053
query: |
  let Tarrask_threats = dynamic(["HackTool:Win64/Tarrask!MS", "HackTool:Win64/Ligolo!MSR", "Behavior:Win32/ScheduledTaskHide.A", "Tarrask"]);
  DeviceInfo
  | extend DeviceName = tolower(DeviceName)
  | join kind=rightouter ( SecurityAlert
  | where ProviderName =~ "MDATP"
  | extend ThreatName = tostring(parse_json(ExtendedProperties).ThreatName)
  | extend ThreatFamilyName = tostring(parse_json(ExtendedProperties).ThreatFamilyName)
  | where ThreatName in~ (Tarrask_threats) or ThreatFamilyName in~ (Tarrask_threats)
  | extend CompromisedEntity = tolower(CompromisedEntity)
  ) on $left.DeviceName == $right.CompromisedEntity
  | summarize by DisplayName, ThreatName, ThreatFamilyName, PublicIP, AlertSeverity, Description, tostring(LoggedOnUsers), DeviceId, TenantId , bin(TimeGenerated, 1d), CompromisedEntity, tostring(LoggedOnUsers), ProductName, Entities
  | extend HostName = tostring(split(CompromisedEntity, ".")[0]), DomainIndex = toint(indexof(CompromisedEntity, '.'))
  | extend HostNameDomain = iff(DomainIndex != -1, substring(CompromisedEntity, DomainIndex + 1), CompromisedEntity)
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: FullName 
        columnName: CompromisedEntity
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: HostNameDomain
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: PublicIP
version: 1.0.4
kind: Scheduled

Stages and Predicates

Parameters

let Tarrask_threats = dynamic(["HackTool:Win64/Tarrask!MS", "HackTool:Win64/Ligolo!MSR", "Behavior:Win32/ScheduledTaskHide.A", "Tarrask"]);

Stage 1: source

DeviceInfo

Stage 2: extend

| extend DeviceName = tolower(DeviceName)

Stage 3: join

| join kind=rightouter ( SecurityAlert
| where ProviderName =~ "MDATP"
| extend ThreatName = tostring(parse_json(ExtendedProperties).ThreatName)
| extend ThreatFamilyName = tostring(parse_json(ExtendedProperties).ThreatFamilyName)
| where ThreatName in~ (Tarrask_threats) or ThreatFamilyName in~ (Tarrask_threats)
| extend CompromisedEntity = tolower(CompromisedEntity)
) on $left.DeviceName == $right.CompromisedEntity

Stage 4: summarize

summarize by DisplayName, ThreatName, ThreatFamilyName, PublicIP, AlertSeverity, Description, DeviceId, TenantId, CompromisedEntity, ProductName, Entities

Stage 5: extend

extend DomainIndex, HostName

Stage 6: extend

extend HostNameDomain
HostNameDomain =
ifDomainIndex != -1substring(CompromisedEntity, (DomainIndex + 1))
elseCompromisedEntity

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
ProviderNameeq
  • MDATP
field:"ProviderName" kind:eq value:"MDATP"
ThreatFamilyNamein
  • Behavior:Win32/ScheduledTaskHide.A
  • HackTool:Win64/Ligolo!MSR
  • HackTool:Win64/Tarrask!MS
  • Tarrask
field:"ThreatFamilyName" kind:in
ThreatNamein
  • Behavior:Win32/ScheduledTaskHide.A
  • HackTool:Win64/Ligolo!MSR
  • HackTool:Win64/Tarrask!MS
  • Tarrask
field:"ThreatName" kind:in

Output Fields

These fields are emitted when the rule matches.

FieldSource
AlertSeveritysummarize
CompromisedEntitysummarize
Descriptionsummarize
DeviceIdsummarize
DisplayNamesummarize
Entitiessummarize
ProductNamesummarize
PublicIPsummarize
TenantIdsummarize
ThreatFamilyNamesummarize
ThreatNamesummarize
DomainIndexextend
HostNameextend
HostNameDomainextend

AWSCloudTrail - Creation of CRUD DynamoDB 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 provide broad CRUD permissions for DynamoDB, followed by policy attachment activity. This sequence can indicate an attempt to grant capabilities that support privilege escalation and should be validated against approved administrative changes.

MITRE ATT&CK Coverage

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: 6f675c17-7a61-440c-abd1-c73ef4d748ec
name: AWSCloudTrail - Creation of CRUD DynamoDB policy and then privilege escalation
description: |
  Identifies creation of IAM policies that provide broad CRUD permissions for DynamoDB, followed by policy attachment activity. This sequence can indicate an attempt to grant capabilities that support
  privilege escalation and should be validated against approved administrative changes.
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 "dynamodb:Create" or Action contains "dynamodb:Put") and (Action contains "dynamodb:Describe" or Action contains "dynamodb:Get" or Action contains "dynamodb:Scan")  and Action contains "dynamodb:Update" and Action contains "dynamodb:Delete") and Resource == "*" and Condition == ""
  | distinct TimeGenerated, EventName, PolicyName, SourceIpAddress, RecipientAccountId, AccountName, AccountUPNSuffix, UserIdentityArn
  | 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 , RecipientAccountId, AccountName, AccountUPNSuffix, UserIdentityArn, SourceIpAddress, 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, AttachEventCount, RecipientAccountId, AccountName, AccountUPNSuffix
  );
  // 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 DynamoDB privilege escalation policy activity: {{PolicyName}} by {{AccountName}}'
  alertDescriptionFormat: 'Detected {{EventName}} for policy {{PolicyName}} from {{SourceIpAddress}}.'
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 , RecipientAccountId, AccountName, AccountUPNSuffix, UserIdentityArn, SourceIpAddress, 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, AttachEventCount, RecipientAccountId, AccountName, AccountUPNSuffix
);

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 "dynamodb:Create" or Action contains "dynamodb:Put") and (Action contains "dynamodb:Describe" or Action contains "dynamodb:Get" or Action contains "dynamodb:Scan")  and Action contains "dynamodb:Update" and Action contains "dynamodb:Delete") and Resource == "*" and Condition == ""

Stage 13: distinct

| distinct TimeGenerated, EventName, PolicyName, SourceIpAddress, RecipientAccountId, AccountName, AccountUPNSuffix, UserIdentityArn

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, RecipientAccountId, AccountName, AccountUPNSuffix, UserIdentityArn, SourceIpAddress, PolicyName

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Actioncontains
  • dynamodb:Create
  • dynamodb:Delete
  • dynamodb:Describe
  • dynamodb:Get
  • dynamodb:Put
  • dynamodb:Scan
  • dynamodb:Update
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

BloodHound Attack Path Finding - Add Owner to Tier Zero Object via MS Graph App Role

#

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 - Add Owner to Tier Zero Object via MS Graph App Role

Rule Body

id: 58c70f97-543b-4116-8624-12ee7ad8a13d
name: BloodHound Attack Path Finding - Add Owner to Tier Zero Object via MS Graph
  App Role
description: BloodHound Attack Path Finding - Add Owner to Tier Zero Object via MS
  Graph App Role
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: BloodHoundEnterprise
    dataTypes:
      - BHEAttackPathsData_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: "BHEAttackPathsData_CL \n| where Finding == \"AzureT0MGAddOwner\""
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 == "AzureT0MGAddOwner"

Indicators

These rows show field, operator, and value matches.

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

BloodHound Attack Path Finding - Tier Zero Group Control via MS Graph App Role

#

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 - Tier Zero Group Control via MS Graph App Role

Rule Body

id: 70e09635-9787-41af-9973-87d22ca15292
name: BloodHound Attack Path Finding - Tier Zero Group Control via MS Graph App Role
description: BloodHound Attack Path Finding - Tier Zero Group Control via MS Graph
  App Role
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: BloodHoundEnterprise
    dataTypes:
      - BHEAttackPathsData_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: "BHEAttackPathsData_CL \n| where Finding == \"AzureT0MGAddMember\""
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 == "AzureT0MGAddMember"

Indicators

These rows show field, operator, and value matches.

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

Cisco SEG - DLP policy violation

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

'Detects DLP policy violation.'

MITRE ATT&CK Coverage

TacticTechniques
Exfiltration

Rule Body

id: df5c34dd-e1e6-4e07-90b1-4309ebfe754c
name: Cisco SEG - DLP policy violation
description: |
  'Detects DLP policy violation.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: CefAma
    dataTypes:
      - CommonSecurityLog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Exfiltration
relevantTechniques:
  - T1030
query: |
  CiscoSEGEvent
  | where NetworkDirection =~ 'Outgoing'
  | where tostring(AdditionalFields) has 'ESADLPVerdict'
  | extend dlp_verdict = extract(@'ESADLPVerdict":"(NOT_EVALUATED|NO TRIGGER|VIOLATION|NO VIOLATION)"', 1, tostring(AdditionalFields))
  | where dlp_verdict =~ 'VIOLATION'
  | extend AccountCustomEntity = SrcUserName
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountCustomEntity
version: 1.0.3
kind: Scheduled

Stages and Predicates

Stage 1: source

CiscoSEGEvent

Stage 2: where

| where NetworkDirection =~ 'Outgoing'

Stage 3: where

| where tostring(AdditionalFields) has 'ESADLPVerdict'

Stage 4: extend

| extend dlp_verdict = extract(@'ESADLPVerdict":"(NOT_EVALUATED|NO TRIGGER|VIOLATION|NO VIOLATION)"', 1, tostring(AdditionalFields))

Stage 5: where

| where dlp_verdict =~ 'VIOLATION'

Stage 6: extend

| extend AccountCustomEntity = SrcUserName

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
dlp_verdictextend
AccountCustomEntityextend

Cisco Umbrella - URI contains IP address

#
Severity
medium
Time window
10m
Source
github.com/Azure/Azure-Sentinel

'Malware can use IP address to communicate with C2.'

MITRE ATT&CK Coverage

TacticTechniques
Command & ControlNo specific technique

Rule Body

id: ee1818ec-5f65-4991-b711-bcf2ab7e36c3
name: Cisco Umbrella - URI contains IP address
description: |
  'Malware can use IP address to communicate with C2.'
severity: Medium
requiredDataConnectors:
  - connectorId: CiscoUmbrellaDataConnector
    dataTypes:
      - Cisco_Umbrella_proxy_CL
queryFrequency: 10m
queryPeriod: 10m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
query: |
  let lbtime = 10m;
  Cisco_Umbrella
  | where TimeGenerated > ago(lbtime)
  | where EventType == 'proxylogs'
  | where DvcAction =~ 'Allowed'
  | where UrlOriginal matches regex @'\Ahttp:\/\/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}.*'
  | project TimeGenerated, SrcIpAddr, Identities
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: Identities
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SrcIpAddr
version: 1.1.1
kind: Scheduled

Stages and Predicates

Parameters

let lbtime = 10m;

Stage 1: source

Cisco_Umbrella

Stage 2: where

| where TimeGenerated > ago(lbtime)

Stage 3: where

| where EventType == 'proxylogs'

Stage 4: where

| where DvcAction =~ 'Allowed'

Stage 5: where

| where UrlOriginal matches regex @'\Ahttp:\/\/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}.*'

Stage 6: project

| project TimeGenerated, SrcIpAddr, Identities

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
Identitiesproject
SrcIpAddrproject
TimeGeneratedproject

Detect Possible Teams BEC Attack by High Teams Recipients

#
Time window
1d
Group by
$left.SenderObjectId, $right.AccountObjectId, SenderEmailAddress
Author
Robbe Van den Daele
Source
github.com/HybridBrothers/Hunting-Queries-Detection-Rules

An external sender suddenly increasing the amount of internal users they are sending messages to, can indicate that external user being compromised and used for BEC Attacks. In these kind of attacks compromised accounts are used to send phishing links or attachments to users in business relationships.

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

References

Telemetry Coverage

Rule Body

// Possible BEC detection by high teams recipients 
let increase_percentage = 200;
let base = (
    MessageEvents
    | where TimeGenerated > ago(14d)
    // Focus on chat messsages
    | where ThreadType == "chat"
    // Only return external users sending messages
    | join kind=leftanti (
        IdentityInfo
        | where TimeGenerated > ago(14d)
        | distinct AccountObjectId
    ) on $left.SenderObjectId == $right.AccountObjectId
    // Make a set of all the chats they are posting to, for every day
    | summarize ChatSet = make_set(ThreadId) by SenderEmailAddress, bin(TimeGenerated, 1d)
    // Count the amount of chats they posted to for each day
    | extend ChatAmmount = array_length(ChatSet)
);
// Get the average of send chats per external user per day
let averageBySender = (
    base
    | summarize AverageChatsBySender = avg(ChatAmmount) by SenderEmailAddress
);
// Check if the sender dubbled their chats to internal users compared to their baseline
base
| where TimeGenerated > ago(1d)
| join kind=inner averageBySender on SenderEmailAddress
| where ChatAmmount > AverageChatsBySender * (increase_percentage / 100)

Stages and Predicates

Parameters

let increase_percentage = 200;

let base is inlined into the numbered stages below.

Let Binding: averageBySender used in Stages 2, 10

let averageBySender = (
    base
    | summarize AverageChatsBySender = avg(ChatAmmount) by SenderEmailAddress
);

Stage 1: source

let base

Stage 2: source

let averageBySender

Stage 3: source

MessageEvents

Stage 4: where

where TimeGenerated > ago(1209600s)

Stage 5: where

where ThreadType == "chat"

Stage 6: join (negated)

join kind=leftanti (IdentityInfo) on $left.SenderObjectId, $right.AccountObjectId

Stage 7: summarize

summarize ChatSet by SenderEmailAddress

Stage 8: extend

extend ChatAmmount

Stage 9: where

where TimeGenerated > ago(86400s)

Stage 10: join

join kind=inner (averageBySender) on SenderEmailAddress

Stage 11: where

where /* macro: (ChatAmmount > (AverageChatsBySender * (200 / 100))) */

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
ChatSetsummarize
SenderEmailAddresssummarize
ChatAmmountextend

Elevation of Privilege attempt detected

#
Status
available
Severity
high
Time window
1d
Group by
SourceIp, ThreatCategory
Source
github.com/Azure/Azure-Sentinel

Identifies Elevation of Privilege attempt Azure Firewall IDPS logs.

MITRE ATT&CK Coverage

TacticTechniques
Initial Access
Credential Access

Rule Body

id: 7a0f78b3-9a55-4ad0-a56d-b6616fdbff6a
name: Elevation of Privilege attempt detected
description: |
  Identifies Elevation of Privilege attempt Azure Firewall IDPS logs.
severity: High
status: Available
requiredDataConnectors:
  - connectorId: AzureFirewall
    dataTypes:
      - AZFWIdpsSignature
queryFrequency: 1h
queryPeriod: 24h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
  - CredentialAccess
relevantTechniques:
  - T1078
  - T1110
query: |
  let TimeWindow   = 90d;    // How far back to look 
  let HitThreshold = 10;     // Minimum hits to alert per SourceIp + Category
  let MinSeverity  = 1;      // Set Minimum Severity
  // Feature flags for optional filters
  let EnableCategoryFilter    = true;   // Filter 1
  let EnableDescriptionFilter = false;  // Filter 2
  let EnableActionFilter      = false;  // Filter 3
  // Filter 1: Categories of interest
  let CategoriesOfInterest = dynamic([
      "Attempted User Privilege Gain",
      "Unsuccessful User Privilege Gain",
      "Successful User Privilege Gain",
      "Attempted Administrator Privilege Gain",
      "Successful Administrator Privilege Gain"
  ]);
  // Filter 2: Descriptions of interest
  let DescriptionsOfInterest = dynamic([
      "attempted-user",
      "unsuccessful-user",
      "successful-user",
      "attempted-admin",
      "successful-admin"
  ]);
  // Filter 3: Action match
  let MatchActions = dynamic(["Deny", "alert"]);
  AZFWIdpsSignature
  | where TimeGenerated >= ago(TimeWindow)
  | where Severity >= MinSeverity
  // Filter 1: Category filter (optional)
  | where (EnableCategoryFilter == false) or (Category has_any (CategoriesOfInterest))
  // Filter 2: Description filter (optional)
  | where (EnableDescriptionFilter == false) or (Description has_any (DescriptionsOfInterest))
  // Filter 3: Action filter (optional)
  | where (EnableActionFilter == false) or (Action in~ (MatchActions))
  | summarize
      StartTime   = min(TimeGenerated),
      EndTime     = max(TimeGenerated),
      TotalHits   = count(),
      MaxSeverity = max(Severity),
      Actions     = make_set(Action, 5),
      Signatures  = make_set(SignatureId, 20),
      Description = make_set(substring(tostring(Description), 0, 120), 3)
      by SourceIp, ThreatCategory = Category
  | where TotalHits >= HitThreshold
  | project
      StartTime,
      EndTime,
      SourceIp,
      ThreatCategory,
      TotalHits,
      MaxSeverity,
      Actions,
      Signatures,
      Description
  | order by MaxSeverity desc, TotalHits desc
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIp
version: 1.0.1
kind: Scheduled

Stages and Predicates

Parameters

let TimeWindow = 90d;
let HitThreshold = 10;
let MinSeverity = 1;
let EnableCategoryFilter = true;
let EnableDescriptionFilter = false;
let EnableActionFilter = false;
let MatchActions = dynamic(["Deny", "alert"]);

Let Binding: CategoriesOfInterest used in Stage 4

let CategoriesOfInterest = dynamic([
    "Attempted User Privilege Gain",
    "Unsuccessful User Privilege Gain",
    "Successful User Privilege Gain",
    "Attempted Administrator Privilege Gain",
    "Successful Administrator Privilege Gain"
]);

Let Binding: DescriptionsOfInterest used in Stage 5

let DescriptionsOfInterest = dynamic([
    "attempted-user",
    "unsuccessful-user",
    "successful-user",
    "attempted-admin",
    "successful-admin"
]);

Stage 1: source

AZFWIdpsSignature

Stage 2: where

| where TimeGenerated >= ago(TimeWindow)

Stage 3: where

| where Severity >= MinSeverity

Stage 4: where

| where (EnableCategoryFilter == false) or (Category has_any (CategoriesOfInterest))

Stage 5: where

| where (EnableDescriptionFilter == false) or (Description has_any (DescriptionsOfInterest))

Stage 6: where

| where (EnableActionFilter == false) or (Action in~ (MatchActions))

Stage 7: summarize

| summarize
    StartTime   = min(TimeGenerated),
    EndTime     = max(TimeGenerated),
    TotalHits   = count(),
    MaxSeverity = max(Severity),
    Actions     = make_set(Action, 5),
    Signatures  = make_set(SignatureId, 20),
    Description = make_set(substring(tostring(Description), 0, 120), 3)
    by SourceIp, ThreatCategory = Category

Stage 8: where

| where TotalHits >= HitThreshold

Stage 9: project

| project
    StartTime,
    EndTime,
    SourceIp,
    ThreatCategory,
    TotalHits,
    MaxSeverity,
    Actions,
    Signatures,
    Description

Stage 10: sort

| order by MaxSeverity desc, TotalHits desc

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Categorymatch
  • Attempted Administrator Privilege Gain transforms: term
  • Attempted User Privilege Gain transforms: term
  • Successful Administrator Privilege Gain transforms: term
  • Successful User Privilege Gain transforms: term
  • Unsuccessful User Privilege Gain transforms: term
field:"Category" kind:match
Severityge
  • 1 transforms: number
field:"Severity" kind:ge value:"1"
TotalHitsge
  • 10 transforms: number
field:"TotalHits" kind:ge value:"10"

Output Fields

These fields are emitted when the rule matches.

FieldSource
Actionsproject
Descriptionproject
EndTimeproject
MaxSeverityproject
Signaturesproject
SourceIpproject
StartTimeproject
ThreatCategoryproject
TotalHitsproject

Infoblox - TI - InfobloxCDC Match Found - Lookalike Domains

#
Status
available
Severity
medium
Time window
14d
Group by
$left.DomainName, $right.DestinationDnsDomain, IndicatorId
Source
github.com/Azure/Azure-Sentinel

'InfobloxCDC Lookalike Domain match found in your Infoblox TIDE Threat Intelligence. Customize query count, scheduling, responses and more. Modify data sources, types and threat properties as desired. This rule depends on a parser based on a Kusto Function to work as expected called InfobloxCDC.'

MITRE ATT&CK Coverage

Rule Body

id: 568730be-b39d-45e3-a392-941e00837d52
name: Infoblox - TI - InfobloxCDC Match Found - Lookalike Domains
description: |
  'InfobloxCDC Lookalike Domain match found in your Infoblox TIDE Threat Intelligence. Customize query count, scheduling, responses and more. Modify data sources, types and threat properties as desired. This rule depends on a parser based on a Kusto Function to work as expected called [**InfobloxCDC**](https://aka.ms/sentinel-InfobloxCloudDataConnector-parser).'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: ThreatIntelligence
    dataTypes: 
      - ThreatIntelligenceIndicator
  - connectorId: CefAma
    dataTypes:
      - CommonSecurityLog
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Impact
relevantTechniques:
  - T1498
  - T1565
query: |
  let dt_lookBack = 1h;
  let ioc_lookBack = 14d;
  let TI = ThreatIntelligenceIndicator
  | where TimeGenerated >= ago(ioc_lookBack)
  | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
  | where Active == true and ExpirationDateTime > now()  
  | where Description == "Infoblox - HOST - Policy"
  | where Tags has_cs "Property: Policy_LookalikeDomains" 
  | where isnotempty(DomainName)
  ;
  let Data = InfobloxCDC
  | extend HitTime = TimeGenerated
  | where TimeGenerated >= ago(dt_lookBack)
  | where isnotempty(DestinationDnsDomain)
  //Remove trailing period at end of domain
  | extend DestinationDnsDomain = trim_end(@"\.$", DestinationDnsDomain)
  ;
  TI | join kind=innerunique Data on $left.DomainName == $right.DestinationDnsDomain
  | where HitTime >= TimeGenerated and HitTime < ExpirationDateTime
  | project LatestIndicatorTime, HitTime, DeviceEventClassID, ThreatLevel, ThreatLevel_Score, ThreatConfidence, DestinationDnsDomain, InfobloxB1FeedName, ThreatClass, ThreatProperty, InfobloxB1PolicyAction, DeviceAction, InfobloxB1PolicyName, SourceIP, DeviceName, SourceMACAddress, SourceUserName, InfobloxB1SrcOSVersion, InfobloxB1ConnectionType, InfobloxB1Network, AdditionalExtensionsParsedNested, 
  AdditionalInformation, Description, ThreatType, TrafficLightProtocolLevel, Type, ConfidenceScore, ExpirationDateTime, SourceSystem, Action, IndicatorId, ExternalIndicatorId, Tags
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIP
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: DeviceName
      - identifier: OSVersion
        columnName: InfobloxB1SrcOSVersion
      - identifier: FullName
        columnName: SourceUserName
  - entityType: DNS
    fieldMappings:
      - identifier: DomainName
        columnName: DestinationDnsDomain
  - entityType: Malware
    fieldMappings:
      - identifier: Name
        columnName: ThreatProperty
      - identifier: Category
        columnName: ThreatClass
customDetails:
  SourceMACAddress: SourceMACAddress
  InfobloxB1FeedName: InfobloxB1FeedName
  InfobloxB1Network: InfobloxB1Network
  InfobloxB1Action: InfobloxB1PolicyAction
  InfobloxB1PolicyName: InfobloxB1PolicyName
eventGroupingSettings:
  aggregationKind: SingleAlert
incidentConfiguration:
  createIncident: true
version: 1.0.4
kind: Scheduled

Stages and Predicates

Parameters

let dt_lookBack = 1h;
let ioc_lookBack = 14d;

let TI is inlined into the numbered stages below.

Let Binding: Data used in Stage 8

let Data = InfobloxCDC
| extend HitTime = TimeGenerated
| where TimeGenerated >= ago(dt_lookBack)
| where isnotempty(DestinationDnsDomain)
| extend DestinationDnsDomain = trim_end(@"\.$", DestinationDnsDomain);

Stages 1 to 7 define let TI as the rule's main pipeline source. Stages 8 to 10 use it.

Stage 1: source

ThreatIntelligenceIndicator

Stage 2: where

| where TimeGenerated >= ago(ioc_lookBack)

Stage 3: summarize

| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId

Stage 4: where

| where Active == true and ExpirationDateTime > now()

Stage 5: where

| where Description == "Infoblox - HOST - Policy"

Stage 6: where

| where Tags has_cs "Property: Policy_LookalikeDomains"

Stage 7: where

| where isnotempty(DomainName)

Stage 8: join

TI
| join kind=innerunique Data on $left.DomainName == $right.DestinationDnsDomain

Stage 9: where

where HitTime >= TimeGenerated and HitTime < ExpirationDateTime

Stage 10: project

project Action, AdditionalExtensionsParsedNested, AdditionalInformation, ConfidenceScore, Description, DestinationDnsDomain, DeviceAction, DeviceEventClassID, DeviceName, ExpirationDateTime, ExternalIndicatorId, HitTime, IndicatorId, InfobloxB1ConnectionType, InfobloxB1FeedName, InfobloxB1Network, InfobloxB1PolicyAction, InfobloxB1PolicyName, InfobloxB1SrcOSVersion, LatestIndicatorTime, SourceIP, SourceMACAddress, SourceSystem, SourceUserName, Tags, ThreatClass, ThreatConfidence, ThreatLevel, ThreatLevel_Score, ThreatProperty, ThreatType, TrafficLightProtocolLevel, Type

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
Actionproject
AdditionalExtensionsParsedNestedproject
AdditionalInformationproject
ConfidenceScoreproject
Descriptionproject
DestinationDnsDomainproject
DeviceActionproject
DeviceEventClassIDproject
DeviceNameproject
ExpirationDateTimeproject
ExternalIndicatorIdproject
HitTimeproject
IndicatorIdproject
InfobloxB1ConnectionTypeproject
InfobloxB1FeedNameproject
InfobloxB1Networkproject
InfobloxB1PolicyActionproject
InfobloxB1PolicyNameproject
InfobloxB1SrcOSVersionproject
LatestIndicatorTimeproject
SourceIPproject
SourceMACAddressproject
SourceSystemproject
SourceUserNameproject
Tagsproject
ThreatClassproject
ThreatConfidenceproject
ThreatLevelproject
ThreatLevel_Scoreproject
ThreatPropertyproject
ThreatTypeproject
TrafficLightProtocolLevelproject
Typeproject

Jira - New site admin user

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

'Detects new site admin user.'

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Rule Body

id: 6bf42891-b54d-4b4e-8533-babc5b3ea4c5
name: Jira - New site admin user
description: |
  'Detects new site admin user.'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: JiraAuditAPI
    dataTypes:
      - JiraAudit
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1078
query: |
  let priv_users = JiraAudit
  | where TimeGenerated > ago(14d)
  | where EventMessage =~ 'User added to group'
  | where ObjectItemName =~ 'site-admins'
  | extend user = todynamic(AssociatedItems)[0]['name']
  | summarize makeset(user);
  JiraAudit
  | where EventMessage =~ "User's password changed"
  | extend user = todynamic(AssociatedItems)[0]['name']
  | where user in (priv_users)
  | extend AccountCustomEntity = user
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountCustomEntity
version: 1.0.1
kind: Scheduled

Stages and Predicates

Let Binding: priv_users used in Stage 4

let priv_users = JiraAudit
| where TimeGenerated > ago(14d)
| where EventMessage =~ 'User added to group'
| where ObjectItemName =~ 'site-admins'
| extend user = todynamic(AssociatedItems)[0]['name']
| summarize makeset(user);

Stage 1: source

JiraAudit

Stage 2: where

| where EventMessage =~ "User's password changed"

Stage 3: extend

| extend user = todynamic(AssociatedItems)[0]['name']

Stage 4: where

| where user in (priv_users)

Stage 5: extend

| extend AccountCustomEntity = user

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
userextend
AccountCustomEntityextend

Jira - New user created

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

'Detects when new user was created.'

MITRE ATT&CK Coverage

TacticTechniques
Persistence

Rule Body

id: 8c90f30f-c612-407c-91a0-c6a6b41ac199
name: Jira - New user created
description: |
  'Detects when new user was created.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: JiraAuditAPI
    dataTypes:
      - JiraAudit
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Persistence
relevantTechniques:
  - T1078
query: |
  JiraAudit
  | where EventMessage =~ 'User created'
  | where ObjectItemTypeName =~ 'USER'
  | project EventCreationTime, UserName, SrcIpAddr, ObjectItemName, AssociatedItems
  | extend AccountCustomEntity = ObjectItemName
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountCustomEntity
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

JiraAudit

Stage 2: where

| where EventMessage =~ 'User created'

Stage 3: where

| where ObjectItemTypeName =~ 'USER'

Stage 4: project

| project EventCreationTime, UserName, SrcIpAddr, ObjectItemName, AssociatedItems

Stage 5: extend

| extend AccountCustomEntity = ObjectItemName

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AssociatedItemsproject
EventCreationTimeproject
ObjectItemNameproject
SrcIpAddrproject
UserNameproject
AccountCustomEntityextend

PaloAlto - Dropping or denying session with traffic

#
Status
available
Severity
medium
Time window
10m
Group by
DstUsername, SrcIpAddr
Source
github.com/Azure/Azure-Sentinel

'Detects dropping or denying session with traffic.'

MITRE ATT&CK Coverage

Rule Body

id: ba663b74-51f4-11ec-bf63-0242ac130002
name: PaloAlto - Dropping or denying session with traffic
description: |
  'Detects dropping or denying session with traffic.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: CefAma
    dataTypes:
      - CommonSecurityLog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1190
  - T1133
query: |
  let threshold = 100;
  PaloAltoCDLEvent
  | where EventResourceId =~ 'TRAFFIC'
  | where EventResult has_any ("deny", "drop", "reject") 
  | where tolong(DstBytes) > 0
  | where tolong(NetworkPackets) > 0
  | summarize count() by SrcIpAddr, DstUsername, bin(TimeGenerated, 10m)
  | where count_ > threshold
  | extend AccountCustomEntity = DstUsername, IPCustomEntity = SrcIpAddr
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountCustomEntity
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPCustomEntity
version: 1.0.4
kind: Scheduled

Stages and Predicates

Parameters

let threshold = 100;

Stage 1: source

PaloAltoCDLEvent

Stage 2: where

| where EventResourceId =~ 'TRAFFIC'

Stage 3: where

| where EventResult has_any ("deny", "drop", "reject")

Stage 4: where

| where tolong(DstBytes) > 0

Stage 5: where

| where tolong(NetworkPackets) > 0

Stage 6: summarize

| summarize count() by SrcIpAddr, DstUsername, bin(TimeGenerated, 10m)

Stage 7: where

| where count_ > threshold

Stage 8: extend

| extend AccountCustomEntity = DstUsername, IPCustomEntity = SrcIpAddr

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
DstBytesgt
  • 0 transforms: tolong
field:"DstBytes" kind:gt value:"0"
EventResourceIdeq
  • TRAFFIC
field:"EventResourceId" kind:eq value:"TRAFFIC"
EventResultmatch
  • deny transforms: term
  • drop transforms: term
  • reject transforms: term
field:"EventResult" kind:match
NetworkPacketsgt
  • 0 transforms: tolong
field:"NetworkPackets" kind:gt value:"0"
count_gt
  • 100 transforms: number
field:"count_" kind:gt value:"100"

Output Fields

These fields are emitted when the rule matches.

FieldSource
DstUsernamesummarize
SrcIpAddrsummarize
AccountCustomEntityextend
IPCustomEntityextend

Process Tree Analysis

#
Group by
DeviceId, FileName, InitiatingProcessCreationTime, InitiatingProcessFileName, InitiatingProcessG1ParentCreationTime, InitiatingProcessG1ParentFileName, InitiatingProcessG1ParentId, InitiatingProcessG2ParentCreationTime, InitiatingProcessG2ParentFileName, InitiatingProcessG2ParentId, InitiatingProcessG3ParentCreationTime, InitiatingProcessG3ParentFileName, InitiatingProcessG3ParentId, InitiatingProcessG4ParentFileName, InitiatingProcessId, InitiatingProcessParentCreationTime, InitiatingProcessParentFileName, InitiatingProcessParentId
Author
Cyb3rMonk
Source
github.com/Cyb3r-Monk/Threat-Hunting-and-Detection

Below queries perform process tree analysis on MDE/MDATP, Azure Sentinel (Sysmon), and Splunk (Sysmon) and displays anomalous trees. All queries run smoothly even in the large environments. Detailed explanation is here

References

Telemetry Coverage

Rule Body

let timeframe = 48h;
// Define of which processes you want to generate process tree
let _selected_processes = dynamic(["winword.exe","excel.exe","powerpnt.exe","acrord32.exe", "FoxitPhantomPDF.exe","MicrosoftPdfReader.exe","SumatraPDF.exe", "onenote.exe"]);
// First, generate the process tree and store it in the cache.
// Renaming fields accordingly to generate a tree up to 7th level
// In each step, project only the required fields to optimize resource usage
let _process_tree_data= materialize
( DeviceProcessEvents
    | where Timestamp > ago(timeframe)
    | where InitiatingProcessFileName in~ (_selected_processes )
    | project DeviceId,DeviceName,
              InitiatingProcessG3ParentFileName=FileName,InitiatingProcessG3ParentSHA1=SHA1,InitiatingProcessG3ParentId=ProcessId, InitiatingProcessG3ParentCommandLine=ProcessCommandLine,InitiatingProcessG3ParentCreationTime=todatetime(ProcessCreationTime),
              InitiatingProcessG4ParentFileName=InitiatingProcessFileName,InitiatingProcessG4ParentSHA1=InitiatingProcessSHA1,InitiatingProcessG4ParentId=InitiatingProcessId,InitiatingProcessG4ParentCommandLine=InitiatingProcessCommandLine, InitiatingProcessG4ParentCreationTime=todatetime(InitiatingProcessCreationTime)
    // Start iteration
    // 1st iteration of join. From now on, query all processes, rename fields, and join accordingly
    | join kind=leftouter (
                DeviceProcessEvents
                    | where Timestamp > ago(timeframe)
                    | project DeviceId, InitiatingProcessG2ParentFileName=FileName,InitiatingProcessG2ParentFolderPath=FolderPath,InitiatingProcessG2ParentSHA1=SHA1, InitiatingProcessG2ParentId=ProcessId,  InitiatingProcessG2ParentCommandLine=ProcessCommandLine, InitiatingProcessG2ParentCreationTime=todatetime(ProcessCreationTime),
                       InitiatingProcessG3ParentFileName=InitiatingProcessFileName,InitiatingProcessG3ParentFolderPath=InitiatingProcessFolderPath,InitiatingProcessG3ParentSHA1=InitiatingProcessSHA1, InitiatingProcessG3ParentId=InitiatingProcessId,  InitiatingProcessG3ParentCommandLine=InitiatingProcessCommandLine, InitiatingProcessG3ParentCreationTime=todatetime(InitiatingProcessCreationTime)
                     )
                     on DeviceId , InitiatingProcessG3ParentFileName, InitiatingProcessG3ParentId, InitiatingProcessG3ParentCreationTime
        // 2nd iteration of join.
        | join kind=leftouter (
                    DeviceProcessEvents
                        | where Timestamp > ago(timeframe)
                        | project DeviceId, InitiatingProcessG1ParentFileName=FileName,InitiatingProcessG1ParentFolderPath=FolderPath,InitiatingProcessG1ParentSHA1=SHA1, InitiatingProcessG1ParentId=ProcessId,  InitiatingProcessG1ParentCommandLine=ProcessCommandLine, InitiatingProcessG1ParentCreationTime=todatetime(ProcessCreationTime),
                        InitiatingProcessG2ParentFileName=InitiatingProcessFileName,InitiatingProcessG2ParentFolderPath=InitiatingProcessFolderPath,InitiatingProcessG2ParentSHA1=InitiatingProcessSHA1, InitiatingProcessG2ParentId=InitiatingProcessId,  InitiatingProcessG2ParentCommandLine=InitiatingProcessCommandLine, InitiatingProcessG2ParentCreationTime=todatetime(InitiatingProcessCreationTime)
                        )
                        on DeviceId , InitiatingProcessG2ParentFileName , InitiatingProcessG2ParentId, InitiatingProcessG2ParentCreationTime
            // 3rd iteration of join.
            | join kind=leftouter (
                        DeviceProcessEvents
                            | where Timestamp > ago(timeframe)
                            | project DeviceId, InitiatingProcessParentFileName=FileName,InitiatingProcessParentFolderPath=FolderPath,InitiatingProcessParentSHA1=SHA1, InitiatingProcessParentId=ProcessId,  InitiatingProcessParentCommandLine=ProcessCommandLine, InitiatingProcessParentCreationTime=ProcessCreationTime,
                            InitiatingProcessG1ParentFileName=InitiatingProcessFileName,InitiatingProcessG1ParentFolderPath=InitiatingProcessFolderPath,InitiatingProcessG1ParentSHA1=InitiatingProcessSHA1, InitiatingProcessG1ParentId=InitiatingProcessId,  InitiatingProcessG1ParentCommandLine=InitiatingProcessCommandLine, InitiatingProcessG1ParentCreationTime=todatetime(InitiatingProcessCreationTime)
                            )
                            on DeviceId , InitiatingProcessG1ParentFileName , InitiatingProcessG1ParentId, InitiatingProcessG1ParentCreationTime
                // 4th iteration of join
                | join kind=leftouter (
                            DeviceProcessEvents
                                | where Timestamp > ago(timeframe)
                                | project DeviceId, InitiatingProcessFileName=FileName,InitiatingProcessSHA1=SHA1, InitiatingProcessId=ProcessId,  InitiatingProcessCommandLine=ProcessCommandLine, InitiatingProcessCreationTime=ProcessCreationTime,
                                InitiatingProcessParentFileName=InitiatingProcessFileName,InitiatingProcessParentSHA1=InitiatingProcessSHA1, InitiatingProcessParentId=InitiatingProcessId,  InitiatingProcessParentCommandLine=InitiatingProcessCommandLine, InitiatingProcessParentCreationTime=InitiatingProcessCreationTime
                                )
                                on DeviceId , InitiatingProcessParentFileName , InitiatingProcessParentId, InitiatingProcessParentCreationTime
                    // 5th iteration of join
                    | join kind=leftouter (
                                DeviceProcessEvents
                                    | where Timestamp > ago(timeframe)
                                    | project Timestamp, DeviceId, FileName,SHA1, ProcessId, ProcessCommandLine, ProcessCreationTime,
                                    InitiatingProcessFileName,InitiatingProcessSHA1, InitiatingProcessId, InitiatingProcessCommandLine, InitiatingProcessCreationTime
                                    )
                                    on DeviceId , InitiatingProcessFileName , InitiatingProcessId, InitiatingProcessCreationTime
);
// Use the cached results and find the rare patterns based on process names.
_process_tree_data
|summarize count() by FileName,InitiatingProcessFileName,InitiatingProcessParentFileName,InitiatingProcessG1ParentFileName,InitiatingProcessG2ParentFileName,InitiatingProcessG3ParentFileName,InitiatingProcessG4ParentFileName
| where count_ < 10 // If the count of a pattern is less than 10, it is anomalous. Threshold can be changed.
// Now, join the anomalous patterns with the original results to get the details.
| join kind=inner _process_tree_data on FileName,InitiatingProcessFileName,InitiatingProcessParentFileName,InitiatingProcessG1ParentFileName,InitiatingProcessG2ParentFileName,InitiatingProcessG3ParentFileName,InitiatingProcessG4ParentFileName
// Now, join the anomalous patterns with the original results to get the details.
|project Timestamp=case(isnotempty(Timestamp),Timestamp,isnotempty(InitiatingProcessParentCreationTime),InitiatingProcessParentCreationTime,isnotempty(InitiatingProcessG1ParentCreationTime),InitiatingProcessG1ParentCreationTime,
    isnotempty(InitiatingProcessG2ParentCreationTime),InitiatingProcessG2ParentCreationTime,isnotempty(InitiatingProcessG3ParentCreationTime),InitiatingProcessG3ParentCreationTime,InitiatingProcessG4ParentCreationTime),
    count_ , DeviceId, DeviceName,
    InitiatingProcessG4ParentFileName,InitiatingProcessG3ParentFileName,InitiatingProcessG2ParentFileName,InitiatingProcessG1ParentFileName,InitiatingProcessParentFileName,InitiatingProcessFileName,FileName,
    InitiatingProcessG4ParentCommandLine, InitiatingProcessG3ParentCommandLine, InitiatingProcessG2ParentCommandLine, InitiatingProcessG1ParentCommandLine, InitiatingProcessCommandLine, ProcessCommandLine,
    InitiatingProcessG4ParentId,  InitiatingProcessG4ParentCreationTime,
    InitiatingProcessG3ParentId, InitiatingProcessG3ParentFolderPath ,InitiatingProcessG3ParentSHA1,  InitiatingProcessG3ParentCreationTime,
    InitiatingProcessG2ParentId,InitiatingProcessG2ParentFolderPath,InitiatingProcessG2ParentSHA1, InitiatingProcessG2ParentCreationTime,
    InitiatingProcessG1ParentId,InitiatingProcessG1ParentFolderPath,InitiatingProcessG1ParentSHA1,  InitiatingProcessG1ParentCreationTime,
    InitiatingProcessParentId, InitiatingProcessParentFolderPath,InitiatingProcessParentSHA1, InitiatingProcessParentCommandLine ,InitiatingProcessParentCreationTime,
    InitiatingProcessId, InitiatingProcessSHA1,  InitiatingProcessCreationTime,
    ProcessId, SHA1,  ProcessCreationTime
| order by Timestamp, DeviceName, InitiatingProcessG4ParentCreationTime , InitiatingProcessG3ParentCreationTime , InitiatingProcessG2ParentCreationTime , InitiatingProcessG1ParentCreationTime , InitiatingProcessCreationTime

let _timeframe = 1d;
// define of which processes you want to generate process tree
let _selected_processes = dynamic(["winword.exe","excel.exe","powerpnt.exe","acrord32.exe", "FoxitPhantomPDF.exe","MicrosoftPdfReader.exe","SumatraPDF.exe", "onenote.exe"]);
// Sysmon logs are not parsed automatically, below function parses the Sysmon EventID=1 logs.
let parse_sysmon_1 = (T:(TimeGenerated:datetime,EventID:int, Source:string,RenderedDescription:string, EventData:string))
{
T
| where TimeGenerated > ago(_timeframe)
| where Source == "Microsoft-Windows-Sysmon" and EventID == 1
| extend RenderedDescription = tostring(split(RenderedDescription, ":")[0])
| extend EventData = parse_xml(EventData).DataItem.EventData.Data
| mv-expand bagexpansion=array EventData
| evaluate bag_unpack(EventData)
| extend Key=tostring(['@Name']), Value=['#text']
| evaluate pivot(Key, any(Value), TimeGenerated, Source, EventLog, Computer, EventLevel, EventLevelName, EventID, UserName, RenderedDescription, MG, ManagementGroupName, Type, _ResourceId)
| extend RuleName = column_ifexists("RuleName", ""), TechniqueId = column_ifexists("TechniqueId", ""),  TechniqueName = column_ifexists("TechniqueName", "")
| parse RuleName with * 'technique_id=' TechniqueId ',' * 'technique_name=' TechniqueName
};
// First, generate the process tree and store it in the cache
let _process_tree_data = materialize (
    // First, get only the processes created by selected process
    Event
    | invoke parse_sysmon_1() //parsing the sysmon logs
    // Get only the required fields. Rrenaming fields accordingly to generate a tree up to 5th level
    | project Computer,G2_ParentProcessId=tostring(ProcessId),G2_ParentProcess=tostring(OriginalFileName),G2_ParentProcessGuid=tostring(ProcessGuid),G2_ParentProcessCommandLine=tostring(CommandLine),
            G3_ParentProcessId=tostring(ParentProcessId),G3_ParentProcess=tostring(parse_path(tostring(ParentImage)).Filename),G3_ParentProcessGuid=tostring(ParentProcessGuid),G3_ParentProcessCommandLine=tostring(ParentCommandLine)
    | where G3_ParentProcess in~ (_selected_processes)
    // Start iteration
    // First iteration of join. From now on, query all processes, rename fields, and join accordingly
    | join kind=leftouter
        (
            Event
            | invoke parse_sysmon_1()
            | project G1_ParentProcessId=tostring(ProcessId),G1_ParentProcess=tostring(OriginalFileName),G1_ParentProcessGuid=tostring(ProcessGuid),G1_ParentProcessCommandLine=tostring(CommandLine),
                G2_ParentProcessId=tostring(ParentProcessId),G2_ParentProcess=tostring(parse_path(tostring(ParentImage)).Filename),G2_ParentProcessGuid=tostring(ParentProcessGuid),G2_ParentProcessCommandLine=tostring(ParentCommandLine)
        ) on G2_ParentProcessGuid
        // Second iteration of join.
        | join kind=leftouter
            (
                Event
                | invoke parse_sysmon_1()
                | project ParentProcessId=tostring(ProcessId),ParentProcess=tostring(OriginalFileName),ParentProcessGuid=tostring(ProcessGuid),ParentProcessCommandLine=tostring(CommandLine),
                        G1_ParentProcessId=tostring(ParentProcessId),G1_ParentProcess=tostring(parse_path(tostring(ParentImage)).Filename),G1_ParentProcessGuid=tostring(ParentProcessGuid),G1_ParentProcessCommandLine=tostring(ParentCommandLine)
            ) on G1_ParentProcessGuid
            // Third iteration of join.
            | join kind=leftouter
                (
                    Event
                    | invoke parse_sysmon_1()
                    | project ProcessId=tostring(ProcessId),Process=tostring(OriginalFileName),ProcessGuid=tostring(ProcessGuid),ProcessCommandLine=tostring(CommandLine),
                            ParentProcessId=tostring(ParentProcessId),ParentProcess=tostring(parse_path(tostring(ParentImage)).Filename),ParentProcessGuid=tostring(ParentProcessGuid),ParentProcessCommandLine=tostring(ParentCommandLine)
                ) on ParentProcessGuid
            );
// Use the cached results and find the rare patterns based on process names.
_process_tree_data
| summarize count() by Process, ParentProcess, G1_ParentProcess, G2_ParentProcess, G3_ParentProcess
| where count_ < 10 // If the count of a pattern is less than 10, it is anomalous. Threshold can be changed.
// Now, join the anomalous patterns with the original results to get the details.
| join kind=inner _process_tree_data on Process, ParentProcess, G1_ParentProcess, G2_ParentProcess, G3_ParentProcess
| project-reorder Computer, count_, G3_ParentProcess, G2_ParentProcess, G1_ParentProcess, ParentProcess, Process,
                  G3_ParentProcessCommandLine, G2_ParentProcessCommandLine, G1_ParentProcessCommandLine, ParentProcessCommandLine, ProcessCommandLine,
                  G3_ParentProcessId, G2_ParentProcessId, G1_ParentProcessId, ParentProcessId, ProcessId

Stages and Predicates

Parameters

let timeframe = 48h;
let _selected_processes = dynamic(["winword.exe","excel.exe","powerpnt.exe","acrord32.exe", "FoxitPhantomPDF.exe","MicrosoftPdfReader.exe","SumatraPDF.exe", "onenote.exe"]);

let _process_tree_data is inlined into the numbered stages below.

Stage 1: source

let _process_tree_data

Stage 2: source

DeviceProcessEvents

Stage 3: where

where Timestamp > ago(172800s)

Stage 4: where

where InitiatingProcessFileName in~ ("FoxitPhantomPDF.exe", "MicrosoftPdfReader.exe", "SumatraPDF.exe", "acrord32.exe", "excel.exe", "onenote.exe", "powerpnt.exe", "winword.exe")

Stage 5: project

project DeviceId, DeviceName, InitiatingProcessG3ParentCommandLine, InitiatingProcessG3ParentCreationTime, InitiatingProcessG3ParentFileName, InitiatingProcessG3ParentId, InitiatingProcessG3ParentSHA1, InitiatingProcessG4ParentCommandLine, InitiatingProcessG4ParentCreationTime, InitiatingProcessG4ParentFileName, InitiatingProcessG4ParentId, InitiatingProcessG4ParentSHA1

Stage 6: join

join kind=leftouter (DeviceProcessEvents) on DeviceId, InitiatingProcessG3ParentFileName, InitiatingProcessG3ParentId, InitiatingProcessG3ParentCreationTime

Stage 7: join

join kind=leftouter (DeviceProcessEvents) on DeviceId, InitiatingProcessG2ParentFileName, InitiatingProcessG2ParentId, InitiatingProcessG2ParentCreationTime

Stage 8: join

join kind=leftouter (DeviceProcessEvents) on DeviceId, InitiatingProcessG1ParentFileName, InitiatingProcessG1ParentId, InitiatingProcessG1ParentCreationTime

Stage 9: join

join kind=leftouter (DeviceProcessEvents) on DeviceId, InitiatingProcessParentFileName, InitiatingProcessParentId, InitiatingProcessParentCreationTime

Stage 10: join

join kind=leftouter (DeviceProcessEvents) on DeviceId, InitiatingProcessFileName, InitiatingProcessId, InitiatingProcessCreationTime

Stage 11: summarize

summarize by FileName, InitiatingProcessFileName, InitiatingProcessParentFileName, InitiatingProcessG1ParentFileName, InitiatingProcessG2ParentFileName, InitiatingProcessG3ParentFileName, InitiatingProcessG4ParentFileName

Stage 12: where

where count_ < 10

Stage 13: join

join kind=inner (_process_tree_data) on FileName, InitiatingProcessFileName, InitiatingProcessParentFileName, InitiatingProcessG1ParentFileName, InitiatingProcessG2ParentFileName, InitiatingProcessG3ParentFileName, InitiatingProcessG4ParentFileName

Stage 14: project

project DeviceId, DeviceName, FileName, InitiatingProcessCommandLine, InitiatingProcessCreationTime, InitiatingProcessFileName, InitiatingProcessG1ParentCommandLine, InitiatingProcessG1ParentCreationTime, InitiatingProcessG1ParentFileName, InitiatingProcessG1ParentFolderPath, InitiatingProcessG1ParentId, InitiatingProcessG1ParentSHA1, InitiatingProcessG2ParentCommandLine, InitiatingProcessG2ParentCreationTime, InitiatingProcessG2ParentFileName, InitiatingProcessG2ParentFolderPath, InitiatingProcessG2ParentId, InitiatingProcessG2ParentSHA1, InitiatingProcessG3ParentCommandLine, InitiatingProcessG3ParentCreationTime, InitiatingProcessG3ParentFileName, InitiatingProcessG3ParentFolderPath, InitiatingProcessG3ParentId, InitiatingProcessG3ParentSHA1, InitiatingProcessG4ParentCommandLine, InitiatingProcessG4ParentCreationTime, InitiatingProcessG4ParentFileName, InitiatingProcessG4ParentId, InitiatingProcessId, InitiatingProcessParentCommandLine, InitiatingProcessParentCreationTime, InitiatingProcessParentFileName, InitiatingProcessParentFolderPath, InitiatingProcessParentId, InitiatingProcessParentSHA1, InitiatingProcessSHA1, ProcessCommandLine, ProcessCreationTime, ProcessId, SHA1, Timestamp, count_

Stage 15: sort

sort by DeviceName, InitiatingProcessCreationTime, InitiatingProcessG1ParentCreationTime, InitiatingProcessG2ParentCreationTime, InitiatingProcessG3ParentCreationTime, InitiatingProcessG4ParentCreationTime, Timestamp

Stage 16: source

let _process_tree_data

Stage 17: source

Event

Stage 18: where

where TimeGenerated > ago(86400s)

Stage 19: where

where EventID == 1 and Source == "Microsoft-Windows-Sysmon"

Stage 20: extend

extend RenderedDescription

Stage 21: extend

extend EventData

Stage 22: mv-expand

mv-expand EventData

Stage 23: evaluate

evaluate

Stage 24: extend

extend Key, Value

Stage 25: evaluate

evaluate

Stage 26: extend

extend RuleName, TechniqueId, TechniqueName

Stage 27: parse

parse

Stage 28: project

project Computer, G2_ParentProcess, G2_ParentProcessCommandLine, G2_ParentProcessGuid, G2_ParentProcessId, G3_ParentProcess, G3_ParentProcessCommandLine, G3_ParentProcessGuid, G3_ParentProcessId

Stage 29: where

where G3_ParentProcess in~ ("FoxitPhantomPDF.exe", "MicrosoftPdfReader.exe", "SumatraPDF.exe", "acrord32.exe", "excel.exe", "onenote.exe", "powerpnt.exe", "winword.exe")

Stage 30: join

join kind=leftouter (Event) on G2_ParentProcessGuid

Stage 31: join

join kind=leftouter (Event) on G1_ParentProcessGuid

Stage 32: join

join kind=leftouter (Event) on ParentProcessGuid

Stage 33: summarize

summarize by Process, ParentProcess, G1_ParentProcess, G2_ParentProcess, G3_ParentProcess

Stage 34: where

where count_ < 10

Stage 35: join

join kind=inner (_process_tree_data) on Process, ParentProcess, G1_ParentProcess, G2_ParentProcess, G3_ParentProcess

Stage 36: project-reorder

project-reorder

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
EventIDeq
  • 1
field:"EventID" kind:eq value:"1"
G3_ParentProcessin
  • FoxitPhantomPDF.exe
  • MicrosoftPdfReader.exe
  • SumatraPDF.exe
  • acrord32.exe
  • excel.exe
  • onenote.exe
  • powerpnt.exe
  • winword.exe
field:"G3_ParentProcess" kind:in
InitiatingProcessFileNamein
  • FoxitPhantomPDF.exe
  • MicrosoftPdfReader.exe
  • SumatraPDF.exe
  • acrord32.exe
  • excel.exe
  • onenote.exe
  • powerpnt.exe
  • winword.exe
field:"InitiatingProcessFileName" kind:in
Sourceeq
  • Microsoft-Windows-Sysmon
field:"Source" kind:eq value:"Microsoft-Windows-Sysmon"
count_lt
  • 10 transforms: number
field:"count_" kind:lt value:"10"

Output Fields

These fields are emitted when the rule matches.

FieldSource
DeviceIdproject
DeviceNameproject
FileNameproject
InitiatingProcessCommandLineproject
InitiatingProcessCreationTimeproject
InitiatingProcessFileNameproject
InitiatingProcessG1ParentCommandLineproject
InitiatingProcessG1ParentCreationTimeproject
InitiatingProcessG1ParentFileNameproject
InitiatingProcessG1ParentFolderPathproject
InitiatingProcessG1ParentIdproject
InitiatingProcessG1ParentSHA1project
InitiatingProcessG2ParentCommandLineproject
InitiatingProcessG2ParentCreationTimeproject
InitiatingProcessG2ParentFileNameproject
InitiatingProcessG2ParentFolderPathproject
InitiatingProcessG2ParentIdproject
InitiatingProcessG2ParentSHA1project
InitiatingProcessG3ParentCommandLineproject
InitiatingProcessG3ParentCreationTimeproject
InitiatingProcessG3ParentFileNameproject
InitiatingProcessG3ParentFolderPathproject
InitiatingProcessG3ParentIdproject
InitiatingProcessG3ParentSHA1project
InitiatingProcessG4ParentCommandLineproject
InitiatingProcessG4ParentCreationTimeproject
InitiatingProcessG4ParentFileNameproject
InitiatingProcessG4ParentIdproject
InitiatingProcessIdproject
InitiatingProcessParentCommandLineproject
InitiatingProcessParentCreationTimeproject
InitiatingProcessParentFileNameproject
InitiatingProcessParentFolderPathproject
InitiatingProcessParentIdproject
InitiatingProcessParentSHA1project
InitiatingProcessSHA1project
ProcessCommandLineproject
ProcessCreationTimeproject
ProcessIdproject
SHA1project
Timestampproject
count_project

SAP BTP - Trust and authorization Identity Provider monitor

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

Identifies CRUD operations on Identity Provider settings within a sub account.

MITRE ATT&CK Coverage

Rule Body

id: 62357c23-ecdc-4edc-9349-8338063af1ef
kind: Scheduled
name: SAP BTP - Trust and authorization Identity Provider monitor
description: Identifies CRUD operations on Identity Provider settings within a sub
  account.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: SAPBTPAuditEvents
    dataTypes:
      - SAPBTPAuditLog_CL
queryFrequency: 15m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CredentialAccess
  - PrivilegeEscalation
relevantTechniques:
  - T1606
  - T1556
  - T1134
query: |
  SAPBTPAuditLog_CL
  | where isnotnull(Message.object)
  | extend Object = Message.object, Attributes = Message.attributes
  | where Object.type == "IdentityProvider"
  | extend CrudType = tostring(parse_json(tostring(Object.id)).crudType)
  | mv-expand Attributes
  | extend MessageText = case(
                             CrudType == "CREATE",
                             "An identity provider was created",
                             CrudType == "UPDATE",
                             "An identity provider was updated",
                             CrudType == "DELETE",
                             "An identity provider was deleted",
                             "Unclassified CRUD operation encountered"
                         )
  | extend NewAttributes = parse_json(replace_regex(tostring(Attributes.new), "\\r", ""))
  | extend OldAttributes = parse_json(replace_regex(tostring(Attributes.old), "\\r", ""))
  | extend IdentityProviderName = case(
                                      CrudType == "CREATE" or CrudType == "UPDATE",
                                      NewAttributes.name,
                                      CrudType == "DELETE",
                                      OldAttributes.name,
                                      "Unknown"
                                  )
  | project
      UpdatedOn,
      UserName,
      MessageText,
      IdentityProviderName,
      Tenant,
      SpaceId,
      CloudApp = "SAP BTP"
  | extend AccountName = split(UserName, "@")[0], UPNSuffix = split(UserName, "@")[1]
eventGroupingSettings:
  aggregationKind: SingleAlert
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: CloudApplication
    fieldMappings:
      - identifier: Name
        columnName: CloudApp
alertDetailsOverride:
  alertDisplayNameFormat: 'SAP BTP: {{MessageText}}'
  alertDescriptionFormat: '{{MessageText}} by {{UserName}}. Identity provider name:
    {{IdentityProviderName}}'
version: 3.0.6

Stages and Predicates

Stage 1: source

SAPBTPAuditLog_CL

Stage 2: where

| where isnotnull(Message.object)

Stage 3: extend

| extend Object = Message.object, Attributes = Message.attributes

Stage 4: where

| where Object.type == "IdentityProvider"

Stage 5: extend

| extend CrudType = tostring(parse_json(tostring(Object.id)).crudType)

Stage 6: mv-expand

| mv-expand Attributes

Stage 7: extend (4 consecutive steps)

| extend MessageText = case(
                           CrudType == "CREATE",
                           "An identity provider was created",
                           CrudType == "UPDATE",
                           "An identity provider was updated",
                           CrudType == "DELETE",
                           "An identity provider was deleted",
                           "Unclassified CRUD operation encountered"
                       )
| extend NewAttributes = parse_json(replace_regex(tostring(Attributes.new), "\\r", ""))
| extend OldAttributes = parse_json(replace_regex(tostring(Attributes.old), "\\r", ""))
| extend IdentityProviderName = case(
                                    CrudType == "CREATE" or CrudType == "UPDATE",
                                    NewAttributes.name,
                                    CrudType == "DELETE",
                                    OldAttributes.name,
                                    "Unknown"
                                )
MessageText =
ifCrudType == "CREATE""An identity provider was created"
elifCrudType == "UPDATE""An identity provider was updated"
elifCrudType == "DELETE""An identity provider was deleted"
else"Unclassified CRUD operation encountered"

Stage 8: project

| project
    UpdatedOn,
    UserName,
    MessageText,
    IdentityProviderName,
    Tenant,
    SpaceId,
    CloudApp = "SAP BTP"

Stage 9: extend

| extend AccountName = split(UserName, "@")[0], UPNSuffix = split(UserName, "@")[1]

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Message.objectis_not_null
  • (no value, null check)
field:"Message.object" kind:is_not_null
Object.typeeq
  • IdentityProvider transforms: kusto_dynamic_path
field:"Message.object.type" kind:eq value:"IdentityProvider"

Output Fields

These fields are emitted when the rule matches.

FieldSource
CloudAppproject
IdentityProviderNameproject
MessageTextproject
SpaceIdproject
Tenantproject
UpdatedOnproject
UserNameproject
AccountNameextend
UPNSuffixextend

Tailscale: Device started advertising subnet routes

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

Identifies when a tailnet device begins advertising subnet routes (subnet-router capability) not present in the previous snapshot. Unexpected advertisement may indicate a compromised node expanding reachable surface area or an unsanctioned admin change.

MITRE ATT&CK Coverage

TacticTechniques
Persistence
Lateral Movement

Rule Body

id: c2b3d4e5-2345-6789-01ab-cdef12345002
name: "Tailscale: Device started advertising subnet routes"
description: |
  Identifies when a tailnet device begins advertising subnet routes (subnet-router capability) not present in the previous snapshot. Unexpected advertisement may indicate a compromised node expanding reachable surface area or an unsanctioned admin change.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: TailscaleCCF
    dataTypes:
      - Tailscale_Devices_CL
queryFrequency: 1h
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - LateralMovement
  - Persistence
relevantTechniques:
  - T1021
  - T1556
query: |
  let recent =
      Tailscale_Devices_CL
      | where TimeGenerated > ago(1h)
      | summarize arg_max(TimeGenerated, *) by DeviceId
      | where array_length(AdvertisedRoutes) > 0;
  let baseline =
      Tailscale_Devices_CL
      | where TimeGenerated between (ago(1d + 1h) .. ago(1h))
      | summarize arg_max(TimeGenerated, *) by DeviceId
      | where array_length(AdvertisedRoutes) > 0
      | distinct DeviceId;
  recent
  | join kind=leftanti baseline on DeviceId
  | project TimeGenerated, DeviceId, DeviceName, Hostname, User, AdvertisedRoutes, EnabledRoutes, LastSeen
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: Hostname
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: User
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: P1D
    matchingMethod: AllEntities
kind: Scheduled
version: 1.0.0

Stages and Predicates

let recent is inlined into the numbered stages below.

Let Binding: baseline used in Stage 5

let baseline = Tailscale_Devices_CL
    | where TimeGenerated between (ago(1d + 1h) .. ago(1h))
    | summarize arg_max(TimeGenerated, *) by DeviceId
    | where array_length(AdvertisedRoutes) > 0
    | distinct DeviceId;

Stages 1 to 4 define let recent as the rule's main pipeline source. Stages 5 to 6 use it.

Stage 1: source

Tailscale_Devices_CL

Stage 2: where

| where TimeGenerated > ago(1h)

Stage 3: summarize

| summarize arg_max(TimeGenerated, *) by DeviceId

Stage 4: where

| where array_length(AdvertisedRoutes) > 0

Stage 5: join (negated)

join kind=leftanti (baseline) on DeviceId

Stage 6: project

project AdvertisedRoutes, DeviceId, DeviceName, EnabledRoutes, Hostname, LastSeen, TimeGenerated, User

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
AdvertisedRoutesgt0excludes:AdvertisedRoutes field:"AdvertisedRoutes" value:"0"

Output Fields

These fields are emitted when the rule matches.

FieldSource
AdvertisedRoutesproject
DeviceIdproject
DeviceNameproject
EnabledRoutesproject
Hostnameproject
LastSeenproject
TimeGeneratedproject
Userproject

UniFi Site Manager: New Device Adopted

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

Identifies when a new device is adopted into the UniFi network. While often legitimate, unexpected adoptions may indicate unauthorized hardware additions.

MITRE ATT&CK Coverage

Rule Body

id: e69be544-9476-35bb-2533-fa8c650dcd46
name: "UniFi Site Manager: New Device Adopted"
description: |
  Identifies when a new device is adopted into the UniFi network. While often legitimate, unexpected adoptions may indicate unauthorized hardware additions.
severity: Informational
status: Available
requiredDataConnectors:
  - connectorId: UniFiSiteManagerConnectorDefinition
    dataTypes:
      - Unifi_SiteManager_Devices_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
  - Persistence
relevantTechniques:
  - T1200
  - T1133
subTechniques: []
query: |
  // UniFi New Device Adopted Detection
  Unifi_SiteManager_Devices_CL
  | where TimeGenerated > ago(1h)
  | where isnotempty(AdoptionTime)
  | extend AdoptionDateTime = todatetime(AdoptionTime)
  | where AdoptionDateTime > ago(1h)
  | summarize arg_max(TimeGenerated, *) by Id
  | extend
      DeviceName = coalesce(Name, "Unnamed"),
      DeviceId = Id,
      Model = Model,
      IPAddress = Ip,
      MACAddress = Mac,
      ProductLine = ProductLine,
      FirmwareVersion = Version,
      Shortname = Shortname
  | extend HostName = DeviceName
  | project
      TimeGenerated,
      AdoptionTime = AdoptionDateTime,
      DeviceName,
      DeviceId,
      Model,
      IPAddress,
      MACAddress,
      ProductLine,
      Status = Status,
      FirmwareVersion,
      Shortname,
      HostName
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: HostName
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPAddress
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: PT24H
    matchingMethod: AllEntities
kind: Scheduled
version: 1.0.0

Stages and Predicates

Stage 1: source

Unifi_SiteManager_Devices_CL

Stage 2: where

| where TimeGenerated > ago(1h)

Stage 3: where

| where isnotempty(AdoptionTime)

Stage 4: extend

| extend AdoptionDateTime = todatetime(AdoptionTime)

Stage 5: where

| where AdoptionDateTime > ago(1h)

Stage 6: summarize

| summarize arg_max(TimeGenerated, *) by Id

Stage 7: extend

| extend
    DeviceName = coalesce(Name, "Unnamed"),
    DeviceId = Id,
    Model = Model,
    IPAddress = Ip,
    MACAddress = Mac,
    ProductLine = ProductLine,
    FirmwareVersion = Version,
    Shortname = Shortname

Stage 8: extend

| extend HostName = DeviceName

Stage 9: project

| project
    TimeGenerated,
    AdoptionTime = AdoptionDateTime,
    DeviceName,
    DeviceId,
    Model,
    IPAddress,
    MACAddress,
    ProductLine,
    Status = Status,
    FirmwareVersion,
    Shortname,
    HostName

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
AdoptionTimeis_not_null
  • (no value, null check)
field:"AdoptionTime" kind:is_not_null

Output Fields

These fields are emitted when the rule matches.

FieldSource
AdoptionTimeproject
DeviceIdproject
DeviceNameproject
FirmwareVersionproject
HostNameproject
IPAddressproject
MACAddressproject
Modelproject
ProductLineproject
Shortnameproject
Statusproject
TimeGeneratedproject

Back to all Kusto rules