Detection rules › Kusto
full_access_as_app Granted To Application
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
| Tactic | Techniques |
|---|---|
| Lateral Movement | T1550.001 Use Alternate Authentication Material: Application Access Token |
Event coverage
| Provider | Event |
|---|---|
| Entra-AuditLogs | Consent to application |
Rules detecting the same action
Other rules on this platform that filter on the same API call or operation.
- Admin promotion after Role Management Application Permission Grant (Kusto)
- Application ID URI Changed (Kusto)
- Application Redirect URL Update (Kusto)
- Changes to Application Logout URL (Kusto)
- Changes to Application Ownership (Kusto)
- Mail.Read Permissions Granted to Application (Kusto)
- Microsoft Entra ID Role Management Permission Grant (Kusto)
- Suspicious application consent for offline access (Kusto)
Rule body kusto
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
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.
| Field | Kind | Values |
|---|---|---|
Category | eq |
|
GrantScope1 | eq |
|
LoggedByService | eq |
|
OperationName | eq |
|
TargetResources | match |
|
displayName | eq |
|
key | eq |
|
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.
| Field | Source |
|---|---|
OAuthAppName | extend |
ModifiedProperties | extend |
InitiatingAppName | extend |
InitiatingAppServicePrincipalId | extend |
InitiatingUserPrincipalName | extend |
InitiatingAadUserId | extend |
InitiatingIpAddress | extend |
GrantInitiatedBy | extend |
Name | extend |
UPNSuffix | extend |