From 098382117e7b042bbcc72dc3ebde77a6db765508 Mon Sep 17 00:00:00 2001 From: Daniel Barranquero <74871504+danibarranqueroo@users.noreply.github.com> Date: Tue, 22 Apr 2025 18:52:34 +0200 Subject: [PATCH] feat(defender): add new check `defender_antispam_connection_filter_policy_safe_list_off` (#7494) Co-authored-by: HugoPBrito Co-authored-by: MrCloudSec --- prowler/CHANGELOG.md | 1 + ...ection_filter_policy_empty_ip_allowlist.py | 4 +- .../__init__.py | 0 ..._filter_policy_safe_list_off.metadata.json | 30 ++++ ..._connection_filter_policy_safe_list_off.py | 43 ++++++ .../services/defender/defender_service.py | 2 + ...n_filter_policy_empty_ip_allowlist_test.py | 2 + ...ection_filter_policy_safe_list_off_test.py | 128 ++++++++++++++++++ .../defender/m365_defender_service_test.py | 2 + 9 files changed, 210 insertions(+), 2 deletions(-) create mode 100644 prowler/providers/m365/services/defender/defender_antispam_connection_filter_policy_safe_list_off/__init__.py create mode 100644 prowler/providers/m365/services/defender/defender_antispam_connection_filter_policy_safe_list_off/defender_antispam_connection_filter_policy_safe_list_off.metadata.json create mode 100644 prowler/providers/m365/services/defender/defender_antispam_connection_filter_policy_safe_list_off/defender_antispam_connection_filter_policy_safe_list_off.py create mode 100644 tests/providers/m365/services/defender/defender_antispam_connection_filter_policy_safe_list_off/defender_antispam_connection_filter_policy_safe_list_off_test.py diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index 210a5003d8..1d0c4f2887 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -18,6 +18,7 @@ All notable changes to the **Prowler SDK** are documented in this file. - Add check for Bypass Disable in every Mailbox for service Defender in M365 [(#7418)](https://github.com/prowler-cloud/prowler/pull/7418) - Add new check `teams_email_sending_to_channel_disabled` [(#7533)](https://github.com/prowler-cloud/prowler/pull/7533) - Add new check for AllowList not used in the Connection Filter Policy from service Defender in M365 [(#7492)](https://github.com/prowler-cloud/prowler/pull/7492) +- Add new check for SafeList not enabled in the Connection Filter Policy from service Defender in M365 [(#7492)](https://github.com/prowler-cloud/prowler/pull/7492) - Add new check for DKIM enabled for service Defender in M365 [(#7485)](https://github.com/prowler-cloud/prowler/pull/7485) ### Fixed diff --git a/prowler/providers/m365/services/defender/defender_antispam_connection_filter_policy_empty_ip_allowlist/defender_antispam_connection_filter_policy_empty_ip_allowlist.py b/prowler/providers/m365/services/defender/defender_antispam_connection_filter_policy_empty_ip_allowlist/defender_antispam_connection_filter_policy_empty_ip_allowlist.py index 054195ae7a..00ace7b17a 100644 --- a/prowler/providers/m365/services/defender/defender_antispam_connection_filter_policy_empty_ip_allowlist/defender_antispam_connection_filter_policy_empty_ip_allowlist.py +++ b/prowler/providers/m365/services/defender/defender_antispam_connection_filter_policy_empty_ip_allowlist/defender_antispam_connection_filter_policy_empty_ip_allowlist.py @@ -6,7 +6,7 @@ from prowler.providers.m365.services.defender.defender_client import defender_cl class defender_antispam_connection_filter_policy_empty_ip_allowlist(Check): """ - Check if the IP Allowlist is not used in the Defender connection filter policy. + Check if the IP Allowlist is not used in the Antispam Connection Filter Policy. Attributes: metadata: Metadata associated with the check (inherited from Check). @@ -16,7 +16,7 @@ class defender_antispam_connection_filter_policy_empty_ip_allowlist(Check): """ Execute the check to verify if the IP Allowlist is not used. - This method checks the Defender connection filter policy to determine if the + This method checks the Antispam Connection Filter Policy to determine if the IP Allowlist is empty or undefined. Returns: diff --git a/prowler/providers/m365/services/defender/defender_antispam_connection_filter_policy_safe_list_off/__init__.py b/prowler/providers/m365/services/defender/defender_antispam_connection_filter_policy_safe_list_off/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/m365/services/defender/defender_antispam_connection_filter_policy_safe_list_off/defender_antispam_connection_filter_policy_safe_list_off.metadata.json b/prowler/providers/m365/services/defender/defender_antispam_connection_filter_policy_safe_list_off/defender_antispam_connection_filter_policy_safe_list_off.metadata.json new file mode 100644 index 0000000000..cf7234e5da --- /dev/null +++ b/prowler/providers/m365/services/defender/defender_antispam_connection_filter_policy_safe_list_off/defender_antispam_connection_filter_policy_safe_list_off.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "m365", + "CheckID": "defender_antispam_connection_filter_policy_safe_list_off", + "CheckTitle": "Ensure the default connection filter policy has the SafeList setting disabled", + "CheckType": [], + "ServiceName": "defender", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "medium", + "ResourceType": "Defender Anti-Spam Policy", + "Description": "This check ensures that the EnableSafeList setting in the default connection filter policy is set to False. The safe list, managed dynamically by Microsoft, allows emails from listed IPs to bypass spam filtering and sender authentication checks, posing a security risk.", + "Risk": "If the safe list is enabled, emails from IPs on this list can bypass essential security checks (SPF, DKIM, DMARC), potentially allowing malicious emails to be delivered directly to users' inboxes.", + "RelatedUrl": "https://learn.microsoft.com/en-us/defender-office-365/connection-filter-policies-configure", + "Remediation": { + "Code": { + "CLI": "Set-HostedConnectionFilterPolicy -Identity Default -EnableSafeList $false", + "NativeIaC": "", + "Other": "1. Navigate to Microsoft 365 Defender https://security.microsoft.com. 2. Click to expand Email & collaboration and select Policies & rules. 3. On the Policies & rules page select Threat policies. 4. Under Policies, select Anti-spam and click on the Connection filter policy (Default). 5. Disable the safe list option. 6. Click Save.", + "Terraform": "" + }, + "Recommendation": { + "Text": "Ensure that the EnableSafeList setting in your connection filter policy is set to False to prevent bypassing essential security checks.", + "Url": "https://learn.microsoft.com/en-us/defender-office-365/create-safe-sender-lists-in-office-365#use-the-ip-allow-list" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/m365/services/defender/defender_antispam_connection_filter_policy_safe_list_off/defender_antispam_connection_filter_policy_safe_list_off.py b/prowler/providers/m365/services/defender/defender_antispam_connection_filter_policy_safe_list_off/defender_antispam_connection_filter_policy_safe_list_off.py new file mode 100644 index 0000000000..7989cefd8c --- /dev/null +++ b/prowler/providers/m365/services/defender/defender_antispam_connection_filter_policy_safe_list_off/defender_antispam_connection_filter_policy_safe_list_off.py @@ -0,0 +1,43 @@ +from typing import List + +from prowler.lib.check.models import Check, CheckReportM365 +from prowler.providers.m365.services.defender.defender_client import defender_client + + +class defender_antispam_connection_filter_policy_safe_list_off(Check): + """ + Check if the Safe List is off in the Antispam Connection Filter Policy. + + Attributes: + metadata: Metadata associated with the check (inherited from Check). + """ + + def execute(self) -> List[CheckReportM365]: + """ + Execute the check to verify if the Safe List is off. + + This method checks the Antispam Connection Filter Policy to determine if the + Safe List is disabled. + + Returns: + List[CheckReportM365]: A list of reports containing the result of the check. + """ + findings = [] + policy = defender_client.connection_filter_policy + if policy: + report = CheckReportM365( + metadata=self.metadata(), + resource=policy, + resource_name="Defender Antispam Connection Filter Policy", + resource_id=policy.identity, + ) + report.status = "PASS" + report.status_extended = f"Safe List is disabled in the Antispam Connection Filter Policy {policy.identity}." + + if policy.enable_safe_list: + report.status = "FAIL" + report.status_extended = f"Safe List is not disabled in the Antispam Connection Filter Policy {policy.identity}." + + findings.append(report) + + return findings diff --git a/prowler/providers/m365/services/defender/defender_service.py b/prowler/providers/m365/services/defender/defender_service.py index 1528dcd4b0..51f53a0606 100644 --- a/prowler/providers/m365/services/defender/defender_service.py +++ b/prowler/providers/m365/services/defender/defender_service.py @@ -104,6 +104,7 @@ class Defender(M365Service): connection_filter_policy = ConnectionFilterPolicy( ip_allow_list=policy.get("IPAllowList", []), identity=policy.get("Identity", ""), + enable_safe_list=policy.get("EnableSafeList", False), ) except Exception as error: logger.error( @@ -205,6 +206,7 @@ class AntiphishingRule(BaseModel): class ConnectionFilterPolicy(BaseModel): ip_allow_list: list identity: str + enable_safe_list: bool class DkimConfig(BaseModel): diff --git a/tests/providers/m365/services/defender/defender_antispam_connection_filter_policy_empty_ip_allowlist/defender_antispam_connection_filter_policy_empty_ip_allowlist_test.py b/tests/providers/m365/services/defender/defender_antispam_connection_filter_policy_empty_ip_allowlist/defender_antispam_connection_filter_policy_empty_ip_allowlist_test.py index e4dfeb772b..83f10ccea9 100644 --- a/tests/providers/m365/services/defender/defender_antispam_connection_filter_policy_empty_ip_allowlist/defender_antispam_connection_filter_policy_empty_ip_allowlist_test.py +++ b/tests/providers/m365/services/defender/defender_antispam_connection_filter_policy_empty_ip_allowlist/defender_antispam_connection_filter_policy_empty_ip_allowlist_test.py @@ -32,6 +32,7 @@ class Test_defender_antispam_connection_filter_policy_empty_ip_allowlist: defender_client.connection_filter_policy = ConnectionFilterPolicy( ip_allow_list=[], identity="Default", + enable_safe_list=False, ) check = defender_antispam_connection_filter_policy_empty_ip_allowlist() @@ -78,6 +79,7 @@ class Test_defender_antispam_connection_filter_policy_empty_ip_allowlist: defender_client.connection_filter_policy = ConnectionFilterPolicy( ip_allow_list=["192.168.0.1", "10.0.0.5"], identity="Default", + enable_safe_list=False, ) check = defender_antispam_connection_filter_policy_empty_ip_allowlist() diff --git a/tests/providers/m365/services/defender/defender_antispam_connection_filter_policy_safe_list_off/defender_antispam_connection_filter_policy_safe_list_off_test.py b/tests/providers/m365/services/defender/defender_antispam_connection_filter_policy_safe_list_off/defender_antispam_connection_filter_policy_safe_list_off_test.py new file mode 100644 index 0000000000..239f5aaf2f --- /dev/null +++ b/tests/providers/m365/services/defender/defender_antispam_connection_filter_policy_safe_list_off/defender_antispam_connection_filter_policy_safe_list_off_test.py @@ -0,0 +1,128 @@ +from unittest import mock + +from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider + + +class Test_defender_antispam_connection_filter_policy_safe_list_off: + def test_safe_list_off(self): + defender_client = mock.MagicMock() + defender_client.audited_tenant = "audited_tenant" + defender_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.defender.defender_antispam_connection_filter_policy_safe_list_off.defender_antispam_connection_filter_policy_safe_list_off.defender_client", + new=defender_client, + ), + ): + from prowler.providers.m365.services.defender.defender_antispam_connection_filter_policy_safe_list_off.defender_antispam_connection_filter_policy_safe_list_off import ( + defender_antispam_connection_filter_policy_safe_list_off, + ) + from prowler.providers.m365.services.defender.defender_service import ( + ConnectionFilterPolicy, + ) + + defender_client.connection_filter_policy = ConnectionFilterPolicy( + ip_allow_list=[], + identity="Default", + enable_safe_list=False, + ) + + check = defender_antispam_connection_filter_policy_safe_list_off() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "Safe List is disabled in the Antispam Connection Filter Policy Default." + ) + assert result[0].resource == defender_client.connection_filter_policy.dict() + assert ( + result[0].resource_name == "Defender Antispam Connection Filter Policy" + ) + assert result[0].resource_id == "Default" + assert result[0].location == "global" + + def test_safe_list_on(self): + defender_client = mock.MagicMock() + defender_client.audited_tenant = "audited_tenant" + defender_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.defender.defender_antispam_connection_filter_policy_safe_list_off.defender_antispam_connection_filter_policy_safe_list_off.defender_client", + new=defender_client, + ), + ): + from prowler.providers.m365.services.defender.defender_antispam_connection_filter_policy_safe_list_off.defender_antispam_connection_filter_policy_safe_list_off import ( + defender_antispam_connection_filter_policy_safe_list_off, + ) + from prowler.providers.m365.services.defender.defender_service import ( + ConnectionFilterPolicy, + ) + + defender_client.connection_filter_policy = ConnectionFilterPolicy( + ip_allow_list=[], + identity="Default", + enable_safe_list=True, + ) + + check = defender_antispam_connection_filter_policy_safe_list_off() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "Safe List is not disabled in the Antispam Connection Filter Policy Default." + ) + assert result[0].resource == defender_client.connection_filter_policy.dict() + assert ( + result[0].resource_name == "Defender Antispam Connection Filter Policy" + ) + assert result[0].resource_id == "Default" + assert result[0].location == "global" + + def test_no_connection_filter_policy(self): + defender_client = mock.MagicMock() + defender_client.audited_tenant = "audited_tenant" + defender_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.defender.defender_antispam_connection_filter_policy_safe_list_off.defender_antispam_connection_filter_policy_safe_list_off.defender_client", + new=defender_client, + ), + ): + from prowler.providers.m365.services.defender.defender_antispam_connection_filter_policy_safe_list_off.defender_antispam_connection_filter_policy_safe_list_off import ( + defender_antispam_connection_filter_policy_safe_list_off, + ) + + defender_client.connection_filter_policy = None + + check = defender_antispam_connection_filter_policy_safe_list_off() + result = check.execute() + + assert len(result) == 0 diff --git a/tests/providers/m365/services/defender/m365_defender_service_test.py b/tests/providers/m365/services/defender/m365_defender_service_test.py index 2d3de6e0f6..28d9d47919 100644 --- a/tests/providers/m365/services/defender/m365_defender_service_test.py +++ b/tests/providers/m365/services/defender/m365_defender_service_test.py @@ -74,6 +74,7 @@ def mock_defender_get_connection_filter_policy(_): return ConnectionFilterPolicy( ip_allow_list=[], identity="Default", + enable_safe_list=False, ) @@ -244,6 +245,7 @@ class Test_Defender_Service: connection_filter_policy = defender_client.connection_filter_policy assert connection_filter_policy.ip_allow_list == [] assert connection_filter_policy.identity == "Default" + assert connection_filter_policy.enable_safe_list is False defender_client.powershell.close() @patch(