Detection rules › Kusto

Kusto rules: 3j

Alsid privileged accounts issues

#
Status
available
Severity
low
Time window
2h
Group by
Severity
Source
github.com/Azure/Azure-Sentinel

'Searches for triggered Indicators of Exposures related to privileged accounts issues'

MITRE ATT&CK Coverage

TacticTechniques
Credential Access

Rule Body

id: a5fe9489-cf8b-47ae-a87e-8f3a13e4203e
name: Alsid privileged accounts issues
description: |
    'Searches for triggered Indicators of Exposures related to privileged accounts issues'
severity: Low
requiredDataConnectors:
  - connectorId: AlsidForAD
    dataTypes:
      - AlsidForADLog_CL
queryFrequency: 2h
queryPeriod: 2h
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - CredentialAccess
relevantTechniques:
  - T1110
query: |
  let SeverityTable=datatable(Severity:string,Level:int) [
  "low", 1,
  "medium", 2,
  "high", 3,
  "critical", 4
  ];
  let codeNameList = datatable(Codename:string)["C-PRIV-ACCOUNTS-SPN", "C-NATIVE-ADM-GROUP-MEMBERS", "C-KRBTGT-PASSWORD", "C-PROTECTED-USERS-GROUP-UNUSED", "C-ADMINCOUNT-ACCOUNT-PROPS", "C-ADM-ACC-USAGE", "C-LAPS-UNSECURE-CONFIG", "C-DISABLED-ACCOUNTS-PRIV-GROUPS"];
  afad_parser
  | where MessageType == 0 and Codename in~ (codeNameList)
  | lookup kind=leftouter SeverityTable on Severity
  | order by Level
  | extend HostName = tostring(split(Host, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(Host, '.'), 1, -1), '.'))
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: DnsDomain
version: 1.0.1
kind: Scheduled

Stages and Predicates

Let Binding: SeverityTable used in Stage 3

let SeverityTable = datatable(Severity:string,Level:int) [
"low", 1,
"medium", 2,
"high", 3,
"critical", 4
];

Let Binding: codeNameList used in Stage 2

let codeNameList = datatable(Codename:string)["C-PRIV-ACCOUNTS-SPN", "C-NATIVE-ADM-GROUP-MEMBERS", "C-KRBTGT-PASSWORD", "C-PROTECTED-USERS-GROUP-UNUSED", "C-ADMINCOUNT-ACCOUNT-PROPS", "C-ADM-ACC-USAGE", "C-LAPS-UNSECURE-CONFIG", "C-DISABLED-ACCOUNTS-PRIV-GROUPS"];

Stage 1: source

afad_parser

Stage 2: where

| where MessageType == 0 and Codename in~ (codeNameList)

Stage 3: kusto:lookup

lookup kind=leftouter (SeverityTable) on Severity

Stage 4: sort

sort by Level

Stage 5: extend

extend DnsDomain, HostName

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
DnsDomainextend
HostNameextend

AppServices AV Scan with Infected Files

#
Severity
informational
Time window
1d
Author
SecurityJedi
Source
github.com/Azure/Azure-Sentinel

'Identifies if an AV scan finds infected files in Azure App Services.'

Rule Body

id: 9d0295ee-cb75-4f2c-9952-e5acfbb67036
name: AppServices AV Scan with Infected Files
description: |
  'Identifies if an AV scan finds infected files in Azure App Services.'
severity: Informational
requiredDataConnectors: []
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 1
query: |
  let timeframe = ago(1d);
  AppServiceAntivirusScanAuditLogs
  | where NumberOfInfectedFiles > 0
  | extend timestamp = TimeGenerated
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: AzureID
        columnName: _ResourceId
version: 1.0.3
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: SecurityJedi
    support:
        tier: Community
    categories:
        domains: [ "Security - Others", "Platform" ]

Stages and Predicates

Parameters

let timeframe = ago(1d);

Stage 1: source

AppServiceAntivirusScanAuditLogs

Stage 2: where

| where NumberOfInfectedFiles > 0

Stage 3: extend

| extend timestamp = TimeGenerated

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
timestampextend

AV detections related to Zinc actors

#
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 Zinc threat actor. 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, 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/09/29/zinc-weaponizing-open-source-software/

MITRE ATT&CK Coverage

TacticTechniques
Impact

Telemetry Coverage

Rule Body

id: 3705158d-e008-49c9-92dd-e538e1549090
name: AV detections related to Zinc actors
description: |
  'This query looks for Microsoft Defender AV detections related to  Zinc threat actor. 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, 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/09/29/zinc-weaponizing-open-source-software/'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - SecurityAlert
      - DeviceInfo
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Impact
relevantTechniques:
  - T1486
tags:
  -  Zinc
query: |
  let Zinc_threats = dynamic(["Trojan:Win32/ZetaNile.A", "Trojan:Win32/EventHorizon.A", "Trojan:Win32/FoggyBrass.A", "Trojan:Win32/FoggyBrass.B", "Trojan:Win32/PhantomStar.A","Trojan:Win32/PhantomStar.C","TrojanDropper:Win32/PhantomStar.A"]);
  DeviceInfo
  | extend DeviceName = tolower(DeviceName)
  | join kind=inner ( SecurityAlert
  | where ProviderName == "MDATP"
  | extend ThreatName = tostring(parse_json(ExtendedProperties).ThreatName)
  | extend ThreatFamilyName = tostring(parse_json(ExtendedProperties).ThreatFamilyName)
  | where ThreatName in~ (Zinc_threats) or ThreatFamilyName in~ (Zinc_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)[0]), DnsDomain = tostring(strcat_array(array_slice(split(CompromisedEntity, '.'), 1, -1), '.'))
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: DnsDomain
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: PublicIP
version: 1.0.2
kind: Scheduled

Stages and Predicates

Let Binding: Zinc_threats used in Stage 3

let Zinc_threats = dynamic(["Trojan:Win32/ZetaNile.A", "Trojan:Win32/EventHorizon.A", "Trojan:Win32/FoggyBrass.A", "Trojan:Win32/FoggyBrass.B", "Trojan:Win32/PhantomStar.A","Trojan:Win32/PhantomStar.C","TrojanDropper:Win32/PhantomStar.A"]);

Stage 1: source

DeviceInfo

Stage 2: extend

| extend DeviceName = tolower(DeviceName)

Stage 3: join

| join kind=inner ( SecurityAlert
| where ProviderName == "MDATP"
| extend ThreatName = tostring(parse_json(ExtendedProperties).ThreatName)
| extend ThreatFamilyName = tostring(parse_json(ExtendedProperties).ThreatFamilyName)
| where ThreatName in~ (Zinc_threats) or ThreatFamilyName in~ (Zinc_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 DnsDomain, HostName

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
ProviderNameeq
  • MDATP
field:"ProviderName" kind:eq value:"MDATP"
ThreatFamilyNamein
  • Trojan:Win32/EventHorizon.A
  • Trojan:Win32/FoggyBrass.A
  • Trojan:Win32/FoggyBrass.B
  • Trojan:Win32/PhantomStar.A
  • Trojan:Win32/PhantomStar.C
  • Trojan:Win32/ZetaNile.A
  • TrojanDropper:Win32/PhantomStar.A
field:"ThreatFamilyName" kind:in
ThreatNamein
  • Trojan:Win32/EventHorizon.A
  • Trojan:Win32/FoggyBrass.A
  • Trojan:Win32/FoggyBrass.B
  • Trojan:Win32/PhantomStar.A
  • Trojan:Win32/PhantomStar.C
  • Trojan:Win32/ZetaNile.A
  • TrojanDropper:Win32/PhantomStar.A
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
DnsDomainextend
HostNameextend

AWSCloudTrail - Privilege escalation with FullAccess managed policy

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

Detects successful attachment of AWS managed policies containing FullAccess permissions to users, roles, or groups, excluding admin-named policies. This action can rapidly expand privilege scope and should be reviewed as potential cloud privilege escalation.

MITRE ATT&CK Coverage

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: afb4191b-a142-4065-a0da-f721ee3d006c
name: AWSCloudTrail - Privilege escalation with FullAccess managed policy
description: |
  Detects successful attachment of AWS managed policies containing FullAccess permissions to users, roles, or
  groups, excluding admin-named policies. This action can rapidly expand privilege scope and should be reviewed
  as potential cloud privilege escalation.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: AWS
    dataTypes:
      - AWSCloudTrail
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - PrivilegeEscalation
  - Persistence
relevantTechniques:
  - T1098.003
query: |
  AWSCloudTrail
  | where  EventName in ("AttachUserPolicy","AttachRolePolicy","AttachGroupPolicy") and isempty(ErrorCode) and isempty(ErrorMessage)
  | where tostring(parse_json(RequestParameters).policyArn) has "FullAccess" and tostring(parse_json(RequestParameters).policyArn) !has "Admin"
  | 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]), "")
  | project TimeGenerated, EventName, EventTypeName, UserIdentityAccountId, UserIdentityPrincipalid, UserAgent, RecipientAccountId, AccountName, AccountUPNSuffix, UserIdentityUserName, SessionMfaAuthenticated, SourceIpAddress, AWSRegion, EventSource, AdditionalEventData, RequestParameters, UserIdentityArn, ResponseElements
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: AccountUPNSuffix
      - identifier: CloudAppAccountId
        columnName: RecipientAccountId
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIpAddress
customDetails:
  EventName: EventName
  EventSource: EventSource
  AWSRegion: AWSRegion
  UserIdentityArn: UserIdentityArn
alertDetailsOverride:
  alertDisplayNameFormat: 'AWS FullAccess managed policy attachment by {{AccountName}}'
  alertDescriptionFormat: 'Detected {{EventName}} from {{SourceIpAddress}} attaching FullAccess managed policy in account {{RecipientAccountId}}.'
version: 1.0.2
kind: Scheduled

Stages and Predicates

Stage 1: source

AWSCloudTrail

Stage 2: where

| where  EventName in ("AttachUserPolicy","AttachRolePolicy","AttachGroupPolicy") and isempty(ErrorCode) and isempty(ErrorMessage)

Stage 3: where

| where tostring(parse_json(RequestParameters).policyArn) has "FullAccess" and tostring(parse_json(RequestParameters).policyArn) !has "Admin"

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: project

| project TimeGenerated, EventName, EventTypeName, UserIdentityAccountId, UserIdentityPrincipalid, UserAgent, RecipientAccountId, AccountName, AccountUPNSuffix, UserIdentityUserName, SessionMfaAuthenticated, SourceIpAddress, AWSRegion, EventSource, AdditionalEventData, RequestParameters, UserIdentityArn, ResponseElements

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
ErrorCodeis_null
  • (no value, null check)
field:"aws::errorCode" kind:is_null
ErrorMessageis_null
  • (no value, null check)
field:"aws::errorMessage" kind:is_null
EventNamein
  • AttachGroupPolicy corpus 17 (kusto 15, panther 2)
  • AttachRolePolicy corpus 17 (kusto 15, panther 2)
  • AttachUserPolicy corpus 17 (kusto 15, panther 2)
field:"aws::eventName" kind:in
RequestParameters.policyArnmatch
  • FullAccess transforms: tostring, term, kusto_accessor_expr:{"expression":"tostring(parse_json(RequestParameters).policyArn)","field":"RequestParameters.policyArn"}, kusto_dynamic_path
field:"RequestParameters.policyArn" kind:match value:"FullAccess"

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
RequestParameters.policyArnmatchAdminexcludes:RequestParameters.policyArn field:"RequestParameters.policyArn" value:"Admin"

Output Fields

These fields are emitted when the rule matches.

FieldSource
AWSRegionproject
AccountNameproject
AccountUPNSuffixproject
AdditionalEventDataproject
EventNameproject
EventSourceproject
EventTypeNameproject
RecipientAccountIdproject
RequestParametersproject
ResponseElementsproject
SessionMfaAuthenticatedproject
SourceIpAddressproject
TimeGeneratedproject
UserAgentproject
UserIdentityAccountIdproject
UserIdentityArnproject
UserIdentityPrincipalidproject
UserIdentityUserNameproject

Cisco SE - Policy update failure

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

'Detects policy updates failures.'

MITRE ATT&CK Coverage

TacticTechniques
Defense Impairment

Rule Body

id: 64fece0a-44db-4bab-844d-fd503dc0aaba
name: Cisco SE - Policy update failure
description: |
  'Detects policy updates failures.'
severity: Medium
requiredDataConnectors:
  - connectorId: CiscoSecureEndpoint
    dataTypes:
      - CiscoSecureEndpoint
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - DefenseEvasion
relevantTechniques:
  - T1562
query: |
  CiscoSecureEndpoint
  | where EventMessage =~ 'Policy Update Failure'
  | extend HostCustomEntity = DstHostname
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: HostCustomEntity
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

CiscoSecureEndpoint

Stage 2: where

| where EventMessage =~ 'Policy Update Failure'

Stage 3: extend

| extend HostCustomEntity = DstHostname

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
HostCustomEntityextend

CMMC 2.0 Level 2 (Advanced) Readiness Posture

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

'CMMC 2.0 Level 2 (Advanced) assessments have deviated from configured threshold baselines. This alert is triggered when CMMC2.0 policy compliance is assessed below 70% compliance in 7 days.'

MITRE ATT&CK Coverage

TacticTechniques
Discovery

Rule Body

id: 7bfe573b-3069-4e81-98fe-9a4cffbcbc24
name: CMMC 2.0 Level 2 (Advanced) Readiness Posture
description: |
  'CMMC 2.0 Level 2 (Advanced) assessments have deviated from configured threshold baselines. This alert is triggered when CMMC2.0 policy compliance is assessed below 70% compliance in 7 days.'
