Skip to main content

Flag Spoofed Senders in Zimbra with a Sieve Filter

Invoice fraud rarely uses a clever forgery. It uses a display name. The message arrives as Anna Nowak <a.nowak.finance@gmail.com>, and because most mail clients show only the name, it reads as if it came from the colleague who signs off payments. A Sieve script can compare what the sender claims with where the message actually came from, and mark the mismatch.

Part of the series on Everyday Uses for Sieve Mail Filters in Zimbra. It builds on Tag External Mail in Zimbra, which marks all external mail; this one marks the subset that is pretending to be internal.

The rule that needs no maintenance

The strongest version of this filter needs no list of names at all. It looks for messages whose From: header mentions one of your own domains anywhere - in the display name, the address, or both - while the message was actually delivered from somewhere else.

require ["envelope", "editheader", "variables"];

if allof (header :contains "from" ["@example.com", "@example.net"],
not envelope :domain :is "from" ["example.com", "example.net"]) {

addheader "X-Sender-Spoofed" "yes";

if allof (header :matches "subject" "*",
not header :contains "subject" "[SUSPICIOUS]") {
replaceheader :newvalue "[SUSPICIOUS] ${1}" "Subject";
}
}

Nobody outside your organization has a legitimate reason to put your domain in the From: header of a message they send from their own server. That makes this rule unusually quiet for how much it catches.

How it works

The two tests look similar and are deliberately different.

header :contains "from" reads the raw From: header, display name and all. That is the point - address would parse the header and hand back only the address part, throwing away the display name, which is exactly where the forgery lives.

envelope :domain :is "from" reads the SMTP envelope sender, which is set by the server that actually delivered the message and is not something the author writes. When the two disagree, something is wrong.

Because the rule compares a claim against a fact, it does not need to know who works for you. It catches "Anna Nowak" <anna@evil.example> only if the attacker also writes your domain into the header, so pair it with the name-based rule below for full coverage.

Adding a list of names

The rule above misses a message from Anna Nowak <a.nowak.finance@gmail.com>, because nothing in it mentions your domain. Catching that needs the names of the people worth impersonating - usually management, finance and HR, which is a short list:

require ["envelope", "editheader", "variables"];

if allof (header :matches "from" ["*Anna Nowak*",
"*Jan Kowalski*",
"*Piotr Zielinski*"],
not envelope :domain :is "from" ["example.com", "example.net"]) {

addheader "X-Sender-Spoofed" "name";

if allof (header :matches "subject" "*",
not header :contains "subject" "[SUSPICIOUS]") {
replaceheader :newvalue "[SUSPICIOUS] ${1}" "Subject";
}
}

The * on both sides of each name matters. :matches compares against the whole header value, so "Anna Nowak" alone would only match a header that is exactly that, and the real header is something like Anna Nowak <anna@example.com>.

Watch out for encoded display names. A name containing anything outside ASCII arrives MIME-encoded, as =?UTF-8?Q?Anna_Nowakowska?= rather than as readable text. Whether your Zimbra version decodes that before the header test sees it is worth confirming with a real message rather than assuming. The safe workaround is to match on an ASCII-only fragment of each name - "*Kowalski*" rather than a name with diacritics.

Applying it

Both scripts use addheader and replaceheader, so they are admin scripts and need header editing switched on:

sudo su - zimbra

zmprov mc default zimbraSieveEditHeaderEnabled TRUE
zmprov md example.com zimbraAdminSieveScriptBefore "$(cat /tmp/spoof.sieve)"

In the admin console from Zimbra 9, the same thing lives under the domain's Advanced page, in Sieve filter rules: tick Edit header commands and paste the script into Sieve rules applied before end user filters.

The Advanced page of the default class of service in the Zimbra admin console, with the Sieve filter rules section: the Sieve reject action and Edit header commands checkboxes, and text boxes for the Sieve rules applied before and after end user filters

If the script saves but nothing happens to your test message, the cause is almost always that zimbraSieveEditHeaderEnabled is still FALSE - the header commands fail silently rather than reporting an error. Troubleshooting Zimbra Sieve Filters covers how to tell that apart from a condition that never matched.

A caught message keeps the display name the sender chose, which is the whole point: the name still reads like a colleague, and the subject in front of it now begins [SUSPICIOUS].

Marking without editing headers

If you would rather not enable header editing, flag and tag work in any script and need no configuration at all:

require ["envelope", "flag", "tag"];

if allof (header :contains "from" ["@example.com", "@example.net"],
not envelope :domain :is "from" ["example.com", "example.net"]) {
tag "Suspicious sender";
flag "priority";
}

This is less visible than a subject marker but takes effect immediately, which makes it a reasonable way to measure how often the rule would fire before you commit to marking subjects.

What it does not catch

  • A compromised internal account. Mail from a real account with a stolen password has a correct envelope and a correct From:. Nothing here will flag it.
  • Lookalike domains. exampIe.com with a capital I is not your domain, so neither rule fires. Catching those needs a list of the lookalikes, which is a different filter and an endless one.
  • Anything before delivery. Sieve runs when the message is stored, so this marks the recipient's copy only. Proper sender authentication - SPF, DKIM and a DMARC policy of reject on your own domain - is what stops the message being accepted in the first place, and it belongs upstream of Sieve rather than in it.

Treat this filter as the last line rather than the first. It is cheap, it needs no new software, and it catches the messages that got through everything else.

Next in this series

Back to Everyday Uses for Sieve Mail Filters in Zimbra, or on to File Suspected Spam by Its Score.