Detection rules › Kusto

Kusto rules: 2q

Apache - Multiple client errors from single IP

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

'Detects multiple client errors from one source in short timeframe'

MITRE ATT&CK Coverage

Rule Body

id: 15f5a956-3af9-11ec-8d3d-0242ac130003
name: Apache - Multiple client errors from single IP
description: |
  'Detects multiple client errors from one source in short timeframe'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: CustomLogsAma
    datatypes:
      - ApacheHTTPServer_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1190
  - T1133
query: |
  let threshold = 100;
  ApacheHTTPServer
  | where HttpStatusCode >= 400 and HttpStatusCode <= 499
  | summarize MultipleClientErrors = count() by SrcIpAddr, bin(TimeGenerated, 5m)
  | where MultipleClientErrors > threshold
  | extend IPCustomEntity = SrcIpAddr
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPCustomEntity
version: 1.0.3
kind: Scheduled

Stages and Predicates

Parameters

let threshold = 100;

Stage 1: source

ApacheHTTPServer

Stage 2: where

| where HttpStatusCode >= 400 and HttpStatusCode <= 499

Stage 3: summarize

| summarize MultipleClientErrors = count() by SrcIpAddr, bin(TimeGenerated, 5m)

Stage 4: where

| where MultipleClientErrors > threshold

Stage 5: extend

| extend IPCustomEntity = SrcIpAddr

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
MultipleClientErrorssummarize
SrcIpAddrsummarize
IPCustomEntityextend

Azure DevOps Administrator Group Monitoring

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

'This detection monitors for additions to projects or project collection administration groups in an Azure DevOps Organization.'

MITRE ATT&CK Coverage

TacticTechniques
Persistence

Rule Body

id: 89e6adbd-612c-4fbe-bc3d-32f81baf3b6c
name: Azure DevOps Administrator Group Monitoring
description: |
  'This detection monitors for additions to projects or project collection administration groups in an Azure DevOps Organization.'
severity: Medium
status: Available
requiredDataConnectors: []
queryFrequency: 4h
queryPeriod: 4h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Persistence
relevantTechniques:
  - T1098
query: |
  // Change to true to monitor for Project Administrator adds to *any* project
  let MonitorAllProjects = false;
  // If MonitorAllProjects is false, trigger only on Project Administrator add for the following projects
  let ProjectsToMonitor = dynamic(['<project_X>','<project_Y>']);
  ADOAuditLogs
  | where Area == "Group" and OperationName == "Group.UpdateGroupMembership.Add"
  | where Details has 'Administrators'
  | where Details has "was added as a member of group" and (Details endswith '\\Project Administrators' or Details endswith '\\Project Collection Administrators')
  | parse Details with AddedIdentity ' was added as a member of group [' EntityName ']\\' GroupName
  | extend Level = iif(GroupName == 'Project Collection Administrators', 'Organization', 'Project'), AddedIdentityId = Data.MemberId
  | extend Severity = iif(Level == 'Organization', 'High', 'Medium'), AlertDetails = strcat('At ', TimeGenerated, ' UTC ', ActorUPN, '/', ActorDisplayName, ' added ', AddedIdentity, ' to the ', EntityName, ' ', Level)
  | where MonitorAllProjects == true or EntityName in (ProjectsToMonitor) or Level == 'Organization'
  | project TimeGenerated, Severity, Adder = ActorUPN, AddedIdentity, AddedIdentityId, AlertDetails, Level, EntityName, GroupName, ActorAuthType = AuthenticationMechanism,
    ActorIpAddress = IpAddress, ActorUserAgent = UserAgent, RawDetails = Details
  | extend timestamp = TimeGenerated
  | extend AccountName = tostring(split(Adder, "@")[0]), AccountUPNSuffix = tostring(split(Adder, "@")[1])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: Adder
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: AccountUPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: ActorIpAddress
version: 1.0.5
kind: Scheduled

Stages and Predicates

Parameters

let MonitorAllProjects = false;
let ProjectsToMonitor = dynamic(['<project_X>','<project_Y>']);

Stage 1: source

ADOAuditLogs

Stage 2: where

| where Area == "Group" and OperationName == "Group.UpdateGroupMembership.Add"

Stage 3: where

| where Details has 'Administrators'

Stage 4: where

| where Details has "was added as a member of group" and (Details endswith '\\Project Administrators' or Details endswith '\\Project Collection Administrators')

Stage 5: parse

| parse Details with AddedIdentity ' was added as a member of group [' EntityName ']\\' GroupName

Stage 6: extend

| extend Level = iif(GroupName == 'Project Collection Administrators', 'Organization', 'Project'), AddedIdentityId = Data.MemberId

Stage 7: extend

| extend Severity = iif(Level == 'Organization', 'High', 'Medium'), AlertDetails = strcat('At ', TimeGenerated, ' UTC ', ActorUPN, '/', ActorDisplayName, ' added ', AddedIdentity, ' to the ', EntityName, ' ', Level)

Stage 8: where

| where MonitorAllProjects == true or EntityName in (ProjectsToMonitor) or Level == 'Organization'

Stage 9: project

| project TimeGenerated, Severity, Adder = ActorUPN, AddedIdentity, AddedIdentityId, AlertDetails, Level, EntityName, GroupName, ActorAuthType = AuthenticationMechanism,
  ActorIpAddress = IpAddress, ActorUserAgent = UserAgent, RawDetails = Details

Stage 10: extend

| extend timestamp = TimeGenerated

Stage 11: extend

| extend AccountName = tostring(split(Adder, "@")[0]), AccountUPNSuffix = tostring(split(Adder, "@")[1])

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Areaeq
  • Group
field:"Area" kind:eq value:"Group"
Detailsends_with
  • \Project Administrators
  • \Project Collection Administrators
field:"Details" kind:ends_with
Detailsmatch
  • Administrators transforms: term
  • was added as a member of group transforms: term
field:"Details" kind:match
EntityNamein
  • <project_X>
  • <project_Y>
field:"EntityName" kind:in
Leveleq
  • Organization
field:"Level" kind:eq value:"Organization"
OperationNameeq
  • Group.UpdateGroupMembership.Add
field:"OperationName" kind:eq value:"Group.UpdateGroupMembership.Add"

Output Fields

These fields are emitted when the rule matches.

FieldSource
ActorAuthTypeproject
ActorIpAddressproject
ActorUserAgentproject
AddedIdentityproject
AddedIdentityIdproject
Adderproject
AlertDetailsproject
EntityNameproject
GroupNameproject
Levelproject
RawDetailsproject
Severityproject
TimeGeneratedproject
timestampextend
AccountNameextend
AccountUPNSuffixextend

BloodHound Attack Path Finding - VM Admin Login Role on Tier Zero System

#

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

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

BloodHound Attack Path Finding - VM Admin Login Role on Tier Zero System

Rule Body

id: f1334bfc-7cdd-4655-b8de-e1398158f4b5
name: BloodHound Attack Path Finding - VM Admin Login Role on Tier Zero System
description: BloodHound Attack Path Finding - VM Admin Login Role on Tier Zero System
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: BloodHoundEnterprise
    dataTypes:
      - BHEAttackPathsData_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: BHEAttackPathsData_CL | where Finding == "AzureT0VMAdminLogin"
entityMappings:
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: domain_name
customDetails:
  Description: ShortDescription
  Remediation: ShortRemediation
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

BHEAttackPathsData_CL

Stage 2: where

| where Finding == "AzureT0VMAdminLogin"

Indicators

These rows show field, operator, and value matches.

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

Cisco Duo - Unexpected authentication factor

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

'Detects when unexpected authentication factor used.'

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Rule Body

id: 16c91a2c-17ad-4985-a9ad-4a4f1cb11830
name: Cisco Duo - Unexpected authentication factor
description: |
  'Detects when unexpected authentication factor used.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: CiscoDuoAuthConnectorDefinition
    dataTypes:
      - DuoAuthentication_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1078
query: |
  let allowed_auth_f = dynamic(['duo_push', 'duo_mobile_passcode']);
  DuoAuthentication_CL
  | where Result =~ 'success'
  | where Factor !in~ (allowed_auth_f)
  | extend IPCustomEntity = AccessDeviceIp, AccountCustomEntity = UserName
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPCustomEntity
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountCustomEntity
version: 1.0.1
kind: Scheduled

Stages and Predicates

Parameters

let allowed_auth_f = dynamic(['duo_push', 'duo_mobile_passcode']);

Stage 1: source

DuoAuthentication_CL

Stage 2: where

| where Result =~ 'success'

Stage 3: where

| where Factor !in~ (allowed_auth_f)

Stage 4: extend