severity: Medium
status: Available
requiredDataConnectors: []
queryFrequency: 7d
queryPeriod: 7d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Discovery
relevantTechniques:
  - T1082
query: |
  SecurityRegulatoryCompliance
  | where ComplianceStandard == "NIST-SP-800-171-R2"
  | extend Level=iff(ComplianceControl in ("3.1.1","3.1.2","3.1.20","3.1.22","3.4.1","3.5.2","3.5.2","3.8.3","3.13.1","3.13.5","3.14.1","3.14.2","3.14.4","3.14.5"), "Level 1: Foundational","Level 2: Advanced")
  | where Level == "Level 2: Advanced"
  | summarize arg_max(TimeGenerated, *) by RecommendationName, AssessedResourceId, Level
  |summarize Failed=countif(State=="Failed"),Passed=countif(State=="Passed"),Total=countif(State=="Passed" or State == "Failed") by Level
  |extend PassedControlsPercentage = (Passed/todouble(Total))*100
  | where PassedControlsPercentage < 70 
  //Adjust Either Passed Thresholds within Organizational Needs
  | extend RemediationLink = strcat('https://portal.azure.com/#blade/Microsoft_Azure_Security/SecurityMenuBlade/22')
  | project Level, Total, PassedControlsPercentage, Passed, Failed, RemediationLink, LastObserved=now()
  | extend URLCustomEntity = RemediationLink
entityMappings:
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: URLCustomEntity
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

SecurityRegulatoryCompliance

Stage 2: where

| where ComplianceStandard == "NIST-SP-800-171-R2"

Stage 3: extend

| extend Level=iff(ComplianceControl in ("3.1.1","3.1.2","3.1.20","3.1.22","3.4.1","3.5.2","3.5.2","3.8.3","3.13.1","3.13.5","3.14.1","3.14.2","3.14.4","3.14.5"), "Level 1: Foundational","Level 2: Advanced")
Level =
ifComplianceControl in ("3.1.1", "3.1.2", "3.1.20", "3.1.22", "3.4.1", "3.5.2", "3.5.2", "3.8.3", "3.13.1", "3.13.5", "3.14.1", "3.14.2", "3.14.4", "3.14.5")"Level 1: Foundational"
else"Level 2: Advanced"

Stage 4: where

| where Level == "Level 2: Advanced"

Stage 5: summarize

| summarize arg_max(TimeGenerated, *) by RecommendationName, AssessedResourceId, Level

Stage 6: summarize

| summarize Failed=countif(State=="Failed"),Passed=countif(State=="Passed"),Total=countif(State=="Passed" or State == "Failed") by Level

Stage 7: extend

| extend PassedControlsPercentage = (Passed/todouble(Total))*100

Stage 8: where

| where PassedControlsPercentage < 70

Stage 9: extend

| extend RemediationLink = strcat('https://portal.azure.com/#blade/Microsoft_Azure_Security/SecurityMenuBlade/22')

Stage 10: project

| project Level, Total, PassedControlsPercentage, Passed, Failed, RemediationLink, LastObserved=now()

Stage 11: 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
Levelproject
Passedproject
PassedControlsPercentageproject
RemediationLinkproject
Totalproject
URLCustomEntityextend

Cross-tenant Access Settings Organization Deleted

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

Organizations are added in the Cross-tenant Access Settings to control communication inbound or outbound for users and applications. This detection notifies when an Organization is deleted from the Microsoft Entra ID Cross-tenant Access Settings.

MITRE ATT&CK Coverage

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: eb8a9c1c-f532-4630-817c-1ecd8a60ed80
name: Cross-tenant Access Settings Organization Deleted
description: |
  'Organizations are added in the Cross-tenant Access Settings to control communication inbound or outbound for users and applications. This detection notifies when an Organization is deleted from the Microsoft Entra ID Cross-tenant Access Settings.'
severity: Medium
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AuditLogs
queryFrequency: 2d
queryPeriod: 2d
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - InitialAccess
  - Persistence
  - Discovery
relevantTechniques:
  - T1078.004
  - T1136.003
  - T1087.004
query: |
  AuditLogs
  | where OperationName has "Delete partner specific cross-tenant access setting"
  | mv-apply TargetResource = TargetResources on
    (
        where TargetResource.type =~ "Policy"
        | extend Properties = TargetResource.modifiedProperties
    )
  | mv-apply Property = Properties on
    (
        where Property.displayName =~ "tenantId"
        | extend ExtTenantDeleted = trim('"',tostring(Property.oldValue))
    )
  | 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))
  | extend InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1])
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

Stage 1: source

AuditLogs

Stage 2: where

| where OperationName has "Delete partner specific cross-tenant access setting"

Stage 3: kusto:mv-apply

| mv-apply TargetResource = TargetResources on
  (
      where TargetResource.type =~ "Policy"
      | extend Properties = TargetResource.modifiedProperties
  )

Stage 4: kusto:mv-apply

| mv-apply Property = Properties on
  (
      where Property.displayName =~ "tenantId"
      | extend ExtTenantDeleted = trim('"',tostring(Property.oldValue))
  )

Stage 5: extend (6 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))
| extend InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1])

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
OperationNamematch
  • Delete partner specific cross-tenant access setting transforms: term
field:"OperationName" kind:match value:"Delete partner specific cross-tenant access setting"
Property.displayNameeq
  • tenantId transforms: kusto_dynamic_path
field:"Property.displayName" kind:eq value:"tenantId"
TargetResource.typeeq
  • Policy transforms: kusto_dynamic_path
field:"TargetResource.type" kind:eq value:"Policy"

Output Fields

These fields are emitted when the rule matches.

FieldSource
InitiatingAppNameextend
InitiatingAppServicePrincipalIdextend
InitiatingUserPrincipalNameextend
InitiatingAadUserIdextend
InitiatingIpAddressextend
InitiatingAccountNameextend
InitiatingAccountUPNSuffixextend

CYFIRMA - High severity Trojan File Hash Indicators with Block Action Rule

#

This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.

Severity
high
Time window
5m
Source
github.com/Azure/Azure-Sentinel

"This KQL query extracts file hash indicators associated with Trojan activity from the CyfirmaIndicators_CL table. It specifically targets indicators containing file hashes linked to Trojan behavior and retrieves MD5, SHA1, and SHA256 values. The query also includes contextual threat intelligence such as threat actors, tags, sources, and geolocation information."

MITRE ATT&CK Coverage

Rule Body

id: 649f525a-1f92-412d-bfc2-ce642e7a7f1f
name: CYFIRMA - High severity Trojan File Hash Indicators with Block Action Rule
description: |
  "This KQL query extracts file hash indicators associated with Trojan activity from the CyfirmaIndicators_CL table. 
  It specifically targets indicators containing file hashes linked to Trojan behavior and retrieves MD5, SHA1, and SHA256 values. 
  The query also includes contextual threat intelligence such as threat actors, tags, sources, and geolocation information."
version: 1.0.1
kind: Scheduled
severity: High
enabled: false
requiredDataConnectors:
  - connectorId: CyfirmaCyberIntelligenceDC
    dataTypes:
      - CyfirmaIndicators_CL
query: |
  //Trojan File Hash Indicators with Block Action
  let timeFrame = 5m;
  CyfirmaIndicators_CL 
  | where ConfidenceScore >= 80
      and TimeGenerated between (ago(timeFrame) .. now())
      and pattern contains 'file:hashes' and RecommendedActions has 'Block' and (Roles has 'Trojan')
  | extend MD5 = extract(@"file:hashes\.md5\s*=\s*'([a-fA-F0-9]{32})'", 1, pattern)
  | extend SHA1 = extract(@"file:hashes\.'SHA-1'\s*=\s*'([a-fA-F0-9]{40})'", 1, pattern)
  | extend SHA256 = extract(@"file:hashes\.'SHA-256'\s*=\s*'([a-fA-F0-9]{64})'", 1, pattern)
  | extend
      Algo_MD5='MD5',
      Algo_SHA1= 'SHA1',
      Algo_SHA256='SHA256',
      ProviderName = 'CYFIRMA',
      ProductName = 'DeCYFIR/DeTCT'
  | project  
      MD5,
      Algo_MD5,
      SHA1,
      Algo_SHA1,
      SHA256,
      Algo_SHA256,
      ThreatActors,
      Sources,
      RecommendedActions,
      Roles,
      Country,
      name,
      Description,
      ConfidenceScore,
      SecurityVendors,
      IndicatorID,
      created,
      modified,
      valid_from,
      Tags,
      ThreatType,
      TimeGenerated,
      ProductName,
      ProviderName
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: GreaterThan
triggerThreshold: 0
suppressionDuration: 5m
suppressionEnabled: true
tactics:
  - InitialAccess
  - Execution
  - Persistence
  - DefenseEvasion
  - CommandAndControl
  - CredentialAccess
relevantTechniques:
  - T1566
  - T1204
  - T1547
  - T1027
  - T1071
  - T1003
  - T1566.001
  - T1547.001
alertDetailsOverride:
  alertDisplayNameFormat: "High-Confidence Trojan File Hash Indicators with Block Action Rule - {{name}} "
  alertDescriptionFormat: "{{Description}} - {{name}} "
  alertDynamicProperties:
    - alertProperty: ProductName
      value: ProductName
    - alertProperty: ProviderName
      value: ProviderName
customDetails:
  ThreatActors: ThreatActors
  Sources: Sources
  RecommendedActions: RecommendedActions
  Roles: Roles
  Country: Country
  Description: Description
  ConfidenceScore: ConfidenceScore
  SecurityVendors: SecurityVendors
  IndicatorID: IndicatorID
  created: created
  modified: modified
  ValidFrom: valid_from
  Tags: Tags
  ThreatType: ThreatType
  TimeGenerated: TimeGenerated
entityMappings:
  - entityType: FileHash
    fieldMappings:
      - identifier: Value
        columnName: MD5
      - identifier: Algorithm
        columnName: Algo_MD5
  - entityType: FileHash
    fieldMappings:
      - identifier: Value
        columnName: SHA1
      - identifier: Algorithm
        columnName: Algo_SHA1
  - entityType: FileHash
    fieldMappings:
      - identifier: Value
        columnName: SHA256
      - identifier: Algorithm
        columnName: Algo_SHA256
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: false
    reopenClosedIncident: false
    lookbackDuration: PT5H
    matchingMethod: AllEntities
eventGroupingSettings:
  aggregationKind: AlertPerResult

Stages and Predicates

Parameters

let timeFrame = 5m;

Stage 1: source

CyfirmaIndicators_CL

Stage 2: where

| where ConfidenceScore >= 80
    and TimeGenerated between (ago(timeFrame) .. now())
    and pattern contains 'file:hashes' and RecommendedActions has 'Block' and (Roles has 'Trojan')

Stage 3: extend (4 consecutive steps)

| extend MD5 = extract(@"file:hashes\.md5\s*=\s*'([a-fA-F0-9]{32})'", 1, pattern)
| extend SHA1 = extract(@"file:hashes\.'SHA-1'\s*=\s*'([a-fA-F0-9]{40})'", 1, pattern)
| extend SHA256 = extract(@"file:hashes\.'SHA-256'\s*=\s*'([a-fA-F0-9]{64})'", 1, pattern)
| extend
    Algo_MD5='MD5',
    Algo_SHA1= 'SHA1',
    Algo_SHA256='SHA256',
    ProviderName = 'CYFIRMA',
    ProductName = 'DeCYFIR/DeTCT'

Stage 4: project

| project  
    MD5,
    Algo_MD5,
    SHA1,
    Algo_SHA1,
    SHA256,
    Algo_SHA256,
    ThreatActors,
    Sources,
    RecommendedActions,
    Roles,
    Country,
    name,
    Description,
    ConfidenceScore,
    SecurityVendors,
    IndicatorID,
    created,
    modified,
    valid_from,
    Tags,
    ThreatType,
    TimeGenerated,
    ProductName,
    ProviderName

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
Algo_MD5project
Algo_SHA1project
Algo_SHA256project
ConfidenceScoreproject
Countryproject
Descriptionproject
IndicatorIDproject
MD5project
ProductNameproject
ProviderNameproject
RecommendedActionsproject
Rolesproject
SHA1project
SHA256project
SecurityVendorsproject
Sourcesproject
Tagsproject
ThreatActorsproject
ThreatTypeproject
TimeGeneratedproject
createdproject
modifiedproject
nameproject
valid_fromproject

Darktrace Model Alert

#
Severity
high
Source
github.com/Azure/Azure-Sentinel

This query searches for Darktrace model alerts and creates a Microsoft Sentinel alert from each matching event. Edit this analytic rule if you would like it to create Microsoft Sentinel incidents.

MITRE ATT&CK Coverage

Rule Body

id: 9392a06f-63a4-4a5d-8ca3-647064b13c28
name: Darktrace Model Alert
kind: NRT
description: |
  This query searches for Darktrace model alerts and creates a Microsoft Sentinel alert
  from each matching event. Edit this analytic rule if you would like it to create
  Microsoft Sentinel incidents.
severity: High
requiredDataConnectors:
  - connectorId: DarktraceActiveAISecurityPlatform
    dataTypes:
      - DarktraceModelAlerts_CL
tactics:
  - InitialAccess
  - Execution
  - LateralMovement
  - CommandAndControl
relevantTechniques:
  - T1190
  - T1059
  - T1021
  - T1071
query: |
  DarktraceModelAlerts_CL
  | where TimeGenerated >= ago(5m)
  | extend SentinelSeverity = case(
  compliance == true, "Informational",
  category == "Informational", "Low",
  category == "Suspicious", "Medium",
  category == "Critical", "High",
  "Informational")
  | extend ProviderName = "Darktrace"
  | mv-apply item = mitreTechniques on (
      extend techniqueId = tostring(item.techniqueId)
      | summarize techniqueIdArray = make_list(techniqueId, 5)
  )
