Could we help you? Please click the banners. We are young and desperately need the money
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.
Domain spoofing in emails typically follows these patterns:
The plugin implements an intelligent detection mechanism that:
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;
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
The plugin includes several mechanisms to prevent false positives:
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 |
sudo cp check_domain_spoofing.pm /etc/spamassassin/
sudo nano /etc/spamassassin/local.cf
spamassassin --lint
sudo systemctl restart spamassassin
spamassassin -D --test-mode < test_email.txt | grep DOMAIN_SPOOFING
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:
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.