| extend IPCustomEntity = AccessDeviceIp, AccountCustomEntity = UserName

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Resulteq
  • success
field:"Result" kind:eq value:"success"

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
Factorinduo_mobile_passcode, duo_pushexcludes:Factor field:"Factor" value:"duo_mobile_passcode" field:"Factor" value:"duo_push"

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountCustomEntityextend
IPCustomEntityextend

Cross-tenant Access Settings Organization Outbound Direct Settings Changed

#
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 Organization Outbound Direct Settings are changed for "Users & Groups" and for "Applications".

MITRE ATT&CK Coverage

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: 0101e08d-99cd-4a97-a9e0-27649c4369ad
name: Cross-tenant Access Settings Organization Outbound Direct Settings Changed
description: |
  'Organizations are added in the Cross-tenant Access Settings to control communication inbound or outbound for users and applications. This detection notifies when Organization Outbound Direct Settings are changed for "Users & Groups" and for "Applications".'
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: |
  // In User & Groups and in Applications, the following "AccessType" values in columns PremodifiedOutboundSettings and ModifiedOutboundSettings are interpreted accordingly
  // When Access Type in premodified outbound settings value was 1 that means that the initial access was allowed. When Access Type in premodified outbound settings value was 2 that means that the initial access was blocked.
  // When Access Type in modified outbound settings value is 1 that means that now access is allowed. When Access Type in modified outbound settings value is 2 that means that now access is blocked.
  AuditLogs
  | where OperationName has "Update a partner 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 =~ "b2bDirectConnectOutbound"
        | extend PremodifiedOutboundSettings = trim('"',tostring(Property.oldValue)),
                 ModifiedOutboundSettings = trim(@'"',tostring(Property.newValue))
    )
  | where PremodifiedOutboundSettings != ModifiedOutboundSettings
  | 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 "Update a partner 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 =~ "b2bDirectConnectOutbound"
      | extend PremodifiedOutboundSettings = trim('"',tostring(Property.oldValue)),
               ModifiedOutboundSettings = trim(@'"',tostring(Property.newValue))
  )

Stage 5: where

| where PremodifiedOutboundSettings != ModifiedOutboundSettings

Stage 6: 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
  • Update a partner cross-tenant access setting transforms: term corpus 4 (kusto 4)
field:"OperationName" kind:match value:"Update a partner cross-tenant access setting"
Property.displayNameeq
  • b2bDirectConnectOutbound transforms: kusto_dynamic_path
field:"Property.displayName" kind:eq value:"b2bDirectConnectOutbound"
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

Cyble Vision Alerts Assets

#

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

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

'Scheduled rule that creates incidents for asset alerts using saved parser Alerts_assets. Mandatory custom details: MappedSeverity, Status, AlertID, Service.'

MITRE ATT&CK Coverage

TacticTechniques
Reconnaissance

Rule Body

id: 0012714c-c595-4dcd-8949-4a5c1d49aaa8
name: Cyble Vision Alerts Assets
description: |
  'Scheduled rule that creates incidents for asset alerts using saved parser Alerts_assets. Mandatory custom details: MappedSeverity, Status, AlertID, Service.'
severity: Low
status: Available
requiredDataConnectors:
- connectorId: CybleVisionAlerts
  dataTypes:
  - CybleVisionAlerts_CL
enabled: true
queryfrequency: 30m
queryPeriod: 30m
triggerOperator: GreaterThan
triggerThreshold: 0
eventGroupingSettings:
  aggregationKind: AlertPerResult
tactics:
- Reconnaissance
relevantTechniques:
- T1592
query: |
  Alerts_assets
  | where Service == "assets"
  | extend MappedSeverity = Severity
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: false
    reopenClosedIncident: false
    lookbackDuration: PT5H
    matchingMethod: AllEntities
customDetails:
  MappedSeverity: Severity
  Status: Status
  AlertID: AlertID
  Service: Service
  AA_KeywordID: AA_KeywordID
  AA_LastDetected: AA_LastDetected
  AA_Value: AA_Value
entityMappings:
- entityType: Host
  fieldMappings:
  - identifier: HostName
    columnName: AA_Value
- entityType: Url
  fieldMappings:
  - identifier: Url
    columnName: AA_Value
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

Alerts_assets

Stage 2: where

| where Service == "assets"

Stage 3: extend

| extend MappedSeverity = Severity

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
MappedSeverityextend

CYFIRMA - Social and Public Exposure - Social Media Threats Activity Detected Rule

#

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

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

"This rule detects medium-severity social threat alerts from CYFIRMA related to impersonation, fake profiles, or malicious activities on social platforms that may target executives, brands, or employees. These threats can result in reputational damage, phishing, or social engineering attacks. Immediate investigation and takedown are recommended to minimize risk."

MITRE ATT&CK Coverage

Rule Body

id: b8149f2f-54da-4f7b-98e1-c01ca47e1e55
name: CYFIRMA - Social and Public Exposure -  Social Media Threats  Activity Detected Rule
description: |
  "This rule detects medium-severity social threat alerts from CYFIRMA related to impersonation, fake profiles, or malicious activities on social platforms that may target executives, brands, or employees. 
  These threats can result in reputational damage, phishing, or social engineering attacks. 
  Immediate investigation and takedown are recommended to minimize risk."
version: 1.0.1
kind: Scheduled
severity: Medium
requiredDataConnectors:
  - connectorId: CyfirmaDigitalRiskAlertsConnector
    dataTypes:
      - CyfirmaSPESocialThreatAlerts_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - ResourceDevelopment
  - Reconnaissance
  - InitialAccess
  - Impact
relevantTechniques:
  - T1585.001
  - T1593
  - T1566
  - T1582
  - T1491
query: |
  // Medium severity - Social and Public Exposure - Social Media Threats Activity Detected
  let timeFrame = 5m;
  CyfirmaSPESocialThreatAlerts_CL
  | where severity == 'High' and TimeGenerated between (ago(timeFrame) .. now())
  | extend
      Description=description,
      FirstSeen=first_seen,
      LastSeen=last_seen,
      RiskScore=risk_score,
      AlertUID=alert_uid,
      UID=uid,
      AssetType=asset_type,
      AssetValue=signature,
      Source=source,
      Impact=impact,
      Recommendation=recommendation,
      ProviderName='CYFIRMA',
      ProductName='DeCYFIR/DeTCT',
      AlertTitle=Alert_title
  | project
      TimeGenerated,
      Description,
      RiskScore,
      FirstSeen,
      LastSeen,
      AlertUID,
      UID,
      AssetType,
      AssetValue,
      Source,
      Impact,
      Recommendation,
      ProductName,
      ProviderName,
      AlertTitle
customDetails:
  AssetType: AssetType
  AssetValue: AssetValue
  RiskScore: RiskScore
  Source: Source
  Impact: Impact
  Description: Description
  Recommendation: Recommendation
  FirstSeen: FirstSeen
  LastSeen: LastSeen
  AlertUID: AlertUID
  UID: UID
  TimeGenerated: TimeGenerated
alertDetailsOverride:
  alertDisplayNameFormat: "CYFIRMA - Medium Severity Alert: Social Media Threat Activity Detected - {{AlertTitle}} "
  alertDescriptionFormat: "{{Description}} "
  alertDynamicProperties:
    - alertProperty: ProductName
      value: ProductName
    - alertProperty: ProviderName
      value: ProviderName
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: false
    reopenClosedIncident: false
    lookbackDuration: PT5H
    matchingMethod: AllEntities
eventGroupingSettings:
  aggregationKind: AlertPerResult

Stages and Predicates

Parameters

let timeFrame = 5m;

Stage 1: source

CyfirmaSPESocialThreatAlerts_CL

Stage 2: where

| where severity == 'High' and TimeGenerated between (ago(timeFrame) .. now())

Stage 3: extend

| extend
    Description=description,
    FirstSeen=first_seen,
    LastSeen=last_seen,
    RiskScore=risk_score,
    AlertUID=alert_uid,
    UID=uid,
    AssetType=asset_type,
    AssetValue=signature,
    Source=source,
    Impact=impact,
    Recommendation=recommendation,
    ProviderName='CYFIRMA',
    ProductName='DeCYFIR/DeTCT',
    AlertTitle=Alert_title

Stage 4: project

| project
    TimeGenerated,
    Description,
    RiskScore,
    FirstSeen,
    LastSeen,
    AlertUID,
    UID,
    AssetType,
    AssetValue,
    Source,
    Impact,
    Recommendation,
    ProductName,
    ProviderName,
    AlertTitle

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

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

full_access_as_app Granted To Application

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

