Panther high experimental python

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)

View Source

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

Field Validations

Loading…

Comments (0)

Loading comments...