Menü schliessen
Created: December 19th 2024
Last updated: January 13th 2025
Categories: Cyber Security,  Linux
Author: Marcus Fleuti

Spamassassin: Detect Domain Spoofing in Emails: A Custom Plugin to Identify Domain Impersonation Attempts

Donation Section: Background
Monero Badge: QR-Code
Monero Badge: Logo Icon Donate with Monero Badge: Logo Text
82uymVXLkvVbB4c4JpTd1tYm1yj1cKPKR2wqmw3XF8YXKTmY7JrTriP4pVwp2EJYBnCFdXhLq4zfFA6ic7VAWCFX5wfQbCC

Introduction

Email-based phishing attacks are becoming increasingly sophisticated, with attackers often impersonating legitimate domains to deceive recipients. One common technique is using a recipient's domain name within the sender's email address or display name while sending from a different domain. This article shows you how to implement a custom SpamAssassin plugin that detects such domain spoofing attempts.

Understanding Domain Spoofing Attacks

Domain spoofing in emails typically follows these patterns:

  • Using the recipient's domain name in the sender's email address (local part)
    Example: sending to employee@company.com from company-support@malicious.com
  • Including the recipient's domain name in the sender's display name
    Example: "Company Help Desk" <support@attacker.com>
  • Mimicking internal communication while sending from external domains
    Example: "IT Department - Company" <it@phishing-domain.com>

How Our Plugin Works

The plugin implements an intelligent detection mechanism that:

  1. Extracts domains from all recipient email addresses (To: and Cc: headers)
  2. Checks if any recipient's domain appears in either:
    • The sender's email address (before the @ symbol)
    • The sender's display name
  3. Verifies if the sender's actual domain matches the recipient's domain
    • If it matches (including subdomains), the email is considered legitimate
    • If it doesn't match, the email is flagged as potential spoofing
  4. Checks if one of the CC addresses' domain(s) are identical to the sender domain
    1. There's cases where a sender (e.g. person@sourcedomain.com) is sending an e-mail to person@targetdomain.com but also to himself as CC. In this case, the plugin would see the domain of the sender in the CC address which would match with the FROM at the same time and the filter would trigger. This caused some false positives which is why the filter returns 0 (=no spam) when the FROM domain equals to one of the CC domains. The filter will continue work fine because spammers very rarely send e-mails to the domains they use for spoofing. There's a slight chance though that certain spoofing e-mails might slip through. For example when the scammer is sending the same message to multiple @outlook.com recipients whilst sending the e-mail with a @outlook.com address himself. This weakens the filter a bit but we consider the elimination of false-positives to be more important.

Plugin Implementation

Step 1: Create the Plugin File

Create a new file named check_domain_spoofing.pm in your SpamAssassin plugins directory (typically /etc/spamassassin/ or /usr/share/spamassassin/) with the following content:

package Mail::SpamAssassin::Plugin::CheckDomainSpoofing;
use strict;
use warnings;
use Mail::SpamAssassin::Plugin;
use vars qw(@ISA);
@ISA = qw(Mail::SpamAssassin::Plugin);

# Configuration: List of whitelisted domains and email addresses (space-separated)
# These will be considered trusted senders and bypass all checks
# Examples:
#   Domains: 'trusteddomain.com company.org'
#   Full addresses: 'ceo@company.com admin@system.org'
#   Mixed: 'trusteddomain.com admin@company.com support@help.com'
my $WHITELISTED_ENTRIES = '';

# Constructor for the SpamAssassin plugin
sub new {
    my ($class, $mailsa) = @_;
    $class = ref($class) || $class;
    my $self = $class->SUPER::new($mailsa);
    bless ($self, $class);
    # Register our evaluation function with SpamAssassin
    $self->register_eval_rule('check_domain_spoofing');
    return $self;
}