This detection looks for the full_access_as_app permission being granted to an OAuth application with Admin Consent. This permission provide access to all Exchange mailboxes via the EWS API can could be exploited to access sensitive data by being added to a compromised application. The application granted this permission should be reviewed to ensure that it is absolutely necessary for the applications function. Ref: https://learn.microsoft.com/graph/auth-limit-mailbox-access

MITRE ATT&CK Coverage

Telemetry Coverage

PlatformRecord / event type
AzureConsent to application

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: 54e22fed-0ec6-4fb2-8312-2a3809a93f63
name: full_access_as_app Granted To Application
description: |
  'This detection looks for the full_access_as_app permission being granted to an OAuth application with Admin Consent.
  This permission provide access to all Exchange mailboxes via the EWS API can could be exploited to access sensitive data by being added to a compromised application. The application granted this permission should be reviewed to ensure that it is absolutely necessary for the applications function.
  Ref: https://learn.microsoft.com/graph/auth-limit-mailbox-access'
severity: Medium
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AuditLogs
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - DefenseEvasion
relevantTechniques:
  - T1550.001
query: |
  AuditLogs
  | where LoggedByService =~ "Core Directory"
  | where Category =~ "ApplicationManagement"
  | where OperationName =~ "Consent to application"
  | where TargetResources has "full_access_as_app"
  | mv-expand TargetResources
  | extend OAuthAppName = TargetResources.displayName
  | extend ModifiedProperties = TargetResources.modifiedProperties 
  | mv-apply Property = ModifiedProperties on 
    (
        where Property.displayName =~ "ConsentContext.isAdminConsent"
        | extend AdminConsent = tostring(Property.newValue)
    )
  | mv-apply Property = ModifiedProperties on 
    (
        where Property.displayName =~ "ConsentAction.Permissions"
        | extend Permissions = tostring(Property.newValue)
    )
  | mv-apply Property = ModifiedProperties on 
    (
        where Property.displayName =~ "TargetId.ServicePrincipalNames"
        | extend AppId = tostring(Property.newValue)
    )
  | mv-apply Property = AdditionalDetails on 
    (
        where Property.key =~ "User-Agent"
        | extend InitiatingUserAgent = replace('"', '', tostring(Property.value))
    )
  | project-away Property
  | parse Permissions with * "ConsentType: " GrantConsentType ", Scope: " GrantScope1 "," *
  | where GrantScope1 =~ "full_access_as_app"
  | extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
  | extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
  | extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
  | extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
  | extend InitiatingIpAddress = tostring(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))
  | project-reorder TimeGenerated, OAuthAppName, AppId, AdminConsent, Permissions, InitiatingUserPrincipalName, InitiatingAadUserId, InitiatingAppName, InitiatingAppServicePrincipalId, InitiatingIpAddress, InitiatingUserAgent, GrantScope1, GrantConsentType
  | extend GrantInitiatedBy = tostring(iff(isnotempty(InitiatingUserPrincipalName), InitiatingUserPrincipalName, InitiatingAppName))
  | extend Name = split(InitiatingUserPrincipalName, "@")[0], UPNSuffix = split(InitiatingUserPrincipalName, "@")[1]
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: InitiatingUserPrincipalName
      - identifier: Name
        columnName: Name
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: Account
    fieldMappings:
      - identifier: AadUserId
        columnName: InitiatingAadUserId
  - entityType: Account
    fieldMappings:
      - identifier: AadUserId
        columnName: InitiatingAppServicePrincipalId
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: InitiatingIpAddress
customDetails:
  OAuthApplication: OAuthAppName
  OAuthAppId: AppId
  UserAgent: InitiatingUserAgent
alertDetailsOverride:
  alertDisplayNameFormat: User or App {{GrantInitiatedBy}} granted full_access_as_app to {{OAuthAppName}}
  alertDescriptionFormat: |
    This detection looks for the full_access_as_app permission being granted to an OAuth application with Admin Consent.
    This permission provide access to all Exchange mailboxes via the EWS API can could be exploited to access sensitive data 
    by being added to a compromised application. The application granted this permission should be reviewed to ensure that it 
    is absolutely necessary for the applications function.
    In this case {{GrantInitiatedBy}} granted full_access_as_app to {{OAuthAppName}} from {{InitiatingIpAddress}}
    Ref: https://learn.microsoft.com/graph/auth-limit-mailbox-access
version: 1.0.2
kind: Scheduled

Stages and Predicates

Stage 1: source

AuditLogs

Stage 2: where

| where LoggedByService =~ "Core Directory"

Stage 3: where

| where Category =~ "ApplicationManagement"

Stage 4: where

| where OperationName =~ "Consent to application"

Stage 5: where

| where TargetResources has "full_access_as_app"

Stage 6: mv-expand

| mv-expand TargetResources

Stage 7: extend

| extend OAuthAppName = TargetResources.displayName

Stage 8: extend

| extend ModifiedProperties = TargetResources.modifiedProperties

Stage 9: kusto:mv-apply

| mv-apply Property = ModifiedProperties on 
  (
      where Property.displayName =~ "ConsentContext.isAdminConsent"
      | extend AdminConsent = tostring(Property.newValue)
  )

Stage 10: kusto:mv-apply

| mv-apply Property = ModifiedProperties on 
  (
      where Property.displayName =~ "ConsentAction.Permissions"
      | extend Permissions = tostring(Property.newValue)
  )

Stage 11: kusto:mv-apply

| mv-apply Property = ModifiedProperties on 
  (
      where Property.displayName =~ "TargetId.ServicePrincipalNames"
      | extend AppId = tostring(Property.newValue)
  )

Stage 12: kusto:mv-apply

| mv-apply Property = AdditionalDetails on 
  (
      where Property.key =~ "User-Agent"
      | extend InitiatingUserAgent = replace('"', '', tostring(Property.value))
  )

Stage 13: project-away

| project-away Property

Stage 14: parse

| parse Permissions with * "ConsentType: " GrantConsentType ", Scope: " GrantScope1 "," *

Stage 15: where

| where GrantScope1 =~ "full_access_as_app"

Stage 16: 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 17: project-reorder

| project-reorder TimeGenerated, OAuthAppName, AppId, AdminConsent, Permissions, InitiatingUserPrincipalName, InitiatingAadUserId, InitiatingAppName, InitiatingAppServicePrincipalId, InitiatingIpAddress, InitiatingUserAgent, GrantScope1, GrantConsentType

Stage 18: extend

| extend GrantInitiatedBy = tostring(iff(isnotempty(InitiatingUserPrincipalName), InitiatingUserPrincipalName, InitiatingAppName))

Stage 19: extend

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

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Categoryeq
  • ApplicationManagement corpus 13 (kusto 13)
field:"Category" kind:eq value:"ApplicationManagement"
GrantScope1eq
  • full_access_as_app
field:"GrantScope1" kind:eq value:"full_access_as_app"
LoggedByServiceeq
  • Core Directory corpus 11 (kusto 10, sigma 1)
field:"azure_ad::logged_by_service" kind:eq value:"Core Directory"
OperationNameeq
  • Consent to application corpus 5 (kusto 5)
field:"OperationName" kind:eq value:"Consent to application"
Property.displayNameeq
  • ConsentAction.Permissions transforms: kusto_dynamic_path
  • ConsentContext.isAdminConsent transforms: kusto_dynamic_path
  • TargetId.ServicePrincipalNames transforms: kusto_dynamic_path
field:"Property.displayName" kind:eq
Property.keyeq
  • User-Agent transforms: kusto_dynamic_path
field:"Property.key" kind:eq value:"User-Agent"
TargetResourcesmatch
  • full_access_as_app transforms: term
field:"azure_ad::target_resources" kind:match value:"full_access_as_app"

Output Fields

These fields are emitted when the rule matches.

FieldSource
OAuthAppNameextend
ModifiedPropertiesextend
InitiatingAppNameextend
InitiatingAppServicePrincipalIdextend
InitiatingUserPrincipalNameextend
InitiatingAadUserIdextend
InitiatingIpAddressextend
GrantInitiatedByextend
Nameextend
UPNSuffixextend

GTI - Data Leak Alert Detected

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

Triggers an incident when a GTI Relevance System Alert of type data_leak is ingested. Data Leak alerts indicate that sensitive organisational data (credentials, PII, intellectual property, source code, databases, etc.) has been found exposed on the dark web, paste sites, or underground forums and matches your organisation profile. Each unique Alert ID is grouped into a single incident.

MITRE ATT&CK Coverage

Rule Body

id: d4e5f6a7-b8c9-0123-defa-234567890124
name: GTI - Data Leak Alert Detected
description: |
  Triggers an incident when a GTI Relevance System Alert of type data_leak is ingested.
  Data Leak alerts indicate that sensitive organisational data (credentials, PII, intellectual property,
  source code, databases, etc.) has been found exposed on the dark web, paste sites, or underground
  forums and matches your organisation profile.
  Each unique Alert ID is grouped into a single incident.
