Detection rules › Kusto

End-user consent stopped due to risk-based consent

Severity
medium
Time window
1d
Author
Microsoft Security Research
Source
github.com/Azure/Azure-Sentinel

Detects a user's consent to an OAuth application being blocked due to it being too risky. These events should be investigated to understand why the user attempted to consent to the applicaiton and what other applicaitons they may have consented to. Ref: https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-applications#end-user-stopped-due-to-risk-based-consent

MITRE ATT&CK coverage

Event coverage

Rule body kusto

id: 009b9bae-23dd-43c4-bcb9-11c4ba7c784a
name: End-user consent stopped due to risk-based consent
description: |
  'Detects a user's consent to an OAuth application being blocked due to it being too risky.
    These events should be investigated to understand why the user attempted to consent to the applicaiton and what other applicaitons they may have consented to.
    Ref: https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-applications#end-user-stopped-due-to-risk-based-consent'
severity: Medium
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AuditLogs
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Persistence
  - PrivilegeEscalation
relevantTechniques:
  - T1078.004
tags:
  - AADSecOpsGuide
query: |
  AuditLogs
    | where OperationName has "Consent to application"
    | where Result =~ "failure"
    | extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
    | extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
    | extend InitiatingIPAddress = tostring(InitiatedBy.user.ipAddress)
    | extend userAgent = iif(AdditionalDetails[0].key == "User-Agent", tostring(AdditionalDetails[0].value), tostring(AdditionalDetails[1].value))
    | where isnotempty(TargetResources)
    | extend TargetAppName = tostring(TargetResources[0].displayName)
    | extend TargetAppId = tostring(TargetResources[0].id)
    | mv-expand TargetResources[0].modifiedProperties
    | extend TargetResources_0_modifiedProperties = columnifexists("TargetResources_0_modifiedProperties", '')
    | where isnotempty(TargetResources_0_modifiedProperties)
    | where TargetResources_0_modifiedProperties.displayName =~ "MethodExecutionResult."
    | extend TargetPropertyDisplayName = tostring(TargetResources_0_modifiedProperties.displayName)
    | extend FailureReason = tostring(parse_json(tostring(TargetResources_0_modifiedProperties.newValue)))
    | where FailureReason contains "Risky"
    | extend InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1])
    | project-reorder TimeGenerated, OperationName, Result, TargetAppName, TargetAppId, FailureReason, InitiatingUserPrincipalName, InitiatingAadUserId, InitiatingIPAddress, userAgent
entityMappings:
  - 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
  - entityType: CloudApplication
    fieldMappings:
      - identifier: AppId
        columnName: TargetAppId
      - identifier: Name
        columnName: TargetAppName
version: 1.1.1
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: Microsoft Security Research
    support:
        tier: Community
    categories:
        domains: [ "Security - Others" ]

Stages and Predicates

Stage 1: source

AuditLogs

Stage 2: where

| where OperationName has "Consent to application"

Stage 3: where

| where Result =~ "failure"

Stage 4: extend (4 consecutive steps)

| extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend InitiatingIPAddress = tostring(InitiatedBy.user.ipAddress)
| extend userAgent = iif(AdditionalDetails[0].key == "User-Agent", tostring(AdditionalDetails[0].value), tostring(AdditionalDetails[1].value))

Stage 5: where

| where isnotempty(TargetResources)

Stage 6: extend

| extend TargetAppName = tostring(TargetResources[0].displayName)

Stage 7: extend

| extend TargetAppId = tostring(TargetResources[0].id)

Stage 8: mv-expand

| mv-expand TargetResources[0].modifiedProperties

Stage 9: extend

| extend TargetResources_0_modifiedProperties = columnifexists("TargetResources_0_modifiedProperties", '')

Stage 10: where

| where isnotempty(TargetResources_0_modifiedProperties)

Stage 11: where

| where TargetResources_0_modifiedProperties.displayName =~ "MethodExecutionResult."

Stage 12: extend

| extend TargetPropertyDisplayName = tostring(TargetResources_0_modifiedProperties.displayName)

Stage 13: extend

| extend FailureReason = tostring(parse_json(tostring(TargetResources_0_modifiedProperties.newValue)))

Stage 14: where

| where FailureReason contains "Risky"

Stage 15: extend

| extend InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1])

Stage 16: project-reorder

| project-reorder TimeGenerated, OperationName, Result, TargetAppName, TargetAppId, FailureReason, InitiatingUserPrincipalName, InitiatingAadUserId, InitiatingIPAddress, userAgent

Indicators

Each row is a field, operator, and value that the rule matches. The corpus column counts how many other rules in the catalog look for the same combination: high numbers point to widely-used, community-vetted indicators. Blank or 1 shows that the indicator is specific to this rule.

FieldKindValues
FailureReasoncontains
  • Risky
OperationNamematch
  • Consent to application transforms: term
Resulteq
  • failure
TargetResourcesis_not_null
  • (no value, null check)
TargetResources_0_modifiedPropertiesis_not_null
  • (no value, null check)
displayNameeq
  • MethodExecutionResult.

Output fields

Fields the rule emits when it matches. Chronicle authors list these in the outcome block; they appear on the detection and $risk_score drives alerting. Sentinel / Defender XDR rules build them up through project / summarize / extend stages. Sentinel maps these into alert fields via entityMappings and customDetails; Defender XDR custom detections surface them as alert fields directly.

FieldSource
InitiatingUserPrincipalNameextend
InitiatingAadUserIdextend
InitiatingIPAddressextend
userAgentextend
TargetAppNameextend
TargetAppIdextend
TargetResources_0_modifiedPropertiesextend
TargetPropertyDisplayNameextend
FailureReasonextend
InitiatingAccountNameextend
InitiatingAccountUPNSuffixextend