mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-15 16:04:18 +00:00
feat(m365): add entra_policy_admin_self_service_password_reset_disabled check (#12990)
Co-authored-by: kinomakino <3662588+kinomakino@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Pedro Martín <pedromarting3@gmail.com>
This commit is contained in:
7 files changed
+265
No files matched your search
@@ -0,0 +1 @@
|
||||
`entra_policy_admin_self_service_password_reset_disabled` check for M365 provider, verifying that Self-Service Password Reset is disabled for administrators
|
||||
@@ -258,6 +258,7 @@
|
||||
"entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced",
|
||||
"entra_legacy_authentication_blocked",
|
||||
"entra_managed_device_required_for_authentication",
|
||||
"entra_policy_admin_self_service_password_reset_disabled",
|
||||
"entra_seamless_sso_disabled",
|
||||
"entra_service_principal_no_secrets_for_permanent_tier0_roles",
|
||||
"entra_users_mfa_enabled",
|
||||
@@ -675,6 +676,7 @@
|
||||
"entra_break_glass_account_fido2_security_key_registered",
|
||||
"entra_emergency_access_exclusion",
|
||||
"entra_app_registration_no_unused_privileged_permissions",
|
||||
"entra_policy_admin_self_service_password_reset_disabled",
|
||||
"entra_policy_ensure_default_user_cannot_create_tenants",
|
||||
"entra_policy_guest_invite_only_for_admin_roles",
|
||||
"entra_policy_msol_powershell_blocked",
|
||||
@@ -735,6 +737,7 @@
|
||||
"entra_emergency_access_exclusion",
|
||||
"entra_identity_protection_sign_in_risk_enabled",
|
||||
"entra_managed_device_required_for_authentication",
|
||||
"entra_policy_admin_self_service_password_reset_disabled",
|
||||
"entra_seamless_sso_disabled",
|
||||
"entra_service_principal_no_secrets_for_permanent_tier0_roles",
|
||||
"entra_users_mfa_enabled"
|
||||
|
||||
Whitespace-only changes.
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"Provider": "m365",
|
||||
"CheckID": "entra_policy_admin_self_service_password_reset_disabled",
|
||||
"CheckTitle": "Disabled Self-Service Password Reset protects administrator accounts from unauthorized recovery",
|
||||
"CheckType": [],
|
||||
"ServiceName": "entra",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "NotDefined",
|
||||
"ResourceGroup": "IAM",
|
||||
"Description": "The tenant authorization policy property `allowedToUseSSPR` controls whether administrators can reset their own passwords through **Self-Service Password Reset (SSPR)**.\n\nWhen disabled, password recovery for privileged accounts must go through other administrators or break-glass procedures, reducing the risk of account takeover via compromised recovery methods.",
|
||||
"Risk": "If SSPR is enabled for administrators, anyone who controls an admin's registered recovery methods (e.g., **SIM swap**, compromised alternate mailbox, social engineering) can reset the admin password and take over a **privileged account** without going through a controlled recovery process.\n\nThis directly threatens the **confidentiality** and **integrity** of the entire tenant.",
|
||||
"RelatedUrl": "",
|
||||
"AdditionalURLs": [
|
||||
"https://learn.microsoft.com/entra/identity/authentication/concept-sspr-policy#administrator-reset-policy-differences",
|
||||
"https://learn.microsoft.com/graph/api/resources/authorizationpolicy",
|
||||
"https://learn.microsoft.com/graph/api/authorizationpolicy-get"
|
||||
],
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "Update-MgPolicyAuthorizationPolicy -AllowedToUseSspr:$false",
|
||||
"NativeIaC": "",
|
||||
"Other": "Administrator SSPR cannot be disabled from the Microsoft Entra admin center: the **Password reset** settings there only apply to non-administrator users. Use Microsoft Graph PowerShell instead:\n1. Connect-MgGraph -Scopes Policy.ReadWrite.Authorization\n2. Update-MgPolicyAuthorizationPolicy -AllowedToUseSspr:$false\n3. Verify with (Get-MgPolicyAuthorizationPolicy).AllowedToUseSspr",
|
||||
"Terraform": ""
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Disable **Self-Service Password Reset** for administrator accounts. Privileged account password recovery should be handled by other administrators or **break-glass procedures** to prevent account takeover through compromised recovery methods.\n\nEnsure a documented recovery path exists before disabling SSPR for admins.",
|
||||
"Url": "https://hub.prowler.com/check/entra_policy_admin_self_service_password_reset_disabled"
|
||||
}
|
||||
},
|
||||
"Categories": [
|
||||
"identity-access"
|
||||
],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": "The service default for allowedToUseSSPR is true, so most tenants that never changed it will FAIL. Before disabling, ensure there is a documented recovery path for admin passwords (other admins, break-glass accounts). Requires Policy.ReadWrite.Authorization to remediate."
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
"""Check that Self-Service Password Reset is disabled for administrators."""
|
||||
|
||||
from typing import List
|
||||
|
||||
from prowler.lib.check.models import Check, CheckReportM365
|
||||
from prowler.providers.m365.services.entra.entra_client import entra_client
|
||||
|
||||
|
||||
class entra_policy_admin_self_service_password_reset_disabled(Check):
|
||||
"""Ensure that Self-Service Password Reset (SSPR) is disabled for administrators.
|
||||
|
||||
This check evaluates the tenant authorization policy to verify that the
|
||||
``allowedToUseSSPR`` property is set to ``false``, preventing administrators
|
||||
from resetting their own passwords through SSPR.
|
||||
|
||||
- PASS: ``allowedToUseSSPR`` is ``false`` (administrators cannot use SSPR).
|
||||
- FAIL: ``allowedToUseSSPR`` is ``true`` (SSPR is enabled for administrators).
|
||||
- MANUAL: The authorization policy or the property could not be read.
|
||||
"""
|
||||
|
||||
def execute(self) -> List[CheckReportM365]:
|
||||
"""Evaluate allowedToUseSSPR on the tenant authorization policy."""
|
||||
findings = []
|
||||
auth_policy = entra_client.authorization_policy
|
||||
|
||||
if auth_policy is None or auth_policy.allowed_to_use_sspr is None:
|
||||
report = CheckReportM365(
|
||||
metadata=self.metadata(),
|
||||
resource=auth_policy if auth_policy else {},
|
||||
resource_name="Authorization Policy",
|
||||
resource_id="authorizationPolicy",
|
||||
)
|
||||
report.status = "MANUAL"
|
||||
report.status_extended = (
|
||||
"Cannot evaluate whether SSPR is disabled for administrators: the "
|
||||
"authorization policy or its allowedToUseSSPR setting could not be "
|
||||
"retrieved. Check the scanning application's permissions and any "
|
||||
"Microsoft Graph API errors in the scan log."
|
||||
)
|
||||
findings.append(report)
|
||||
return findings
|
||||
|
||||
report = CheckReportM365(
|
||||
metadata=self.metadata(),
|
||||
resource=auth_policy,
|
||||
resource_name=auth_policy.name,
|
||||
resource_id=auth_policy.id,
|
||||
)
|
||||
|
||||
if not auth_policy.allowed_to_use_sspr:
|
||||
report.status = "PASS"
|
||||
report.status_extended = (
|
||||
"Self-Service Password Reset (SSPR) is disabled for administrators "
|
||||
"(allowedToUseSSPR is false in the authorization policy)."
|
||||
)
|
||||
else:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = (
|
||||
"Self-Service Password Reset (SSPR) is enabled for administrators "
|
||||
"(allowedToUseSSPR is true in the authorization policy)."
|
||||
)
|
||||
|
||||
findings.append(report)
|
||||
return findings
|
||||
@@ -231,6 +231,9 @@ class Entra(M365Service):
|
||||
),
|
||||
guest_invite_settings=auth_policy.allow_invites_from,
|
||||
guest_user_role_id=auth_policy.guest_user_role_id,
|
||||
allowed_to_use_sspr=getattr(
|
||||
auth_policy, "allowed_to_use_s_s_p_r", None
|
||||
),
|
||||
block_msol_powershell=getattr(
|
||||
auth_policy, "block_msol_power_shell", None
|
||||
),
|
||||
@@ -2574,6 +2577,7 @@ class AuthorizationPolicy(BaseModel):
|
||||
default_user_role_permissions: Optional[DefaultUserRolePermissions]
|
||||
guest_invite_settings: Optional[str]
|
||||
guest_user_role_id: Optional[UUID]
|
||||
allowed_to_use_sspr: Optional[bool] = None
|
||||
block_msol_powershell: Optional[bool] = None
|
||||
|
||||
|
||||
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
from unittest import mock
|
||||
|
||||
from prowler.providers.m365.services.entra.entra_service import (
|
||||
AuthorizationPolicy,
|
||||
)
|
||||
from tests.providers.m365.m365_fixtures import set_mocked_m365_provider
|
||||
|
||||
|
||||
class Test_entra_policy_admin_self_service_password_reset_disabled:
|
||||
def test_no_auth_policy(self):
|
||||
"""When authorization_policy is None, the check returns a MANUAL finding."""
|
||||
entra_client = mock.MagicMock()
|
||||
entra_client.authorization_policy = None
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_m365_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.m365.services.entra.entra_policy_admin_self_service_password_reset_disabled.entra_policy_admin_self_service_password_reset_disabled.entra_client",
|
||||
new=entra_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.m365.services.entra.entra_policy_admin_self_service_password_reset_disabled.entra_policy_admin_self_service_password_reset_disabled import (
|
||||
entra_policy_admin_self_service_password_reset_disabled,
|
||||
)
|
||||
|
||||
check = entra_policy_admin_self_service_password_reset_disabled()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "MANUAL"
|
||||
assert "Cannot evaluate" in result[0].status_extended
|
||||
assert "could not be retrieved" in result[0].status_extended
|
||||
assert result[0].resource_name == "Authorization Policy"
|
||||
assert result[0].resource_id == "authorizationPolicy"
|
||||
|
||||
def test_auth_policy_sspr_none(self):
|
||||
"""When allowed_to_use_sspr is None, the check returns a MANUAL finding."""
|
||||
entra_client = mock.MagicMock()
|
||||
entra_client.authorization_policy = AuthorizationPolicy(
|
||||
id="authorizationPolicy",
|
||||
name="Authorization Policy",
|
||||
description="",
|
||||
default_user_role_permissions=None,
|
||||
guest_invite_settings=None,
|
||||
guest_user_role_id=None,
|
||||
allowed_to_use_sspr=None,
|
||||
)
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_m365_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.m365.services.entra.entra_policy_admin_self_service_password_reset_disabled.entra_policy_admin_self_service_password_reset_disabled.entra_client",
|
||||
new=entra_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.m365.services.entra.entra_policy_admin_self_service_password_reset_disabled.entra_policy_admin_self_service_password_reset_disabled import (
|
||||
entra_policy_admin_self_service_password_reset_disabled,
|
||||
)
|
||||
|
||||
check = entra_policy_admin_self_service_password_reset_disabled()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "MANUAL"
|
||||
assert "Cannot evaluate" in result[0].status_extended
|
||||
assert result[0].resource_name == "Authorization Policy"
|
||||
assert result[0].resource_id == "authorizationPolicy"
|
||||
|
||||
def test_sspr_enabled_fail(self):
|
||||
"""When allowed_to_use_sspr is True, the check returns FAIL."""
|
||||
entra_client = mock.MagicMock()
|
||||
entra_client.authorization_policy = AuthorizationPolicy(
|
||||
id="authorizationPolicy",
|
||||
name="Authorization Policy",
|
||||
description="",
|
||||
default_user_role_permissions=None,
|
||||
guest_invite_settings=None,
|
||||
guest_user_role_id=None,
|
||||
allowed_to_use_sspr=True,
|
||||
)
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_m365_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.m365.services.entra.entra_policy_admin_self_service_password_reset_disabled.entra_policy_admin_self_service_password_reset_disabled.entra_client",
|
||||
new=entra_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.m365.services.entra.entra_policy_admin_self_service_password_reset_disabled.entra_policy_admin_self_service_password_reset_disabled import (
|
||||
entra_policy_admin_self_service_password_reset_disabled,
|
||||
)
|
||||
|
||||
check = entra_policy_admin_self_service_password_reset_disabled()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== "Self-Service Password Reset (SSPR) is enabled for administrators "
|
||||
"(allowedToUseSSPR is true in the authorization policy)."
|
||||
)
|
||||
assert result[0].resource_id == "authorizationPolicy"
|
||||
assert result[0].resource_name == "Authorization Policy"
|
||||
assert result[0].location == "global"
|
||||
|
||||
def test_sspr_disabled_pass(self):
|
||||
"""When allowed_to_use_sspr is False, the check returns PASS."""
|
||||
entra_client = mock.MagicMock()
|
||||
entra_client.authorization_policy = AuthorizationPolicy(
|
||||
id="authorizationPolicy",
|
||||
name="Authorization Policy",
|
||||
description="",
|
||||
default_user_role_permissions=None,
|
||||
guest_invite_settings=None,
|
||||
guest_user_role_id=None,
|
||||
allowed_to_use_sspr=False,
|
||||
)
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_m365_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.m365.services.entra.entra_policy_admin_self_service_password_reset_disabled.entra_policy_admin_self_service_password_reset_disabled.entra_client",
|
||||
new=entra_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.m365.services.entra.entra_policy_admin_self_service_password_reset_disabled.entra_policy_admin_self_service_password_reset_disabled import (
|
||||
entra_policy_admin_self_service_password_reset_disabled,
|
||||
)
|
||||
|
||||
check = entra_policy_admin_self_service_password_reset_disabled()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== "Self-Service Password Reset (SSPR) is disabled for administrators "
|
||||
"(allowedToUseSSPR is false in the authorization policy)."
|
||||
)
|
||||
assert result[0].resource_id == "authorizationPolicy"
|
||||
assert result[0].resource_name == "Authorization Policy"
|
||||
assert result[0].location == "global"
|
||||
Reference in new issue
Block a user