severity: High
status: Available
requiredDataConnectors:
  - connectorId: GoogleThreatIntelligenceRelevanceSystemAlertsAPI
    dataTypes:
      - RelevanceSystemAlerts_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Exfiltration
  - Impact
  - CredentialAccess
  - Collection
relevantTechniques:
  - T1567
  - T1530
  - T1552
  - T1485
query: |
  GTIRelevanceSystemAlerts
  | where DetailType == "data_leak"
  | project
      TimeGenerated,
      AlertId,
      AlertName          = DisplayName,
      AlertState         = State,
      LeakSeverity       = DataLeakSeverity,
      DiscoveryDocuments = DataLeakDiscoveryDocumentIds,
      PriorityLevel,
      PriorityReasoning,
      SeverityLevel,
      SeverityConfidence,
      SeverityReasoning,
      RelevanceLevel,
      RelevanceConfidence,
      RelevanceReasoning,
      FindingCount,
      AiSummary,
      CommonThemes       = RelevanceEvidenceCommonThemes,
      DistinctThemes     = RelevanceEvidenceDistinctThemes,
      AuditCreateTime,
      AuditUpdateTime
alertDetailsOverride:
  alertDisplayNameFormat: 'GTI Data Leak: {{AlertName}}'
  alertDescriptionFormat: |
    Leak Severity: {{LeakSeverity}} | Priority: {{PriorityLevel}}

    Summary: {{AiSummary}}
customDetails:
  AlertId: AlertId
  AlertState: AlertState
  LeakSeverity: LeakSeverity
  PriorityLevel: PriorityLevel
  SeverityLevel: SeverityLevel
  RelevanceLevel: RelevanceLevel
  FindingCount: FindingCount
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: P5D
    matchingMethod: Selected
    groupByCustomDetails:
      - AlertId
eventGroupingSettings:
  aggregationKind: AlertPerResult
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

GTIRelevanceSystemAlerts

Stage 2: where

| where DetailType == "data_leak"

Stage 3: project

| project
    TimeGenerated,
    AlertId,
    AlertName          = DisplayName,
    AlertState         = State,
    LeakSeverity       = DataLeakSeverity,
    DiscoveryDocuments = DataLeakDiscoveryDocumentIds,
    PriorityLevel,
    PriorityReasoning,
    SeverityLevel,
    SeverityConfidence,
    SeverityReasoning,
    RelevanceLevel,
    RelevanceConfidence,
    RelevanceReasoning,
    FindingCount,
    AiSummary,
    CommonThemes       = RelevanceEvidenceCommonThemes,
    DistinctThemes     = RelevanceEvidenceDistinctThemes,
    AuditCreateTime,
    AuditUpdateTime

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AiSummaryproject
AlertIdproject
AlertNameproject
AlertStateproject
AuditCreateTimeproject
AuditUpdateTimeproject
CommonThemesproject
DiscoveryDocumentsproject
DistinctThemesproject
FindingCountproject
LeakSeverityproject
PriorityLevelproject
PriorityReasoningproject
RelevanceConfidenceproject
RelevanceLevelproject
RelevanceReasoningproject
SeverityConfidenceproject
SeverityLevelproject
SeverityReasoningproject
TimeGeneratedproject

Hunt for devices doing first RDP session

#
Author
Robbe Van den Daele
Source
github.com/HybridBrothers/Hunting-Queries-Detection-Rules

This hunting query can help you find devices doing an RDP connection for the first time in 30 days. While this can be normal behavior, it might be interesting to look at why this device is suddenly doing an RDP connection.

MITRE ATT&CK Coverage

References

Telemetry Coverage

Rule Body

let historic_rdp_devices = toscalar(
    DeviceNetworkEvents
    | where Timestamp > ago (30d)
    | where ActionType == "ConnectionSuccess"
    | where RemotePort == 3389
    | summarize make_set(DeviceId)
);
DeviceNetworkEvents
| where Timestamp > ago(1h)
| where ActionType == "ConnectionSuccess"
| where RemotePort == 3389
| where DeviceId !in (historic_rdp_devices)

let historic_rdp_devices = toscalar(
    DeviceNetworkEvents
    | where TimeGenerated > ago (30d)
    | where ActionType == "ConnectionSuccess"
    | where RemotePort == 3389
    | summarize make_set(DeviceId)
);
DeviceNetworkEvents
| where TimeGenerated > ago(1h)
| where ActionType == "ConnectionSuccess"
| where RemotePort == 3389
| where DeviceId !in (historic_rdp_devices)

Stages and Predicates

Let Binding: historic_rdp_devices used in Stage 5

let historic_rdp_devices = toscalar(
    DeviceNetworkEvents
    | where Timestamp > ago (30d)
    | where ActionType == "ConnectionSuccess"
    | where RemotePort == 3389
    | summarize make_set(DeviceId)
);

Stage 1: source

DeviceNetworkEvents

Stage 2: where

| where Timestamp > ago(1h)

Stage 3: where

| where ActionType == "ConnectionSuccess"

Stage 4: where

| where RemotePort == 3389

Stage 5: where

| where DeviceId !in (historic_rdp_devices)

Indicators

These rows show field, operator, and value matches.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
DeviceIdeqhistoric_rdp_devicesexcludes:DeviceId field:"DeviceId" value:"historic_rdp_devices"

Lumen TI IPAddress in IdentityLogonEvents

#
Severity
medium
Time window
14d
Group by
$left.TI_ipEntity, $right.ILE_ipEntity, ILE_ipEntity, Id
Source
github.com/Azure/Azure-Sentinel

This query maps Lumen IP indicators of compromise (IOCs) from threat intelligence (TI), by searching for matches in Azure AD sign-in logs.

MITRE ATT&CK Coverage

TacticTechniques
Command & Control

Telemetry Coverage

Rule Body

id: a7cd18cd-1503-47ec-8dca-65d750540637
name: Lumen TI IPAddress in IdentityLogonEvents
displayName: Lumen TI IPAddress in IdentityLogonEvents
description: |
  This query maps Lumen IP indicators of compromise (IOCs) from threat intelligence (TI), by searching for matches in Azure AD sign-in logs.
severity: Medium
requiredDataConnectors:
  - connectorId: LumenThreatFeedConnector
    dataTypes:
      - ThreatIntelligenceIndicator
  - connectorId: ThreatIntelligenceUploadIndicatorsAPI
    dataTypes:
      - ThreatIntelligenceIndicator
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AADNonInteractiveUserSignInLogs
      - AADServicePrincipalSignInLogs
      - AADManagedIdentitySignInLogs
      - ADFSSignInLogs
queryFrequency: 4h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
suppressionDuration: 5h
suppressionEnabled: true
tactics:
  - CommandAndControl
relevantTechniques:
  - T1071
