-
Notifications
You must be signed in to change notification settings - Fork 180
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Enhanced Microsoft Exchange External Forwarding Detection (#1529)
Co-authored-by: Panther Bot <[email protected]>
- Loading branch information
1 parent
be4a294
commit 17515a1
Showing
6 changed files
with
277 additions
and
105 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
92 changes: 71 additions & 21 deletions
92
rules/microsoft_rules/microsoft_exchange_external_forwarding.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,31 +1,81 @@ | ||
from panther_config import config | ||
from panther_msft_helpers import is_external_address, m365_alert_context | ||
|
||
FORWARDING_PARAMETERS = { | ||
"ForwardingSmtpAddress", | ||
"ForwardTo", | ||
"ForwardingAddress", | ||
"RedirectTo", | ||
"ForwardAsAttachmentTo", | ||
} | ||
|
||
SUSPICIOUS_PATTERNS = { | ||
"DeliverToMailboxAndForward": "False", # Only forward, don't keep copy | ||
"DeleteMessage": "True", # Delete after forwarding | ||
"StopProcessingRules": "True", # Stop processing other rules | ||
} | ||
|
||
|
||
def rule(event): | ||
if event.get("operation", "") in ("Set-Mailbox", "New-InboxRule"): | ||
for param in event.get("parameters", []): | ||
if param.get("Name", "") in ("ForwardingSmtpAddress", "ForwardTo", "ForwardingAddress"): | ||
to_email = param.get("Value", "") | ||
if ( | ||
to_email.lower().replace("smtp:", "") | ||
in config.MS_EXCHANGE_ALLOWED_FORWARDING_DESTINATION_EMAILS | ||
): | ||
return False | ||
for domain in config.MS_EXCHANGE_ALLOWED_FORWARDING_DESTINATION_DOMAINS: | ||
if to_email.lower().replace("smtp:", "").endswith(domain): | ||
return False | ||
"""Alert on suspicious or external email forwarding configurations.""" | ||
# Skip non-forwarding related operations | ||
if event.get("operation") not in ("Set-Mailbox", "New-InboxRule"): | ||
return False | ||
|
||
# Get organization domains from userid and organizationname | ||
onmicrosoft_domain = event.get("organizationname", "").lower() | ||
userid = event.get("userid", "").lower() | ||
try: | ||
primary_domain = userid.split("@")[1] | ||
except (IndexError, AttributeError): | ||
primary_domain = onmicrosoft_domain if onmicrosoft_domain else None | ||
|
||
if not primary_domain: | ||
return True # Alert if we can't determine organization | ||
|
||
# Check each parameter | ||
for param in event.get("parameters", []): | ||
param_name = param.get("Name", "") | ||
param_value = param.get("Value", "") | ||
|
||
# Check for suspicious patterns | ||
if param_name in SUSPICIOUS_PATTERNS and param_value == SUSPICIOUS_PATTERNS[param_name]: | ||
return True | ||
|
||
# Check for external forwarding | ||
if param_name in FORWARDING_PARAMETERS and param_value: | ||
if is_external_address(param_value, primary_domain, onmicrosoft_domain): | ||
return True | ||
|
||
return False | ||
|
||
|
||
def title(event): | ||
to_email = "<no-recipient-found>" | ||
for param in event.get("parameters", []): | ||
if param.get("Name", "") in ("ForwardingSmtpAddress", "ForwardTo"): | ||
to_email = param.get("Value", "") | ||
break | ||
parameters = event.get("parameters", []) | ||
forwarding_addresses = [] | ||
suspicious_configs = [] | ||
|
||
for param in parameters: | ||
param_name = param.get("Name", "") | ||
param_value = param.get("Value", "") | ||
|
||
if param_name in FORWARDING_PARAMETERS and param_value: | ||
# Handle smtp: prefix | ||
if param_value.lower().startswith("smtp:"): | ||
param_value = param_value[5:] | ||
# Handle multiple addresses | ||
addresses = param_value.split(";") | ||
forwarding_addresses.extend(addr.strip() for addr in addresses if addr.strip()) | ||
if param_name in SUSPICIOUS_PATTERNS and param_value == SUSPICIOUS_PATTERNS[param_name]: | ||
suspicious_configs.append(f"{param_name}={param_value}") | ||
|
||
to_emails = ", ".join(forwarding_addresses) if forwarding_addresses else "<no-recipient-found>" | ||
suspicious_str = f" [Suspicious: {', '.join(suspicious_configs)}]" if suspicious_configs else "" | ||
|
||
return ( | ||
"Microsoft365: External Forwarding Created From " | ||
f"[{event.get('userid', '')}] to " | ||
f"[{to_email}]" | ||
f"Microsoft365: External Forwarding Created From [{event.get('userid', '')}] " | ||
f"to [{to_emails}]{suspicious_str}" | ||
) | ||
|
||
|
||
def alert_context(event): | ||
return m365_alert_context(event) |
Oops, something went wrong.