Back to Checks
m365

Apps with Exchange mailbox permissions must be scoped via Application Access Policy

exchange_application_access_policy_restricts_mailbox_apps
Service: exchangeSeverity: medium
OfficialBuilt-inVerified
Categories:identity-accessemail-security
Resource Type:NotDefined

Microsoft 365 Exchange Online applications with mailbox access permissions should be restricted using Application Access Policies.

This check evaluates whether applications granted Exchange-related Microsoft Graph application permissions are scoped using Exchange Online ApplicationAccessPolicy objects.

Prowler logoProwlerprowler-provider-m365

Risk

Applications with unrestricted Exchange mailbox permissions may gain tenant-wide mailbox access. Without Application Access Policies, compromised or over-privileged applications can read or manipulate mail across all mailboxes, leading to unauthorized access, data exfiltration, phishing, and loss of confidentiality and integrity.

Run this check with Prowler

Run in Prowler Cloud
Run
prowler m365 --checks exchange_application_access_policy_restricts_mailbox_apps

Recommendation

Restrict applications with Exchange mailbox permissions using Application Access Policies. Apply least privilege by limiting mailbox scope to only required users or groups. Regularly review app permissions and remove unused or excessive mailbox access.

Remediation

CLI

New-ApplicationAccessPolicy -AppId <AppId> -PolicyScopeGroupId <Group> -AccessRight RestrictAccess -Description "Restrict mailbox access"

Other

1Connect to Exchange Online PowerShell
2Run Get-ApplicationAccessPolicy to review existing policies
3Identify applications with Exchange-related Graph application permissions
4Create an Application Access Policy for each required application
5Scope mailbox access using a dedicated mail-enabled security group

Check Code

import importlib
from typing import List

from prowler.lib.check.models import Check, CheckReportM365
from prowler.providers.m365.services.entra.entra_client import entra_client

exchange_client = importlib.import_module(
    "prowler.providers.m365.services.exchange.exchange_client"
).exchange_client


class exchange_application_access_policy_restricts_mailbox_apps(Check):
    """
    Check if applications with Exchange mailbox permissions
    are restricted using Exchange Application Access Policies.
    """

    def execute(self) -> List[CheckReportM365]:
        findings = []

        application_access_policies = exchange_client.application_access_policies
        if application_access_policies is None:
            report = CheckReportM365(
                metadata=self.metadata(),
                resource=exchange_client.organization_config,
                resource_name="Exchange Online",
                resource_id="ExchangeOnlineTenant",
            )
            report.status = "MANUAL"
            report.status_extended = (
                "Exchange Online PowerShell is unavailable. "
                "Enable Exchange Online PowerShell credentials to evaluate "
                "Application Access Policies."
            )
            findings.append(report)
            return findings

        mailbox_permission_collection_error = getattr(
            entra_client,
            "exchange_mailbox_permission_service_principals_error",
            None,
        )
        if isinstance(mailbox_permission_collection_error, str):
            report = CheckReportM365(
                metadata=self.metadata(),
                resource=exchange_client.organization_config,
                resource_name="Exchange Online",
                resource_id="ExchangeOnlineTenant",
            )
            report.status = "MANUAL"
            report.status_extended = (
                "Microsoft Graph mailbox permission collection failed. "
                "Manually verify whether applications with Exchange mailbox "
                "permissions are restricted using Application Access Policies."
            )
            findings.append(report)
            return findings

        policy_app_ids = {
            policy.app_id.lower()
            for policy in application_access_policies
            if getattr(policy, "app_id", None)
            and getattr(policy, "access_right", None) == "RestrictAccess"
        }

        service_principals = (
            entra_client.exchange_mailbox_permission_service_principals.values()
        )
        for service_principal in service_principals:
            report = CheckReportM365(
                metadata=self.metadata(),
                resource=service_principal,
                resource_name=service_principal.name,
                resource_id=service_principal.id,
            )
            permissions = ", ".join(service_principal.exchange_mailbox_permissions)

            if service_principal.app_id.lower() in policy_app_ids:
                report.status = "PASS"
                report.status_extended = (
                    f"Service principal '{service_principal.name}' "
                    f"({service_principal.app_id}) "
                    "has Exchange mailbox permissions "
                    f"({permissions}) and is restricted using an Application "
                    "Access Policy."
                )
            else:
                report.status = "FAIL"
                report.status_extended = (
                    f"Service principal '{service_principal.name}' "
                    f"({service_principal.app_id}) "
                    "has Exchange mailbox permissions "
                    f"({permissions}) but is not restricted using an Application "
                    "Access Policy."
                )

            findings.append(report)

        return findings