mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-04 19:21:51 +00:00
feat(m365): add defender_zap_for_teams_enabled security check (#9838)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: HugoPBrito <hugopbrit@gmail.com>
This commit is contained in:
@@ -6,6 +6,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
|
||||
|
||||
### Added
|
||||
|
||||
- `defender_zap_for_teams_enabled` check for M365 provider [(#9838)](https://github.com/prowler-cloud/prowler/pull/9838)
|
||||
- `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)
|
||||
|
||||
@@ -693,7 +693,9 @@
|
||||
{
|
||||
"Id": "2.4.4",
|
||||
"Description": "Zero-hour auto purge (ZAP) is a protection feature that retroactively detects and neutralizes malware and high confidence phishing. When ZAP for Teams protection blocks a message, the message is blocked for everyone in the chat. The initial block happens right after delivery, but ZAP occurs up to 48 hours after delivery.",
|
||||
"Checks": [],
|
||||
"Checks": [
|
||||
"defender_zap_for_teams_enabled"
|
||||
],
|
||||
"Attributes": [
|
||||
{
|
||||
"Section": "2 Microsoft 365 Defender",
|
||||
|
||||
@@ -755,7 +755,9 @@
|
||||
{
|
||||
"Id": "2.4.4",
|
||||
"Description": "Zero-hour auto purge (ZAP) is a protection feature that retroactively detects and neutralizes malware and high confidence phishing. When ZAP for Teams protection blocks a message, the message is blocked for everyone in the chat. The initial block happens right after delivery, but ZAP occurs up to 48 hours after delivery.",
|
||||
"Checks": [],
|
||||
"Checks": [
|
||||
"defender_zap_for_teams_enabled"
|
||||
],
|
||||
"Attributes": [
|
||||
{
|
||||
"Section": "2 Microsoft 365 Defender",
|
||||
|
||||
@@ -112,12 +112,13 @@
|
||||
}
|
||||
],
|
||||
"Checks": [
|
||||
"entra_identity_protection_sign_in_risk_enabled",
|
||||
"entra_identity_protection_user_risk_enabled",
|
||||
"defender_antiphishing_policy_configured",
|
||||
"defender_antispam_outbound_policy_configured",
|
||||
"defender_malware_policy_notifications_internal_users_malware_enabled",
|
||||
"defender_antiphishing_policy_configured",
|
||||
"entra_admin_users_phishing_resistant_mfa_enabled"
|
||||
"defender_zap_for_teams_enabled",
|
||||
"entra_admin_users_phishing_resistant_mfa_enabled",
|
||||
"entra_identity_protection_sign_in_risk_enabled",
|
||||
"entra_identity_protection_user_risk_enabled"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -344,15 +345,15 @@
|
||||
}
|
||||
],
|
||||
"Checks": [
|
||||
"defender_antispam_outbound_policy_configured",
|
||||
"defender_malware_policy_notifications_internal_users_malware_enabled",
|
||||
"defender_malware_policy_common_attachments_filter_enabled",
|
||||
"defender_malware_policy_comprehensive_attachments_filter_applied",
|
||||
"defender_antispam_connection_filter_policy_empty_ip_allowlist",
|
||||
"defender_antispam_connection_filter_policy_safe_list_off",
|
||||
"defender_antispam_outbound_policy_configured",
|
||||
"defender_antispam_outbound_policy_forwarding_disabled",
|
||||
"defender_antispam_policy_inbound_no_allowed_domains"
|
||||
"defender_antispam_policy_inbound_no_allowed_domains",
|
||||
"defender_malware_policy_common_attachments_filter_enabled",
|
||||
"defender_malware_policy_comprehensive_attachments_filter_applied",
|
||||
"defender_malware_policy_notifications_internal_users_malware_enabled",
|
||||
"defender_zap_for_teams_enabled"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -368,11 +369,11 @@
|
||||
}
|
||||
],
|
||||
"Checks": [
|
||||
"defender_antispam_outbound_policy_configured",
|
||||
"defender_malware_policy_common_attachments_filter_enabled",
|
||||
"defender_malware_policy_comprehensive_attachments_filter_applied",
|
||||
"defender_malware_policy_notifications_internal_users_malware_enabled",
|
||||
"defender_antispam_outbound_policy_configured",
|
||||
"defender_malware_policy_notifications_internal_users_malware_enabled"
|
||||
"defender_zap_for_teams_enabled"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -691,6 +692,7 @@
|
||||
"defender_malware_policy_common_attachments_filter_enabled",
|
||||
"defender_malware_policy_comprehensive_attachments_filter_applied",
|
||||
"defender_malware_policy_notifications_internal_users_malware_enabled",
|
||||
"defender_zap_for_teams_enabled",
|
||||
"teams_external_domains_restricted",
|
||||
"teams_external_users_cannot_start_conversations"
|
||||
]
|
||||
|
||||
@@ -823,6 +823,26 @@ class M365PowerShell(PowerShellSession):
|
||||
"Get-SharingPolicy | ConvertTo-Json -Depth 10", json_parse=True
|
||||
)
|
||||
|
||||
def get_teams_protection_policy(self) -> dict:
|
||||
"""
|
||||
Get Teams Protection Policy.
|
||||
|
||||
Retrieves the Teams protection policy settings including Zero-hour auto purge (ZAP) configuration.
|
||||
|
||||
Returns:
|
||||
dict: Teams protection policy settings in JSON format.
|
||||
|
||||
Example:
|
||||
>>> get_teams_protection_policy()
|
||||
{
|
||||
"Identity": "Teams Protection Policy",
|
||||
"ZapEnabled": True
|
||||
}
|
||||
"""
|
||||
return self.execute(
|
||||
"Get-TeamsProtectionPolicy | ConvertTo-Json -Depth 10", json_parse=True
|
||||
)
|
||||
|
||||
def get_shared_mailboxes(self) -> dict:
|
||||
"""
|
||||
Get Exchange Online Shared Mailboxes.
|
||||
|
||||
@@ -8,7 +8,20 @@ from prowler.providers.m365.m365_provider import M365Provider
|
||||
|
||||
|
||||
class Defender(M365Service):
|
||||
"""
|
||||
Microsoft 365 Defender service implementation.
|
||||
|
||||
Provides access to Microsoft Defender for Office 365 configurations including
|
||||
malware policies, spam filtering, anti-phishing, and Teams protection settings.
|
||||
"""
|
||||
|
||||
def __init__(self, provider: M365Provider):
|
||||
"""
|
||||
Initialize the Defender service.
|
||||
|
||||
Args:
|
||||
provider: The M365 provider instance.
|
||||
"""
|
||||
super().__init__(provider)
|
||||
self.malware_policies = []
|
||||
self.outbound_spam_policies = {}
|
||||
@@ -20,6 +33,7 @@ class Defender(M365Service):
|
||||
self.inbound_spam_policies = []
|
||||
self.inbound_spam_rules = {}
|
||||
self.report_submission_policy = None
|
||||
self.teams_protection_policy = None
|
||||
if self.powershell:
|
||||
if self.powershell.connect_exchange_online():
|
||||
self.malware_policies = self._get_malware_filter_policy()
|
||||
@@ -33,6 +47,7 @@ class Defender(M365Service):
|
||||
self.inbound_spam_policies = self._get_inbound_spam_filter_policy()
|
||||
self.inbound_spam_rules = self._get_inbound_spam_filter_rule()
|
||||
self.report_submission_policy = self._get_report_submission_policy()
|
||||
self.teams_protection_policy = self._get_teams_protection_policy()
|
||||
self.powershell.close()
|
||||
|
||||
def _get_malware_filter_policy(self):
|
||||
@@ -350,6 +365,12 @@ class Defender(M365Service):
|
||||
return inbound_spam_rules
|
||||
|
||||
def _get_report_submission_policy(self):
|
||||
"""
|
||||
Retrieve the Defender report submission policy.
|
||||
|
||||
Returns:
|
||||
ReportSubmissionPolicy: The report submission policy configuration.
|
||||
"""
|
||||
logger.info("Microsoft365 - Getting Defender report submission policy...")
|
||||
report_submission_policy = None
|
||||
try:
|
||||
@@ -387,6 +408,28 @@ class Defender(M365Service):
|
||||
)
|
||||
return report_submission_policy
|
||||
|
||||
def _get_teams_protection_policy(self):
|
||||
"""
|
||||
Retrieve the Teams protection policy including ZAP settings.
|
||||
|
||||
Returns:
|
||||
TeamsProtectionPolicy: The Teams protection policy configuration.
|
||||
"""
|
||||
logger.info("Microsoft365 - Getting Teams protection policy...")
|
||||
teams_protection_policy = None
|
||||
try:
|
||||
policy = self.powershell.get_teams_protection_policy()
|
||||
if policy:
|
||||
teams_protection_policy = TeamsProtectionPolicy(
|
||||
identity=policy.get("Identity", ""),
|
||||
zap_enabled=policy.get("ZapEnabled", True),
|
||||
)
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
return teams_protection_policy
|
||||
|
||||
|
||||
class MalwarePolicy(BaseModel):
|
||||
enable_file_filter: bool
|
||||
@@ -470,6 +513,8 @@ class InboundSpamRule(BaseModel):
|
||||
|
||||
|
||||
class ReportSubmissionPolicy(BaseModel):
|
||||
"""Model for Defender report submission policy settings."""
|
||||
|
||||
report_junk_to_customized_address: bool
|
||||
report_not_junk_to_customized_address: bool
|
||||
report_phish_to_customized_address: bool
|
||||
@@ -478,3 +523,10 @@ class ReportSubmissionPolicy(BaseModel):
|
||||
report_phish_addresses: list[str]
|
||||
report_chat_message_enabled: bool
|
||||
report_chat_message_to_customized_address_enabled: bool
|
||||
|
||||
|
||||
class TeamsProtectionPolicy(BaseModel):
|
||||
"""Model for Teams protection policy settings including ZAP configuration."""
|
||||
|
||||
identity: str
|
||||
zap_enabled: bool
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"Provider": "m365",
|
||||
"CheckID": "defender_zap_for_teams_enabled",
|
||||
"CheckTitle": "Zero-hour auto purge (ZAP) protects Microsoft Teams from malware and phishing",
|
||||
"CheckType": [],
|
||||
"ServiceName": "defender",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "Teams Protection Policy",
|
||||
"ResourceGroup": "collaboration",
|
||||
"Description": "Zero-hour auto purge (ZAP) is a protection feature that retroactively detects and neutralizes **malware** and **high confidence phishing** in Teams messages.\n\nWhen ZAP blocks a message, it is blocked for everyone in the chat. The initial block happens right after delivery, but ZAP can occur up to 48 hours after delivery.",
|
||||
"Risk": "Without ZAP enabled, malicious content delivered to Teams chats remains accessible to users for up to 48 hours after delivery, even after being identified as harmful.\n\nThis extended exposure window could lead to:\n- **Malware infections** from weaponized attachments or links\n- **Phishing attacks** compromising user credentials and MFA tokens\n- **Lateral movement** as attackers exploit compromised accounts within the organization",
|
||||
"RelatedUrl": "",
|
||||
"AdditionalURLs": [
|
||||
"https://learn.microsoft.com/en-us/defender-office-365/zero-hour-auto-purge?view=o365-worldwide#zero-hour-auto-purge-zap-in-microsoft-teams",
|
||||
"https://learn.microsoft.com/en-us/defender-office-365/mdo-support-teams-about?view=o365-worldwide"
|
||||
],
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "Set-TeamsProtectionPolicy -Identity 'Teams Protection Policy' -ZapEnabled $true",
|
||||
"NativeIaC": "",
|
||||
"Other": "1. Navigate to Microsoft Defender https://security.microsoft.com/\n2. Click to expand System and select Settings > Email & collaboration > Microsoft Teams protection\n3. Set Zero-hour auto purge (ZAP) to On (Default)",
|
||||
"Terraform": ""
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Enable Zero-hour auto purge (ZAP) for Microsoft Teams to ensure malicious content is automatically removed from chats after detection, even if it was delivered before being identified as harmful.",
|
||||
"Url": "https://hub.prowler.com/check/defender_zap_for_teams_enabled"
|
||||
}
|
||||
},
|
||||
"Categories": [
|
||||
"email-security",
|
||||
"e5"
|
||||
],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": ""
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
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_zap_for_teams_enabled(Check):
|
||||
"""Check if Zero-hour auto purge (ZAP) is enabled for Microsoft Teams.
|
||||
|
||||
ZAP is a protection feature that retroactively detects and neutralizes malware
|
||||
and high confidence phishing in Teams messages.
|
||||
|
||||
- PASS: ZAP is enabled for Teams protection.
|
||||
- FAIL: ZAP is not enabled for Teams protection.
|
||||
|
||||
Attributes:
|
||||
metadata: Metadata associated with the check (inherited from Check).
|
||||
"""
|
||||
|
||||
def execute(self) -> List[CheckReportM365]:
|
||||
"""Execute the check for Teams ZAP protection status.
|
||||
|
||||
This method checks if Zero-hour auto purge (ZAP) is enabled for Microsoft Teams
|
||||
to ensure malicious content is automatically removed from chats after detection.
|
||||
|
||||
Returns:
|
||||
List[CheckReportM365]: A list of reports containing the result of the check.
|
||||
"""
|
||||
findings = []
|
||||
teams_protection_policy = defender_client.teams_protection_policy
|
||||
|
||||
if teams_protection_policy:
|
||||
report = CheckReportM365(
|
||||
metadata=self.metadata(),
|
||||
resource=teams_protection_policy,
|
||||
resource_name="Teams Protection Policy",
|
||||
resource_id="teamsProtectionPolicy",
|
||||
)
|
||||
|
||||
if teams_protection_policy.zap_enabled:
|
||||
report.status = "PASS"
|
||||
report.status_extended = (
|
||||
"Zero-hour auto purge (ZAP) is enabled for Microsoft Teams."
|
||||
)
|
||||
else:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = (
|
||||
"Zero-hour auto purge (ZAP) is not enabled for Microsoft Teams."
|
||||
)
|
||||
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
from unittest import mock
|
||||
|
||||
from prowler.providers.m365.services.defender.defender_service import (
|
||||
TeamsProtectionPolicy,
|
||||
)
|
||||
from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider
|
||||
|
||||
|
||||
class Test_defender_zap_for_teams_enabled:
|
||||
def test_zap_enabled_pass(self):
|
||||
"""Test PASS scenario when ZAP is enabled for Teams."""
|
||||
defender_client = mock.MagicMock()
|
||||
defender_client.audited_tenant = "audited_tenant"
|
||||
defender_client.audited_domain = DOMAIN
|
||||
defender_client.teams_protection_policy = TeamsProtectionPolicy(
|
||||
identity="Teams Protection Policy",
|
||||
zap_enabled=True,
|
||||
)
|
||||
|
||||
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_zap_for_teams_enabled.defender_zap_for_teams_enabled.defender_client",
|
||||
new=defender_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.m365.services.defender.defender_zap_for_teams_enabled.defender_zap_for_teams_enabled import (
|
||||
defender_zap_for_teams_enabled,
|
||||
)
|
||||
|
||||
check = defender_zap_for_teams_enabled()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== "Zero-hour auto purge (ZAP) is enabled for Microsoft Teams."
|
||||
)
|
||||
assert result[0].resource == defender_client.teams_protection_policy.dict()
|
||||
assert result[0].resource_name == "Teams Protection Policy"
|
||||
assert result[0].resource_id == "teamsProtectionPolicy"
|
||||
assert result[0].location == "global"
|
||||
|
||||
def test_zap_disabled_fail(self):
|
||||
"""Test FAIL scenario when ZAP is disabled for Teams."""
|
||||
defender_client = mock.MagicMock()
|
||||
defender_client.audited_tenant = "audited_tenant"
|
||||
defender_client.audited_domain = DOMAIN
|
||||
defender_client.teams_protection_policy = TeamsProtectionPolicy(
|
||||
identity="Teams Protection Policy",
|
||||
zap_enabled=False,
|
||||
)
|
||||
|
||||
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_zap_for_teams_enabled.defender_zap_for_teams_enabled.defender_client",
|
||||
new=defender_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.m365.services.defender.defender_zap_for_teams_enabled.defender_zap_for_teams_enabled import (
|
||||
defender_zap_for_teams_enabled,
|
||||
)
|
||||
|
||||
check = defender_zap_for_teams_enabled()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== "Zero-hour auto purge (ZAP) is not enabled for Microsoft Teams."
|
||||
)
|
||||
assert result[0].resource == defender_client.teams_protection_policy.dict()
|
||||
assert result[0].resource_name == "Teams Protection Policy"
|
||||
assert result[0].resource_id == "teamsProtectionPolicy"
|
||||
assert result[0].location == "global"
|
||||
|
||||
def test_teams_protection_policy_none(self):
|
||||
"""Test scenario when Teams protection policy is not available."""
|
||||
defender_client = mock.MagicMock()
|
||||
defender_client.audited_tenant = "audited_tenant"
|
||||
defender_client.audited_domain = DOMAIN
|
||||
defender_client.teams_protection_policy = None
|
||||
|
||||
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_zap_for_teams_enabled.defender_zap_for_teams_enabled.defender_client",
|
||||
new=defender_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.m365.services.defender.defender_zap_for_teams_enabled.defender_zap_for_teams_enabled import (
|
||||
defender_zap_for_teams_enabled,
|
||||
)
|
||||
|
||||
check = defender_zap_for_teams_enabled()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 0
|
||||
Reference in New Issue
Block a user