eventGroupingSettings:
  aggregationKind: AlertPerResult
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: accountName
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: sourceIp
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: destIp
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: destHost
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: sourceHost
customDetails:
  DeviceHostname: deviceHostname
  DeviceCredentials: deviceCredentials
  Compliance: compliance
  Score: score
  CustomLabel: customLabel
  Category: category
alertDetailsOverride:
  alertDisplayNameFormat: 'Darktrace Model Alert: {{modelName}}  '
  alertDescriptionFormat: '{{message}}'
  alertSeverityColumnName: SentinelSeverity
  alertDynamicProperties:
    - alertProperty: AlertLink
      value: alertUrl
    - alertProperty: ProductName
      value: darktraceProduct
    - alertProperty: ProviderName
      value: ProviderName
    - alertProperty: Techniques
      value: techniqueIdArray
incidentConfiguration:
  createIncident: false
  groupingConfiguration:
    enabled: false
    reopenClosedIncident: false
    lookbackDuration: PT5H
    matchingMethod: AllEntities
version: 1.1.0

Stages and Predicates

Stage 1: source

DarktraceModelAlerts_CL

Stage 2: where

| where TimeGenerated >= ago(5m)

Stage 3: extend

| extend SentinelSeverity = case(
compliance == true, "Informational",
category == "Informational", "Low",
category == "Suspicious", "Medium",
category == "Critical", "High",
"Informational")
SentinelSeverity =
ifcompliance == true"Informational"
elifcategory == "Informational""Low"
elifcategory == "Suspicious""Medium"
elifcategory == "Critical""High"
else"Informational"

Stage 4: extend

| extend ProviderName = "Darktrace"

Stage 5: kusto:mv-apply

| mv-apply item = mitreTechniques on (
    extend techniqueId = tostring(item.techniqueId)
    | summarize techniqueIdArray = make_list(techniqueId, 5)
)

Stage 6: summarize aggregation inside the mv-apply branch

summarize

Detect excessive NXDOMAIN DNS queries - Anomaly based (ASIM DNS Solution)

#
Status
available
Severity
medium
Time window
14d
Group by
SrcIpAddr, exists, sumexist
Source
github.com/Azure/Azure-Sentinel

This rule makes use of the series decompose anomaly method to generate an alert when client requests excessive amount of DNS queries to non-existent domains. This helps in identifying possible C2 communications. It utilizes ASIM normalization and is applied to any source that supports the ASIM DNS schema.

MITRE ATT&CK Coverage

TacticTechniques
Command & Control

Telemetry Coverage

ProviderRecord / event type
SysmonEvent ID 22: DNSEvent (DNS query)

Rule Body

id: 02f23312-1a33-4390-8b80-f7cd4df4dea0
name: Detect excessive NXDOMAIN DNS queries - Anomaly based (ASIM DNS Solution)
description: |
  'This rule makes use of the series decompose anomaly method to generate an alert when client requests excessive amount of DNS queries to non-existent domains. This helps in identifying possible C2 communications. It utilizes [ASIM](https://aka.ms/AboutASIM) normalization and is applied to any source that supports the ASIM DNS schema.'
severity: Medium
status: Available 
tags:
  - Schema: ASimDns
    SchemaVersion: 0.1.6
requiredDataConnectors: []
queryFrequency: 1d
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
relevantTechniques:
  - T1568
  - T1008
query: |
  let threshold = 2.5;
  let min_t = ago(14d);
  let max_t = now();
  let dt = 1d;
  let summarizationexist = (
    union isfuzzy=true 
        (
        DNS_Summarized_Logs_ip_CL
        | where EventTime_t > ago(1d) 
        | project v = int(2)
        ),
        (
        print int(1) 
        | project v = print_0
        )
    | summarize maxv = max(v)
    | extend sumexist = (maxv > 1)
    );
  let allData = union isfuzzy=true
        (
        (datatable(exists: int, sumexist: bool)[1, false]
        | join (summarizationexist) on sumexist)
        | join (
            _Im_Dns(responsecodename='NXDOMAIN', starttime=todatetime(min_t), endtime=max_t)
            | summarize Count=count() by SrcIpAddr, bin(TimeGenerated, 1h)
            | extend EventTime = TimeGenerated, Count = toint(Count), exists=int(1)
            )
            on exists
        | project-away exists, maxv, sum*
        ),
        (
        DNS_Summarized_Logs_ip_CL
        | where EventTime_t > min_t and EventResultDetails_s == 'NXDOMAIN'
        | summarize Count=toint(sum(count__d)) by SrcIpAddr=SrcIpAddr_s, bin(EventTime=EventTime_t, 1h)
        );
  allData
  | make-series EventCount=sum(Count) on EventTime from min_t to max_t step dt by SrcIpAddr
  | extend (anomalies, score, baseline) = series_decompose_anomalies(EventCount, threshold, -1, 'linefit')
  | mv-expand anomalies, score, baseline, EventTime, EventCount
  | extend
    anomalies = toint(anomalies),
    score = toint(score),
    baseline = toint(baseline),
    EventTime = todatetime(EventTime),
    Total = tolong(EventCount)
  | where EventTime >= ago(dt)
  | where score >= threshold * 2
  | join kind=inner(_Im_Dns(responsecodename='NXDOMAIN', starttime=ago(dt), endtime=max_t)
    | summarize DNSQueries = make_set(DnsQuery) by SrcIpAddr)
    on SrcIpAddr
  | project-away SrcIpAddr1
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SrcIpAddr
eventGroupingSettings:
  aggregationKind: AlertPerResult
customDetails:
  DNSQueries: DNSQueries
  AnomalyScore: score
  baseline: baseline
  Total: Total
alertDetailsOverride:
  alertDisplayNameFormat: "[Anomaly] Excessive NXDOMAIN DNS Queries has been detected from client IP: '{{SrcIpAddr}}'"
  alertDescriptionFormat: "This client is generating excessive amount of DNS queries for non-existent domains. This can be an indication of possible C2 communications.\n\nBaseline for 'NXDOMAIN' error count for this client: '{{baseline}}'\n\nCurrent 'NXDOMAIN' error count for this client: '{{Total}}'\n\nDNS queries requested by the client include:\n\n'{{DNSQueries}}'"
version: 1.0.2
kind: Scheduled

Stages and Predicates

Parameters

let threshold = 2.5;
let min_t = ago(14d);
let max_t = now();
let dt = 1d;

Let Binding: summarizationexist

let summarizationexist = (
  union isfuzzy=true 
      (
      DNS_Summarized_Logs_ip_CL
      | where EventTime_t > ago(1d) 
      | project v = int(2)
      ),
      (
      print int(1) 
      | project v = print_0
      )
  | summarize maxv = max(v)
  | extend sumexist = (maxv > 1)
  );

Union isfuzzy=true (2 Sources)

Each leg below queries one source; the rule matches if any leg does. Sources: datatable(exists:, DNS_Summarized_Logs_ip_CL

Leg 1: datatable(exists:

(datatable(exists: int, sumexist: bool)[1, false]
      | join (summarizationexist) on sumexist)
      | join (
          _Im_Dns(responsecodename='NXDOMAIN', starttime=todatetime(min_t), endtime=max_t)
          | summarize Count=count() by SrcIpAddr, bin(TimeGenerated, 1h)
          | extend EventTime = TimeGenerated, Count = toint(Count), exists=int(1)
          )
          on exists
      | project-away exists, maxv, sum*

Leg 2: DNS_Summarized_Logs_ip_CL

DNS_Summarized_Logs_ip_CL
      | where EventTime_t > min_t and EventResultDetails_s == 'NXDOMAIN'
      | summarize Count=toint(sum(count__d)) by SrcIpAddr=SrcIpAddr_s, bin(EventTime=EventTime_t, 1h)

Applied to the Combined Result

| make-series EventCount=sum(Count) on EventTime from min_t to max_t step dt by SrcIpAddr | extend (anomalies, score, baseline) = series_decompose_anomalies(EventCount, threshold, -1, 'linefit') | mv-expand anomalies, score, baseline, EventTime, EventCount | extend
  anomalies = toint(anomalies),
  score = toint(score),
  baseline = toint(baseline),
  EventTime = todatetime(EventTime),
  Total = tolong(EventCount) | where EventTime >= ago(dt) | where score >= threshold * 2 | join kind=inner(_Im_Dns(responsecodename='NXDOMAIN', starttime=ago(dt), endtime=max_t)
  | summarize DNSQueries = make_set(DnsQuery) by SrcIpAddr)
  on SrcIpAddr | project-away SrcIpAddr1

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
EventCountsummarize
SrcIpAddrsummarize
anomaliesextend
baselineextend
scoreextend
EventTimeextend
Totalextend

Google SecOps - GCTI Threat Intelligence Finding

#
Status
available
Severity
high
Time window
10m
Source
github.com/Azure/Azure-Sentinel

Creates incidents in Microsoft Sentinel when Google Security Operations raises an active threat intelligence alert (GCTI_FINDING). These alerts are generated by Google's global threat intel corpus and represent high-confidence threats, distinct from customer-authored rule detections.

MITRE ATT&CK Coverage

Rule Body

id: b3e7f921-5c4a-4d8e-a2f9-7b1d3e6c9a5f
name: Google SecOps - GCTI Threat Intelligence Finding
description: |
  Creates incidents in Microsoft Sentinel when Google Security Operations raises an active threat intelligence alert (GCTI_FINDING). These alerts are generated by Google's global threat intel corpus and represent high-confidence threats, distinct from customer-authored rule detections.
severity: High
status: Available
requiredDataConnectors:
  - connectorId: GSDetectionAlerts
    dataTypes:
      - DetectionAlerts_CL
queryFrequency: 10m
queryPeriod: 10m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
  - Execution
  - CommandAndControl
  - Exfiltration
relevantTechniques:
  - T1078
  - T1566
  - T1071
  - T1048
query: |
  GoogleSecOpsDetectionAlerts
  | where detectionType == "GCTI_FINDING"
  | where alertState == "ALERTING"
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: varPrincipalIp
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: varTargetIp
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: varSourceIp
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: varCorrelationIp
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: urlBackToProduct
customDetails:
  RuleName: ruleName
  RuleId: ruleId
  Severity: severity
  RiskScore: riskScore
  DetectionType: detectionType
  DetectionTime: detectionTime
  Description: description
  PrincipalIP: varPrincipalIp
  TargetIP: varTargetIp
  SourceIP: varSourceIp
  CorrelationIP: varCorrelationIp
  PrincipalHostname: varPrincipalHostname
  TargetHostname: varTargetHostname
  SourceHostname: varSourceHostname
  PrincipalUser: varPrincipalUserUserid
  SourceUser: varSourceUserUserid
  TargetUser: varTargetUserUserid
  alert_identifier: id
alertDetailsOverride:
  alertDisplayNameFormat: "GCTI Finding: {{ruleName}} : {{id}}"
  alertDescriptionFormat: "Google Threat Intelligence confirmed finding. Rule: {{ruleName}}. {{description}}"
eventGroupingSettings:
  aggregationKind: AlertPerResult
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: P1D
    matchingMethod: Selected
    groupByCustomDetails:
      - alert_identifier
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

GoogleSecOpsDetectionAlerts

Stage 2: where

| where detectionType == "GCTI_FINDING"

Stage 3: where

| where alertState == "ALERTING"

Indicators

These rows show field, operator, and value matches.

Guardian- Token Limit Policy Violation Detection

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

'This alert creates an incident when Token Limit Policy Violation detected from the Guardian.'

Rule Body

id: b7798389-6823-4af0-94e6-6135c4f8264e
name: Guardian- Token Limit Policy Violation Detection
description: |
  'This alert creates an incident when Token Limit 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 =~ 'Token Limit'
  | 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- Token Limit Policy Violation detection
  alertDescriptionFormat: |
    This query detects Token Limit 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 =~ 'Token Limit'

Stage 3: where

| where Severity =~ 'Informational'

Indicators

These rows show field, operator, and value matches.

Guest accounts added in Entra ID Groups other than the ones specified

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

Guest Accounts are added in the Organization Tenants to perform various tasks i.e projects execution, support etc.. This detection notifies when guest users are added to Microsoft Entra ID Groups other than the ones specified and poses a risk to gain access to sensitive apps or data.

MITRE ATT&CK Coverage

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: 6ab1f7b2-61b8-442f-bc81-96afe7ad8c53
name: Guest accounts added in Entra ID Groups other than the ones specified
description: |
  'Guest Accounts are added in the Organization Tenants to perform various tasks i.e projects execution, support etc.. This detection notifies when guest users are added to Microsoft Entra ID Groups other than the ones specified and poses a risk to gain access to sensitive apps or data.'
severity: High
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AuditLogs
queryFrequency: 2h
queryPeriod: 2h
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - InitialAccess
  - Persistence
  - Discovery
relevantTechniques:
  - T1078.004
  - T1136.003
  - T1087.004
query: |
  // OBJECT ID of AAD Groups can be found by navigating to Azure Active Directory then from menu on the left, select Groups and from the list shown of AAD Groups, the Second Column shows the ObjectID of each
  let GroupIDs = dynamic(["List with Custom AAD GROUP OBJECT ID 1","Custom AAD GROUP OBJECT ID 2"]);
  AuditLogs
  | where OperationName in ('Add member to group', 'Add owner to group')
  | 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))
  // Uncomment the following line to filter events where the inviting user was a guest user
  //| where InitiatedBy has_any ("CUSTOM DOMAIN NAME#", "#EXT#")
  | mv-apply TargetResource = TargetResources on 
    (
        where TargetResource.type =~ "User"
        | extend InvitedUserPrincipalName = trim(@'"',tostring(TargetResource.userPrincipalName)),
                 Properties = TargetResource.modifiedProperties
    )
  | mv-apply Property = Properties on 
    (
        where Property.displayName =~ "Group.DisplayName"
        | extend AADGroup = trim('"',tostring(Property.newValue))
    )
  | where InvitedUserPrincipalName has_any ("CUSTOM DOMAIN NAME#", "#EXT#")
  | mv-apply Property = Properties on
     (
       where Property.displayName =~ "Group.ObjectID"
       | extend AADGroupId = trim('"',tostring(Property.newValue))
     )
  | project-away TargetResource, Property
  | where AADGroupId !in (GroupIDs)
  | extend Name = tostring(split(InitiatingUserPrincipalName,'@',0)[0]), UPNSuffix = tostring(split(InitiatingUserPrincipalName,'@',1)[0])
  | extend InvitedUserName = tostring(split(InvitedUserPrincipalName,'@',0)[0]), InvitedUPNSuffix = tostring(split(InvitedUserPrincipalName,'@',1)[0])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: InvitedUserPrincipalName
      - identifier: Name
        columnName: InvitedUserName
      - identifier: UPNSuffix
        columnName: InvitedUPNSuffix
  - 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
  - entityType: SecurityGroup
    fieldMappings:
      - identifier: DistinguishedName
        columnName: AADGroup
      - identifier: ObjectGuid
        columnName: AADGroupId
