Skip to content
Original file line number Diff line number Diff line change
@@ -1 +1 @@
`entra_device_registration_join_restricted`, `entra_device_registration_max_devices_per_user_limited`, `entra_device_registration_global_admins_not_local_admins`, `entra_device_registration_registering_user_not_local_admin`, `entra_device_registration_laps_enabled` and `entra_policy_default_user_cannot_read_bitlocker_keys` checks for M365 provider, covering CIS Microsoft 365 Foundations Benchmark v7.0.0 device registration controls (5.1.4.x)
6 M365 Entra checks covering CIS Microsoft 365 Foundations Benchmark v7.0.0 device registration controls (5.1.4.x): `entra_device_registration_join_restricted`, `entra_device_registration_max_devices_per_user_limited`, `entra_device_registration_global_admins_not_local_admins`, `entra_device_registration_registering_user_not_local_admin`, `entra_device_registration_laps_enabled`, and `entra_policy_default_user_cannot_read_bitlocker_keys`.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
7 M365 Entra checks covering CIS Microsoft 365 Foundations Benchmark v7.0.0 password protection, default user permissions, and guest invitation domain restrictions
28 changes: 21 additions & 7 deletions prowler/compliance/m365/cis_7.0_m365.json
Original file line number Diff line number Diff line change
Expand Up @@ -1166,7 +1166,9 @@
{
"Id": "5.1.3.1",
"Description": "This setting allows users in the organization to create new security groups and add members to these groups in the Azure portal, API, or PowerShell. These new groups also show up in the Access Panel for all other users. If the policy setting on the group allows it, other users can create requests to join these groups. The recommended state is Users can create security groups in Azure portals, API or PowerShell set to No.",
"Checks": [],
"Checks": [
"entra_policy_default_user_cannot_create_security_groups"
],
"Attributes": [
{
"Section": "5 Microsoft Entra admin center",
Expand Down Expand Up @@ -1229,7 +1231,9 @@
{
"Id": "5.1.3.4",
"Description": "All users within a Microsoft Entra organization are permitted to create new Microsoft 365 groups and add members to those groups through the Azure portal, API, or PowerShell. Newly created groups also appear in the Access Panel for all other users. When the applicable group policy settings allow it, users can submit requests to join these groups. The recommended state is No.",
"Checks": [],
"Checks": [
"entra_policy_default_user_cannot_create_m365_groups"
],
"Attributes": [
{
"Section": "5 Microsoft Entra admin center",
Expand Down Expand Up @@ -1526,7 +1530,9 @@
{
"Id": "5.1.6.1",
"Description": "B2B collaboration is a feature within Microsoft Entra External ID that allows for guest invitations to an organization. Ensure users can only send invitations to specified domains. Note: This list works independently from OneDrive for Business and SharePoint Online allow/block lists. To restrict individual file sharing in SharePoint Online, set up an allow or blocklist for OneDrive for Business and SharePoint Online. For instance, in SharePoint or OneDrive users can still share with external users from prohibited domains by using Anyone links if they haven't been disabled.",
"Checks": [],
"Checks": [
"entra_policy_guest_invitations_restricted_to_allowed_domains"
],
"Attributes": [
{
"Section": "5 Microsoft Entra admin center",
Expand Down Expand Up @@ -2016,7 +2022,9 @@
{
"Id": "5.2.3.2",
"Description": "With Entra Password Protection, default global banned password lists are automatically applied to all users in an Entra ID tenant. To support business and security needs, custom banned password lists can be defined. When users change or reset their passwords, these banned password lists are checked to enforce the use of strong passwords. A custom banned password list should include some of the following examples: - Brand names - Product names - Locations, such as company headquarters - Company-specific internal terms - Abbreviations that have specific company meaning",
"Checks": [],
"Checks": [
"entra_password_protection_custom_banned_list_enforced"
],
"Attributes": [
{
"Section": "5 Microsoft Entra admin center",
Expand All @@ -2037,7 +2045,9 @@
{
"Id": "5.2.3.3",
"Description": "Microsoft Entra Password Protection provides a global and custom banned password list. A password change request fails if there's a match in these banned password list. To protect on-premises Active Directory Domain Services (AD DS) environment, install and configure Entra Password Protection. Note: This recommendation applies to Hybrid deployments only and will have no impact unless working with on-premises Active Directory.",
"Checks": [],
"Checks": [
"entra_password_protection_on_premises_enforced"
],
"Attributes": [
{
"Section": "5 Microsoft Entra admin center",
Expand Down Expand Up @@ -2146,7 +2156,9 @@
{
"Id": "5.2.3.8",
"Description": "The account lockout threshold determines how many failed login attempts are permitted prior to placing the account in a locked-out state and initiating a variable lockout duration. The recommended Lockout threshold is 10 or less.",
"Checks": [],
"Checks": [
"entra_password_protection_lockout_threshold_limited"
],
"Attributes": [
{
"Section": "5 Microsoft Entra admin center",
Expand All @@ -2167,7 +2179,9 @@
{
"Id": "5.2.3.9",
"Description": "The account lockout duration value determines how long an account retains the status of lockout, and therefore how long before a user can continue to attempt to login after passing the lockout threshold. The recommended state is Lockout duration in seconds is at least 60.",
"Checks": [],
"Checks": [
"entra_password_protection_lockout_duration_configured"
],
"Attributes": [
{
"Section": "5 Microsoft Entra admin center",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"Provider": "m365",
"CheckID": "entra_password_protection_custom_banned_list_enforced",
"CheckTitle": "Entra custom banned password list is enforced",
"CheckType": [],
"ServiceName": "entra",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "NotDefined",
"ResourceGroup": "IAM",
"Description": "The tenant Password Rule Settings should enforce a **custom banned password list** (**EnableBannedPasswordCheck** true with a non-empty **BannedPasswordList**). This blocks organization-specific weak or predictable passwords (e.g., company name, products, locations) in addition to Microsoft's global banned list.",
"Risk": "Without a custom **banned password** list, users can choose passwords that are predictable for the specific organization (brand names, local terms), which are easy targets for **password spraying** and guessing attacks.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://learn.microsoft.com/en-us/entra/identity/authentication/concept-password-ban-bad"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "1. Navigate to the Microsoft Entra admin center at https://entra.microsoft.com/\n2. Go to **Protection** > **Authentication methods** > **Password protection**\n3. Set **Enforce custom list** to **Yes**\n4. Add organization-specific terms to the **Custom banned password list**\n5. Click **Save**",
"Terraform": ""
},
"Recommendation": {
"Text": "Enable the custom banned password list and populate it with terms relevant to the organization to strengthen protection against weak passwords beyond the global banned list.",
"Url": "https://hub.prowler.com/check/entra_password_protection_custom_banned_list_enforced"
}
},
"Categories": [
"identity-access",
"e3"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from typing import List

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 (
PASSWORD_RULE_SETTINGS_TEMPLATE_ID,
)


class entra_password_protection_custom_banned_list_enforced(Check):
"""Check if the Entra custom banned password list is enforced.

The Password Rule Settings directory setting should enforce a custom banned
password list (EnableBannedPasswordCheck) with a non-empty BannedPasswordList so
that organization-specific weak passwords are rejected in addition to the global
banned list.

- PASS: The custom banned password list is enforced and non-empty.
- FAIL: The custom banned password list is not enforced or is empty.
"""

def execute(self) -> List[CheckReportM365]:
"""Execute the custom banned password list enforcement check.

Evaluate whether the Password Rule Settings directory setting enforces a
non-empty custom banned password list. When the settings object is absent,
no finding is produced.

Returns:
List[CheckReportM365]: A list with a single report when the Password Rule
Settings exist, or an empty list when they are absent.
"""
findings = []
settings = entra_client.directory_settings.get(
PASSWORD_RULE_SETTINGS_TEMPLATE_ID
)
if not settings:
return findings

report = CheckReportM365(
metadata=self.metadata(),
resource=settings or {},
resource_name="Password Rule Settings",
resource_id=PASSWORD_RULE_SETTINGS_TEMPLATE_ID,
)
report.status = "FAIL"
report.status_extended = (
"The custom banned password list is not enforced in the tenant."
)

if settings:
enforced = (
str(settings.get("EnableBannedPasswordCheck", "")).lower() == "true"
)
banned_list = settings.get("BannedPasswordList", "") or ""
if enforced and banned_list.strip():
report.status = "PASS"
report.status_extended = (
"The custom banned password list is enforced in the tenant."
)

findings.append(report)
return findings
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"Provider": "m365",
"CheckID": "entra_password_protection_lockout_duration_configured",
"CheckTitle": "Smart lockout duration is set to 60 seconds or more",
"CheckType": [],
"ServiceName": "entra",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "low",
"ResourceType": "NotDefined",
"ResourceGroup": "IAM",
"Description": "The tenant Password Rule Settings should set the smart **LockoutDurationInSeconds** to **60 or more**. The lockout duration determines how long an account remains locked out before the user can attempt to sign in again.",
"Risk": "A short lockout duration allows attackers to resume **brute-force** or **password-spray** attempts sooner, reducing the effectiveness of **smart lockout** as a throttling control.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://learn.microsoft.com/en-us/entra/identity/authentication/howto-password-smart-lockout"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "1. Navigate to the Microsoft Entra admin center at https://entra.microsoft.com/\n2. Go to **Protection** > **Authentication methods** > **Password protection**\n3. Set **Lockout duration in seconds** to **60** or higher\n4. Click **Save**",
"Terraform": ""
},
"Recommendation": {
"Text": "Set the smart lockout duration to 60 seconds or more so locked-out accounts remain locked long enough to throttle automated password attacks.",
"Url": "https://hub.prowler.com/check/entra_password_protection_lockout_duration_configured"
}
},
"Categories": [
"identity-access",
"e3"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
from typing import List

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 (
PASSWORD_RULE_SETTINGS_TEMPLATE_ID,
)

# CIS recommends a lockout duration of 60 seconds or more.
MIN_LOCKOUT_DURATION_SECONDS = 60


class entra_password_protection_lockout_duration_configured(Check):
"""Check if the smart lockout duration is set to 60 seconds or more.

The Password Rule Settings directory setting should set LockoutDurationInSeconds
to 60 or more so a locked-out account remains locked long enough to slow down
automated attacks.

- PASS: The lockout duration is 60 seconds or more.
- FAIL: The lockout duration is less than 60 seconds or not configured.
"""

def execute(self) -> List[CheckReportM365]:
Comment thread
coderabbitai[bot] marked this conversation as resolved.
"""Execute the smart lockout duration check.

Returns:
List[CheckReportM365]: Reports for the Password Rule Settings, or an
empty list when the settings are absent.
"""
findings = []
settings = entra_client.directory_settings.get(
PASSWORD_RULE_SETTINGS_TEMPLATE_ID
)
if not settings:
return findings

report = CheckReportM365(
metadata=self.metadata(),
resource=settings or {},
resource_name="Password Rule Settings",
resource_id=PASSWORD_RULE_SETTINGS_TEMPLATE_ID,
)
report.status = "FAIL"
report.status_extended = (
"The smart lockout duration is not set to 60 seconds or more."
)

if settings:
try:
duration = int(settings.get("LockoutDurationInSeconds"))
except (TypeError, ValueError):
duration = None
if duration is not None and duration >= MIN_LOCKOUT_DURATION_SECONDS:
report.status = "PASS"
report.status_extended = (
f"The smart lockout duration is set to {duration} seconds, at or "
f"above the recommended minimum of {MIN_LOCKOUT_DURATION_SECONDS}."
)

findings.append(report)
return findings
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"Provider": "m365",
"CheckID": "entra_password_protection_lockout_threshold_limited",
"CheckTitle": "Smart lockout threshold is set to 10 or less",
"CheckType": [],
"ServiceName": "entra",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "NotDefined",
"ResourceGroup": "IAM",
"Description": "The tenant Password Rule Settings should set the smart **LockoutThreshold** to **10 or less**. The lockout threshold determines how many failed sign-in attempts are permitted before an account is placed in a locked-out state.",
"Risk": "A high lockout threshold gives attackers more attempts per account during **password spraying** and **brute-force** attacks before lockout is triggered, increasing the chance of a successful credential compromise.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://learn.microsoft.com/en-us/entra/identity/authentication/howto-password-smart-lockout"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "1. Navigate to the Microsoft Entra admin center at https://entra.microsoft.com/\n2. Go to **Protection** > **Authentication methods** > **Password protection**\n3. Set **Lockout threshold** to **10** or less\n4. Click **Save**",
"Terraform": ""
},
"Recommendation": {
"Text": "Set the smart lockout threshold to 10 or less so accounts lock after a small number of failed sign-in attempts, limiting brute-force and password-spray attacks.",
"Url": "https://hub.prowler.com/check/entra_password_protection_lockout_threshold_limited"
}
},
"Categories": [
"identity-access",
"e3"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from typing import List

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 (
PASSWORD_RULE_SETTINGS_TEMPLATE_ID,
)

# CIS recommends a smart lockout threshold of 10 or less.
MAX_LOCKOUT_THRESHOLD = 10


class entra_password_protection_lockout_threshold_limited(Check):
"""Check if the smart lockout threshold is set to 10 or less.

The Password Rule Settings directory setting should set LockoutThreshold to 10 or
less so that accounts are locked after a small number of failed sign-in attempts.

- PASS: The lockout threshold is 10 or less.
- FAIL: The lockout threshold is greater than 10 or not configured.
"""

def execute(self) -> List[CheckReportM365]:
"""Execute the smart lockout threshold check.

Evaluate whether the Password Rule Settings directory setting limits the smart
lockout threshold to the recommended maximum. When the settings object is
absent, no finding is produced.

Returns:
List[CheckReportM365]: A list with a single report when the Password Rule
Settings exist, or an empty list when they are absent.
"""
findings = []
settings = entra_client.directory_settings.get(
PASSWORD_RULE_SETTINGS_TEMPLATE_ID
)
if not settings:
return findings

report = CheckReportM365(
metadata=self.metadata(),
resource=settings or {},
resource_name="Password Rule Settings",
resource_id=PASSWORD_RULE_SETTINGS_TEMPLATE_ID,
)
report.status = "FAIL"
report.status_extended = "The smart lockout threshold is not set to 10 or less."

if settings:
try:
threshold = int(settings.get("LockoutThreshold"))
except (TypeError, ValueError):
threshold = None
if threshold is not None and threshold <= MAX_LOCKOUT_THRESHOLD:
report.status = "PASS"
report.status_extended = (
f"The smart lockout threshold is set to {threshold}, within the "
f"recommended limit of {MAX_LOCKOUT_THRESHOLD}."
)

findings.append(report)
return findings
Loading
Loading