query: |
  let dt_lookBack = 1d;  // Data lookback for AAD sign-in logs
  let ioc_lookBack = 14d; // TI lookback
  let IP_Indicators =
      ThreatIntelIndicators
      | where TimeGenerated >= ago(ioc_lookBack)
      | where IsActive == true and ValidUntil > now()
      | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id
      | where SourceSystem == 'Lumen'
      | where ObservableKey == 'ipv4-addr:value'
      | extend TI_ipEntity = ObservableValue
      | where ipv4_is_private(TI_ipEntity) == false and TI_ipEntity !startswith 'fe80' and TI_ipEntity !startswith '::' and TI_ipEntity !startswith '127.';
  let SignInUnion =
      union isfuzzy=true
      (
          AADNonInteractiveUserSignInLogs
          | where TimeGenerated >= ago(dt_lookBack)
          | extend
              ILE_ipEntity = IPAddress,
              AccountDisplayName = coalesce(UserDisplayName, UserPrincipalName),
              Application = coalesce(column_ifexists('AppDisplayName',''), column_ifexists('ResourceDisplayName','')),
              LogonType = column_ifexists('ClientAppUsed',''),
              IdentityLogonEvents_TimeGenerated = TimeGenerated
          | project IdentityLogonEvents_TimeGenerated, ILE_ipEntity, AccountDisplayName, Application, LogonType, IPAddress, Type
      ),
      (
          AADServicePrincipalSignInLogs
          | where TimeGenerated >= ago(dt_lookBack)
          | extend
              ILE_ipEntity = IPAddress,
              AccountDisplayName = coalesce(ServicePrincipalName, tostring(ServicePrincipalId)),
              Application = coalesce(column_ifexists('AppDisplayName',''), column_ifexists('ResourceDisplayName','')),
              LogonType = "ServicePrincipal",
              IdentityLogonEvents_TimeGenerated = TimeGenerated
          | project IdentityLogonEvents_TimeGenerated, ILE_ipEntity, AccountDisplayName, Application, LogonType, IPAddress, Type
      ),
      (
          AADManagedIdentitySignInLogs
          | where TimeGenerated >= ago(dt_lookBack)
          | extend
              ILE_ipEntity = IPAddress,
              AccountDisplayName = coalesce(ServicePrincipalName, tostring(ServicePrincipalId)),
              Application = ResourceDisplayName,
              LogonType = "ManagedIdentity",
              IdentityLogonEvents_TimeGenerated = TimeGenerated
          | project IdentityLogonEvents_TimeGenerated, ILE_ipEntity, AccountDisplayName, Application, LogonType, IPAddress, Type
      ),
      (
          ADFSSignInLogs
          | where TimeGenerated >= ago(dt_lookBack)
          | extend
              ILE_ipEntity = IPAddress,
              AccountDisplayName = coalesce(UserDisplayName, UserPrincipalName),
              Application = coalesce(AppDisplayName, ResourceDisplayName),
              LogonType = coalesce(Requirement, AuthenticationRequirement),
              IdentityLogonEvents_TimeGenerated = TimeGenerated
          | project IdentityLogonEvents_TimeGenerated, ILE_ipEntity, AccountDisplayName, Application, LogonType, IPAddress, Type
      );
  IP_Indicators
  | join kind=innerunique (SignInUnion) on $left.TI_ipEntity == $right.ILE_ipEntity
  | where IdentityLogonEvents_TimeGenerated < ValidUntil
  | summarize arg_max(IdentityLogonEvents_TimeGenerated, *), StartTime = min(IdentityLogonEvents_TimeGenerated), EndTime = max(IdentityLogonEvents_TimeGenerated) by Id, ILE_ipEntity
  | project timestamp = EndTime, StartTime, EndTime, AccountDisplayName, IPAddress, Application, LogonType, Id, Tags, ValidUntil, Confidence, TI_ipEntity, ILE_ipEntity, Type
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: ILE_ipEntity
version: 1.0.2
kind: Scheduled

Stages and Predicates

Parameters

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

let IP_Indicators is inlined into the numbered stages below.

Let Binding: SignInUnion used in Stage 9

let SignInUnion = union isfuzzy=true
    (
        AADNonInteractiveUserSignInLogs
        | where TimeGenerated >= ago(dt_lookBack)
        | extend
            ILE_ipEntity = IPAddress,
            AccountDisplayName = coalesce(UserDisplayName, UserPrincipalName),
            Application = coalesce(column_ifexists('AppDisplayName',''), column_ifexists('ResourceDisplayName','')),
            LogonType = column_ifexists('ClientAppUsed',''),
            IdentityLogonEvents_TimeGenerated = TimeGenerated
        | project IdentityLogonEvents_TimeGenerated, ILE_ipEntity, AccountDisplayName, Application, LogonType, IPAddress, Type
    ),
    (
        AADServicePrincipalSignInLogs
        | where TimeGenerated >= ago(dt_lookBack)
        | extend
            ILE_ipEntity = IPAddress,
            AccountDisplayName = coalesce(ServicePrincipalName, tostring(ServicePrincipalId)),
            Application = coalesce(column_ifexists('AppDisplayName',''), column_ifexists('ResourceDisplayName','')),
            LogonType = "ServicePrincipal",
            IdentityLogonEvents_TimeGenerated = TimeGenerated
        | project IdentityLogonEvents_TimeGenerated, ILE_ipEntity, AccountDisplayName, Application, LogonType, IPAddress, Type
    ),
    (
        AADManagedIdentitySignInLogs
        | where TimeGenerated >= ago(dt_lookBack)
        | extend
            ILE_ipEntity = IPAddress,
            AccountDisplayName = coalesce(ServicePrincipalName, tostring(ServicePrincipalId)),
            Application = ResourceDisplayName,
            LogonType = "ManagedIdentity",
            IdentityLogonEvents_TimeGenerated = TimeGenerated
        | project IdentityLogonEvents_TimeGenerated, ILE_ipEntity, AccountDisplayName, Application, LogonType, IPAddress, Type
    ),
    (
        ADFSSignInLogs
        | where TimeGenerated >= ago(dt_lookBack)
        | extend
            ILE_ipEntity = IPAddress,
            AccountDisplayName = coalesce(UserDisplayName, UserPrincipalName),
            Application = coalesce(AppDisplayName, ResourceDisplayName),
            LogonType = coalesce(Requirement, AuthenticationRequirement),
            IdentityLogonEvents_TimeGenerated = TimeGenerated
        | project IdentityLogonEvents_TimeGenerated, ILE_ipEntity, AccountDisplayName, Application, LogonType, IPAddress, Type
    );

Stages 1 to 8 define let IP_Indicators as the rule's main pipeline source. Stages 9 to 12 use it.

Stage 1: source

ThreatIntelIndicators

Stage 2: where

| where TimeGenerated >= ago(ioc_lookBack)

Stage 3: where

| where IsActive == true and ValidUntil > now()

Stage 4: summarize

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

Stage 5: where

| where SourceSystem == 'Lumen'

Stage 6: where

| where ObservableKey == 'ipv4-addr:value'

Stage 7: extend

| extend TI_ipEntity = ObservableValue

Stage 8: where

| where ipv4_is_private(TI_ipEntity) == false and TI_ipEntity !startswith 'fe80' and TI_ipEntity !startswith '::' and TI_ipEntity !startswith '127.'

Stage 9: join

join kind=innerunique (SignInUnion) on $left.TI_ipEntity, $right.ILE_ipEntity

Stage 10: where

where IdentityLogonEvents_TimeGenerated < ValidUntil

Stage 11: summarize

summarize EndTime, StartTime by Id, ILE_ipEntity

Stage 12: project

project AccountDisplayName, Application, Confidence, EndTime, ILE_ipEntity, IPAddress, Id, LogonType, StartTime, TI_ipEntity, Tags, Type, ValidUntil, timestamp

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
IsActiveeq
  • true corpus 12 (kusto 12)
field:"IsActive" kind:eq value:"true"
ObservableKeyeq
  • ipv4-addr:value corpus 10 (kusto 10)
field:"ObservableKey" kind:eq value:"ipv4-addr:value"
SourceSystemeq
  • Lumen corpus 8 (kusto 8)
field:"SourceSystem" kind:eq value:"Lumen"

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
TI_ipEntitycidr_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:TI_ipEntity
TI_ipEntitystarts_with127.excludes:TI_ipEntity field:"TI_ipEntity" value:"127."
TI_ipEntitystarts_with::excludes:TI_ipEntity field:"TI_ipEntity" value:"::"
TI_ipEntitystarts_withfe80excludes:TI_ipEntity field:"TI_ipEntity" value:"fe80"

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountDisplayNameproject
Applicationproject
Confidenceproject
EndTimeproject
ILE_ipEntityproject
IPAddressproject
Idproject
LogonTypeproject
StartTimeproject
TI_ipEntityproject
Tagsproject
Typeproject
ValidUntilproject
timestampproject

McAfee ePO - Error sending alert

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

Detects when error sending alert occurs.

MITRE ATT&CK Coverage

TacticTechniques
Stealth
Defense Impairment

Telemetry Coverage

Rule Body

id: 1e3bcd0f-10b2-4fbd-854f-1c6f33acc36a
name: McAfee ePO - Error sending alert
description: |
  'Detects when error sending alert occurs.'
severity: Medium
requiredDataConnectors:
  - connectorId: SyslogAma
    datatypes:
      - Syslog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - DefenseEvasion
relevantTechniques:
  - T1562
  - T1070
query: |
  McAfeeEPOEvent
  | where EventId == '1062'
  | extend IPCustomEntity = DvcIpAddr
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPCustomEntity
version: 1.0.2
kind: Scheduled

Stages and Predicates

Stage 1: source

McAfeeEPOEvent

Stage 2: where

| where EventId == '1062'

Stage 3: extend

| extend IPCustomEntity = DvcIpAddr

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
IPCustomEntityextend

Mimecast Secure Email Gateway - Internal Email Protect

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

'Detects threats from internal email threat protection.'

MITRE ATT&CK Coverage

TacticTechniques
Persistence
Lateral Movement

Rule Body

id: d3bd7640-3600-49f9-8d10-6fe312e68b4f
name: Mimecast Secure Email Gateway - Internal Email Protect
description: |
  'Detects threats from internal email threat protection.'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: MimecastSEGAPI
    dataTypes:
      - MimecastCG
