Skip to content

Commit 3b87548

Browse files
feat(m365): add device registration MFA and harden Intune enrollment CA check (#10222)
Co-authored-by: Hugo Brito <hugopbrito@users.noreply.github.com> Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
1 parent 442b379 commit 3b87548

File tree

10 files changed

+663
-253
lines changed

10 files changed

+663
-253
lines changed

prowler/CHANGELOG.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
66

77
### 🚀 Added
88

9+
- `apikeys_api_restricted_with_gemini_api` and `gemini_api_disabled` checks for GCP provider [(#10280)](https://github.com/prowler-cloud/prowler/pull/10280)
910
- `cloudfront_distributions_logging_enabled` detects Standard Logging v2 via CloudWatch Log Delivery [(#10090)](https://github.com/prowler-cloud/prowler/pull/10090)
1011
- `glue_etl_jobs_no_secrets_in_arguments` check for plaintext secrets in AWS Glue ETL job arguments [(#10368)](https://github.com/prowler-cloud/prowler/pull/10368)
1112
- `awslambda_function_no_dead_letter_queue`, `awslambda_function_using_cross_account_layers`, and `awslambda_function_env_vars_not_encrypted_with_cmk` checks for AWS Lambda [(#10381)](https://github.com/prowler-cloud/prowler/pull/10381)
@@ -14,8 +15,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
1415
- `entra_conditional_access_policy_block_o365_elevated_insider_risk` check for M365 provider [(#10232)](https://github.com/prowler-cloud/prowler/pull/10232)
1516
- `--resource-group` and `--list-resource-groups` CLI flags to filter checks by resource group across all providers [(#10479)](https://github.com/prowler-cloud/prowler/pull/10479)
1617
- CIS Google Workspace Foundations Benchmark v1.3.0 compliance [(#10462)](https://github.com/prowler-cloud/prowler/pull/10462)
17-
- `apikeys_api_restricted_with_gemini_api` check for GCP provider [(#10280)](https://github.com/prowler-cloud/prowler/pull/10280)
18-
- `gemini_api_disabled` check for GCP provider [(#10280)](https://github.com/prowler-cloud/prowler/pull/10280)
18+
- `entra_conditional_access_policy_device_registration_mfa_required` check and `entra_intune_enrollment_sign_in_frequency_every_time` enhancement for M365 provider [(#10222)](https://github.com/prowler-cloud/prowler/pull/10222)
1919

2020
### 🔄 Changed
2121

prowler/compliance/m365/cis_6.0_m365.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1584,7 +1584,7 @@
15841584
"Id": "5.2.2.11",
15851585
"Description": "Sign-in frequency defines the time period before a user is asked to sign in again when attempting to access a resource. Ensure sign-in frequency for Intune Enrollment is set to 'Every time'.",
15861586
"Checks": [
1587-
"entra_admin_users_sign_in_frequency_enabled"
1587+
"entra_intune_enrollment_sign_in_frequency_every_time"
15881588
],
15891589
"Attributes": [
15901590
{

prowler/compliance/m365/iso27001_2022_m365.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,8 @@
246246
"entra_break_glass_account_fido2_security_key_registered",
247247
"entra_default_app_management_policy_enabled",
248248
"entra_all_apps_conditional_access_coverage",
249+
"entra_conditional_access_policy_device_registration_mfa_required",
250+
"entra_intune_enrollment_sign_in_frequency_every_time",
249251
"entra_conditional_access_policy_device_code_flow_blocked",
250252
"entra_legacy_authentication_blocked",
251253
"entra_managed_device_required_for_authentication",
@@ -623,6 +625,8 @@
623625
"entra_admin_users_phishing_resistant_mfa_enabled",
624626
"entra_conditional_access_policy_approved_client_app_required_for_mobile",
625627
"entra_conditional_access_policy_app_enforced_restrictions",
628+
"entra_conditional_access_policy_device_registration_mfa_required",
629+
"entra_intune_enrollment_sign_in_frequency_every_time",
626630
"entra_managed_device_required_for_authentication",
627631
"entra_managed_device_required_for_mfa_registration",
628632
"entra_users_mfa_capable",
@@ -700,6 +704,8 @@
700704
"entra_admin_users_mfa_enabled",
701705
"entra_admin_users_sign_in_frequency_enabled",
702706
"entra_all_apps_conditional_access_coverage",
707+
"entra_conditional_access_policy_device_registration_mfa_required",
708+
"entra_intune_enrollment_sign_in_frequency_every_time",
703709
"entra_break_glass_account_fido2_security_key_registered",
704710
"entra_conditional_access_policy_approved_client_app_required_for_mobile",
705711
"entra_conditional_access_policy_device_code_flow_blocked",

prowler/providers/m365/services/entra/entra_conditional_access_policy_device_registration_mfa_required/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
"Provider": "m365",
3+
"CheckID": "entra_conditional_access_policy_device_registration_mfa_required",
4+
"CheckTitle": "Conditional Access policies enforce MFA for device registration",
5+
"CheckType": [],
6+
"ServiceName": "entra",
7+
"SubServiceName": "",
8+
"ResourceIdTemplate": "",
9+
"Severity": "high",
10+
"ResourceType": "NotDefined",
11+
"ResourceGroup": "IAM",
12+
"Description": "Microsoft Entra **Conditional Access** policies can require **multifactor authentication (MFA)** for **device registration** operations.\n\nThis control ensures users must complete MFA before **registering or joining devices** to the directory, reducing the likelihood that compromised credentials can be used to register rogue devices.",
13+
"Risk": "Without MFA for device registration, attackers with stolen credentials could register unauthorized devices into the directory, gain persistence, and bypass compliance-based Conditional Access protections that rely on trusted device state.",
14+
"RelatedUrl": "",
15+
"AdditionalURLs": [
16+
"https://learn.microsoft.com/en-us/entra/identity/conditional-access/policy-all-users-device-registration",
17+
"https://learn.microsoft.com/en-us/entra/identity/conditional-access/concept-conditional-access-cloud-apps#user-actions"
18+
],
19+
"Remediation": {
20+
"Code": {
21+
"CLI": "",
22+
"NativeIaC": "",
23+
"Other": "1. Navigate to the Microsoft Entra admin center (https://entra.microsoft.com).\n2. Expand **Protection** > **Conditional Access** and select **Policies**.\n3. Click **New policy**.\n4. Under **Users**, include **All users**.\n5. Under **Target resources**, select **User actions** and check **Register or join devices**.\n6. Under **Grant**, select **Grant access** and require **multifactor authentication**.\n7. Set the policy to **Report-only** until validated, then enable it.",
24+
"Terraform": ""
25+
},
26+
"Recommendation": {
27+
"Text": "Enforce **MFA** through **Conditional Access** for **device registration** so users must verify identity before registering or joining devices to the directory.",
28+
"Url": "https://hub.prowler.com/check/entra_conditional_access_policy_device_registration_mfa_required"
29+
}
30+
},
31+
"Categories": [
32+
"identity-access",
33+
"e3"
34+
],
35+
"DependsOn": [],
36+
"RelatedTo": [
37+
"entra_managed_device_required_for_mfa_registration",
38+
"entra_intune_enrollment_sign_in_frequency_every_time"
39+
],
40+
"Notes": "Requires Entra ID P1 or later license."
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
from prowler.lib.check.models import Check, CheckReportM365
2+
from prowler.providers.m365.services.entra.entra_client import entra_client
3+
from prowler.providers.m365.services.entra.entra_service import (
4+
ConditionalAccessGrantControl,
5+
ConditionalAccessPolicyState,
6+
UserAction,
7+
)
8+
9+
10+
class entra_conditional_access_policy_device_registration_mfa_required(Check):
11+
"""Ensure MFA is required for device registration."""
12+
13+
def execute(self) -> list[CheckReportM365]:
14+
findings = []
15+
16+
report = CheckReportM365(
17+
metadata=self.metadata(),
18+
resource={},
19+
resource_name="Conditional Access Policies",
20+
resource_id="conditionalAccessPolicies",
21+
)
22+
report.status = "FAIL"
23+
report.status_extended = (
24+
"No Conditional Access Policy requires MFA for device registration."
25+
)
26+
27+
reporting_policy = None
28+
29+
for policy in entra_client.conditional_access_policies.values():
30+
if policy.state == ConditionalAccessPolicyState.DISABLED:
31+
continue
32+
33+
if "All" not in policy.conditions.user_conditions.included_users:
34+
continue
35+
36+
if (
37+
UserAction.REGISTER_DEVICE
38+
not in policy.conditions.application_conditions.included_user_actions
39+
):
40+
continue
41+
42+
if (
43+
ConditionalAccessGrantControl.MFA
44+
not in policy.grant_controls.built_in_controls
45+
):
46+
continue
47+
48+
if policy.state == ConditionalAccessPolicyState.ENABLED:
49+
report = CheckReportM365(
50+
metadata=self.metadata(),
51+
resource=policy,
52+
resource_name=policy.display_name,
53+
resource_id=policy.id,
54+
)
55+
report.status = "PASS"
56+
report.status_extended = (
57+
f"Conditional Access Policy '{policy.display_name}' enforces MFA "
58+
"for device registration."
59+
)
60+
break
61+
62+
if (
63+
policy.state == ConditionalAccessPolicyState.ENABLED_FOR_REPORTING
64+
and reporting_policy is None
65+
):
66+
reporting_policy = policy
67+
68+
if report.status == "FAIL" and reporting_policy:
69+
report.status_extended = (
70+
f"Conditional Access Policy '{reporting_policy.display_name}' reports "
71+
"MFA for device registration but does not enforce it."
72+
)
73+
74+
findings.append(report)
75+
return findings
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,30 @@
11
{
22
"Provider": "m365",
33
"CheckID": "entra_intune_enrollment_sign_in_frequency_every_time",
4-
"CheckTitle": "Conditional Access enforces Every Time sign-in frequency for Intune Enrollment",
4+
"CheckTitle": "Conditional Access requires strong authentication and Every Time sign-in frequency for Intune Enrollment",
55
"CheckType": [],
66
"ServiceName": "entra",
77
"SubServiceName": "",
88
"ResourceIdTemplate": "",
99
"Severity": "high",
1010
"ResourceType": "NotDefined",
1111
"ResourceGroup": "IAM",
12-
"Description": "Microsoft Entra **Conditional Access** for **Microsoft Intune Enrollment** enforces the session control **sign-in frequency** set to `Every time` for all users.\n\nThis evaluates whether an active policy targets the Intune Enrollment app and requires reauthentication on each enrollment attempt.",
13-
"Risk": "Absent `Every time` reauth at enrollment, attackers with stolen or replayed credentials can enroll rogue devices and obtain compliant access.\n\nImpacts:\n- Confidentiality: data exposure from unauthorized devices\n- Integrity: untrusted endpoints modifying resources\n- Availability: persistence via device-based access paths",
12+
"Description": "Microsoft Entra **Conditional Access** for **Microsoft Intune Enrollment** must require **strong authentication** and set **sign-in frequency** to `Every time` for all users.\n\nThis check evaluates whether an active policy targets the Intune Enrollment app, requires MFA or authentication strength, and forces reauthentication on each enrollment attempt.",
13+
"Risk": "Absent strong authentication and `Every time` reauthentication at enrollment, attackers with stolen or replayed credentials can enroll rogue devices and obtain compliant access.\n\nImpacts:\n- Confidentiality: data exposure from unauthorized devices\n- Integrity: untrusted endpoints modifying resources\n- Availability: persistence via device-based access paths",
1414
"RelatedUrl": "",
1515
"AdditionalURLs": [
1616
"https://learn.microsoft.com/en-us/intune/intune-service/fundamentals/deployment-guide-enrollment",
1717
"https://learn.microsoft.com/en-us/entra/identity/conditional-access/concept-conditional-access-session#sign-in-frequency"
1818
],
1919
"Remediation": {
2020
"Code": {
21-
"CLI": "az rest --method POST --url https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies --headers 'Content-Type=application/json' --body '{\"displayName\":\"Intune Enrollment - Every time\",\"state\":\"enabled\",\"conditions\":{\"users\":{\"includeUsers\":[\"All\"]},\"applications\":{\"includeApplications\":[\"d4ebce55-015a-49b5-a083-c84d1797ae8c\"]}},\"sessionControls\":{\"signInFrequency\":{\"isEnabled\":true,\"type\":\"everyTime\"}}}'",
21+
"CLI": "az rest --method POST --url https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies --headers 'Content-Type=application/json' --body '{\"displayName\":\"Intune Enrollment - MFA and Every time\",\"state\":\"enabled\",\"conditions\":{\"users\":{\"includeUsers\":[\"All\"]},\"applications\":{\"includeApplications\":[\"d4ebce55-015a-49b5-a083-c84d1797ae8c\"]}},\"grantControls\":{\"operator\":\"OR\",\"builtInControls\":[\"mfa\"]},\"sessionControls\":{\"signInFrequency\":{\"isEnabled\":true,\"type\":\"everyTime\"}}}'",
2222
"NativeIaC": "",
23-
"Other": "1. Sign in to Microsoft Entra admin center (entra.microsoft.com)\n2. Go to Protection > Conditional Access > Policies > New policy\n3. Users > Include: select All users\n4. Target resources (Resources/Cloud apps) > Select resources: choose Microsoft Intune Enrollment\n5. Session > Sign-in frequency: select Every time\n6. Enable policy: On\n7. Create the policy",
23+
"Other": "1. Sign in to Microsoft Entra admin center (entra.microsoft.com)\n2. Go to Protection > Conditional Access > Policies > New policy\n3. Users > Include: select All users\n4. Target resources (Resources/Cloud apps) > Select resources: choose Microsoft Intune Enrollment (App ID: `d4ebce55-015a-49b5-a083-c84d1797ae8c`)\n5. Grant > Grant access: select either Require multifactor authentication or Require authentication strength\n6. Session > Sign-in frequency: select Every time\n7. Enable policy: On\n8. Create the policy",
2424
"Terraform": "```hcl\nresource \"azuread_conditional_access_policy\" \"<example_resource_name>\" {\n display_name = \"<example_resource_name>\"\n state = \"enabled\"\n\n conditions {\n users {\n include_users = [\"All\"] # critical: include all users\n }\n applications {\n include_applications = [\"d4ebce55-015a-49b5-a083-c84d1797ae8c\"] # critical: target Microsoft Intune Enrollment app\n }\n }\n\n session_controls {\n sign_in_frequency {\n is_enabled = true # critical: enable sign-in frequency control\n type = \"everyTime\" # critical: require reauthentication every time\n }\n }\n}\n```"
2525
},
2626
"Recommendation": {
27-
"Text": "Implement a **Conditional Access** policy on the **Intune Enrollment** app that sets sign-in frequency to `Every time` and applies broadly.\n\nCombine with **MFA** and device **compliance** requirements, use **least privilege** exclusions sparingly, and monitor sign-in/audit logs to strengthen **defense in depth**.",
27+
"Text": "Implement a **Conditional Access** policy on the **Intune Enrollment** app that requires **MFA** or **authentication strength** and sets sign-in frequency to `Every time`.\n\nMicrosoft Entra requires this grant control when `Every time` is configured for Intune Enrollment, so Prowler validates both conditions together in a single check.",
2828
"Url": "https://hub.prowler.com/check/entra_intune_enrollment_sign_in_frequency_every_time"
2929
}
3030
},
@@ -34,5 +34,5 @@
3434
],
3535
"DependsOn": [],
3636
"RelatedTo": [],
37-
"Notes": ""
37+
"Notes": "This check intentionally validates both the grant control and session control together. Microsoft Entra requires `Require multifactor authentication` or `Require authentication strength` when `Sign-in frequency = Every time` is configured for Microsoft Intune Enrollment, so these conditions cannot be meaningfully separated into independent policies for this scenario."
3838
}

prowler/providers/m365/services/entra/entra_intune_enrollment_sign_in_frequency_every_time/entra_intune_enrollment_sign_in_frequency_every_time.py

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
from prowler.lib.check.models import Check, CheckReportM365
22
from prowler.providers.m365.services.entra.entra_client import entra_client
33
from prowler.providers.m365.services.entra.entra_service import (
4+
ConditionalAccessGrantControl,
45
ConditionalAccessPolicyState,
6+
GrantControlOperator,
57
SignInFrequencyInterval,
68
)
79

810

911
class entra_intune_enrollment_sign_in_frequency_every_time(Check):
10-
"""Ensure sign-in frequency for Intune Enrollment is set to 'Every time'."""
12+
"""Ensure Intune enrollment enforces strong auth and Every Time sign-in."""
1113

1214
def execute(self) -> list[CheckReportM365]:
1315
"""Execute the check to ensure that sign-in frequency for Intune Enrollment is set to 'Every time'.
@@ -24,7 +26,10 @@ def execute(self) -> list[CheckReportM365]:
2426
resource_id="conditionalAccessPolicies",
2527
)
2628
report.status = "FAIL"
27-
report.status_extended = "No Conditional Access Policy enforces Every Time sign-in frequency for Intune Enrollment."
29+
report.status_extended = (
30+
"No Conditional Access Policy requires strong authentication and "
31+
"enforces Every Time sign-in frequency for Intune Enrollment."
32+
)
2833

2934
for policy in entra_client.conditional_access_policies.values():
3035
if policy.state == ConditionalAccessPolicyState.DISABLED:
@@ -45,6 +50,23 @@ def execute(self) -> list[CheckReportM365]:
4550
if "All" not in policy.conditions.user_conditions.included_users:
4651
continue
4752

53+
requires_mfa = (
54+
ConditionalAccessGrantControl.MFA
55+
in policy.grant_controls.built_in_controls
56+
)
57+
requires_authentication_strength = (
58+
policy.grant_controls.authentication_strength is not None
59+
)
60+
61+
if not (requires_mfa or requires_authentication_strength):
62+
continue
63+
64+
if (
65+
policy.grant_controls.operator == GrantControlOperator.OR
66+
and len(policy.grant_controls.built_in_controls) > 1
67+
):
68+
continue
69+
4870
if not policy.session_controls.sign_in_frequency.is_enabled:
4971
continue
5072

@@ -60,10 +82,18 @@ def execute(self) -> list[CheckReportM365]:
6082
)
6183
if policy.state == ConditionalAccessPolicyState.ENABLED_FOR_REPORTING:
6284
report.status = "FAIL"
63-
report.status_extended = f"Conditional Access Policy {policy.display_name} reports Every Time sign-in frequency for Intune Enrollment but does not enforce it."
85+
report.status_extended = (
86+
f"Conditional Access Policy '{policy.display_name}' reports "
87+
"strong authentication and Every Time sign-in frequency for "
88+
"Intune Enrollment but does not enforce them."
89+
)
6490
else:
6591
report.status = "PASS"
66-
report.status_extended = f"Conditional Access Policy {policy.display_name} enforces Every Time sign-in frequency for Intune Enrollment."
92+
report.status_extended = (
93+
f"Conditional Access Policy '{policy.display_name}' requires "
94+
"strong authentication and enforces Every Time sign-in "
95+
"frequency for Intune Enrollment."
96+
)
6797
break
6898

6999
findings.append(report)

0 commit comments

Comments
 (0)