feat(defender): add new check defender_antiphishing_policy_configured (#7453)

This commit is contained in:
Daniel Barranquero
2025-04-18 18:42:19 +02:00
committed by Adrián Jesús Peña Rodríguez
parent 972c88e421
commit 7800320189
8 changed files with 625 additions and 2 deletions
+1
View File
@@ -10,6 +10,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
- Add check for unused Service Accounts in GCP [(#7419)](https://github.com/prowler-cloud/prowler/pull/7419).
- Add Powershell to Microsoft365 [(#7331)](https://github.com/prowler-cloud/prowler/pull/7331).
- Add service Defender to Microsoft365 with one check for Common Attachments filter enabled in Malware Policies [(#7425)](https://github.com/prowler-cloud/prowler/pull/7425).
- Add check for Antiphishing Policy well configured in service Defender in M365 [(#7453)](https://github.com/prowler-cloud/prowler/pull/7453).
- Add check for Notifications for Internal users enabled in Malware Policies from service Defender in M365 [(#7435)](https://github.com/prowler-cloud/prowler/pull/7435).
- Support CLOUDSDK_AUTH_ACCESS_TOKEN in GCP [(#7495)](https://github.com/prowler-cloud/prowler/pull/7495).
- Add service Exchange to Microsoft365 with one check for Organizations Mailbox Auditing enabled [(#7408)](https://github.com/prowler-cloud/prowler/pull/7408)
@@ -207,6 +207,49 @@ class M365PowerShell(PowerShellSession):
"""
return self.execute("Get-MalwareFilterPolicy | ConvertTo-Json")
def get_antiphishing_policy(self) -> dict:
"""
Get Defender Antiphishing Policy.
Retrieves the current Defender anti-phishing policy settings.
Returns:
dict: Antiphishing policy settings in JSON format.
Example:
>>> get_antiphishing_policy()
{
"EnableSpoofIntelligence": true,
"AuthenticationFailAction": "Quarantine",
"DmarcRejectAction": "Quarantine",
"DmarcQuarantineAction": "Quarantine",
"EnableFirstContactSafetyTips": true,
"EnableUnauthenticatedSender": true,
"EnableViaTag": true,
"HonorDmarcPolicy": true,
"IsDefault": false
}
"""
return self.execute("Get-AntiPhishPolicy | ConvertTo-Json")
def get_antiphishing_rules(self) -> dict:
"""
Get Defender Antiphishing Rules.
Retrieves the current Defender anti-phishing rules.
Returns:
dict: Antiphishing rules in JSON format.
Example:
>>> get_antiphishing_rules()
{
"Name": "Rule1",
"State": Enabled,
}
"""
return self.execute("Get-AntiPhishRule | ConvertTo-Json")
def get_organization_config(self) -> dict:
"""
Get Exchange Online Organization Configuration.
@@ -0,0 +1,30 @@
{
"Provider": "m365",
"CheckID": "defender_antiphishing_policy_configured",
"CheckTitle": "Ensure anti-phishing policies are properly configured and active.",
"CheckType": [],
"ServiceName": "defender",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "low",
"ResourceType": "Defender Anti-Phishing Policy",
"Description": "Ensure that anti-phishing policies are created and configured for specific users, groups, or domains, taking precedence over the default policy. This check verifies the existence of rules within policies and validates specific policy settings such as spoof intelligence, DMARC actions, safety tips, and unauthenticated sender actions.",
"Risk": "Without anti-phishing policies, organizations may rely solely on default settings, which might not adequately protect against phishing attacks targeted at specific users, groups, or domains. This increases the risk of successful phishing attempts and potential data breaches.",
"RelatedUrl": "https://learn.microsoft.com/en-us/microsoft-365/security/office-365-security/set-up-anti-phishing-policies?view=o365-worldwide",
"Remediation": {
"Code": {
"CLI": "$params = @{Name='<policy_name>';PhishThresholdLevel=3;EnableTargetedUserProtection=$true;EnableOrganizationDomainsProtection=$true;EnableMailboxIntelligence=$true;EnableMailboxIntelligenceProtection=$true;EnableSpoofIntelligence=$true;TargetedUserProtectionAction='Quarantine';TargetedDomainProtectionAction='Quarantine';MailboxIntelligenceProtectionAction='Quarantine';TargetedUserQuarantineTag='DefaultFullAccessWithNotificationPolicy';MailboxIntelligenceQuarantineTag='DefaultFullAccessWithNotificationPolicy';TargetedDomainQuarantineTag='DefaultFullAccessWithNotificationPolicy';EnableFirstContactSafetyTips=$true;EnableSimilarUsersSafetyTips=$true;EnableSimilarDomainsSafetyTips=$true;EnableUnusualCharactersSafetyTips=$true;HonorDmarcPolicy=$true}; New-AntiPhishPolicy @params; New-AntiPhishRule -Name $params.Name -AntiPhishPolicy $params.Name -RecipientDomainIs (Get-AcceptedDomain).Name -Priority 0",
"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-phishing 5. Ensure policies have rules with the state set to 'on' and validate settings: spoof intelligence enabled, spoof intelligence action set to 'Quarantine', DMARC reject and quarantine actions, safety tips enabled, unauthenticated sender action enabled, show tag enabled, and honor DMARC policy enabled. If not, modify them to be as recommended.",
"Terraform": ""
},
"Recommendation": {
"Text": "Create and configure anti-phishing policies for specific users, groups, or domains to enhance protection against phishing attacks.",
"Url": "https://learn.microsoft.com/en-us/microsoft-365/security/office-365-security/set-up-anti-phishing-policies?view=o365-worldwide"
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -0,0 +1,59 @@
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_antiphishing_policy_configured(Check):
"""
Check if an anti-phishing policy is established and properly configured in the Defender service.
Attributes:
metadata: Metadata associated with the check (inherited from Check).
"""
def execute(self) -> List[CheckReportM365]:
"""
Execute the check to verify if an anti-phishing policy is established and properly configured.
This method checks the Defender anti-phishing policies to ensure they are configured
according to best practices.
Returns:
List[CheckReportM365]: A list of reports containing the result of the check.
"""
findings = []
for policy_name, policy in defender_client.antiphishing_policies.items():
report = CheckReportM365(
metadata=self.metadata(),
resource=policy,
resource_name="Defender Anti-Phishing Policy",
resource_id=policy_name,
)
report.status = "FAIL"
report.status_extended = (
f"Anti-phishing policy {policy_name} is not properly configured."
)
if (
not policy.default
and policy_name in defender_client.antiphising_rules
and defender_client.antiphising_rules[policy_name].state.lower()
== "enabled"
) or policy.default:
if (
policy.spoof_intelligence
and policy.spoof_intelligence_action.lower() == "quarantine"
and policy.dmarc_reject_action.lower() == "quarantine"
and policy.dmarc_quarantine_action.lower() == "quarantine"
and policy.safety_tips
and policy.unauthenticated_sender_action
and policy.show_tag
and policy.honor_dmarc_policy
):
report.status = "PASS"
report.status_extended = f"Anti-phishing policy {policy_name} is properly configured and enabled."
findings.append(report)
return findings
@@ -10,6 +10,8 @@ class Defender(M365Service):
super().__init__(provider)
self.powershell.connect_exchange_online()
self.malware_policies = self._get_malware_filter_policy()
self.antiphishing_policies = self._get_antiphising_policy()
self.antiphising_rules = self._get_antiphising_rules()
self.powershell.close()
def _get_malware_filter_policy(self):
@@ -39,9 +41,73 @@ class Defender(M365Service):
)
return malware_policies
def _get_antiphising_policy(self):
logger.info("Microsoft365 - Getting Defender antiphishing policy...")
antiphishing_policies = {}
try:
antiphishing_policy = self.powershell.get_antiphishing_policy()
if isinstance(antiphishing_policy, dict):
antiphishing_policy = [antiphishing_policy]
for policy in antiphishing_policy:
if policy:
antiphishing_policies[policy.get("Name", "")] = AntiphishingPolicy(
spoof_intelligence=policy.get("EnableSpoofIntelligence", True),
spoof_intelligence_action=policy.get(
"AuthenticationFailAction", ""
),
dmarc_reject_action=policy.get("DmarcRejectAction", ""),
dmarc_quarantine_action=policy.get("DmarcQuarantineAction", ""),
safety_tips=policy.get("EnableFirstContactSafetyTips", True),
unauthenticated_sender_action=policy.get(
"EnableUnauthenticatedSender", True
),
show_tag=policy.get("EnableViaTag", True),
honor_dmarc_policy=policy.get("HonorDmarcPolicy", True),
default=policy.get("IsDefault", False),
)
except Exception as error:
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
return antiphishing_policies
def _get_antiphising_rules(self):
logger.info("Microsoft365 - Getting Defender antiphishing rules...")
antiphishing_rules = {}
try:
antiphishing_rule = self.powershell.get_antiphishing_rules()
if isinstance(antiphishing_rule, dict):
antiphishing_rule = [antiphishing_rule]
for rule in antiphishing_rule:
if rule:
antiphishing_rules[rule.get("Name", "")] = AntiphishingRule(
state=rule.get("State", ""),
)
except Exception as error:
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
return antiphishing_rules
class DefenderMalwarePolicy(BaseModel):
enable_file_filter: bool
identity: str
enable_internal_sender_admin_notifications: bool
internal_sender_admin_address: str
class AntiphishingPolicy(BaseModel):
spoof_intelligence: bool
spoof_intelligence_action: str
dmarc_reject_action: str
dmarc_quarantine_action: str
safety_tips: bool
unauthenticated_sender_action: bool
show_tag: bool
honor_dmarc_policy: bool
default: bool
class AntiphishingRule(BaseModel):
state: str
@@ -0,0 +1,320 @@
from unittest import mock
from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider
class Test_defender_antiphishing_policy_configured:
def test_properly_configured_custom_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_antiphishing_policy_configured.defender_antiphishing_policy_configured.defender_client",
new=defender_client,
),
):
from prowler.providers.m365.services.defender.defender_antiphishing_policy_configured.defender_antiphishing_policy_configured import (
defender_antiphishing_policy_configured,
)
from prowler.providers.m365.services.defender.defender_service import (
AntiphishingPolicy,
AntiphishingRule,
)
defender_client.antiphishing_policies = {
"Policy1": AntiphishingPolicy(
spoof_intelligence=True,
spoof_intelligence_action="Quarantine",
dmarc_reject_action="Quarantine",
dmarc_quarantine_action="Quarantine",
safety_tips=True,
unauthenticated_sender_action=True,
show_tag=True,
honor_dmarc_policy=True,
default=False,
)
}
defender_client.antiphising_rules = {
"Policy1": AntiphishingRule(state="Enabled")
}
check = defender_antiphishing_policy_configured()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "Anti-phishing policy Policy1 is properly configured and enabled."
)
assert (
result[0].resource
== defender_client.antiphishing_policies["Policy1"].dict()
)
assert result[0].resource_name == "Defender Anti-Phishing Policy"
assert result[0].resource_id == "Policy1"
assert result[0].location == "global"
def test_not_properly_configured_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_antiphishing_policy_configured.defender_antiphishing_policy_configured.defender_client",
new=defender_client,
),
):
from prowler.providers.m365.services.defender.defender_antiphishing_policy_configured.defender_antiphishing_policy_configured import (
defender_antiphishing_policy_configured,
)
from prowler.providers.m365.services.defender.defender_service import (
AntiphishingPolicy,
AntiphishingRule,
)
defender_client.antiphishing_policies = {
"Policy2": AntiphishingPolicy(
spoof_intelligence=False,
spoof_intelligence_action="None",
dmarc_reject_action="None",
dmarc_quarantine_action="None",
safety_tips=False,
unauthenticated_sender_action=False,
show_tag=False,
honor_dmarc_policy=False,
default=False,
)
}
defender_client.antiphising_rules = {
"Policy2": AntiphishingRule(state="Enabled")
}
check = defender_antiphishing_policy_configured()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "Anti-phishing policy Policy2 is not properly configured."
)
assert (
result[0].resource
== defender_client.antiphishing_policies["Policy2"].dict()
)
assert result[0].resource_name == "Defender Anti-Phishing Policy"
assert result[0].resource_id == "Policy2"
assert result[0].location == "global"
def test_properly_configured_default_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_antiphishing_policy_configured.defender_antiphishing_policy_configured.defender_client",
new=defender_client,
),
):
from prowler.providers.m365.services.defender.defender_antiphishing_policy_configured.defender_antiphishing_policy_configured import (
defender_antiphishing_policy_configured,
)
from prowler.providers.m365.services.defender.defender_service import (
AntiphishingPolicy,
)
defender_client.antiphishing_policies = {
"Default": AntiphishingPolicy(
spoof_intelligence=True,
spoof_intelligence_action="Quarantine",
dmarc_reject_action="Quarantine",
dmarc_quarantine_action="Quarantine",
safety_tips=True,
unauthenticated_sender_action=True,
show_tag=True,
honor_dmarc_policy=True,
default=True,
)
}
defender_client.antiphising_rules = {}
check = defender_antiphishing_policy_configured()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "Anti-phishing policy Default is properly configured and enabled."
)
assert (
result[0].resource
== defender_client.antiphishing_policies["Default"].dict()
)
assert result[0].resource_name == "Defender Anti-Phishing Policy"
assert result[0].resource_id == "Default"
assert result[0].location == "global"
def test_default_policy_not_properly_configured(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_antiphishing_policy_configured.defender_antiphishing_policy_configured.defender_client",
new=defender_client,
),
):
from prowler.providers.m365.services.defender.defender_antiphishing_policy_configured.defender_antiphishing_policy_configured import (
defender_antiphishing_policy_configured,
)
from prowler.providers.m365.services.defender.defender_service import (
AntiphishingPolicy,
)
defender_client.antiphishing_policies = {
"Default": AntiphishingPolicy(
spoof_intelligence=False,
spoof_intelligence_action="None",
dmarc_reject_action="None",
dmarc_quarantine_action="None",
safety_tips=False,
unauthenticated_sender_action=False,
show_tag=False,
honor_dmarc_policy=False,
default=True,
)
}
defender_client.antiphising_rules = {}
check = defender_antiphishing_policy_configured()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "Anti-phishing policy Default is not properly configured."
)
assert (
result[0].resource
== defender_client.antiphishing_policies["Default"].dict()
)
assert result[0].resource_name == "Defender Anti-Phishing Policy"
assert result[0].resource_id == "Default"
assert result[0].location == "global"
def test_no_antiphishing_policies(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_antiphishing_policy_configured.defender_antiphishing_policy_configured.defender_client",
new=defender_client,
),
):
from prowler.providers.m365.services.defender.defender_antiphishing_policy_configured.defender_antiphishing_policy_configured import (
defender_antiphishing_policy_configured,
)
defender_client.antiphishing_policies = {}
defender_client.antiphising_rules = {}
check = defender_antiphishing_policy_configured()
result = check.execute()
assert result == []
def test_custom_policy_without_rule(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_antiphishing_policy_configured.defender_antiphishing_policy_configured.defender_client",
new=defender_client,
),
):
from prowler.providers.m365.services.defender.defender_antiphishing_policy_configured.defender_antiphishing_policy_configured import (
defender_antiphishing_policy_configured,
)
from prowler.providers.m365.services.defender.defender_service import (
AntiphishingPolicy,
)
defender_client.antiphishing_policies = {
"PolicyX": AntiphishingPolicy(
spoof_intelligence=True,
spoof_intelligence_action="Quarantine",
dmarc_reject_action="Quarantine",
dmarc_quarantine_action="Quarantine",
safety_tips=True,
unauthenticated_sender_action=True,
show_tag=True,
honor_dmarc_policy=True,
default=False,
)
}
defender_client.antiphising_rules = {}
check = defender_antiphishing_policy_configured()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "Anti-phishing policy PolicyX is not properly configured."
)
assert (
result[0].resource
== defender_client.antiphishing_policies["PolicyX"].dict()
)
assert result[0].resource_name == "Defender Anti-Phishing Policy"
assert result[0].resource_id == "PolicyX"
assert result[0].location == "global"
@@ -3,6 +3,8 @@ from unittest.mock import patch
from prowler.providers.m365.models import M365IdentityInfo
from prowler.providers.m365.services.defender.defender_service import (
AntiphishingPolicy,
AntiphishingRule,
Defender,
DefenderMalwarePolicy,
)
@@ -26,6 +28,44 @@ def mock_defender_get_malware_filter_policy(_):
]
def mock_defender_get_antiphising_policy(_):
return {
"Policy1": AntiphishingPolicy(
spoof_intelligence=True,
spoof_intelligence_action="Quarantine",
dmarc_reject_action="Reject",
dmarc_quarantine_action="Quarantine",
safety_tips=True,
unauthenticated_sender_action=True,
show_tag=True,
honor_dmarc_policy=True,
default=False,
),
"Policy2": AntiphishingPolicy(
spoof_intelligence=False,
spoof_intelligence_action="None",
dmarc_reject_action="None",
dmarc_quarantine_action="None",
safety_tips=False,
unauthenticated_sender_action=False,
show_tag=False,
honor_dmarc_policy=False,
default=True,
),
}
def mock_defender_get_antiphising_rules(_):
return {
"Policy1": AntiphishingRule(
state="Enabled",
),
"Policy2": AntiphishingRule(
state="Disabled",
),
}
class Test_Defender_Service:
def test_get_client(self):
with (
@@ -58,8 +98,6 @@ class Test_Defender_Service:
)
)
malware_policies = defender_client.malware_policies
assert malware_policies[0].enable_file_filter is False
assert malware_policies[0].identity == "Policy1"
assert (
malware_policies[0].enable_internal_sender_admin_notifications is False
)
@@ -73,3 +111,69 @@ class Test_Defender_Service:
malware_policies[1].internal_sender_admin_address
== "security@example.com"
)
defender_client.powershell.close()
@patch(
"prowler.providers.m365.services.defender.defender_service.Defender._get_antiphising_policy",
new=mock_defender_get_antiphising_policy,
)
def test_get_antiphising_policy(self):
with (
mock.patch(
"prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online"
),
):
defender_client = Defender(
set_mocked_m365_provider(
identity=M365IdentityInfo(tenant_domain=DOMAIN)
)
)
antiphishing_policies = defender_client.antiphishing_policies
assert antiphishing_policies["Policy1"].spoof_intelligence is True
assert (
antiphishing_policies["Policy1"].spoof_intelligence_action
== "Quarantine"
)
assert antiphishing_policies["Policy1"].dmarc_reject_action == "Reject"
assert (
antiphishing_policies["Policy1"].dmarc_quarantine_action == "Quarantine"
)
assert antiphishing_policies["Policy1"].safety_tips is True
assert (
antiphishing_policies["Policy1"].unauthenticated_sender_action is True
)
assert antiphishing_policies["Policy1"].show_tag is True
assert antiphishing_policies["Policy1"].honor_dmarc_policy is True
assert antiphishing_policies["Policy1"].default is False
assert antiphishing_policies["Policy2"].spoof_intelligence is False
assert antiphishing_policies["Policy2"].spoof_intelligence_action == "None"
assert antiphishing_policies["Policy2"].dmarc_reject_action == "None"
assert antiphishing_policies["Policy2"].dmarc_quarantine_action == "None"
assert antiphishing_policies["Policy2"].safety_tips is False
assert (
antiphishing_policies["Policy2"].unauthenticated_sender_action is False
)
assert antiphishing_policies["Policy2"].show_tag is False
assert antiphishing_policies["Policy2"].honor_dmarc_policy is False
assert antiphishing_policies["Policy2"].default is True
defender_client.powershell.close()
@patch(
"prowler.providers.m365.services.defender.defender_service.Defender._get_antiphising_rules",
new=mock_defender_get_antiphising_rules,
)
def test_get_antiphising_rules(self):
with (
mock.patch(
"prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online"
),
):
defender_client = Defender(
set_mocked_m365_provider(
identity=M365IdentityInfo(tenant_domain=DOMAIN)
)
)
antiphishing_rules = defender_client.antiphising_rules
assert antiphishing_rules["Policy1"].state == "Enabled"
assert antiphishing_rules["Policy2"].state == "Disabled"
defender_client.powershell.close()