enabled: true
queryFrequency: 15m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
tactics:
- LateralMovement
- Persistence
- Exfiltration
relevantTechniques:
- T1534
- T1546
query: |
  MimecastCG
  | where Type == "email_iep"
  | extend  SenderEnvelope = ['Sender Envelope']  , MessageId = ['Message ID']
suppressionDuration: 5h
suppressionEnabled: false
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: P7D
    matchingMethod: AllEntities
eventGroupingSettings:
  aggregationKind: AlertPerResult
entityMappings:
- entityType: MailMessage
  fieldMappings:
  - identifier: Sender
    columnName: SenderEnvelope
  - identifier: Recipient
    columnName: Recipients
  - identifier: InternetMessageId
    columnName: MessageId
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

MimecastCG

Stage 2: where

| where Type == "email_iep"

Stage 3: extend

| extend  SenderEnvelope = ['Sender Envelope']  , MessageId = ['Message ID']

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Typeeq
  • email_iep
field:"Type" kind:eq value:"email_iep"

Output Fields

These fields are emitted when the rule matches.

FieldSource
MessageIdextend
SenderEnvelopeextend

Mimecast Secure Email Gateway - Internal Email Protect

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

Detects threats from internal email threat protection

MITRE ATT&CK Coverage

TacticTechniques
Persistence
Lateral Movement

Rule Body

id: 5b66d176-e344-4abf-b915-e5f09a6430ef
name: Mimecast Secure Email Gateway - Internal Email Protect
description: Detects threats from internal email threat protection
severity: High
requiredDataConnectors:
  - connectorId: MimecastSIEMAPI
    dataTypes:
      - MimecastSIEM_CL
enabled: true
query: MimecastSIEM_CL| where mimecastEventId_s=="mail_ttp_iep"
queryFrequency: 5m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
suppressionDuration: 5h
suppressionEnabled: false
tactics:
- LateralMovement
- Persistence
- Exfiltration
relevantTechniques:
- T1534
- T1546
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: 1d
    matchingMethod: AllEntities
eventGroupingSettings:
  aggregationKind: SingleAlert
customDetails:
  Subject: Subject_s
  Route: Route_s
  UrlCategory: UrlCategory_s
  ScanResultInfo: ScanResultInfo_s
entityMappings:
- entityType: MailMessage
  fieldMappings:
  - identifier: Sender
    columnName: Sender_s
  - identifier: Recipient
    columnName: Recipient_s
  - identifier: InternetMessageId
    columnName: MsgId_s
- entityType: URL
  fieldMappings:
  - identifier: Url
    columnName: URL_s
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

MimecastSIEM_CL

Stage 2: where

| where mimecastEventId_s=="mail_ttp_iep"

Indicators

These rows show field, operator, and value matches.

PROD (TM003.1) - OBJECT - Moved or Recovered a Tier Level Account

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

Moving or Recovering of a Tier Model object.

MITRE ATT&CK Coverage

Telemetry Coverage

Rule Body

id: 1d858e12-777d-4156-b0af-b5ef410739e6
name: 'PROD (TM003.1) - OBJECT - Moved or Recovered a Tier Level Account'
description: |
  Moving or Recovering of a Tier Model object.
severity: Medium
requiredDataConnectors:
  - connectorId: SecurityEvents
    dataTypes:
      - SecurityEvent
  - connectorId: WindowsSecurityEvents
    dataTypes:
      - SecurityEvent
queryFrequency: 30m
queryPeriod: 30m
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - Persistence
relevantTechniques:
  - T1078.002
query: |
  SecurityEvent 
  | where (EventID == 5138
      or EventID == 5139)
      and (EventData matches regex @"(?i)OU=Domain Controllers"
          or EventData matches regex @"(?i)OU=Tier 0 Accounts" 
          or EventData matches regex @"(?i)OU=Tier 1 Accounts" 
          or EventData matches regex @"(?i)OU=Tier 2 Accounts"
          or EventData matches regex @"(?i)OU=Tier 0 Service Accounts" 
          or EventData matches regex @"(?i)OU=Tier 1 Service Accounts" 
          or EventData matches regex @"(?i)OU=Tier 2 Service Accounts"
          or EventData matches regex @"(?i)OU=Tier 0 Groups"
          or EventData matches regex @"(?i)OU=Tier 1 Groups"
          or EventData matches regex @"(?i)OU=Tier 2 Groups"
          or EventData matches regex @"(?i)OU=Tier 0 PAW"
          or EventData matches regex @"(?i)OU=Tier 1 PAW"
          or EventData matches regex @"(?i)OU=Tier 2 PAW"
          or EventData matches regex @"(?i)OU=Tier 0 Member Servers"
          or EventData matches regex @"(?i)OU=Tier 1 Member Servers")
  | extend OldObject_e = extract("<Data Name=\"OldObjectDN\">([^<]+)</Data>", 1, EventData)
  | extend OldObject = extract("(?i)CN=([^,]+)", 1, OldObject_e)
  | extend OldObjectTier = extract("(?i)OU=(Tier [^ ]+)", 1, OldObject_e)
  | extend OldObjectTier = iff(isempty(OldObjectTier), "Outside Tier Model", OldObjectTier)
  | extend NewObject_e = extract("<Data Name=\"NewObjectDN\">([^<]+)</Data>", 1, EventData)
  | extend NewObject = extract("(?i)CN=([^,]+)", 1, NewObject_e)
  | extend NewObjectTier = extract("(?i)OU=(Tier [^ ]+)", 1, NewObject_e)
  | extend SubjectUserName = extract("<Data Name=\"SubjectUserName\">([^<]+)</Data>", 1, EventData)
  | extend SubjectDomainName = extract("<Data Name=\"SubjectDomainName\">([^<]+)</Data>", 1, EventData)
  | extend Account = strcat(SubjectDomainName, "\\", SubjectUserName)
  | extend NewObjectTier = iff(isempty(NewObjectTier), 
      iff(NewObject_e matches regex @"(?i)OU=Domain Controllers", "Tier 0", "Outside Tier Model"), 
      NewObjectTier)
  | extend ObjectClass = extract("<Data Name=\"ObjectClass\">([^<]+)</Data>", 1, EventData)
  | extend ActivityType = extract("([a-zA-Z]+)\\.$", 1, Activity)
  | extend Domain = extract("([^.]+\\.[^.]+)$", 1, Computer)
  | where OldObject != NewObjectTier
  | project TimeGenerated
      , ObjectName = OldObject
      , NewObjectTier
      , NewObjectOU = NewObject_e
      , ObjectClass
      , PreviousTier = OldObjectTier
      , PreviousOU = OldObject_e
      , ActivityType
      , Account
      , Domain
      , Computer
      , Channel
      , EventID
      , EventData
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: ObjectName
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: Computer
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: Account
customDetails:
  ObjectName: ObjectName
  NewObjectTier: NewObjectTier
  NewObjectOU: NewObjectOU
  ObjectClass: ObjectClass
  PreviousTier: PreviousTier
  PreviousOU: PreviousOU
  ActivityType: ActivityType
  Account: Account
  Domain: Domain
  Computer: Computer
  Channel: Channel
  EventID: EventID
  EventData: EventData
eventGroupingSettings:
  aggregationKind: AlertPerResult
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: false
    reopenClosedIncident: false
    lookbackDuration: PT5H
    matchingMethod: AllEntities
alertDetailsOverride:
  alertDisplayNameFormat: '(TM003.1) A {{NewObjectTier}} {{ObjectClass}} was {{ActivityType}}'
  alertDescriptionFormat: '{{ObjectName}} was moved from {{PreviousOU}} to the following OU {{NewObjectOU}}.'
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

SecurityEvent

Stage 2: where

| where (EventID == 5138
    or EventID == 5139)
    and (EventData matches regex @"(?i)OU=Domain Controllers"
        or EventData matches regex @"(?i)OU=Tier 0 Accounts" 
        or EventData matches regex @"(?i)OU=Tier 1 Accounts" 
        or EventData matches regex @"(?i)OU=Tier 2 Accounts"
        or EventData matches regex @"(?i)OU=Tier 0 Service Accounts" 
        or EventData matches regex @"(?i)OU=Tier 1 Service Accounts" 
        or EventData matches regex @"(?i)OU=Tier 2 Service Accounts"
        or EventData matches regex @"(?i)OU=Tier 0 Groups"
        or EventData matches regex @"(?i)OU=Tier 1 Groups"
        or EventData matches regex @"(?i)OU=Tier 2 Groups"
        or EventData matches regex @"(?i)OU=Tier 0 PAW"
        or EventData matches regex @"(?i)OU=Tier 1 PAW"
        or EventData matches regex @"(?i)OU=Tier 2 PAW"
        or EventData matches regex @"(?i)OU=Tier 0 Member Servers"
        or EventData matches regex @"(?i)OU=Tier 1 Member Servers")