# Main evaluation function that performs domain spoofing detection
# Returns:
#   1 if potential spoofing is detected
#   0 if the email appears legitimate
sub check_domain_spoofing {
    my ($self, $pms) = @_;
    
    # Step 1: Extract and validate the From address
    my $from_addr = lc($pms->get('From:addr'));
    return 0 unless $from_addr;  # No From address, skip checks
    
    # Step 2: Check whitelist
    # First check full email address match
    foreach my $whitelisted (split(/\s+/, lc($WHITELISTED_ENTRIES))) {
        # If the whitelisted entry contains @, treat it as a full email address
        if ($whitelisted =~ /\@/) {
            if (lc($from_addr) eq $whitelisted) {
                # Exact email address match found
                return 0;
            }
        }
        # Otherwise treat it as a domain name
        elsif ($from_addr =~ /\@(?:.*?\.)?([^.]+\.[^.]+)$/i) {
            my $sender_domain = lc($1);
            if ($sender_domain eq $whitelisted) {
                # Domain match found
                return 0;
            }
        }
    }
    
    # Step 3: Extract sender's domain for self-CC check
    return 0 unless $from_addr =~ /\@(?:.*?\.)?([^.]+\.[^.]+)$/;
    my $sender_domain = $1;
    
    # Step 4: Process CC addresses first to check for self-CC behavior
    # Spammers typically don't CC themselves, so if we find a self-CC,
    # we can consider the email legitimate
    my @cc_addresses = $pms->get('Cc:addr');
    my @recipient_addresses;  # Will hold all recipient addresses for later checks
    
    foreach my $cc_addr (@cc_addresses) {
        next unless $cc_addr;
        $cc_addr = lc($cc_addr);
        
        # Check if any CC address is from the same domain as sender
        if ($cc_addr =~ /\@(?:.*?\.)?([^.]+\.[^.]+)$/) {
            my $cc_domain = $1;
            if ($cc_domain eq $sender_domain) {
                # Found a self-CC, indicating legitimate sender behavior
                return 0;
            }
        }
        push @recipient_addresses, $cc_addr;
    }
    
    # Step 5: Add To: addresses to the recipient list
    my @to_addresses = $pms->get('To:addr');
    foreach my $addr (@to_addresses) {
        next unless $addr;
        push @recipient_addresses, lc($addr);
    }
    
    return 0 unless @recipient_addresses;  # No valid recipients found
    
    # Step 6: Get From display name for additional checking
    my $from_name = lc($pms->get('From:name'));
    
    # Step 7: Main spoofing detection logic
    foreach my $recipient_addr (@recipient_addresses) {
        # Extract domain and TLD from recipient address
        # Example: for "user@sub.example.com", extracts "example" and "com"
        next unless $recipient_addr =~ /\@(?:.*?\.)?([^.]+)\.([^.]+)$/;
        my ($recipient_domain, $recipient_tld) = ($1, $2);
        
        # Check for spoofing indicators:
        # 1. Recipient's domain appears in FROM local-part or display name
        # 2. BUT the actual FROM domain is different
        
        # First check: Does recipient domain appear in From address or name?
        next unless (
            # Check if domain appears in local-part of From address
            # Example: example.support@malicious.com
            ($from_addr && $from_addr =~ /^[^@]*\Q$recipient_domain\E[^@]*@/i) ||
            # Check if domain appears in From display name
            # Example: "Example Support" <support@malicious.com>
            ($from_name && $from_name =~ /\Q$recipient_domain\E/i)
        );
        
        # Second check: Is this actually legitimate same-domain communication?
        # If FROM address domain matches recipient domain, it's legitimate
        next if $from_addr =~ /\@(?:.*?\.)?$recipient_domain\.$recipient_tld$/i;
        
        # If we get here, we found a potential spoofing attempt:
        # - Recipient's domain appears in FROM local-part or display name
        # - BUT the actual FROM address is from a different domain
        return 1;
    }
    
    # No spoofing detected
    return 0;
}

1;

Step 2: Configure SpamAssassin

Add the following configuration to your local.cf file:

loadplugin Mail::SpamAssassin::Plugin::CheckDomainSpoofing check_domain_spoofing.pm

### Detect domain spoofing attempts where scammers use legitimate domain names in their FROM addresses
### This rule checks if either the sender's display name OR email address contains a recipient's domain name
### but sends from a different domain.
### 
### Examples that would be marked as SPAM:
### FROM: "ACME Corp Support" <support@phishing.com>    TO: employee@acme.com       -> SPAM (contains "acme" in display name)
### FROM: John Smith <john.smith.acme@gmail.com>        TO: employee@acme.com       -> SPAM (contains "acme" in email)
### FROM: "IT Team - acme.com" <it@malicious.net>      TO: employee@acme.com       -> SPAM (contains "acme" in display name)
###
### Examples that would pass (legitimate traffic):
### FROM: "John Smith" <john.smith@acme.com>           TO: employee@acme.com       -> OK (same domain)
### FROM: Newsletter <news@mail.acme.com>              TO: employee@acme.com       -> OK (subdomain)
### FROM: "Support Team" <support@vendor.com>          TO: employee@acme.com       -> OK (no "acme" anywhere)

header          DOMAIN_SPOOFING       eval:check_domain_spoofing()
describe        DOMAIN_SPOOFING       Sender impersonates recipient domain
score           DOMAIN_SPOOFING       5.0

How It Prevents False Positives

The plugin includes several mechanisms to prevent false positives:

  1. Subdomain Support: Legitimate emails from subdomains (e.g., mail.company.com) are recognized as valid senders for company.com recipients
  2. Full Domain Matching: The plugin properly handles domain parts, avoiding partial matches (e.g., "company1.com" won't match "company.com")
  3. Case Insensitive: All comparisons are case-insensitive to catch variations in capitalization
  4. Display Name and Email Checks: Checks both parts of the From header to catch different spoofing techniques

Comparison with Other Solutions

Feature This Plugin SPF DMARC
Detects Display Name Spoofing Yes No No
Catches Freemailer Abuse Yes No Limited
Easy to Implement Yes No No
Requires DNS Configuration No Yes Yes

Installation and Testing

  1. Copy the plugin file to your SpamAssassin plugins directory:
    sudo cp check_domain_spoofing.pm /etc/spamassassin/
  2. Add the configuration to local.cf:
    sudo nano /etc/spamassassin/local.cf
  3. Check the configuration:
    spamassassin --lint
  4. Restart SpamAssassin:
    sudo systemctl restart spamassassin
  5. Test with a sample email:
    spamassassin -D --test-mode < test_email.txt | grep DOMAIN_SPOOFING

Scoring Guidelines

The recommended score for this rule depends on your environment, where in most cases Emails with spam scores > 5.0 are considered to be spam:

  • Conservative ( 1.0-2.0 ): For initial testing or environments where some false positives might occur
  • Moderate ( 2.0-3.0 ): Recommended for most environments
  • Aggressive ( 3.0 - 5.0 ): For environments with strict security requirements and where domain spoofing strongly correlates with spam
  • Fatality ( 5.0+ ): For the "I'm feeling lucky" kind of system administrators 😉

Conclusion

This SpamAssassin plugin provides an effective defense against domain spoofing attacks by detecting attempts to impersonate legitimate domains in email communications. While it works well alongside existing email authentication methods like SPF, DKIM, and DMARC, it adds an extra layer of protection by catching spoofing attempts that these protocols might miss, particularly in the display name and local part of email addresses.

The plugin is especially effective against social engineering attacks where scammers try to establish trust by including the recipient's domain name in their sender information. By implementing this plugin, you can better protect your users from these increasingly common phishing attempts.