mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-24 13:01:56 +00:00
feat(m365): add exchange_shared_mailbox_sign_in_disabled check (#9828)
This commit is contained in:
@@ -8,6 +8,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
|
||||
|
||||
- `compute_instance_suspended_without_persistent_disks` check for GCP provider [(#9747)](https://github.com/prowler-cloud/prowler/pull/9747)
|
||||
- `codebuild_project_webhook_filters_use_anchored_patterns` check for AWS provider to detect CodeBreach vulnerability [(#9840)](https://github.com/prowler-cloud/prowler/pull/9840)
|
||||
- `exchange_shared_mailbox_sign_in_disabled` check for M365 provider [(#9828)](https://github.com/prowler-cloud/prowler/pull/9828)
|
||||
|
||||
### Changed
|
||||
|
||||
|
||||
@@ -121,7 +121,9 @@
|
||||
{
|
||||
"Id": "1.2.2",
|
||||
"Description": "Shared mailboxes are used when multiple people need access to the same mailbox, such as a company information or support email address, reception desk, or other function that might be shared by multiple people.Users with permissions to the group mailbox can send as or send on behalf of the mailbox email address if the administrator has given that user permissions to do that. This is particularly useful for help and support mailboxes because users can send emails from \"Contoso Support\" or \"Building A Reception Desk.\"Shared mailboxes are created with a corresponding user account using a system generated password that is unknown at the time of creation.The recommended state is `Sign in blocked` for `Shared mailboxes`.",
|
||||
"Checks": [],
|
||||
"Checks": [
|
||||
"exchange_shared_mailbox_sign_in_disabled"
|
||||
],
|
||||
"Attributes": [
|
||||
{
|
||||
"Section": "1 Microsoft 365 admin center",
|
||||
|
||||
@@ -121,7 +121,9 @@
|
||||
{
|
||||
"Id": "1.2.2",
|
||||
"Description": "Shared mailboxes are used when multiple people need access to the same mailbox, such as a company information or support email address, reception desk, or other function that might be shared by multiple people. Shared mailboxes are created with a corresponding user account using a system generated password that is unknown at the time of creation. The recommended state is Sign in blocked for Shared mailboxes.",
|
||||
"Checks": [],
|
||||
"Checks": [
|
||||
"exchange_shared_mailbox_sign_in_disabled"
|
||||
],
|
||||
"Attributes": [
|
||||
{
|
||||
"Section": "1 Microsoft 365 admin center",
|
||||
|
||||
@@ -823,6 +823,32 @@ class M365PowerShell(PowerShellSession):
|
||||
"Get-SharingPolicy | ConvertTo-Json -Depth 10", json_parse=True
|
||||
)
|
||||
|
||||
def get_shared_mailboxes(self) -> dict:
|
||||
"""
|
||||
Get Exchange Online Shared Mailboxes.
|
||||
|
||||
Retrieves all shared mailboxes from Exchange Online with their external
|
||||
directory object IDs for cross-referencing with Entra ID user accounts.
|
||||
|
||||
Returns:
|
||||
dict: Shared mailbox information in JSON format.
|
||||
|
||||
Example:
|
||||
>>> get_shared_mailboxes()
|
||||
[
|
||||
{
|
||||
"DisplayName": "Support Mailbox",
|
||||
"UserPrincipalName": "support@contoso.com",
|
||||
"ExternalDirectoryObjectId": "12345678-1234-1234-1234-123456789012",
|
||||
"Identity": "support@contoso.com"
|
||||
}
|
||||
]
|
||||
"""
|
||||
return self.execute(
|
||||
"Get-EXOMailbox -RecipientTypeDetails SharedMailbox -ResultSize Unlimited | Select-Object DisplayName, UserPrincipalName, ExternalDirectoryObjectId, Identity | ConvertTo-Json -Depth 10",
|
||||
json_parse=True,
|
||||
)
|
||||
|
||||
def get_user_account_status(self) -> dict:
|
||||
"""
|
||||
Get User Account Status.
|
||||
@@ -833,7 +859,7 @@ class M365PowerShell(PowerShellSession):
|
||||
dict: User account status settings in JSON format.
|
||||
"""
|
||||
return self.execute(
|
||||
"$dict=@{}; Get-User -ResultSize Unlimited | ForEach-Object { $dict[$_.Id] = @{ AccountDisabled = $_.AccountDisabled } }; $dict | ConvertTo-Json -Depth 10",
|
||||
"$dict=@{}; Get-User -ResultSize Unlimited | ForEach-Object { $dict[$_.ExternalDirectoryObjectId] = @{ AccountDisabled = $_.AccountDisabled } }; $dict | ConvertTo-Json -Depth 10",
|
||||
json_parse=True,
|
||||
)
|
||||
|
||||
|
||||
@@ -9,7 +9,20 @@ from prowler.providers.m365.m365_provider import M365Provider
|
||||
|
||||
|
||||
class Exchange(M365Service):
|
||||
"""
|
||||
Exchange Online service for Microsoft 365.
|
||||
|
||||
This service provides access to Exchange Online resources and configurations
|
||||
including organization settings, mailboxes, transport rules, and policies.
|
||||
"""
|
||||
|
||||
def __init__(self, provider: M365Provider):
|
||||
"""
|
||||
Initialize the Exchange service.
|
||||
|
||||
Args:
|
||||
provider: The M365Provider instance for authentication and configuration.
|
||||
"""
|
||||
super().__init__(provider)
|
||||
self.organization_config = None
|
||||
self.mailboxes_config = []
|
||||
@@ -19,6 +32,7 @@ class Exchange(M365Service):
|
||||
self.mailbox_policies = []
|
||||
self.role_assignment_policies = []
|
||||
self.mailbox_audit_properties = []
|
||||
self.shared_mailboxes = []
|
||||
|
||||
if self.powershell:
|
||||
if self.powershell.connect_exchange_online():
|
||||
@@ -30,6 +44,7 @@ class Exchange(M365Service):
|
||||
self.mailbox_policies = self._get_mailbox_policy()
|
||||
self.role_assignment_policies = self._get_role_assignment_policies()
|
||||
self.mailbox_audit_properties = self._get_mailbox_audit_properties()
|
||||
self.shared_mailboxes = self._get_shared_mailboxes()
|
||||
self.powershell.close()
|
||||
|
||||
def _get_organization_config(self):
|
||||
@@ -211,6 +226,12 @@ class Exchange(M365Service):
|
||||
return role_assignment_policies
|
||||
|
||||
def _get_mailbox_audit_properties(self):
|
||||
"""
|
||||
Get mailbox audit properties for all mailboxes.
|
||||
|
||||
Returns:
|
||||
list[MailboxAuditProperties]: List of mailbox audit property configurations.
|
||||
"""
|
||||
logger.info("Microsoft365 - Getting mailbox audit properties...")
|
||||
mailbox_audit_properties = []
|
||||
try:
|
||||
@@ -248,6 +269,44 @@ class Exchange(M365Service):
|
||||
)
|
||||
return mailbox_audit_properties
|
||||
|
||||
def _get_shared_mailboxes(self):
|
||||
"""
|
||||
Get all shared mailboxes from Exchange Online.
|
||||
|
||||
Retrieves shared mailboxes with their external directory object IDs
|
||||
for cross-referencing with Entra ID user accounts.
|
||||
|
||||
Returns:
|
||||
list[SharedMailbox]: List of shared mailbox configurations.
|
||||
"""
|
||||
logger.info("Microsoft365 - Getting shared mailboxes...")
|
||||
shared_mailboxes = []
|
||||
try:
|
||||
shared_mailboxes_data = self.powershell.get_shared_mailboxes()
|
||||
if not shared_mailboxes_data:
|
||||
return shared_mailboxes
|
||||
if isinstance(shared_mailboxes_data, dict):
|
||||
shared_mailboxes_data = [shared_mailboxes_data]
|
||||
for shared_mailbox in shared_mailboxes_data:
|
||||
if shared_mailbox:
|
||||
shared_mailboxes.append(
|
||||
SharedMailbox(
|
||||
name=shared_mailbox.get("DisplayName", ""),
|
||||
user_principal_name=shared_mailbox.get(
|
||||
"UserPrincipalName", ""
|
||||
),
|
||||
external_directory_object_id=shared_mailbox.get(
|
||||
"ExternalDirectoryObjectId", ""
|
||||
),
|
||||
identity=shared_mailbox.get("Identity", ""),
|
||||
)
|
||||
)
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
return shared_mailboxes
|
||||
|
||||
|
||||
class Organization(BaseModel):
|
||||
name: str
|
||||
@@ -342,6 +401,8 @@ class AuditDelegate(Enum):
|
||||
|
||||
|
||||
class AuditOwner(Enum):
|
||||
"""Audit actions for mailbox owner operations."""
|
||||
|
||||
APPLY_RECORD = "ApplyRecord"
|
||||
CREATE = "Create"
|
||||
HARD_DELETE = "HardDelete"
|
||||
@@ -353,3 +414,20 @@ class AuditOwner(Enum):
|
||||
UPDATE_CALENDAR_DELEGATION = "UpdateCalendarDelegation"
|
||||
UPDATE_FOLDER_PERMISSIONS = "UpdateFolderPermissions"
|
||||
UPDATE_INBOX_RULES = "UpdateInboxRules"
|
||||
|
||||
|
||||
class SharedMailbox(BaseModel):
|
||||
"""
|
||||
Model for Exchange Online shared mailbox.
|
||||
|
||||
Attributes:
|
||||
name: Display name of the shared mailbox.
|
||||
user_principal_name: User principal name (email) of the shared mailbox.
|
||||
external_directory_object_id: The Entra ID object ID for cross-referencing.
|
||||
identity: Identity of the shared mailbox in Exchange.
|
||||
"""
|
||||
|
||||
name: str
|
||||
user_principal_name: str
|
||||
external_directory_object_id: str
|
||||
identity: str
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"Provider": "m365",
|
||||
"CheckID": "exchange_shared_mailbox_sign_in_disabled",
|
||||
"CheckTitle": "Shared mailbox has sign-in blocked",
|
||||
"CheckType": [],
|
||||
"ServiceName": "exchange",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "Shared Mailbox",
|
||||
"ResourceGroup": "IAM",
|
||||
"Description": "Shared mailboxes are used for collaboration and should not permit direct sign-in. This check verifies that the **AccountEnabled** property is set to `false` in Entra ID for all shared mailboxes, preventing direct authentication.",
|
||||
"Risk": "When sign-in is enabled on shared mailboxes, users with the password can bypass delegation controls and access the mailbox directly. This undermines **accountability** since actions cannot be attributed to individual users, and it increases the attack surface for credential-based attacks.",
|
||||
"RelatedUrl": "",
|
||||
"AdditionalURLs": [
|
||||
"https://learn.microsoft.com/en-us/microsoft-365/admin/email/about-shared-mailboxes",
|
||||
"https://learn.microsoft.com/en-us/microsoft-365/admin/email/create-a-shared-mailbox#block-sign-in-for-the-shared-mailbox-account"
|
||||
],
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "Get-EXOMailbox -RecipientTypeDetails SharedMailbox | ForEach-Object { Update-MgUser -UserId $_.ExternalDirectoryObjectId -AccountEnabled:$false }",
|
||||
"NativeIaC": "",
|
||||
"Other": "1. Navigate to Entra admin center (https://entra.microsoft.com/)\n2. Expand Identity > Users and select All users\n3. Search for and select the shared mailbox user account\n4. In the properties pane, go to Account status\n5. Uncheck 'Account enabled' and click Save\n6. Repeat for all shared mailbox accounts",
|
||||
"Terraform": ""
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Block sign-in for all shared mailboxes to ensure users can only access them through delegation. This enforces accountability and reduces security risks from shared credentials.",
|
||||
"Url": "https://hub.prowler.com/check/exchange_shared_mailbox_sign_in_disabled"
|
||||
}
|
||||
},
|
||||
"Categories": [
|
||||
"identity-access"
|
||||
],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": ""
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
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.exchange.exchange_client import exchange_client
|
||||
|
||||
|
||||
class exchange_shared_mailbox_sign_in_disabled(Check):
|
||||
"""
|
||||
Verify that sign-in is blocked for all shared mailboxes.
|
||||
|
||||
Shared mailboxes are designed for collaboration and should not permit direct
|
||||
sign-in. Users should access shared mailboxes through delegation only, which
|
||||
ensures accountability and proper access controls.
|
||||
|
||||
- PASS: Shared mailbox has sign-in blocked (AccountEnabled = False in Entra ID).
|
||||
- FAIL: Shared mailbox has sign-in enabled (AccountEnabled = True in Entra ID).
|
||||
"""
|
||||
|
||||
def execute(self) -> List[CheckReportM365]:
|
||||
"""
|
||||
Execute the check to verify shared mailbox sign-in status.
|
||||
|
||||
Cross-references shared mailboxes from Exchange Online with user accounts
|
||||
in Entra ID to determine if sign-in is blocked.
|
||||
|
||||
Returns:
|
||||
List[CheckReportM365]: A list of reports with the sign-in status for
|
||||
each shared mailbox.
|
||||
"""
|
||||
findings = []
|
||||
|
||||
for shared_mailbox in exchange_client.shared_mailboxes:
|
||||
report = CheckReportM365(
|
||||
metadata=self.metadata(),
|
||||
resource=shared_mailbox,
|
||||
resource_name=shared_mailbox.name or shared_mailbox.user_principal_name,
|
||||
resource_id=shared_mailbox.external_directory_object_id
|
||||
or shared_mailbox.identity,
|
||||
)
|
||||
|
||||
# Look up the user in Entra ID by their external directory object ID
|
||||
entra_user = entra_client.users.get(
|
||||
shared_mailbox.external_directory_object_id
|
||||
)
|
||||
|
||||
if not entra_user:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"Shared mailbox {shared_mailbox.user_principal_name} could not be found in Entra ID for verification."
|
||||
elif entra_user.account_enabled:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"Shared mailbox {shared_mailbox.user_principal_name} has sign-in enabled."
|
||||
else:
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"Shared mailbox {shared_mailbox.user_principal_name} has sign-in blocked."
|
||||
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
@@ -9,6 +9,7 @@ from prowler.providers.m365.services.exchange.exchange_service import (
|
||||
MailboxAuditProperties,
|
||||
Organization,
|
||||
RoleAssignmentPolicy,
|
||||
SharedMailbox,
|
||||
TransportConfig,
|
||||
TransportRule,
|
||||
)
|
||||
@@ -143,6 +144,23 @@ def mock_exchange_get_mailbox_audit_properties(_):
|
||||
]
|
||||
|
||||
|
||||
def mock_exchange_get_shared_mailboxes(_):
|
||||
return [
|
||||
SharedMailbox(
|
||||
name="Support Mailbox",
|
||||
user_principal_name="support@contoso.com",
|
||||
external_directory_object_id="12345678-1234-1234-1234-123456789012",
|
||||
identity="support@contoso.com",
|
||||
),
|
||||
SharedMailbox(
|
||||
name="Info Mailbox",
|
||||
user_principal_name="info@contoso.com",
|
||||
external_directory_object_id="87654321-4321-4321-4321-210987654321",
|
||||
identity="info@contoso.com",
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
class Test_Exchange_Service:
|
||||
def test_get_client(self):
|
||||
with (
|
||||
@@ -429,3 +447,37 @@ class Test_Exchange_Service:
|
||||
assert role_assignment_policies[1].assigned_roles == []
|
||||
|
||||
exchange_client.powershell.close()
|
||||
|
||||
@patch(
|
||||
"prowler.providers.m365.services.exchange.exchange_service.Exchange._get_shared_mailboxes",
|
||||
new=mock_exchange_get_shared_mailboxes,
|
||||
)
|
||||
def test_get_shared_mailboxes(self):
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online"
|
||||
),
|
||||
):
|
||||
exchange_client = Exchange(
|
||||
set_mocked_m365_provider(
|
||||
identity=M365IdentityInfo(tenant_domain=DOMAIN)
|
||||
)
|
||||
)
|
||||
shared_mailboxes = exchange_client.shared_mailboxes
|
||||
assert len(shared_mailboxes) == 2
|
||||
assert shared_mailboxes[0].name == "Support Mailbox"
|
||||
assert shared_mailboxes[0].user_principal_name == "support@contoso.com"
|
||||
assert (
|
||||
shared_mailboxes[0].external_directory_object_id
|
||||
== "12345678-1234-1234-1234-123456789012"
|
||||
)
|
||||
assert shared_mailboxes[0].identity == "support@contoso.com"
|
||||
assert shared_mailboxes[1].name == "Info Mailbox"
|
||||
assert shared_mailboxes[1].user_principal_name == "info@contoso.com"
|
||||
assert (
|
||||
shared_mailboxes[1].external_directory_object_id
|
||||
== "87654321-4321-4321-4321-210987654321"
|
||||
)
|
||||
assert shared_mailboxes[1].identity == "info@contoso.com"
|
||||
|
||||
exchange_client.powershell.close()
|
||||
|
||||
+317
@@ -0,0 +1,317 @@
|
||||
from unittest import mock
|
||||
|
||||
from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider
|
||||
|
||||
|
||||
class Test_exchange_shared_mailbox_sign_in_disabled:
|
||||
def test_no_shared_mailboxes(self):
|
||||
exchange_client = mock.MagicMock()
|
||||
exchange_client.audited_tenant = "audited_tenant"
|
||||
exchange_client.audited_domain = DOMAIN
|
||||
exchange_client.shared_mailboxes = []
|
||||
|
||||
entra_client = mock.MagicMock()
|
||||
entra_client.users = {}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_m365_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online"
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.m365.services.exchange.exchange_shared_mailbox_sign_in_disabled.exchange_shared_mailbox_sign_in_disabled.exchange_client",
|
||||
new=exchange_client,
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.m365.services.exchange.exchange_shared_mailbox_sign_in_disabled.exchange_shared_mailbox_sign_in_disabled.entra_client",
|
||||
new=entra_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.m365.services.exchange.exchange_shared_mailbox_sign_in_disabled.exchange_shared_mailbox_sign_in_disabled import (
|
||||
exchange_shared_mailbox_sign_in_disabled,
|
||||
)
|
||||
|
||||
check = exchange_shared_mailbox_sign_in_disabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 0
|
||||
|
||||
def test_sign_in_disabled(self):
|
||||
"""PASS: Shared mailbox has sign-in blocked (AccountEnabled = False in Entra ID)."""
|
||||
exchange_client = mock.MagicMock()
|
||||
exchange_client.audited_tenant = "audited_tenant"
|
||||
exchange_client.audited_domain = DOMAIN
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_m365_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online"
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.m365.services.exchange.exchange_shared_mailbox_sign_in_disabled.exchange_shared_mailbox_sign_in_disabled.exchange_client",
|
||||
new=exchange_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.m365.services.entra.entra_service import User
|
||||
from prowler.providers.m365.services.exchange.exchange_service import (
|
||||
SharedMailbox,
|
||||
)
|
||||
from prowler.providers.m365.services.exchange.exchange_shared_mailbox_sign_in_disabled.exchange_shared_mailbox_sign_in_disabled import (
|
||||
exchange_shared_mailbox_sign_in_disabled,
|
||||
)
|
||||
|
||||
shared_mailbox = SharedMailbox(
|
||||
name="Support Mailbox",
|
||||
user_principal_name="support@contoso.com",
|
||||
external_directory_object_id="12345678-1234-1234-1234-123456789012",
|
||||
identity="support@contoso.com",
|
||||
)
|
||||
exchange_client.shared_mailboxes = [shared_mailbox]
|
||||
|
||||
entra_user = User(
|
||||
id="12345678-1234-1234-1234-123456789012",
|
||||
name="Support Mailbox",
|
||||
on_premises_sync_enabled=False,
|
||||
account_enabled=False,
|
||||
)
|
||||
entra_client = mock.MagicMock()
|
||||
entra_client.users = {
|
||||
"12345678-1234-1234-1234-123456789012": entra_user,
|
||||
}
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.m365.services.exchange.exchange_shared_mailbox_sign_in_disabled.exchange_shared_mailbox_sign_in_disabled.entra_client",
|
||||
new=entra_client,
|
||||
):
|
||||
check = exchange_shared_mailbox_sign_in_disabled()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== "Shared mailbox support@contoso.com has sign-in blocked."
|
||||
)
|
||||
assert result[0].resource_name == "Support Mailbox"
|
||||
assert result[0].resource_id == "12345678-1234-1234-1234-123456789012"
|
||||
assert result[0].location == "global"
|
||||
|
||||
def test_sign_in_enabled(self):
|
||||
"""FAIL: Shared mailbox has sign-in enabled (AccountEnabled = True in Entra ID)."""
|
||||
exchange_client = mock.MagicMock()
|
||||
exchange_client.audited_tenant = "audited_tenant"
|
||||
exchange_client.audited_domain = DOMAIN
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_m365_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online"
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.m365.services.exchange.exchange_shared_mailbox_sign_in_disabled.exchange_shared_mailbox_sign_in_disabled.exchange_client",
|
||||
new=exchange_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.m365.services.entra.entra_service import User
|
||||
from prowler.providers.m365.services.exchange.exchange_service import (
|
||||
SharedMailbox,
|
||||
)
|
||||
from prowler.providers.m365.services.exchange.exchange_shared_mailbox_sign_in_disabled.exchange_shared_mailbox_sign_in_disabled import (
|
||||
exchange_shared_mailbox_sign_in_disabled,
|
||||
)
|
||||
|
||||
shared_mailbox = SharedMailbox(
|
||||
name="Info Mailbox",
|
||||
user_principal_name="info@contoso.com",
|
||||
external_directory_object_id="87654321-4321-4321-4321-210987654321",
|
||||
identity="info@contoso.com",
|
||||
)
|
||||
exchange_client.shared_mailboxes = [shared_mailbox]
|
||||
|
||||
entra_user = User(
|
||||
id="87654321-4321-4321-4321-210987654321",
|
||||
name="Info Mailbox",
|
||||
on_premises_sync_enabled=False,
|
||||
account_enabled=True,
|
||||
)
|
||||
entra_client = mock.MagicMock()
|
||||
entra_client.users = {
|
||||
"87654321-4321-4321-4321-210987654321": entra_user,
|
||||
}
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.m365.services.exchange.exchange_shared_mailbox_sign_in_disabled.exchange_shared_mailbox_sign_in_disabled.entra_client",
|
||||
new=entra_client,
|
||||
):
|
||||
check = exchange_shared_mailbox_sign_in_disabled()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== "Shared mailbox info@contoso.com has sign-in enabled."
|
||||
)
|
||||
assert result[0].resource_name == "Info Mailbox"
|
||||
assert result[0].resource_id == "87654321-4321-4321-4321-210987654321"
|
||||
assert result[0].location == "global"
|
||||
|
||||
def test_user_not_found_in_entra(self):
|
||||
"""FAIL: Shared mailbox not found in Entra ID for verification."""
|
||||
exchange_client = mock.MagicMock()
|
||||
exchange_client.audited_tenant = "audited_tenant"
|
||||
exchange_client.audited_domain = DOMAIN
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_m365_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online"
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.m365.services.exchange.exchange_shared_mailbox_sign_in_disabled.exchange_shared_mailbox_sign_in_disabled.exchange_client",
|
||||
new=exchange_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.m365.services.exchange.exchange_service import (
|
||||
SharedMailbox,
|
||||
)
|
||||
from prowler.providers.m365.services.exchange.exchange_shared_mailbox_sign_in_disabled.exchange_shared_mailbox_sign_in_disabled import (
|
||||
exchange_shared_mailbox_sign_in_disabled,
|
||||
)
|
||||
|
||||
shared_mailbox = SharedMailbox(
|
||||
name="Orphan Mailbox",
|
||||
user_principal_name="orphan@contoso.com",
|
||||
external_directory_object_id="00000000-0000-0000-0000-000000000000",
|
||||
identity="orphan@contoso.com",
|
||||
)
|
||||
exchange_client.shared_mailboxes = [shared_mailbox]
|
||||
|
||||
entra_client = mock.MagicMock()
|
||||
entra_client.users = {}
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.m365.services.exchange.exchange_shared_mailbox_sign_in_disabled.exchange_shared_mailbox_sign_in_disabled.entra_client",
|
||||
new=entra_client,
|
||||
):
|
||||
check = exchange_shared_mailbox_sign_in_disabled()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== "Shared mailbox orphan@contoso.com could not be found in Entra ID for verification."
|
||||
)
|
||||
assert result[0].resource_name == "Orphan Mailbox"
|
||||
assert result[0].resource_id == "00000000-0000-0000-0000-000000000000"
|
||||
assert result[0].location == "global"
|
||||
|
||||
def test_multiple_shared_mailboxes_mixed_status(self):
|
||||
"""Test multiple shared mailboxes with different sign-in statuses."""
|
||||
exchange_client = mock.MagicMock()
|
||||
exchange_client.audited_tenant = "audited_tenant"
|
||||
exchange_client.audited_domain = DOMAIN
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_m365_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online"
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.m365.services.exchange.exchange_shared_mailbox_sign_in_disabled.exchange_shared_mailbox_sign_in_disabled.exchange_client",
|
||||
new=exchange_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.m365.services.entra.entra_service import User
|
||||
from prowler.providers.m365.services.exchange.exchange_service import (
|
||||
SharedMailbox,
|
||||
)
|
||||
from prowler.providers.m365.services.exchange.exchange_shared_mailbox_sign_in_disabled.exchange_shared_mailbox_sign_in_disabled import (
|
||||
exchange_shared_mailbox_sign_in_disabled,
|
||||
)
|
||||
|
||||
mailbox_disabled = SharedMailbox(
|
||||
name="Secure Mailbox",
|
||||
user_principal_name="secure@contoso.com",
|
||||
external_directory_object_id="11111111-1111-1111-1111-111111111111",
|
||||
identity="secure@contoso.com",
|
||||
)
|
||||
mailbox_enabled = SharedMailbox(
|
||||
name="Insecure Mailbox",
|
||||
user_principal_name="insecure@contoso.com",
|
||||
external_directory_object_id="22222222-2222-2222-2222-222222222222",
|
||||
identity="insecure@contoso.com",
|
||||
)
|
||||
mailbox_orphan = SharedMailbox(
|
||||
name="Unknown Mailbox",
|
||||
user_principal_name="unknown@contoso.com",
|
||||
external_directory_object_id="33333333-3333-3333-3333-333333333333",
|
||||
identity="unknown@contoso.com",
|
||||
)
|
||||
|
||||
exchange_client.shared_mailboxes = [
|
||||
mailbox_disabled,
|
||||
mailbox_enabled,
|
||||
mailbox_orphan,
|
||||
]
|
||||
|
||||
user_disabled = User(
|
||||
id="11111111-1111-1111-1111-111111111111",
|
||||
name="Secure Mailbox",
|
||||
on_premises_sync_enabled=False,
|
||||
account_enabled=False,
|
||||
)
|
||||
user_enabled = User(
|
||||
id="22222222-2222-2222-2222-222222222222",
|
||||
name="Insecure Mailbox",
|
||||
on_premises_sync_enabled=False,
|
||||
account_enabled=True,
|
||||
)
|
||||
|
||||
entra_client = mock.MagicMock()
|
||||
entra_client.users = {
|
||||
"11111111-1111-1111-1111-111111111111": user_disabled,
|
||||
"22222222-2222-2222-2222-222222222222": user_enabled,
|
||||
}
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.m365.services.exchange.exchange_shared_mailbox_sign_in_disabled.exchange_shared_mailbox_sign_in_disabled.entra_client",
|
||||
new=entra_client,
|
||||
):
|
||||
check = exchange_shared_mailbox_sign_in_disabled()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 3
|
||||
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== "Shared mailbox secure@contoso.com has sign-in blocked."
|
||||
)
|
||||
|
||||
assert result[1].status == "FAIL"
|
||||
assert (
|
||||
result[1].status_extended
|
||||
== "Shared mailbox insecure@contoso.com has sign-in enabled."
|
||||
)
|
||||
|
||||
assert result[2].status == "FAIL"
|
||||
assert (
|
||||
result[2].status_extended
|
||||
== "Shared mailbox unknown@contoso.com could not be found in Entra ID for verification."
|
||||
)
|
||||
Reference in New Issue
Block a user