Stage 3: extend (14 consecutive steps)

| extend OldObject_e = extract("<Data Name=\"OldObjectDN\">([^<]+)</Data>", 1, EventData)
| extend OldObject = extract("(?i)CN=([^,]+)", 1, OldObject_e)
| extend OldObjectTier = extract("(?i)OU=(Tier [^ ]+)", 1, OldObject_e)
| extend OldObjectTier = iff(isempty(OldObjectTier), "Outside Tier Model", OldObjectTier)
| extend NewObject_e = extract("<Data Name=\"NewObjectDN\">([^<]+)</Data>", 1, EventData)
| extend NewObject = extract("(?i)CN=([^,]+)", 1, NewObject_e)
| extend NewObjectTier = extract("(?i)OU=(Tier [^ ]+)", 1, NewObject_e)
| extend SubjectUserName = extract("<Data Name=\"SubjectUserName\">([^<]+)</Data>", 1, EventData)
| extend SubjectDomainName = extract("<Data Name=\"SubjectDomainName\">([^<]+)</Data>", 1, EventData)
| extend Account = strcat(SubjectDomainName, "\\", SubjectUserName)
| extend NewObjectTier = iff(isempty(NewObjectTier), 
    iff(NewObject_e matches regex @"(?i)OU=Domain Controllers", "Tier 0", "Outside Tier Model"), 
    NewObjectTier)
| extend ObjectClass = extract("<Data Name=\"ObjectClass\">([^<]+)</Data>", 1, EventData)
| extend ActivityType = extract("([a-zA-Z]+)\\.$", 1, Activity)
| extend Domain = extract("([^.]+\\.[^.]+)$", 1, Computer)

Stage 4: where

| where OldObject != NewObjectTier

Stage 5: project

| project TimeGenerated
    , ObjectName = OldObject
    , NewObjectTier
    , NewObjectOU = NewObject_e
    , ObjectClass
    , PreviousTier = OldObjectTier
    , PreviousOU = OldObject_e
    , ActivityType
    , Account
    , Domain
    , Computer
    , Channel
    , EventID
    , EventData

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
EventDataregex_match
  • (?i)OU=Domain Controllers corpus 8 (kusto 8)
  • (?i)OU=Tier 0 Accounts corpus 4 (kusto 4)
  • (?i)OU=Tier 0 Groups corpus 3 (kusto 3)
  • (?i)OU=Tier 0 Member Servers corpus 8 (kusto 8)
  • (?i)OU=Tier 0 PAW corpus 6 (kusto 6)
  • (?i)OU=Tier 0 Service Accounts corpus 4 (kusto 4)
  • (?i)OU=Tier 1 Accounts corpus 4 (kusto 4)
  • (?i)OU=Tier 1 Groups corpus 3 (kusto 3)
  • (?i)OU=Tier 1 Member Servers corpus 8 (kusto 8)
  • (?i)OU=Tier 1 PAW corpus 6 (kusto 6)
  • (?i)OU=Tier 1 Service Accounts corpus 4 (kusto 4)
  • (?i)OU=Tier 2 Accounts corpus 4 (kusto 4)
  • (?i)OU=Tier 2 Groups corpus 3 (kusto 3)
  • (?i)OU=Tier 2 PAW corpus 6 (kusto 6)
  • (?i)OU=Tier 2 Service Accounts corpus 4 (kusto 4)
field:"EventData" kind:regex_match
EventIDeq
  • 5138
  • 5139
field:"EventID" kind:eq

Output Fields

These fields are emitted when the rule matches.

FieldSource
Accountproject
ActivityTypeproject
Channelproject
Computerproject
Domainproject
EventDataproject
EventIDproject
NewObjectOUproject
NewObjectTierproject
ObjectClassproject
ObjectNameproject
PreviousOUproject
PreviousTierproject
TimeGeneratedproject

Snowflake - Multiple login failures by user

#
Status
available
Severity
high
Time window
5m
Group by
TargetUsername
Source
github.com/Azure/Azure-Sentinel

'Detects multiple login failures by user.'

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Rule Body

id: e05cc333-d499-430f-907c-7f28a9e4d1b5
name: Snowflake - Multiple login failures by user
description: |
  'Detects multiple login failures by user.'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: Snowflake
    dataTypes:
      - Snowflake
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1078
query: |
  let threshold = 10;
  Snowflake
  | where EventType =~ 'LOGIN'
  | where LoginIsSuccess =~ 'No'
  | summarize count() by TargetUsername, bin(TimeGenerated, 5m)
  | where count_ > threshold
  | extend AccountCustomEntity = TargetUsername
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountCustomEntity
version: 1.0.2
kind: Scheduled

Stages and Predicates

Parameters

let threshold = 10;

Stage 1: source

Snowflake

Stage 2: where

| where EventType =~ 'LOGIN'

Stage 3: where

| where LoginIsSuccess =~ 'No'

Stage 4: summarize

| summarize count() by TargetUsername, bin(TimeGenerated, 5m)

Stage 5: where

| where count_ > threshold

Stage 6: extend

| extend AccountCustomEntity = TargetUsername

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
TargetUsernamesummarize
AccountCustomEntityextend

TI Map IP Entity to Duo Security

#
Severity
medium
Time window
14d
Group by
$left.TI_ipEntity, $right.AccessDvcIpAddr, Id, ObservableValue
Source
github.com/Azure/Azure-Sentinel

'This query maps any IP indicators of compromise (IOCs) from threat intelligence (TI), by searching for matches in DuoSecurity.'

MITRE ATT&CK Coverage

TacticTechniques
Command & Control

Rule Body

id: 4988c238-a118-442c-80bd-6c689a1b2e97
name: TI Map IP Entity to Duo Security
description: |
  'This query maps any IP indicators of compromise (IOCs) from threat intelligence (TI), by searching for matches in DuoSecurity.'
severity: Medium
requiredDataConnectors:
  - connectorId: ThreatIntelligence
    dataTypes:
      - ThreatIntelIndicators
  - connectorId: ThreatIntelligenceTaxii
    dataTypes:
      - ThreatIntelIndicators
  - connectorId: CiscoDuoSecurity
    dataTypes:
      - CiscoDuo
  - connectorId: MicrosoftDefenderThreatIntelligence
    dataTypes:
      - ThreatIntelIndicators
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
relevantTechniques:
  - T1071
query: |
  let dt_lookBack = 1h;
  let ioc_lookBack = 14d;
  ThreatIntelIndicators
  //extract key part of kv pair
       | extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))
       | where IndicatorType in ("ipv4-addr", "ipv6-addr", "network-traffic")
       | extend NetworkSourceIP = toupper(ObservableValue)
       | extend TrafficLightProtocolLevel = tostring(parse_json(AdditionalFields).TLPLevel)
  // Picking up only IOC's that contain the entities we want
  | where TimeGenerated >= ago(ioc_lookBack)
  // As there is potentially more than 1 indicator type for matching IP, taking NetworkIP first, then others if that is empty.
  // Taking the first non-empty value based on potential IOC match availability
  | extend TI_ipEntity = iff(isnotempty(NetworkSourceIP), NetworkSourceIP, NetworkSourceIP)
  | extend TI_ipEntity = iff(isempty(TI_ipEntity) and isnotempty(NetworkSourceIP), NetworkSourceIP, TI_ipEntity)
  | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id, ObservableValue
  | where IsActive and (ValidUntil > now() or isempty(ValidUntil))
   | join (
       CiscoDuo
       | where TimeGenerated >= ago(dt_lookBack)
       | where isnotempty(AccessDvcIpAddr)
       | extend Duo_TimeGenerated = IsoTimestamp
   )
   on $left.TI_ipEntity == $right.AccessDvcIpAddr
   | where TimeGenerated >= ago(ioc_lookBack)
   | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id, ObservableValue
   | where IsActive and (ValidUntil > now() or isempty(ValidUntil))
   | extend Description = tostring(parse_json(Data).description)
   | extend ActivityGroupNames = extract(@"ActivityGroup:(\S+)", 1, tostring(parse_json(Data).labels))
   | project LatestIndicatorTime, Description, ActivityGroupNames, Id, ValidUntil, Confidence, Duo_TimeGenerated,
   TI_ipEntity, DstUserName, AuthFactor, EventResult, SrcAppName, EventType, TransactionId, SrcUserId, AccessDvcIpAddr, SrcGeoCity, AccessDvcLocationState, SrcGeoCountry
   | extend timestamp = Duo_TimeGenerated, Name = tostring(split(DstUserName, '@', 0)[0]), UPNSuffix = tostring(split(DstUserName, '@', 1)[0])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: DstUserName
      - identifier: Name
        columnName: Name
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: AccessDvcIpAddr
version: 1.0.10
kind: Scheduled

