Detection rules › Panther
Panther rules: microsoft
| Rule | Severity |
|---|---|
| Microsoft Exchange External Forwarding | high |
| Microsoft Graph Passthrough | medium |
Microsoft Exchange External Forwarding
#Detects when a user creates email forwarding rules to external organizations in Microsoft Exchange Online. This can indicate data exfiltration attempts, where an attacker sets up forwarding to collect emails outside the organization. The rule detects both mailbox forwarding (Set-Mailbox) and inbox rules (New-InboxRule). The detection includes: 1. External organization forwarding based on domain comparison 2. Suspicious forwarding patterns like: - Forwarding without keeping a copy - Deleting messages after forwarding - Stopping rule processing after forwarding 3. Multiple forwarding destinations 4. Various forwarding methods (SMTP, redirect, forward as attachment)
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| Collection | |
| Exfiltration |
Detection logic
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):
"""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 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):
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 (
f"Microsoft365: External Forwarding Created From [{event.get('userid', '')}] "
f"to [{to_emails}]{suspicious_str}"
)
def severity(event):
if not is_suspicious_pattern(event):
return "LOW"
return "DEFAULT"
def alert_context(event):
return m365_alert_context(event)
def is_suspicious_pattern(event):
parameters = event.get("parameters", [])
for param in parameters:
param_name = param.get("Name", "")
param_value = param.get("Value", "")
if param_name in SUSPICIOUS_PATTERNS and param_value == SUSPICIOUS_PATTERNS[param_name]:
return True
return False
Rule specification
AnalysisType: rule
Description: >
Detects when a user creates email forwarding rules to external organizations in Microsoft Exchange Online.
This can indicate data exfiltration attempts, where an attacker sets up forwarding to collect emails outside
the organization. The rule detects both mailbox forwarding (Set-Mailbox) and inbox rules (New-InboxRule).
The detection includes:
1. External organization forwarding based on domain comparison
2. Suspicious forwarding patterns like:
- Forwarding without keeping a copy
- Deleting messages after forwarding
- Stopping rule processing after forwarding
3. Multiple forwarding destinations
4. Various forwarding methods (SMTP, redirect, forward as attachment)
DisplayName: "Microsoft Exchange External Forwarding"
Enabled: true
Filename: microsoft_exchange_external_forwarding.py
Reports:
MITRE ATT&CK:
- TA0003:T1137.005 # Persistence - Office Application Startup: Outlook Rules
- TA0009:T1114.003 # Collection - Email Collection: Email Forwarding Rule
- TA0010:T1020 # Exfiltration - Automated Exfiltration
Reference: https://learn.microsoft.com/en-us/microsoft-365/security/office-365-security/outbound-spam-policies-external-email-forwarding?view=o365-worldwide
Severity: High
Tags:
- Microsoft365
- Exchange
- Data Exfiltration
- Email Security
DedupPeriodMinutes: 60
LogTypes:
- Microsoft365.Audit.Exchange
RuleID: "Microsoft365.Exchange.External.Forwarding"
Threshold: 1
SummaryAttributes:
- userid
- parameters
- organizationname
Runbook: |
1. Investigate the forwarding configuration:
- Check if the forwarding is legitimate and approved
- Verify the destination addresses
- Review any suspicious patterns (deletion, no copy kept)
2. If unauthorized:
- Remove the forwarding rule
- Check for any data that may have been forwarded
- Review the user's recent activity
3. If authorized:
- Document the business justification
- Ensure it complies with security policies
- Monitor for any changes to the forwarding configuration
Stages and Predicates
Fires on Microsoft365.Audit.Exchange events when the condition below holds.
Condition
operationis one ofSet-Mailbox,New-InboxRule
This rule also runs imperative logic the parser cannot express as a filter. The conditions above are the structured part it could extract.
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
operation | in |
| field:"operation" kind:in |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
operation | Operation |
organization_id | OrganizationId |
client_ip | ClientIp |
extended_properties | ExtendedProperties |
modified_properties | ModifiedProperties |
application | Application |
actor | Actor |
userid |
Response runbook
1. Investigate the forwarding configuration:
- Check if the forwarding is legitimate and approved
- Verify the destination addresses
- Review any suspicious patterns (deletion, no copy kept)
2. If unauthorized:
- Remove the forwarding rule
- Check for any data that may have been forwarded
- Review the user's recent activity
3. If authorized:
- Document the business justification
- Ensure it complies with security policies
- Monitor for any changes to the forwarding configuration
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"clientip": "1.2.3.4",
"creationtime": "2022-12-12 22:19:00",
"externalaccess": false,
"id": "111-22-33",
"objectid": "homer.simpson",
"operation": "Set-Mailbox",
"organizationid": "11-aa-bb",
"organizationname": "simpsons.onmicrosoft.com",
"originatingserver": "QWERTY (1.2.3.4)",
"parameters": [
{
"Name": "Identity",
"Value": "homer.simpson@simpsons.onmicrosoft.com"
},
{
"Name": "ForwardingSmtpAddress",
"Value": "smtp:peter.griffin@familyguy.com"
},
{
"Name": "DeliverToMailboxAndForward",
"Value": "False"
}
],
"recordtype": 1,
"resultstatus": "True",
"userid": "homer.simpson@simpsons.onmicrosoft.com",
"userkey": "12345",
"usertype": 2,
"workload": "Exchange"
}
Microsoft Graph Passthrough
#This is a third-party alert feed, not a detection over modeled telemetry. Another security product raised the finding; this rule forwards or reshapes it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.
The Microsoft Graph security API federates queries to all onboarded security providers, including Azure AD Identity Protection, Microsoft 365, Microsoft Defender (Cloud, Endpoint, Identity) and Microsoft Sentinel
Detection logic
from panther_msft_helpers import msft_graph_alert_context
SEVERITY_MAP = {
"informational": "INFO",
"low": "LOW",
"medium": "MEDIUM",
"high": "HIGH",
}
def rule(event):
return event.get("status") == "new" and event.get("severity", "").lower() != "informational"
def title(event):
return f"Microsoft Graph Alert ({event.get('title')})"
def dedup(event):
return event.get("id")
def severity(event):
return SEVERITY_MAP.get(event.get("severity", "").lower(), "INFO")
def alert_context(event):
return msft_graph_alert_context(event)
Rule specification
AnalysisType: rule
Description: The Microsoft Graph security API federates queries to all onboarded security providers, including Azure AD Identity Protection, Microsoft 365, Microsoft Defender (Cloud, Endpoint, Identity) and Microsoft Sentinel
Reference: https://learn.microsoft.com/en-us/graph/api/resources/security-alert?view=graph-rest-1.0
DisplayName: "Microsoft Graph Passthrough"
Enabled: true
Filename: microsoft_graph_passthrough.py
Severity: Medium
DedupPeriodMinutes: 60
LogTypes:
- MicrosoftGraph.SecurityAlertV2
RuleID: "Microsoft.Graph.Passthrough"
Threshold: 1
Stages and Predicates
Fires on MicrosoftGraph.SecurityAlertV2 events when all of the conditions below hold.
Condition
statusisnewseverityis notinformational
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
severity | ne |
| field:"severity" kind:ne value:"informational" |
status | eq |
| field:"status" kind:eq value:"new" |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field |
|---|
category |
description |
evidence |
serviceSource |
productName |
incidentId |
alertWebUrl |
title |
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"category": "AnonymousLogin",
"createdDateTime": "2026-04-01T14:31:48.438Z",
"description": "Sign-in from an anonymous IP address (e.g. Tor browser, anonymizer VPNs)",
"evidence": [
{
"at_sign_odata_type": "#microsoft.graph.security.userEvidence",
"userAccount": {
"accountName": "homer.simpson",
"azureAdUserId": "011d5ede-0faa-4946-a25e-b2cd0c47a52c",
"domainName": "corporation.onmicrosoft.com",
"userPrincipalName": "homer.simpson@corporation.onmicrosoft.com"
}
},
{
"at_sign_odata_type": "#microsoft.graph.security.ipEvidence",
"countryLetterCode": "US",
"ipAddress": "185.220.103.6"
}
],
"firstActivityDateTime": "2026-04-01T14:31:48.438Z",
"id": "abcd12345efghijk6789",
"lastActivityDateTime": "2026-04-01T14:31:48.438Z",
"lastUpdateDateTime": "2026-04-01T14:34:56.229Z",
"productName": "Microsoft Defender for Identity",
"serviceSource": "microsoftDefenderForIdentity",
"severity": "medium",
"status": "new",
"tenantId": "12345-abcde-a1b2k3",
"title": "Anonymous IP address"
}