version: 1.0.6
kind: Scheduled

Stages and Predicates

Parameters

let GroupIDs = dynamic(["List with Custom AAD GROUP OBJECT ID 1","Custom AAD GROUP OBJECT ID 2"]);

Stage 1: source

AuditLogs

Stage 2: where

| where OperationName in ('Add member to group', 'Add owner to group')

Stage 3: 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 4: kusto:mv-apply

| mv-apply TargetResource = TargetResources on 
  (
      where TargetResource.type =~ "User"
      | extend InvitedUserPrincipalName = trim(@'"',tostring(TargetResource.userPrincipalName)),
               Properties = TargetResource.modifiedProperties
  )

Stage 5: kusto:mv-apply

| mv-apply Property = Properties on 
  (
      where Property.displayName =~ "Group.DisplayName"
      | extend AADGroup = trim('"',tostring(Property.newValue))
  )

Stage 6: where

| where InvitedUserPrincipalName has_any ("CUSTOM DOMAIN NAME#", "#EXT#")

Stage 7: kusto:mv-apply

| mv-apply Property = Properties on
   (
     where Property.displayName =~ "Group.ObjectID"
     | extend AADGroupId = trim('"',tostring(Property.newValue))
   )

Stage 8: project-away

| project-away TargetResource, Property

Stage 9: where

| where AADGroupId !in (GroupIDs)

Stage 10: extend

| extend Name = tostring(split(InitiatingUserPrincipalName,'@',0)[0]), UPNSuffix = tostring(split(InitiatingUserPrincipalName,'@',1)[0])

Stage 11: extend

| extend InvitedUserName = tostring(split(InvitedUserPrincipalName,'@',0)[0]), InvitedUPNSuffix = tostring(split(InvitedUserPrincipalName,'@',1)[0])

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
InvitedUserPrincipalNamematch
  • #EXT# transforms: term
  • CUSTOM DOMAIN NAME# transforms: term
field:"InvitedUserPrincipalName" kind:match
OperationNamein
  • Add member to group
  • Add owner to group
field:"OperationName" kind:in
Property.displayNameeq
  • Group.DisplayName transforms: kusto_dynamic_path
  • Group.ObjectID transforms: kusto_dynamic_path
field:"Property.displayName" kind:eq
TargetResource.typeeq
  • User transforms: kusto_dynamic_path
field:"TargetResource.type" kind:eq value:"User"

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
AADGroupIdinCustom AAD GROUP OBJECT ID 2, List with Custom AAD GROUP OBJECT ID 1excludes:AADGroupId field:"AADGroupId" value:"Custom AAD GROUP OBJECT ID 2" field:"AADGroupId" value:"List with Custom AAD GROUP OBJECT ID 1"

Output Fields

These fields are emitted when the rule matches.

FieldSource
InitiatingAppNameextend
InitiatingAppServicePrincipalIdextend
InitiatingUserPrincipalNameextend
InitiatingAadUserIdextend
InitiatingIpAddressextend
Nameextend
UPNSuffixextend
InvitedUPNSuffixextend
InvitedUserNameextend

GWorkspace - Two-step authentification disabled for a user

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

Triggers on two-step authentification disabled for a user.

MITRE ATT&CK Coverage

TacticTechniques
Credential Access

Telemetry Coverage

Rule Body

id: c8cc02d0-5da6-11ec-bf63-0242ac130002
name: GWorkspace - Two-step authentification disabled for a user
description: |
  'Triggers on two-step authentification disabled for a user.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: GoogleWorkspaceReportsAPI
    dataTypes:
      - GWorkspaceActivityReports
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CredentialAccess
relevantTechniques:
  - T1111
query: |
  GWorkspaceActivityReports 
  | where EventMessage =~ 'TURN_OFF_2_STEP_VERIFICATION'
  | extend AccountCustomEntity = ActorEmail
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountCustomEntity
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

GWorkspaceActivityReports

Stage 2: where

| where EventMessage =~ 'TURN_OFF_2_STEP_VERIFICATION'

Stage 3: extend

| extend AccountCustomEntity = ActorEmail

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountCustomEntityextend

Multi-Factor Authentication Disabled for a User

#
Status
available
Severity
medium
Time window
1h
Group by
AADTenantId, Category, CorrelationId, IPAddress, InitiatedByUser, Operation, Source, TenantorInstance_Detail, Type, User
Source
github.com/Azure/Azure-Sentinel

Multi-Factor Authentication (MFA) helps prevent credential compromise. This alert identifies when an attempt has been made to deactivate MFA for a user.

MITRE ATT&CK Coverage

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: 65c78944-930b-4cae-bd79-c3664ae30ba7
name: Multi-Factor Authentication Disabled for a User
description: |
  'Multi-Factor Authentication (MFA) helps prevent credential compromise. This alert identifies when an attempt has been made to deactivate MFA for a user.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
     - AuditLogs
  - connectorId: AWS
    dataTypes:
      - AWSCloudTrail
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CredentialAccess
  - Persistence
relevantTechniques:
  - T1098
  - T1556
query: |
 (union isfuzzy=true
 (AuditLogs
 | where OperationName =~ "Disable Strong Authentication"
 | extend _parsedIntiatedByUser = parse_json(tostring(InitiatedBy.user))
 | extend _parsedIntiatedByApp = parse_json(tostring(InitiatedBy.app))
 | extend IPAddress = tostring(_parsedIntiatedByUser.ipAddress)
 | extend InitiatedByUser = iff(isnotempty(tostring(_parsedIntiatedByUser.userPrincipalName)),
  tostring(_parsedIntiatedByUser.userPrincipalName), tostring(_parsedIntiatedByApp.displayName))
 | extend Targetprop = todynamic(TargetResources)
 | extend TargetUser = tostring(Targetprop[0].userPrincipalName)
 | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated) by User = TargetUser, InitiatedByUser , Operation = OperationName , CorrelationId, IPAddress, Category, Source = SourceSystem , AADTenantId, Type
 ),
 (AWSCloudTrail
 | where EventName in~ ("DeactivateMFADevice", "DeleteVirtualMFADevice")
 | extend _parsedRequestParameters = parse_json(RequestParameters)
 | extend InstanceProfileName = tostring(_parsedRequestParameters.InstanceProfileName)
 | extend TargetUser = tostring(_parsedRequestParameters.userName)
 | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated) by User = TargetUser, Source = EventSource , Operation = EventName , TenantorInstance_Detail = InstanceProfileName, IPAddress = SourceIpAddress
 )
 )
 | extend timestamp = StartTimeUtc, UserName = tostring(split(User, '@', 0)[0]), UPNSuffix = tostring(split(User, '@', 1)[0])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: UserName
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPAddress
version: 1.0.5
kind: Scheduled

Stages and Predicates

Union isfuzzy=true (2 Sources)

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

Leg 1: AuditLogs

AuditLogs
| where OperationName =~ "Disable Strong Authentication"
| extend _parsedIntiatedByUser = parse_json(tostring(InitiatedBy.user))
| extend _parsedIntiatedByApp = parse_json(tostring(InitiatedBy.app))
| extend IPAddress = tostring(_parsedIntiatedByUser.ipAddress)
| extend InitiatedByUser = iff(isnotempty(tostring(_parsedIntiatedByUser.userPrincipalName)),
 tostring(_parsedIntiatedByUser.userPrincipalName), tostring(_parsedIntiatedByApp.displayName))
| extend Targetprop = todynamic(TargetResources)
| extend TargetUser = tostring(Targetprop[0].userPrincipalName)
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated) by User = TargetUser, InitiatedByUser , Operation = OperationName , CorrelationId, IPAddress, Category, Source = SourceSystem , AADTenantId, Type

Leg 2: AWSCloudTrail

AWSCloudTrail
| where EventName in~ ("DeactivateMFADevice", "DeleteVirtualMFADevice")
| extend _parsedRequestParameters = parse_json(RequestParameters)
| extend InstanceProfileName = tostring(_parsedRequestParameters.InstanceProfileName)
| extend TargetUser = tostring(_parsedRequestParameters.userName)
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated) by User = TargetUser, Source = EventSource , Operation = EventName , TenantorInstance_Detail = InstanceProfileName, IPAddress = SourceIpAddress

Applied to the Combined Result

| extend timestamp = StartTimeUtc, UserName = tostring(split(User, '@', 0)[0]), UPNSuffix = tostring(split(User, '@', 1)[0])

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
EventNamein
  • DeactivateMFADevice
  • DeleteVirtualMFADevice
field:"aws::eventName" kind:in
OperationNameeq
  • Disable Strong Authentication
field:"OperationName" kind:eq value:"Disable Strong Authentication"

Output Fields

These fields are emitted when the rule matches.

FieldSource
EndTimeUtcsummarize
IPAddresssummarize
Operationsummarize
Sourcesummarize
StartTimeUtcsummarize
TenantorInstance_Detailsummarize
Usersummarize
UPNSuffixextend
UserNameextend
timestampextend

Netskope - DLP Incident Spike

#
Status
available
Severity
high
Time window
1h
Group by
Hostname, User, Userip
Source
github.com/Azure/Azure-Sentinel

Detects a spike in Netskope DLP incidents within a short window. A sudden increase in DLP violations for a single user or DLP profile can indicate active data exfiltration, a misconfigured policy, or bulk handling of sensitive data. Triggers when a user generates more DLP incidents in the last hour than a configurable threshold.

MITRE ATT&CK Coverage

Rule Body

id: c3f8e4d6-0d57-4a3b-9e2c-4f6a8b0d3e52
name: Netskope - DLP Incident Spike
description: |
  Detects a spike in Netskope DLP incidents within a short window. A sudden increase in
  DLP violations for a single user or DLP profile can indicate active data exfiltration,
  a misconfigured policy, or bulk handling of sensitive data. Triggers when a user
  generates more DLP incidents in the last hour than a configurable threshold.
severity: High
status: Available
requiredDataConnectors:
  - connectorId: NetskopeAlertEventsConnector
    dataTypes:
      - NetskopeAlertEvents_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Exfiltration
  - Collection
relevantTechniques:
  - T1567
  - T1530
query: |
  let dlpIncidentThreshold = 10;
  NetskopeAlertEvents_CL
  | where TimeGenerated > ago(1h)
  | where AlertType =~ "DLP" or isnotempty(DlpProfile) or isnotempty(DlpIncidentId)
  | summarize
      IncidentCount = count(),
      DistinctIncidents = dcount(DlpIncidentId),
      Profiles = make_set(DlpProfile, 20),
      Rules = make_set(DlpRule, 20),
      Applications = make_set(App, 20),
      Files = make_set(DlpFile, 20),
      Activities = make_set(Activity, 10),
      Actions = make_set(Action, 10),
      FirstSeen = min(TimeGenerated),
      LastSeen = max(TimeGenerated)
      by User, Userip, Hostname
  | where IncidentCount > dlpIncidentThreshold
  | order by IncidentCount desc
  | project
      LastSeen,
      User,
      Userip,
      Hostname,
      IncidentCount,
      DistinctIncidents,
      Profiles,
      Rules,
      Applications,
      Files,
      Activities,
      Actions,
      FirstSeen
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: User
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: Userip
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: Hostname
version: 1.0.0
kind: Scheduled

Stages and Predicates

Parameters

let dlpIncidentThreshold = 10;

Stage 1: source

NetskopeAlertEvents_CL

Stage 2: where

| where TimeGenerated > ago(1h)

Stage 3: where

| where AlertType =~ "DLP" or isnotempty(DlpProfile) or isnotempty(DlpIncidentId)

Stage 4: summarize

| summarize
    IncidentCount = count(),
    DistinctIncidents = dcount(DlpIncidentId),
    Profiles = make_set(DlpProfile, 20),
    Rules = make_set(DlpRule, 20),
    Applications = make_set(App, 20),
    Files = make_set(DlpFile, 20),
    Activities = make_set(Activity, 10),
    Actions = make_set(Action, 10),
    FirstSeen = min(TimeGenerated),
    LastSeen = max(TimeGenerated)
    by User, Userip, Hostname

Stage 5: where

| where IncidentCount > dlpIncidentThreshold

Stage 6: sort

| order by IncidentCount desc

Stage 7: project

