Back to Checks
m365

Conditional Access policy blocks device code flow to prevent phishing attacks

entra_conditional_access_policy_device_code_flow_blocked
Service: entraSeverity: high
OfficialBuilt-inVerified
Categories:identity-accesstrust-boundariese3
Resource Type:NotDefined

Conditional Access policies restrict the device code authentication flow, which is commonly abused in phishing campaigns to hijack user sessions. A policy targeting deviceCodeFlow in authentication flow conditions with a block grant control prevents this attack vector.

Risk

Device code flow is heavily exploited in phishing attacks such as Storm-2372, where attackers trick users into entering device codes on legitimate Microsoft login pages. Without a blocking policy, attackers can obtain tokens and gain persistent access to organizational resources.

Run this check with Prowler

Run in Prowler Cloud
Run
prowler m365 --checks entra_conditional_access_policy_device_code_flow_blocked

Recommendation

Block device code flow via Conditional Access to mitigate phishing attacks that abuse this authentication method. Exclude only break-glass accounts and legitimate service accounts that require device code flow. Regularly review exceptions to minimize the attack surface.

Remediation

Other

1Navigate to the Microsoft Entra admin center at https://entra.microsoft.com.
2Expand **Protection** > **Conditional Access** and select **Policies**.
3Click **New policy**.
4Under **Users**, include **All users** and exclude break-glass accounts.
5Under **Target resources**, include **All cloud apps**.
6Under **Conditions** > **Authentication flows**, select **Device code flow**.
7Under **Grant**, select **Block access**.
8Set the policy to **On** and click **Create**.

Check Code

from prowler.lib.check.models import Check, CheckReportM365
from prowler.providers.m365.services.entra.entra_client import entra_client
from prowler.providers.m365.services.entra.entra_service import (
    ConditionalAccessGrantControl,
    ConditionalAccessPolicyState,
    TransferMethod,
)


class entra_conditional_access_policy_device_code_flow_blocked(Check):
    """Check if at least one Conditional Access policy blocks device code flow.

    This check ensures that at least one enabled Conditional Access policy
    targets the device code authentication flow and blocks access, protecting
    against phishing attacks that abuse this flow (e.g., Storm-2372).

    - PASS: An enabled Conditional Access policy blocks device code flow.
    - FAIL: No Conditional Access policy restricts device code flow.
    """

    def execute(self) -> list[CheckReportM365]:
        """Execute the check to verify device code flow is blocked by a Conditional Access policy.

        Returns:
            A list of reports containing the result of the check.
        """
        findings = []
        report = CheckReportM365(
            metadata=self.metadata(),
            resource={},
            resource_name="Conditional Access Policies",
            resource_id="conditionalAccessPolicies",
        )
        report.status = "FAIL"
        report.status_extended = "No Conditional Access Policy blocks device code flow."

        for policy in entra_client.conditional_access_policies.values():
            if policy.state == ConditionalAccessPolicyState.DISABLED:
                continue

            if "All" not in policy.conditions.user_conditions.included_users:
                continue

            if (
                "All"
                not in policy.conditions.application_conditions.included_applications
            ):
                continue

            if not policy.conditions.authentication_flows:
                continue

            if (
                TransferMethod.DEVICE_CODE_FLOW
                not in policy.conditions.authentication_flows.transfer_methods
            ):
                continue

            if (
                ConditionalAccessGrantControl.BLOCK
                in policy.grant_controls.built_in_controls
            ):
                report = CheckReportM365(
                    metadata=self.metadata(),
                    resource=policy,
                    resource_name=policy.display_name,
                    resource_id=policy.id,
                )
                if policy.state == ConditionalAccessPolicyState.ENABLED_FOR_REPORTING:
                    report.status = "FAIL"
                    report.status_extended = f"Conditional Access Policy '{policy.display_name}' reports device code flow but does not block it."
                else:
                    report.status = "PASS"
                    report.status_extended = f"Conditional Access Policy '{policy.display_name}' blocks device code flow."
                    break

        findings.append(report)
        return findings