Stages and Predicates

Parameters

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

Stage 1: source

ThreatIntelIndicators

Stage 2: extend

| extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))

Stage 3: where

| where IndicatorType in ("ipv4-addr", "ipv6-addr", "network-traffic")

Stage 4: extend

| extend NetworkSourceIP = toupper(ObservableValue)

Stage 5: extend

| extend TrafficLightProtocolLevel = tostring(parse_json(AdditionalFields).TLPLevel)

Stage 6: where

| where TimeGenerated >= ago(ioc_lookBack)

Stage 7: extend

| extend TI_ipEntity = iff(isnotempty(NetworkSourceIP), NetworkSourceIP, NetworkSourceIP)
TI_ipEntity =
ifisnotempty(NetworkSourceIP)NetworkSourceIP
elseNetworkSourceIP

Stage 8: extend

| extend TI_ipEntity = iff(isempty(TI_ipEntity) and isnotempty(NetworkSourceIP), NetworkSourceIP, TI_ipEntity)
TI_ipEntity =
ifisempty(TI_ipEntity) and isnotempty(NetworkSourceIP)NetworkSourceIP
elseTI_ipEntity

Stage 9: summarize

| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id, ObservableValue

Stage 10: where

| where IsActive and (ValidUntil > now() or isempty(ValidUntil))

Stage 11: join

| join (
     CiscoDuo
     | where TimeGenerated >= ago(dt_lookBack)
     | where isnotempty(AccessDvcIpAddr)
     | extend Duo_TimeGenerated = IsoTimestamp
 )
 on $left.TI_ipEntity == $right.AccessDvcIpAddr

Stage 12: where

where TimeGenerated >= ago(1209600s)

Stage 13: summarize

summarize LatestIndicatorTime by Id, ObservableValue

Stage 14: where

where (isempty(ValidUntil) or ValidUntil > now()) and IsActive == true

Stage 15: extend

extend Description

Stage 16: extend

extend ActivityGroupNames

Stage 17: project

project AccessDvcIpAddr, AccessDvcLocationState, ActivityGroupNames, AuthFactor, Confidence, Description, DstUserName, Duo_TimeGenerated, EventResult, EventType, Id, LatestIndicatorTime, SrcAppName, SrcGeoCity, SrcGeoCountry, SrcUserId, TI_ipEntity, TransactionId, ValidUntil

Stage 18: extend

extend Name, UPNSuffix, timestamp

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
AccessDvcIpAddris_not_null
  • (no value, null check)
field:"AccessDvcIpAddr" kind:is_not_null
IndicatorTypein
  • ipv4-addr
  • ipv6-addr
  • network-traffic
field:"IndicatorType" kind:in
IsActiveeq
  • true transforms: boolean
field:"IsActive" kind:eq value:"true"
ValidUntilis_null
  • (no value, null check)
field:"ValidUntil" kind:is_null

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccessDvcIpAddrproject
AccessDvcLocationStateproject
ActivityGroupNamesproject
AuthFactorproject
Confidenceproject
Descriptionproject
DstUserNameproject
Duo_TimeGeneratedproject
EventResultproject
EventTypeproject
Idproject
LatestIndicatorTimeproject
SrcAppNameproject
SrcGeoCityproject
SrcGeoCountryproject
SrcUserIdproject
TI_ipEntityproject
TransactionIdproject
ValidUntilproject
Nameextend
UPNSuffixextend
timestampextend

Unusual identity creation using exchange powershell

#
Severity
high
Time window
12h
Author
Microsoft Security Research
Source
github.com/Azure/Azure-Sentinel

The query below identifies creation of unusual identity by the Europium actor to mimic Microsoft Exchange Health Manager Service account using Exchange PowerShell commands Reference: https://www.microsoft.com/security/blog/2022/09/08/microsoft-investigates-iranian-attacks-against-the-albanian-government/

MITRE ATT&CK Coverage

TacticTechniques
Persistence

Telemetry Coverage

Rule Body

id: 0a3f4f4f-46ad-4562-acd6-f17730a5aef4
name: Unusual identity creation using exchange powershell
description: | 
  ' The query below identifies creation of unusual identity by the Europium actor to mimic Microsoft Exchange Health Manager Service account using Exchange PowerShell commands
    Reference: https://www.microsoft.com/security/blog/2022/09/08/microsoft-investigates-iranian-attacks-against-the-albanian-government/'
severity: High 
requiredDataConnectors:
  - connectorId: SecurityEvents
    dataTypes:
      - SecurityEvent
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - DeviceProcessEvents
queryFrequency: 12h 
queryPeriod: 12h 
triggerOperator: gt 
triggerThreshold: 0 
tactics: 
  - Persistence
relevantTechniques:
  - T1136
tags:
  - Europium
query: |
  (union isfuzzy=true
  (SecurityEvent
  | where EventID==4688
  | where CommandLine has_any ("New-Mailbox","Update-RoleGroupMember") and CommandLine has "HealthMailbox55x2yq"
  | project TimeGenerated, DeviceName = Computer, AccountName = SubjectUserName, AccountDomain = SubjectDomainName, ProcessName, ProcessNameFullPath = NewProcessName, EventID, Activity, CommandLine, EventSourceName, Type
  | extend InitiatingProcessAccount = strcat(AccountDomain, "\\", AccountName)
  ),
  (DeviceProcessEvents
  | where ProcessCommandLine has_any ("New-Mailbox","Update-RoleGroupMember") and ProcessCommandLine has "HealthMailbox55x2yq"
  | extend timestamp = TimeGenerated, AccountDomain = InitiatingProcessAccountDomain, AccountName = InitiatingProcessAccountName
  | extend InitiatingProcessAccount = strcat(AccountDomain, "\\", AccountName)
  )
  )
  | extend HostName = tostring(split(DeviceName, ".")[0]), DomainIndex = toint(indexof(DeviceName, '.'))
  | extend HostNameDomain = iff(DomainIndex != -1, substring(DeviceName, DomainIndex + 1), DeviceName)
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: InitiatingProcessAccount
      - identifier: Name
        columnName: AccountName
      - identifier: NTDomain
        columnName: AccountDomain
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: DeviceName
      - identifier: HostName
        columnName: HostName
      - identifier: NTDomain
        columnName: HostNameDomain
version: 1.1.1
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: Microsoft Security Research
    support:
        tier: Community
    categories:
        domains: [ "Security - Threat Protection", "Identity" ]

Stages and Predicates

Union isfuzzy=true (2 Sources)

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

Leg 1: SecurityEvent

SecurityEvent
| where EventID==4688
| where CommandLine has_any ("New-Mailbox","Update-RoleGroupMember") and CommandLine has "HealthMailbox55x2yq"
| project TimeGenerated, DeviceName = Computer, AccountName = SubjectUserName, AccountDomain = SubjectDomainName, ProcessName, ProcessNameFullPath = NewProcessName, EventID, Activity, CommandLine, EventSourceName, Type
| extend InitiatingProcessAccount = strcat(AccountDomain, "\\", AccountName)

Leg 2: DeviceProcessEvents

DeviceProcessEvents
| where ProcessCommandLine has_any ("New-Mailbox","Update-RoleGroupMember") and ProcessCommandLine has "HealthMailbox55x2yq"
| extend timestamp = TimeGenerated, AccountDomain = InitiatingProcessAccountDomain, AccountName = InitiatingProcessAccountName
| extend InitiatingProcessAccount = strcat(AccountDomain, "\\", AccountName)

Applied to the Combined Result

| extend HostName = tostring(split(DeviceName, ".")[0]), DomainIndex = toint(indexof(DeviceName, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(DeviceName, DomainIndex + 1), DeviceName)

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
CommandLinematch
  • HealthMailbox55x2yq transforms: term
  • New-Mailbox transforms: term
  • Update-RoleGroupMember transforms: term
field:"CommandLine" kind:match
EventIDeq
  • 4688 corpus 23 (kusto 23)
field:"EventID" kind:eq value:"4688"
ProcessCommandLinematch
  • HealthMailbox55x2yq transforms: term
  • New-Mailbox transforms: term
  • Update-RoleGroupMember transforms: term
field:"CommandLine" kind:match

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountDomainextend
AccountNameextend
Activityproject
CommandLineproject
DeviceNameproject
EventIDproject
EventSourceNameproject
ProcessNameproject
ProcessNameFullPathproject
TimeGeneratedproject
Typeproject
InitiatingProcessAccountextend
timestampextend
DomainIndexextend
HostNameextend
HostNameDomainextend

Back to all Kusto rules