| project
    LastSeen,
    User,
    Userip,
    Hostname,
    IncidentCount,
    DistinctIncidents,
    Profiles,
    Rules,
    Applications,
    Files,
    Activities,
    Actions,
    FirstSeen

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
Actionsproject
Activitiesproject
Applicationsproject
DistinctIncidentsproject
Filesproject
FirstSeenproject
Hostnameproject
IncidentCountproject
LastSeenproject
Profilesproject
Rulesproject
Userproject
Useripproject

Netskope - Repeated or Critical Policy Violations

#
Status
available
Severity
high
Time window
1h
Group by
CsUsername, XCCountry, XCDevice
Source
github.com/Azure/Azure-Sentinel

Detects users with repeated policy violations or critical policy blocks. Monitors policy enforcement effectiveness and compliance.

MITRE ATT&CK Coverage

Rule Body

id: dacab67e-fcf3-41c6-a191-579c7be1814d
name: Netskope - Repeated or Critical Policy Violations
description: |
  Detects users with repeated policy violations or critical policy blocks. Monitors policy enforcement effectiveness and compliance.
severity: High
status: Available
requiredDataConnectors:
  - connectorId: NetskopeWebTxConnector
    dataTypes:
      - NetskopeWebTransactions_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - DefenseEvasion
  - Exfiltration
relevantTechniques:
  - T1562
  - T1048
query: |
  let violationThreshold = 5;
  NetskopeWebTransactions_CL
  | where TimeGenerated > ago(1h)
  | where isnotempty(CsUsername)
  | where isnotempty(XPolicyAction) and isnotempty(XPolicyName)
  | where XPolicyAction =~ 'block' or XPolicyAction =~ 'alert'
  | summarize 
      ViolationCount = count(),
      Policies = make_set(XPolicyName),
      PolicyActions = make_set(XPolicyAction),
      Apps = make_set(XCsApp),
      Hosts = make_set(CsHost),
      Activities = make_set(XCsAppActivity),
      BlockCount = countif(XPolicyAction =~ 'block'),
      AlertCount = countif(XPolicyAction =~ 'alert'),
      FirstViolation = min(TimeGenerated),
      LastViolation = max(TimeGenerated)
      by CsUsername, XCCountry, XCDevice
  | where ViolationCount >= violationThreshold or BlockCount > 0
  | extend ViolationSeverity = case(
      BlockCount > 10, 'Critical',
      BlockCount > 5, 'High',
      ViolationCount > 20, 'High',
      ViolationCount > 10, 'Medium',
      'Low')
  | project 
      TimeGenerated = LastViolation,
      User = CsUsername,
      TotalViolations = ViolationCount,
      BlockedAttempts = BlockCount,
      AlertedActions = AlertCount,
      ViolatedPolicies = Policies,
      PolicyActions,
      ApplicationsInvolved = Apps,
      TargetHosts = Hosts,
      Activities,
      Country = XCCountry,
      Device = XCDevice,
      ViolationSeverity,
      FirstOccurrence = FirstViolation,
      LastOccurrence = LastViolation
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: User
version: 1.0.0
kind: Scheduled

Stages and Predicates

Parameters

let violationThreshold = 5;

Stage 1: source

NetskopeWebTransactions_CL

Stage 2: where

| where TimeGenerated > ago(1h)

Stage 3: where

| where isnotempty(CsUsername)

Stage 4: where

| where isnotempty(XPolicyAction) and isnotempty(XPolicyName)

Stage 5: where

| where XPolicyAction =~ 'block' or XPolicyAction =~ 'alert'

Stage 6: summarize

| summarize 
    ViolationCount = count(),
    Policies = make_set(XPolicyName),
    PolicyActions = make_set(XPolicyAction),
    Apps = make_set(XCsApp),
    Hosts = make_set(CsHost),
    Activities = make_set(XCsAppActivity),
    BlockCount = countif(XPolicyAction =~ 'block'),
    AlertCount = countif(XPolicyAction =~ 'alert'),
    FirstViolation = min(TimeGenerated),
    LastViolation = max(TimeGenerated)
    by CsUsername, XCCountry, XCDevice

Stage 7: where

| where ViolationCount >= violationThreshold or BlockCount > 0

Stage 8: extend

| extend ViolationSeverity = case(
    BlockCount > 10, 'Critical',
    BlockCount > 5, 'High',
    ViolationCount > 20, 'High',
    ViolationCount > 10, 'Medium',
    'Low')
ViolationSeverity =
ifBlockCount > 10'Critical'
elifBlockCount > 5'High'
elifViolationCount > 20'High'
elifViolationCount > 10'Medium'
else'Low'

Stage 9: project

| project 
    TimeGenerated = LastViolation,
    User = CsUsername,
    TotalViolations = ViolationCount,
    BlockedAttempts = BlockCount,
    AlertedActions = AlertCount,
    ViolatedPolicies = Policies,
    PolicyActions,
    ApplicationsInvolved = Apps,
    TargetHosts = Hosts,
    Activities,
    Country = XCCountry,
    Device = XCDevice,
    ViolationSeverity,
    FirstOccurrence = FirstViolation,
    LastOccurrence = LastViolation

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
Activitiesproject
AlertedActionsproject
ApplicationsInvolvedproject
BlockedAttemptsproject
Countryproject
Deviceproject
FirstOccurrenceproject
LastOccurrenceproject
PolicyActionsproject
TargetHostsproject
TimeGeneratedproject
TotalViolationsproject
Userproject
ViolatedPoliciesproject
ViolationSeverityproject

New CloudShell User

#
Status
available
Severity
low
Time window
1d
Group by
AzureIP, Caller, CategoryValue, HTTPRequest, OperationList, Properties, ResourceGroup, SubscriptionId, TenantId, TimeKey, Type, UserIP
Source
github.com/Azure/Azure-Sentinel

Identifies when a user creates an Azure CloudShell for the first time. Monitor this activity to ensure only the expected users are using CloudShell.

MITRE ATT&CK Coverage

Telemetry Coverage

Rule Body

id: 6d7214d9-4a28-44df-aafb-0910b9e6ae3e
name: New CloudShell User
description: |
  'Identifies when a user creates an Azure CloudShell for the first time.
  Monitor this activity to ensure only the expected users are using CloudShell.'
severity: Low
status: Available
requiredDataConnectors:
  - connectorId: AzureActivity
    dataTypes:
      - AzureActivity
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Execution
relevantTechniques:
  - T1059
query: |
  let match_window = 3m;
  AzureActivity
  | where ResourceGroup has "cloud-shell"
  | where (OperationNameValue =~ "Microsoft.Storage/storageAccounts/listKeys/action")
  | where ActivityStatusValue =~ "Success"
  | extend TimeKey = bin(TimeGenerated, match_window), AzureIP = CallerIpAddress
  | join kind = inner
  (AzureActivity
  | where ResourceGroup has "cloud-shell"
  | where (OperationNameValue =~ "Microsoft.Storage/storageAccounts/write")
  | extend TimeKey = bin(TimeGenerated, match_window), UserIP = CallerIpAddress
  ) on Caller, TimeKey
  | summarize count() by TimeKey, Caller, ResourceGroup, SubscriptionId, TenantId, AzureIP, UserIP, HTTPRequest, Type, Properties, CategoryValue, OperationList = strcat(OperationNameValue, ' , ', OperationNameValue1)
  | extend Name = tostring(split(Caller,'@',0)[0]), UPNSuffix = tostring(split(Caller,'@',1)[0])

entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: Caller
      - identifier: Name
        columnName: Name
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: UserIP
version: 2.0.3
kind: Scheduled

Stages and Predicates

Parameters

let match_window = 3m;

Stage 1: source

AzureActivity

Stage 2: where

| where ResourceGroup has "cloud-shell"

Stage 3: where

| where (OperationNameValue =~ "Microsoft.Storage/storageAccounts/listKeys/action")

Stage 4: where

| where ActivityStatusValue =~ "Success"

Stage 5: extend

| extend TimeKey = bin(TimeGenerated, match_window), AzureIP = CallerIpAddress

Stage 6: join

| join kind = inner
(AzureActivity
| where ResourceGroup has "cloud-shell"
| where (OperationNameValue =~ "Microsoft.Storage/storageAccounts/write")
| extend TimeKey = bin(TimeGenerated, match_window), UserIP = CallerIpAddress
) on Caller, TimeKey

Stage 7: summarize

summarize OperationList by TimeKey, Caller, ResourceGroup, SubscriptionId, TenantId, AzureIP, UserIP, HTTPRequest, Type, Properties, CategoryValue, OperationList

Stage 8: extend

extend Name, UPNSuffix

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
ActivityStatusValueeq
  • Success corpus 2 (kusto 2)
field:"ActivityStatusValue" kind:eq value:"Success"
OperationNameValueeq
  • Microsoft.Storage/storageAccounts/listKeys/action
  • Microsoft.Storage/storageAccounts/write
field:"azure_ad::operation_name_value" kind:eq
ResourceGroupmatch
  • cloud-shell transforms: term
field:"ResourceGroup" kind:match value:"cloud-shell"

Output Fields

These fields are emitted when the rule matches.

FieldSource
AzureIPsummarize
Callersummarize
CategoryValuesummarize
HTTPRequestsummarize
OperationListsummarize
Propertiessummarize
ResourceGroupsummarize
SubscriptionIdsummarize
TenantIdsummarize
TimeKeysummarize
Typesummarize
UserIPsummarize
Nameextend
UPNSuffixextend

Oracle - Command in URI

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

'Detects command in URI'

MITRE ATT&CK Coverage

Rule Body

id: 6ae36a5e-573f-11ec-bf63-0242ac130002
name: Oracle - Command in URI
description: |
  'Detects command in URI'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: CustomLogsAma
    dataTypes:
      - OracleWebLogicServer_CL
queryFrequency: 15m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1190
  - T1133
query: |
  OracleWebLogicServerEvent
  | where UrlOriginal has_any ('cat%20/etc/passwd', '/etc/passwd', 'ping -i', '/usr/bin/id(', '%2f%75%73%72%2f%62%69%6e%2f%69%64', 'phpinfo()' , '%70%68%70%69%6e%66%6f%28%29', ';id', '%3b%69%64', '/bin/bash -c', '%2f%62%69%6e%2f%62%61%73%68%20%2d%63%27', '/bin/bash', '%2f%62%69%6e%2f%62%61%73%68', 'sleep(', '%73%6c%65%65%70%28', 'curl', '%63%75%72%6c', '&dir', '%26%64%69%72', '& dir', '%26%20%64%69%72', '<script>', '%3c%73%63%72%69%70%74%3e', 'eval(', '%65%76%61%6c%28', 'exec(', '%65%78%65%63%28' , 'whoami', '%77%68%6f%61%6d%69', 'wget', 'python', 'gcc' , 'uname', 'systeminfo', '%77%67%65%74', '%70%79%74%68%6f%6e', '%75%6e%61%6d%65', '%73%79%73%74%65%6d%69%6e%66%6f') 
  | extend UrlCustomEntity = UrlOriginal
entityMappings:
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: UrlCustomEntity
version: 1.0.3
kind: Scheduled

Stages and Predicates

Stage 1: source

OracleWebLogicServerEvent

Stage 2: where

| where UrlOriginal has_any ('cat%20/etc/passwd', '/etc/passwd', 'ping -i', '/usr/bin/id(', '%2f%75%73%72%2f%62%69%6e%2f%69%64', 'phpinfo()' , '%70%68%70%69%6e%66%6f%28%29', ';id', '%3b%69%64', '/bin/bash -c', '%2f%62%69%6e%2f%62%61%73%68%20%2d%63%27', '/bin/bash', '%2f%62%69%6e%2f%62%61%73%68', 'sleep(', '%73%6c%65%65%70%28', 'curl', '%63%75%72%6c', '&dir', '%26%64%69%72', '& dir', '%26%20%64%69%72', '<script>', '%3c%73%63%72%69%70%74%3e', 'eval(', '%65%76%61%6c%28', 'exec(', '%65%78%65%63%28' , 'whoami', '%77%68%6f%61%6d%69', 'wget', 'python', 'gcc' , 'uname', 'systeminfo', '%77%67%65%74', '%70%79%74%68%6f%6e', '%75%6e%61%6d%65', '%73%79%73%74%65%6d%69%6e%66%6f')

Stage 3: extend

| extend UrlCustomEntity = UrlOriginal

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
UrlOriginalmatch
  • %26%20%64%69%72 transforms: term
  • %26%64%69%72 transforms: term
  • %2f%62%69%6e%2f%62%61%73%68 transforms: term
  • %2f%62%69%6e%2f%62%61%73%68%20%2d%63%27 transforms: term
  • %2f%75%73%72%2f%62%69%6e%2f%69%64 transforms: term
  • %3b%69%64 transforms: term
  • %3c%73%63%72%69%70%74%3e transforms: term
  • %63%75%72%6c transforms: term
  • %65%76%61%6c%28 transforms: term
  • %65%78%65%63%28 transforms: term
  • %70%68%70%69%6e%66%6f%28%29 transforms: term
  • %70%79%74%68%6f%6e transforms: term
  • %73%6c%65%65%70%28 transforms: term
  • %73%79%73%74%65%6d%69%6e%66%6f transforms: term
  • %75%6e%61%6d%65 transforms: term
  • %77%67%65%74 transforms: term
  • %77%68%6f%61%6d%69 transforms: term
  • & dir transforms: term
  • &dir transforms: term
  • /bin/bash transforms: term
  • /bin/bash -c transforms: term
  • /etc/passwd transforms: term
  • /usr/bin/id( transforms: term
  • ;id transforms: term
  • <script> transforms: term
  • cat%20/etc/passwd transforms: term
  • curl transforms: term
  • eval( transforms: term
  • exec( transforms: term
  • gcc transforms: term
  • phpinfo() transforms: term
  • ping -i transforms: term
  • python transforms: term
  • sleep( transforms: term
  • systeminfo transforms: term
  • uname transforms: term
  • wget transforms: term
  • whoami transforms: term
field:"UrlOriginal" kind:match

Output Fields

These fields are emitted when the rule matches.

FieldSource
UrlCustomEntityextend

Pathlock TDnR - SAP Instance Profile Changes

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

Detects changes to SAP instance profile parameters (RZ10), forwarded by Pathlock Threat Detection and Response. Instance profile modifications can alter security-critical parameters such as login/password_max_new_valid, auth/rfc_authority_check, or rec/client, potentially weakening system security posture.

MITRE ATT&CK Coverage

TacticTechniques
Persistence
Defense Impairment

Rule Body

id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c64
name: Pathlock TDnR - SAP Instance Profile Changes
kind: Scheduled
description: >-
  Detects changes to SAP instance profile parameters (RZ10), forwarded by Pathlock Threat Detection
  and Response. Instance profile modifications can alter security-critical parameters such as
  login/password_max_new_valid, auth/rfc_authority_check, or rec/client, potentially weakening
  system security posture.
severity: High
status: Available
requiredDataConnectors:
  - connectorId: Pathlock_TDnR
    dataTypes:
      - Pathlock_TDnR_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - DefenseEvasion
  - Persistence
relevantTechniques:
  - T1562
  - T1098
query: |
  Pathlock_TDnR_CL
  | where DataSource == "SYSPROFILE_CHANGES"
  | project TimeGenerated, Sysid, DataSource, Eventid, Instance, Hostname, Bname,
            Tcode, Report, Area, Subid, SrcIp, DestIp, AffectedUser, LogLine,
            MsgType, MsgId, MsgNo, MessageV1, MessageV2, MessageV3, MessageV4, CentralTs
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: Bname
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: Hostname
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SrcIp
eventGroupingSettings:
  aggregationKind: SingleAlert
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: 5h
    matchingMethod: AnyAlert
    groupByEntities: []
    groupByAlertDetails: []
    groupByCustomDetails: []
suppressionDuration: 5h
suppressionEnabled: false
version: 1.0.0

Stages and Predicates

Stage 1: source

Pathlock_TDnR_CL

Stage 2: where

| where DataSource == "SYSPROFILE_CHANGES"

Stage 3: project

| project TimeGenerated, Sysid, DataSource, Eventid, Instance, Hostname, Bname,
          Tcode, Report, Area, Subid, SrcIp, DestIp, AffectedUser, LogLine,
          MsgType, MsgId, MsgNo, MessageV1, MessageV2, MessageV3, MessageV4, CentralTs

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AffectedUserproject
Areaproject
Bnameproject
CentralTsproject
DataSourceproject
DestIpproject
Eventidproject
Hostnameproject
Instanceproject
LogLineproject
MessageV1project
MessageV2project
MessageV3project
MessageV4project
MsgIdproject
MsgNoproject
MsgTypeproject
Reportproject
SrcIpproject
Subidproject
Sysidproject
Tcodeproject
TimeGeneratedproject

Red Canary Threat Detection

#

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.

Severity
high
Time window
1d
Group by
detection_id_s
Source
github.com/Azure/Azure-Sentinel

Triggers Incidents using detection data assembled by Red Canary.

MITRE ATT&CK Coverage

Rule Body

id: 6d263abb-6445-45cc-93e9-c593d3d77b89
kind: Scheduled
name: Red Canary Threat Detection
description: Triggers Incidents using detection data assembled by Red Canary.
severity: High
requiredDataConnectors:
  - connectorId: RedCanary_ConnectorDefinition
    dataTypes:
      - RedCanaryDetections_CL
queryPeriod: 1d
queryFrequency: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Collection
  - CommandAndControl
  - CredentialAccess
  - DefenseEvasion
  - Discovery
  - Execution
  - Exfiltration
  - Impact
  - InitialAccess
  - LateralMovement
  - Persistence
  - PrivilegeEscalation
relevantTechniques:
  - T1566
  - T1059
  - T1547
  - T1548
  - T1562
  - T1003
  - T1087
  - T1021
  - T1119
  - T1071
  - T1041
  - T1499
query: |
  let detectionLookback = 1d;
  let newDetectionWindow = 5m;
  RedCanaryDetections_CL
  | where TimeGenerated >= ago(detectionLookback)
  | where isnotempty(detection_id_s)
  | summarize FirstSeen = min(TimeGenerated), arg_max(TimeGenerated, *) by detection_id_s
  | where FirstSeen >= ago(newDetectionWindow)
  | extend    process_iocs_raw = column_ifexists('process_iocs_s', '[]'),
              child_process_iocs_raw = column_ifexists('child_process_iocs_s', '[]'),
              cross_process_iocs_raw = column_ifexists('cross_process_iocs_s', '[]'),
              file_modification_iocs_raw = column_ifexists('file_modification_iocs_s', '[]'),
              identities_raw = column_ifexists('identities_s', '[]')
  | extend    process_ioc_array = todynamic(iff(isempty(process_iocs_raw), '[]', process_iocs_raw)),
              child_process_ioc_array = todynamic(iff(isempty(child_process_iocs_raw), '[]', child_process_iocs_raw)),
              cross_process_ioc_array = todynamic(iff(isempty(cross_process_iocs_raw), '[]', cross_process_iocs_raw)),
              file_mod_ioc_array = todynamic(iff(isempty(file_modification_iocs_raw), '[]', file_modification_iocs_raw)),
              identities_array = todynamic(iff(isempty(identities_raw), '[]', identities_raw))
  | extend    evidence_array = array_concat(process_ioc_array, child_process_ioc_array, cross_process_ioc_array, file_mod_ioc_array)
  | extend    evidence = todynamic(evidence_array[0]),
              identity = todynamic(identities_array[0])
  | extend    file_hash_array = todynamic(evidence.file_hashes)
  | extend    file_hash = todynamic(file_hash_array[0])
  | project   detection_id_s,
              detection_url_s = column_ifexists('detection_url_s', ''),
              detection_headline_s = column_ifexists('detection_headline_s', ''),
              detection_details_s = column_ifexists('detection_details_s', ''),
              detection_severity_s = column_ifexists('detection_severity_s', ''),
              host_name_s = column_ifexists('host_name_s', ''),
              host_full_name_s = column_ifexists('host_full_name_s', ''),
              host_os_family_s = column_ifexists('host_os_family_s', ''),
              host_os_version_s = column_ifexists('host_os_version_s', ''),
              tactics_s = column_ifexists('tactics_s', ''),
              process_id = tostring(evidence.process_id),
              process_command_line = tostring(evidence.process_command_line),
              process_creation_time_utc = tostring(evidence.process_creation_time_utc),
              file_hash_algorithm = tostring(file_hash.algorithm),
              file_hash_value = tostring(file_hash.value),
              file_directory = tostring(evidence.file_directory),
              file_name = tostring(evidence.file_name),
              user_name = tostring(identity.user_name),
              user_uid = tostring(identity.user_uid)
eventGroupingSettings:
  aggregationKind: AlertPerResult
entityMappings:
  - entityType: Process
    fieldMappings:
      - identifier: ProcessId
        columnName: process_id
      - identifier: CommandLine
        columnName: process_command_line
      - identifier: CreationTimeUtc
        columnName: process_creation_time_utc
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: user_name
      - identifier: Sid
        columnName: user_uid
      - identifier: Name
        columnName: user_name
  - entityType: File
    fieldMappings:
      - identifier: Directory
        columnName: file_directory
      - identifier: Name
        columnName: file_name
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: host_name_s
      - identifier: FullName
        columnName: host_full_name_s
      - identifier: OSFamily
        columnName: host_os_family_s
  - entityType: FileHash
    fieldMappings:
      - identifier: Algorithm
        columnName: file_hash_algorithm
      - identifier: Value
        columnName: file_hash_value
customDetails:
  detection_id: detection_id_s
alertDetailsOverride:
  alertDisplayNameFormat: Red Canary has published Detection-{{detection_id_s}}
  alertDescriptionFormat: |
    Red Canary has published a {{detection_severity_s}} severity detection with details:

    {{detection_details_s}}

    View the Detection at: {{detection_url_s}}
  alertTacticsColumnName: tactics_s
  alertSeverityColumnName: detection_severity_s
version: 1.0.2
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: 7d
    matchingMethod: Selected
    groupByEntities: []
    groupByAlertDetails: []
    groupByCustomDetails:
      - detection_id

Stages and Predicates

Parameters

let detectionLookback = 1d;
let newDetectionWindow = 5m;

Stage 1: source

RedCanaryDetections_CL

Stage 2: where

| where TimeGenerated >= ago(detectionLookback)

Stage 3: where

| where isnotempty(detection_id_s)

Stage 4: summarize

| summarize FirstSeen = min(TimeGenerated), arg_max(TimeGenerated, *) by detection_id_s

Stage 5: where

| where FirstSeen >= ago(newDetectionWindow)

Stage 6: extend (6 consecutive steps)

| extend    process_iocs_raw = column_ifexists('process_iocs_s', '[]'),
            child_process_iocs_raw = column_ifexists('child_process_iocs_s', '[]'),
            cross_process_iocs_raw = column_ifexists('cross_process_iocs_s', '[]'),
            file_modification_iocs_raw = column_ifexists('file_modification_iocs_s', '[]'),
            identities_raw = column_ifexists('identities_s', '[]')
| extend    process_ioc_array = todynamic(iff(isempty(process_iocs_raw), '[]', process_iocs_raw)),
            child_process_ioc_array = todynamic(iff(isempty(child_process_iocs_raw), '[]', child_process_iocs_raw)),
            cross_process_ioc_array = todynamic(iff(isempty(cross_process_iocs_raw), '[]', cross_process_iocs_raw)),
            file_mod_ioc_array = todynamic(iff(isempty(file_modification_iocs_raw), '[]', file_modification_iocs_raw)),
            identities_array = todynamic(iff(isempty(identities_raw), '[]', identities_raw))
| extend    evidence_array = array_concat(process_ioc_array, child_process_ioc_array, cross_process_ioc_array, file_mod_ioc_array)
| extend    evidence = todynamic(evidence_array[0]),
            identity = todynamic(identities_array[0])
| extend    file_hash_array = todynamic(evidence.file_hashes)
| extend    file_hash = todynamic(file_hash_array[0])

Stage 7: project

| project   detection_id_s,
            detection_url_s = column_ifexists('detection_url_s', ''),
            detection_headline_s = column_ifexists('detection_headline_s', ''),
            detection_details_s = column_ifexists('detection_details_s', ''),
            detection_severity_s = column_ifexists('detection_severity_s', ''),
            host_name_s = column_ifexists('host_name_s', ''),
            host_full_name_s = column_ifexists('host_full_name_s', ''),
            host_os_family_s = column_ifexists('host_os_family_s', ''),
            host_os_version_s = column_ifexists('host_os_version_s', ''),
            tactics_s = column_ifexists('tactics_s', ''),
            process_id = tostring(evidence.process_id),
            process_command_line = tostring(evidence.process_command_line),
            process_creation_time_utc = tostring(evidence.process_creation_time_utc),
            file_hash_algorithm = tostring(file_hash.algorithm),
            file_hash_value = tostring(file_hash.value),
            file_directory = tostring(evidence.file_directory),
            file_name = tostring(evidence.file_name),
            user_name = tostring(identity.user_name),
            user_uid = tostring(identity.user_uid)

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
detection_id_sis_not_null
  • (no value, null check)
field:"detection_id_s" kind:is_not_null

Output Fields

These fields are emitted when the rule matches.

FieldSource
detection_details_sproject
detection_headline_sproject
detection_id_sproject
detection_severity_sproject
detection_url_sproject
file_directoryproject
file_hash_algorithmproject
file_hash_valueproject
file_nameproject
host_full_name_sproject
host_name_sproject
host_os_family_sproject
host_os_version_sproject
process_command_lineproject
process_creation_time_utcproject
process_idproject
tactics_sproject
user_nameproject
user_uidproject

Theom - Shadow DB large datastore value

#

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
high
Time window
5m
Source
github.com/Azure/Azure-Sentinel

"Creates Sentinel incidents for critical/high Theom risks, associated with ruleId TRIS0035 (Theom has observed shadow (or clone) databases/tables with large financial value. As per this requirement, use this information to apply data access control lists or access permissions and enforce data retention policies)"

MITRE ATT&CK Coverage

Rule Body

id: 7cf83fce-276a-4b12-a876-7b1bc0683cd6
name: Theom - Shadow DB large datastore value
description: |
   "Creates Sentinel incidents for critical/high Theom risks, associated with ruleId TRIS0035 (Theom has observed shadow (or clone) databases/tables with large financial value. As per this requirement, use this information to apply data access control lists or access permissions and enforce data retention policies)"
severity: High
status : Available
requiredDataConnectors: 
  - connectorId: Theom
    dataTypes:
      - TheomAlerts_CL
queryPeriod: 5m
queryFrequency: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Collection
relevantTechniques:
  - T1560
  - T1530
query: |
   TheomAlerts_CL
     | where customProps_RuleId_s == "TRIS0035" and (priority_s == "P1" or priority_s == "P2")
eventGroupingSettings:
  aggregationKind: AlertPerResult
alertDetailsOverride:
  alertDisplayNameFormat: "Theom Alert ID: {{id_s}} "
  alertDescriptionFormat: "\nSummary: {{summary_s}}  \nAdditional info: {{details_s}}\nPlease investigate further on Theom UI at {{deepLink_s}}\n"
entityMappings:
  - entityType: CloudApplication
    fieldMappings:
      - identifier: Name
        columnName: customProps_AssetName_s
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: deepLink_s
version: 1.0.2
kind: Scheduled

Stages and Predicates

Stage 1: source

TheomAlerts_CL

Stage 2: where

| where customProps_RuleId_s == "TRIS0035" and (priority_s == "P1" or priority_s == "P2")

Indicators

These rows show field, operator, and value matches.

Threat Essentials - Mail redirect via ExO transport rule

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

Identifies when Exchange Online transport rule configured to forward emails. This could be an adversary mailbox configured to collect mail from multiple user accounts.

MITRE ATT&CK Coverage

TacticTechniques
Collection
Exfiltration

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: d7c575b2-84f5-48cb-92c5-70d7e8246284
name: Threat Essentials - Mail redirect via ExO transport rule
description: |
  'Identifies when Exchange Online transport rule configured to forward emails.
  This could be an adversary mailbox configured to collect mail from multiple user accounts.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: Office365
    dataTypes:
      - OfficeActivity
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Collection
  - Exfiltration
relevantTechniques:
  - T1114
  - T1020
query: |
  OfficeActivity
  | where OfficeWorkload == "Exchange"
  | where Operation in~ ("New-TransportRule", "Set-TransportRule")
  | extend p = parse_json(Parameters)
  | extend RuleName = case(
    Operation =~ "Set-TransportRule", tostring(OfficeObjectId),
    Operation =~ "New-TransportRule", tostring(p[1].Value),
    "Unknown"
    )
  | mvexpand p
  | where (p.Name =~ "BlindCopyTo" or p.Name =~ "RedirectMessageTo") and isnotempty(p.Value)
  | extend RedirectTo = p.Value
  | extend ClientIPOnly = case(
    ClientIP has "." and ClientIP has ":", tostring(split(ClientIP,":")[0]),
    ClientIP has "." and ClientIP has "-", tostring(split(ClientIP,"-")[0]),
    ClientIP has "[", tostring(trim_start(@'[[]',tostring(split(ClientIP,"]")[0]))),
    ClientIP
    )
  | extend Port = case(
    ClientIP has "." and ClientIP has ":", (split(ClientIP,":")[1]),
    ClientIP has "." and ClientIP has "-", (split(ClientIP,"-")[1]),
    ClientIP has "[" and ClientIP has ":", tostring(split(ClientIP,"]:")[1]),
    ClientIP has "[" and ClientIP has "-", tostring(split(ClientIP,"]-")[1]),
    ClientIP
    )
  | extend ClientIP = ClientIPOnly
  | project TimeGenerated, RedirectTo, ClientIP, Port, UserId, Operation, RuleName
  | extend Name=split(UserId, "@")[0], UPNSuffix=split(UserId, "@")[1]
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: Name
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: ClientIP
version: 1.0.2
kind: Scheduled

Stages and Predicates

Stage 1: source

OfficeActivity

Stage 2: where

| where OfficeWorkload == "Exchange"

Stage 3: where

| where Operation in~ ("New-TransportRule", "Set-TransportRule")

Stage 4: extend

| extend p = parse_json(Parameters)

Stage 5: extend

| extend RuleName = case(
  Operation =~ "Set-TransportRule", tostring(OfficeObjectId),
  Operation =~ "New-TransportRule", tostring(p[1].Value),
  "Unknown"
  )
RuleName =
ifOperation =~ "Set-TransportRule"tostring(OfficeObjectId)
elifOperation =~ "New-TransportRule"tostring(p[1].Value)
else"Unknown"

Stage 6: mv-expand

| mvexpand p

Stage 7: where

| where (p.Name =~ "BlindCopyTo" or p.Name =~ "RedirectMessageTo") and isnotempty(p.Value)

Stage 8: extend (4 consecutive steps)

| extend RedirectTo = p.Value
| extend ClientIPOnly = case(
  ClientIP has "." and ClientIP has ":", tostring(split(ClientIP,":")[0]),
  ClientIP has "." and ClientIP has "-", tostring(split(ClientIP,"-")[0]),
  ClientIP has "[", tostring(trim_start(@'[[]',tostring(split(ClientIP,"]")[0]))),
  ClientIP
  )
| extend Port = case(
  ClientIP has "." and ClientIP has ":", (split(ClientIP,":")[1]),
  ClientIP has "." and ClientIP has "-", (split(ClientIP,"-")[1]),
  ClientIP has "[" and ClientIP has ":", tostring(split(ClientIP,"]:")[1]),
  ClientIP has "[" and ClientIP has "-", tostring(split(ClientIP,"]-")[1]),
  ClientIP
  )
| extend ClientIP = ClientIPOnly

Stage 9: project

| project TimeGenerated, RedirectTo, ClientIP, Port, UserId, Operation, RuleName

Stage 10: extend

| extend Name=split(UserId, "@")[0], UPNSuffix=split(UserId, "@")[1]

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
OfficeWorkloadeq
  • Exchange corpus 2 (kusto 2)
field:"m365::Workload" kind:eq value:"Exchange"
Operationin
  • New-TransportRule corpus 2 (kusto 2)
  • Set-TransportRule corpus 3 (kusto 3)
field:"Operation" kind:in
p.Nameeq
  • BlindCopyTo transforms: kusto_dynamic_path
  • RedirectMessageTo transforms: kusto_dynamic_path
field:"p.Name" kind:eq
p.Valueis_not_null
  • (no value, null check)
field:"p.Value" kind:is_not_null

Output Fields

These fields are emitted when the rule matches.

FieldSource
ClientIPproject
Operationproject
Portproject
RedirectToproject
RuleNameproject
TimeGeneratedproject
UserIdproject
Nameextend
UPNSuffixextend

Ubiquiti - Large ICMP to external server

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

'Detects large ICMP packets to external host.'

MITRE ATT&CK Coverage

TacticTechniques
Command & Control
Exfiltration

Rule Body

id: 6df85d74-e32f-4b71-80e5-bfe2af00be1c
name: Ubiquiti - Large ICMP to external server
description: |
  'Detects large ICMP packets to external host.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: CustomLogsAma
    dataTypes:
      - Ubiquiti_CL
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Exfiltration
  - CommandAndControl
relevantTechniques:
  - T1041
  - T1572
query: |
  UbiquitiAuditEvent
  | where EventCategory =~ 'firewall'
  | where ipv4_is_private(SrcIpAddr)
  | where ipv4_is_private(DstIpAddr) == 'False'
  | where NetworkProtocol =~ 'ICMP'
  | summarize avg_packet_length = avg(toint(NetworkBytes))
  | extend a = 1
  | join (UbiquitiAuditEvent
    | where EventCategory =~ 'firewall'
    | where ipv4_is_private(SrcIpAddr)
    | where ipv4_is_private(DstIpAddr) == 'False'
    | where NetworkProtocol =~ 'ICMP'
    | extend a = 1) on a
  | where toint(NetworkBytes) > 2*avg_packet_length
  | extend IPCustomEntity = SrcIpAddr
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPCustomEntity
version: 1.0.2
kind: Scheduled

Stages and Predicates

Stage 1: source

UbiquitiAuditEvent

Stage 2: where

| where EventCategory =~ 'firewall'

Stage 3: where

| where ipv4_is_private(SrcIpAddr)

Stage 4: where

| where ipv4_is_private(DstIpAddr) == 'False'

Stage 5: where

| where NetworkProtocol =~ 'ICMP'

Stage 6: summarize

| summarize avg_packet_length = avg(toint(NetworkBytes))

Stage 7: extend

| extend a = 1

Stage 8: join

| join (UbiquitiAuditEvent
  | where EventCategory =~ 'firewall'
  | where ipv4_is_private(SrcIpAddr)
  | where ipv4_is_private(DstIpAddr) == 'False'
  | where NetworkProtocol =~ 'ICMP'
  | extend a = 1) on a

Stage 9: where

where <unknown:toint(, NetworkBytes)> > (avg_packet_length * 2)

Stage 10: extend

extend IPCustomEntity

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
EventCategoryeq
  • firewall
field:"EventCategory" kind:eq value:"firewall"
NetworkProtocoleq
  • ICMP
field:"NetworkProtocol" kind:eq value:"ICMP"
SrcIpAddrcidr_match
  • 10.0.0.0/8
  • 127.0.0.0/8
  • 169.254.0.0/16
  • 172.16.0.0/12
  • 192.168.0.0/16
field:"SrcIpAddr" kind:cidr_match

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
DstIpAddrcidr_match10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16, 127.0.0.0/8excludes:DstIpAddr

Output Fields

These fields are emitted when the rule matches.

FieldSource
avg_packet_lengthsummarize
aextend
IPCustomEntityextend

Unauthorized PLC changes (Microsoft Defender for IoT)

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

'This alert leverages Defender for IoT to detect unauthorized changes to PLC ladder logic code indicating new functionality in the PLC, improper configuration of an application, or malicious activity on the network.'

MITRE ATT&CK Coverage

TacticTechniques
Persistence
Impair Process Control

Rule Body

id: c2fb27c7-5f67-49c4-aaf3-d82934234a69
name: Unauthorized PLC changes (Microsoft Defender for IoT)
description: |
  'This alert leverages Defender for IoT to detect unauthorized changes to PLC ladder logic code indicating new functionality in the PLC, improper configuration of an application, or malicious activity on the network.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: IoT
    dataTypes:
      - SecurityAlert (ASC for IoT)
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Persistence
relevantTechniques:
  - T0839
query: |
  let alertList = dynamic(["Unpermitted Usage of Internal Indication (IIN)", "Modbus Address Range Violation", "Function Code Raised Unauthorized Exception", "Unauthorized Access to Siemens S7 Data Block", "Unauthorized Access to Wonderware Tag", "Unauthorized MMS Program Access", "GOOSE Message Type Settings", "Sampled Values Message Type Settings", "Foxboro I/A Unauthorized Operation", "New Activity Detected - CIP Class", "New Activity Detected - CIP Class Service", "New Activity Detected - CIP PCCC Command", "New Activity Detected - CIP Symbol", "New Activity Detected - EtherNet/IP I/O Connection", "New Activity Detected - EtherNet/IP Protocol Command", "New Activity Detected - GSM Message Code", "New Activity Detected - LonTalk Command Codes", "New Activity Detected - LonTalk Network Variable", "New Activity Detected - Ovation Data Request", "New Activity Detected - Read/Write Command (AMS Index Group)", "New Activity Detected - Read/Write Command (AMS Index Offset)", "New Activity Detected - Unauthorized DeltaV Message Type", "New Activity Detected - Unauthorized DeltaV ROC Operation", "New Activity Detected - Using AMS Protocol Command", "New Activity Detected - Using Siemens SICAM Command", "New Activity Detected - Using Suitelink Protocol command", "New Activity Detected - Using Suitelink Protocol sessions", "New Activity Detected - Using Yokogawa VNetIP Command", "Omron FINS Unauthorized Command", "Toshiba Computer Link Unauthorized Command", "Unauthorized ABB Totalflow File Operation", "Unauthorized ABB Totalflow Register Operation", "Unauthorized Access to Siemens S7 Plus Object", "Unauthorized BACNet Object Access", "Unauthorized BACNet Route", "Unauthorized Emerson ROC Operation", "Unauthorized GE SRTP File Access", "Unauthorized GE SRTP Protocol Command", "Unauthorized GE SRTP System Memory Operation", "Unauthorized Mitsubishi MELSEC Command", "Unauthorized MMS Service", "Unauthorized OPC UA Activity", "Unauthorized OPC UA Request/Response", "Unauthorized Profinet Frame Type", "Unauthorized SAIA S-Bus Command", "Unauthorized Siemens S7 Execution of Control Function", "Unauthorized Siemens S7 Execution of User Defined Function", "Unauthorized Siemens S7 Plus Block Access", "Unauthorized Siemens S7 Plus Operation", "Unauthorized SNMP Operation", "Unpermitted Modbus Schneider Electric Extension", "Unpermitted Usage of ASDU Types", "Unpermitted Usage of DNP3 Function Code", "Unpermitted Usage of Modbus Function Code", "Unauthorized Operation was detected by a User Defined Rule", "Unauthorized PLC Configuration Read", "Unauthorized PLC Programming", "Unauthorized PLC Configuration Write", "Unauthorized PLC Program Upload"]);
  SecurityAlert
  | where ProviderName == "IoTSecurity"
  | where AlertName has_any (alertList) 
    or ExtendedProperties has_any ("Illegal Beckhoff AMS Command", "Beckhoff AMS Command Failure")
  | extend ExtendedProperties = parse_json(ExtendedProperties)
  | where tostring(ExtendedProperties.isNew) == "True"
  | extend DeviceId = tostring(ExtendedProperties.DeviceId), 
           SourceDeviceAddress = tostring(ExtendedProperties.SourceDeviceAddress), 
           DestDeviceAddress = tostring(ExtendedProperties.DestinationDeviceAddress), 
           RemediationSteps = tostring(parse_json(RemediationSteps)[0]), 
           Protocol = tostring(ExtendedProperties.Protocol), 
           AlertManagementUri = tostring(ExtendedProperties.AlertManagementUri)
  | project
    TimeGenerated,
    DeviceId,
    ProductName,
    ProductComponentName,
    AlertSeverity,
    AlertName,
    Description,
    Protocol,
    SourceDeviceAddress,
    DestDeviceAddress,
    RemediationSteps,
    Tactics,
    Entities,
    VendorOriginalId,
    AlertLink,
    AlertManagementUri,
    Techniques
entityMappings:
sentinelEntitiesMappings:
  - columnName: Entities
eventGroupingSettings:
  aggregationKind: AlertPerResult
customDetails:
  Sensor: DeviceId
  Protocol: Protocol
  VendorOriginalId: VendorOriginalId
  AlertManagementUri: AlertManagementUri
alertDetailsOverride:
  alertDisplayNameFormat: (MDIoT) {{AlertName}}
  alertDescriptionFormat: (MDIoT) {{Description}}
  alertTacticsColumnName: Tactics
  alertSeverityColumnName: AlertSeverity
  alertDynamicProperties: 
    - alertProperty: ProductName
      value: ProductName
    - alertProperty: RemediationSteps
      value: RemediationSteps
    - alertProperty: Techniques
      value: Techniques
    - alertProperty: ProductComponentName
      value: ProductComponentName
    - alertProperty: AlertLink
      value: AlertLink
version: 1.0.3
kind: Scheduled

Stages and Predicates

Let Binding: alertList used in Stage 3

let alertList = dynamic(["Unpermitted Usage of Internal Indication (IIN)", "Modbus Address Range Violation", "Function Code Raised Unauthorized Exception", "Unauthorized Access to Siemens S7 Data Block", "Unauthorized Access to Wonderware Tag", "Unauthorized MMS Program Access", "GOOSE Message Type Settings", "Sampled Values Message Type Settings", "Foxboro I/A Unauthorized Operation", "New Activity Detected - CIP Class", "New Activity Detected - CIP Class Service", "New Activity Detected - CIP PCCC Command", "New Activity Detected - CIP Symbol", "New Activity Detected - EtherNet/IP I/O Connection", "New Activity Detected - EtherNet/IP Protocol Command", "New Activity Detected - GSM Message Code", "New Activity Detected - LonTalk Command Codes", "New Activity Detected - LonTalk Network Variable", "New Activity Detected - Ovation Data Request", "New Activity Detected - Read/Write Command (AMS Index Group)", "New Activity Detected - Read/Write Command (AMS Index Offset)", "New Activity Detected - Unauthorized DeltaV Message Type", "New Activity Detected - Unauthorized DeltaV ROC Operation", "New Activity Detected - Using AMS Protocol Command", "New Activity Detected - Using Siemens SICAM Command", "New Activity Detected - Using Suitelink Protocol command", "New Activity Detected - Using Suitelink Protocol sessions", "New Activity Detected - Using Yokogawa VNetIP Command", "Omron FINS Unauthorized Command", "Toshiba Computer Link Unauthorized Command", "Unauthorized ABB Totalflow File Operation", "Unauthorized ABB Totalflow Register Operation", "Unauthorized Access to Siemens S7 Plus Object", "Unauthorized BACNet Object Access", "Unauthorized BACNet Route", "Unauthorized Emerson ROC Operation", "Unauthorized GE SRTP File Access", "Unauthorized GE SRTP Protocol Command", "Unauthorized GE SRTP System Memory Operation", "Unauthorized Mitsubishi MELSEC Command", "Unauthorized MMS Service", "Unauthorized OPC UA Activity", "Unauthorized OPC UA Request/Response", "Unauthorized Profinet Frame Type", "Unauthorized SAIA S-Bus Command", "Unauthorized Siemens S7 Execution of Control Function", "Unauthorized Siemens S7 Execution of User Defined Function", "Unauthorized Siemens S7 Plus Block Access", "Unauthorized Siemens S7 Plus Operation", "Unauthorized SNMP Operation", "Unpermitted Modbus Schneider Electric Extension", "Unpermitted Usage of ASDU Types", "Unpermitted Usage of DNP3 Function Code", "Unpermitted Usage of Modbus Function Code", "Unauthorized Operation was detected by a User Defined Rule", "Unauthorized PLC Configuration Read", "Unauthorized PLC Programming", "Unauthorized PLC Configuration Write", "Unauthorized PLC Program Upload"]);

Stage 1: source

SecurityAlert

Stage 2: where

| where ProviderName == "IoTSecurity"

Stage 3: where

| where AlertName has_any (alertList) 
  or ExtendedProperties has_any ("Illegal Beckhoff AMS Command", "Beckhoff AMS Command Failure")

Stage 4: extend

| extend ExtendedProperties = parse_json(ExtendedProperties)

Stage 5: where

| where tostring(ExtendedProperties.isNew) == "True"

Stage 6: extend

| extend DeviceId = tostring(ExtendedProperties.DeviceId), 
         SourceDeviceAddress = tostring(ExtendedProperties.SourceDeviceAddress), 
         DestDeviceAddress = tostring(ExtendedProperties.DestinationDeviceAddress), 
         RemediationSteps = tostring(parse_json(RemediationSteps)[0]), 
         Protocol = tostring(ExtendedProperties.Protocol), 
         AlertManagementUri = tostring(ExtendedProperties.AlertManagementUri)

Stage 7: project

| project
  TimeGenerated,
  DeviceId,
  ProductName,
  ProductComponentName,
  AlertSeverity,
  AlertName,
  Description,
  Protocol,
  SourceDeviceAddress,
  DestDeviceAddress,
  RemediationSteps,
  Tactics,
  Entities,
  VendorOriginalId,
  AlertLink,
  AlertManagementUri,
  Techniques

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
AlertNamematch
  • Foxboro I/A Unauthorized Operation transforms: term
  • Function Code Raised Unauthorized Exception transforms: term
  • GOOSE Message Type Settings transforms: term
  • Modbus Address Range Violation transforms: term
  • New Activity Detected - CIP Class transforms: term
  • New Activity Detected - CIP Class Service transforms: term
  • New Activity Detected - CIP PCCC Command transforms: term
  • New Activity Detected - CIP Symbol transforms: term
  • New Activity Detected - EtherNet/IP I/O Connection transforms: term
  • New Activity Detected - EtherNet/IP Protocol Command transforms: term
  • New Activity Detected - GSM Message Code transforms: term
  • New Activity Detected - LonTalk Command Codes transforms: term
  • New Activity Detected - LonTalk Network Variable transforms: term corpus 2 (kusto 2)
  • New Activity Detected - Ovation Data Request transforms: term corpus 2 (kusto 2)
  • New Activity Detected - Read/Write Command (AMS Index Group) transforms: term corpus 2 (kusto 2)
  • New Activity Detected - Read/Write Command (AMS Index Offset) transforms: term corpus 2 (kusto 2)
  • New Activity Detected - Unauthorized DeltaV Message Type transforms: term corpus 2 (kusto 2)
  • New Activity Detected - Unauthorized DeltaV ROC Operation transforms: term corpus 2 (kusto 2)
  • New Activity Detected - Using AMS Protocol Command transforms: term corpus 2 (kusto 2)
  • New Activity Detected - Using Siemens SICAM Command transforms: term corpus 2 (kusto 2)
  • New Activity Detected - Using Suitelink Protocol command transforms: term corpus 2 (kusto 2)
  • New Activity Detected - Using Suitelink Protocol sessions transforms: term corpus 2 (kusto 2)
  • New Activity Detected - Using Yokogawa VNetIP Command transforms: term corpus 2 (kusto 2)
  • Omron FINS Unauthorized Command transforms: term corpus 2 (kusto 2)
  • Sampled Values Message Type Settings transforms: term
  • Toshiba Computer Link Unauthorized Command transforms: term corpus 2 (kusto 2)
  • Unauthorized ABB Totalflow File Operation transforms: term corpus 2 (kusto 2)
  • Unauthorized ABB Totalflow Register Operation transforms: term corpus 2 (kusto 2)
  • Unauthorized Access to Siemens S7 Data Block transforms: term
  • Unauthorized Access to Siemens S7 Plus Object transforms: term corpus 2 (kusto 2)
  • Unauthorized Access to Wonderware Tag transforms: term
  • Unauthorized BACNet Object Access transforms: term corpus 2 (kusto 2)
  • Unauthorized BACNet Route transforms: term corpus 2 (kusto 2)
  • Unauthorized Emerson ROC Operation transforms: term corpus 2 (kusto 2)
  • Unauthorized GE SRTP File Access transforms: term corpus 2 (kusto 2)
  • Unauthorized GE SRTP Protocol Command transforms: term corpus 2 (kusto 2)
  • Unauthorized GE SRTP System Memory Operation transforms: term corpus 2 (kusto 2)
  • Unauthorized MMS Program Access transforms: term
  • Unauthorized MMS Service transforms: term corpus 2 (kusto 2)
  • Unauthorized Mitsubishi MELSEC Command transforms: term corpus 2 (kusto 2)
  • Unauthorized OPC UA Activity transforms: term corpus 2 (kusto 2)
  • Unauthorized OPC UA Request/Response transforms: term corpus 2 (kusto 2)
  • Unauthorized Operation was detected by a User Defined Rule transforms: term corpus 2 (kusto 2)
  • Unauthorized PLC Configuration Read transforms: term corpus 2 (kusto 2)
  • Unauthorized PLC Configuration Write transforms: term corpus 2 (kusto 2)
  • Unauthorized PLC Program Upload transforms: term corpus 2 (kusto 2)
  • Unauthorized PLC Programming transforms: term corpus 2 (kusto 2)
  • Unauthorized Profinet Frame Type transforms: term corpus 2 (kusto 2)
  • Unauthorized SAIA S-Bus Command transforms: term corpus 2 (kusto 2)
  • Unauthorized SNMP Operation transforms: term corpus 2 (kusto 2)
  • Unauthorized Siemens S7 Execution of Control Function transforms: term corpus 2 (kusto 2)
  • Unauthorized Siemens S7 Execution of User Defined Function transforms: term corpus 2 (kusto 2)
  • Unauthorized Siemens S7 Plus Block Access transforms: term corpus 2 (kusto 2)
  • Unauthorized Siemens S7 Plus Operation transforms: term corpus 2 (kusto 2)
  • Unpermitted Modbus Schneider Electric Extension transforms: term corpus 2 (kusto 2)
  • Unpermitted Usage of ASDU Types transforms: term corpus 2 (kusto 2)
  • Unpermitted Usage of DNP3 Function Code transforms: term corpus 2 (kusto 2)
  • Unpermitted Usage of Internal Indication (IIN) transforms: term
  • Unpermitted Usage of Modbus Function Code transforms: term corpus 2 (kusto 2)
field:"AlertName" kind:match
ExtendedPropertiesmatch
  • Beckhoff AMS Command Failure transforms: term
  • Illegal Beckhoff AMS Command transforms: term
field:"ExtendedProperties" kind:match
ExtendedProperties.isNeweq
  • True transforms: tostring, kusto_dynamic_path corpus 15 (kusto 15)
field:"ExtendedProperties.isNew" kind:eq value:"True"
ProviderNameeq
  • IoTSecurity corpus 15 (kusto 15)
field:"ProviderName" kind:eq value:"IoTSecurity"

Output Fields

These fields are emitted when the rule matches.

FieldSource
AlertLinkproject
AlertManagementUriproject
AlertNameproject
AlertSeverityproject
Descriptionproject
DestDeviceAddressproject
DeviceIdproject
Entitiesproject
ProductComponentNameproject
ProductNameproject
Protocolproject
RemediationStepsproject
SourceDeviceAddressproject
Tacticsproject
Techniquesproject
TimeGeneratedproject
VendorOriginalIdproject

VMware ESXi - Root login

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

'Detects when root user login from uncommon IP address.'

MITRE ATT&CK Coverage

TacticTechniques
Initial Access
Privilege Escalation

Rule Body

id: deb448a8-6a9d-4f8c-8a95-679a0a2cd62c
name: VMware ESXi - Root login
description: |
  'Detects when root user login from uncommon IP address.'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: SyslogAma
    datatypes:
      - Syslog
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
  - PrivilegeEscalation
relevantTechniques:
  - T1078
query: |
  let p_lookback = 14d;
  let t_lookback = 1h;
  let root_ips = VMwareESXi
  | where TimeGenerated between (ago(p_lookback) .. ago(t_lookback))
  | where SyslogMessage has_all ('UserLoginSessionEvent', 'root', 'logged in')
  | extend SrcIpAddr = extract(@'root@(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})', 1, SyslogMessage)
  | summarize makeset(SrcIpAddr);
  VMwareESXi
  | where TimeGenerated > ago(t_lookback)
  | where SyslogMessage has_all ('UserLoginSessionEvent', 'root', 'logged in')
  | extend SrcIpAddr = extract(@'root@(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})', 1, SyslogMessage)
  | where SrcIpAddr !in (root_ips)
  | extend IPCustomEntity = SrcIpAddr
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPCustomEntity
version: 1.0.3
kind: Scheduled

Stages and Predicates

Parameters

let p_lookback = 14d;
let t_lookback = 1h;

Let Binding: root_ips used in Stage 5

let root_ips = VMwareESXi
| where TimeGenerated between (ago(p_lookback) .. ago(t_lookback))
| where SyslogMessage has_all ('UserLoginSessionEvent', 'root', 'logged in')
| extend SrcIpAddr = extract(@'root@(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})', 1, SyslogMessage)
| summarize makeset(SrcIpAddr);

Stage 1: source

VMwareESXi

Stage 2: where

| where TimeGenerated > ago(t_lookback)

Stage 3: where

| where SyslogMessage has_all ('UserLoginSessionEvent', 'root', 'logged in')

Stage 4: extend

| extend SrcIpAddr = extract(@'root@(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})', 1, SyslogMessage)

Stage 5: where

| where SrcIpAddr !in (root_ips)

Stage 6: extend

| extend IPCustomEntity = SrcIpAddr

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
SyslogMessagematch
  • UserLoginSessionEvent transforms: term
  • logged in transforms: term
  • root transforms: term
field:"SyslogMessage" kind:match

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
SrcIpAddreqroot_ipsexcludes:SrcIpAddr field:"SrcIpAddr" value:"root_ips"

Output Fields

These fields are emitted when the rule matches.

FieldSource
SrcIpAddrextend
IPCustomEntityextend

Back to all Kusto rules