From 1baeefde3e01fe3949cc5a33ce389cb5392c7053 Mon Sep 17 00:00:00 2001 From: cw-sublime Date: Mon, 1 Jun 2026 21:55:12 -0400 Subject: [PATCH 1/7] Create detection rule for BEC tax document requests --- detection-rules/tax_w2_impersonation.yml | 74 ++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 detection-rules/tax_w2_impersonation.yml diff --git a/detection-rules/tax_w2_impersonation.yml b/detection-rules/tax_w2_impersonation.yml new file mode 100644 index 00000000000..1507728c53a --- /dev/null +++ b/detection-rules/tax_w2_impersonation.yml @@ -0,0 +1,74 @@ +name: "BEC Impersonation: Tax document request" +description: "Detects messages requesting W-2 tax documents or related tax information that exhibit authentication failures such as DMARC or SPF failures, or mismatched reply-to addresses. The rule identifies senders using common administrative local parts and filters for messages containing W-2 language combined with request entities detected through natural language processing." +type: "rule" +severity: "medium" +source: | + type.inbound + // and 150 < length(body.current_thread.text) < 750 + and sender.email.local_part in~ ( + "contact", + "no-reply", + "noreply", + "info", + "admin" + ) + and not ( + sender.email.domain.domain in $org_domains + and coalesce(headers.auth_summary.dmarc.pass, false) + ) + + // mismatched From and Reply-to + and ( + ( + length(headers.reply_to) > 0 + and all(headers.reply_to, + .email.domain.root_domain != sender.email.domain.root_domain + ) + ) + or not headers.auth_summary.dmarc.pass + or not headers.auth_summary.spf.pass + ) + + // W-2 Language with a request + and ( + strings.contains(strings.replace_confusables(subject.base), 'W-2') + or strings.icontains(subject.base, 'w2') + or strings.icontains(subject.base, 'wage') + or strings.icontains(subject.base, 'tax form') + or strings.icontains(subject.base, 'irs') + ) + and strings.contains(body.current_thread.text, 'W-2') + and any(ml.nlu_classifier(body.current_thread.text).entities, + .name == "request" + ) + and not ( + strings.ilike(sender.display_name, + "*Excel*", + "*SharePoint*", + "*PowerPoint*", + "*OneNote*", + "*Microsoft*" + ) + and coalesce(headers.auth_summary.dmarc.pass, false) + ) + and not any(ml.nlu_classifier(body.current_thread.text).intents, + .name == "benign" and .confidence == "high" + ) + + // negate highly trusted sender domains unless they fail DMARC authentication + and not ( + sender.email.domain.root_domain in $high_trust_sender_root_domains + and coalesce(headers.auth_summary.dmarc.pass, false) + ) + + +attack_types: + - "BEC/Fraud" +tactics_and_techniques: + - "Social engineering" + - "Spoofing" +detection_methods: + - "Content analysis" + - "Header analysis" + - "Natural Language Understanding" + - "Sender analysis" From e81d8858378236bde5c584489b8b96555da0f91e Mon Sep 17 00:00:00 2001 From: CI Bot Date: Tue, 2 Jun 2026 01:59:31 +0000 Subject: [PATCH 2/7] Auto-format MQL and add rule IDs --- detection-rules/tax_w2_impersonation.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/detection-rules/tax_w2_impersonation.yml b/detection-rules/tax_w2_impersonation.yml index 1507728c53a..b3ef91a0ec0 100644 --- a/detection-rules/tax_w2_impersonation.yml +++ b/detection-rules/tax_w2_impersonation.yml @@ -72,3 +72,4 @@ detection_methods: - "Header analysis" - "Natural Language Understanding" - "Sender analysis" +id: "4834a45e-6d70-5ad9-9043-024eea995e95" From dcf5226cdd5065f457ef6670e4e381a80f2f864a Mon Sep 17 00:00:00 2001 From: cw-sublime Date: Thu, 4 Jun 2026 20:22:34 -0400 Subject: [PATCH 3/7] Refine BEC impersonation rule for tax document requests --- detection-rules/tax_w2_impersonation.yml | 27 ++++++++++++------------ 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/detection-rules/tax_w2_impersonation.yml b/detection-rules/tax_w2_impersonation.yml index b3ef91a0ec0..172bcfb93b2 100644 --- a/detection-rules/tax_w2_impersonation.yml +++ b/detection-rules/tax_w2_impersonation.yml @@ -1,10 +1,10 @@ -name: "BEC Impersonation: Tax document request" +name: "BEC: Tax document request" description: "Detects messages requesting W-2 tax documents or related tax information that exhibit authentication failures such as DMARC or SPF failures, or mismatched reply-to addresses. The rule identifies senders using common administrative local parts and filters for messages containing W-2 language combined with request entities detected through natural language processing." type: "rule" severity: "medium" source: | type.inbound - // and 150 < length(body.current_thread.text) < 750 + and length(body.current_thread.text) < 500 and sender.email.local_part in~ ( "contact", "no-reply", @@ -19,25 +19,27 @@ source: | // mismatched From and Reply-to and ( - ( - length(headers.reply_to) > 0 - and all(headers.reply_to, - .email.domain.root_domain != sender.email.domain.root_domain - ) + length(headers.reply_to) > 0 + and all(headers.reply_to, + .email.domain.root_domain != sender.email.domain.root_domain ) - or not headers.auth_summary.dmarc.pass - or not headers.auth_summary.spf.pass ) // W-2 Language with a request and ( - strings.contains(strings.replace_confusables(subject.base), 'W-2') - or strings.icontains(subject.base, 'w2') + strings.contains(subject.base, 'W-2') or strings.icontains(subject.base, 'wage') or strings.icontains(subject.base, 'tax form') or strings.icontains(subject.base, 'irs') + or regex.icontains(subject.base, 'w2\b') + ) + // body text containing variations of "W2" + and ( + strings.icontains(body.current_thread.text, "w2") + or strings.icontains(body.current_thread.text, "W-2") + or strings.icontains(body.current_thread.text, "Ẇ-2's") + or strings.icontains(body.current_thread.text, "wage") ) - and strings.contains(body.current_thread.text, 'W-2') and any(ml.nlu_classifier(body.current_thread.text).entities, .name == "request" ) @@ -49,7 +51,6 @@ source: | "*OneNote*", "*Microsoft*" ) - and coalesce(headers.auth_summary.dmarc.pass, false) ) and not any(ml.nlu_classifier(body.current_thread.text).intents, .name == "benign" and .confidence == "high" From 9cba097822000f3e32017946d07980630b2e5bba Mon Sep 17 00:00:00 2001 From: cw-sublime Date: Fri, 5 Jun 2026 10:46:49 -0400 Subject: [PATCH 4/7] Fixing jobs --- detection-rules/tax_w2_impersonation.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/detection-rules/tax_w2_impersonation.yml b/detection-rules/tax_w2_impersonation.yml index 172bcfb93b2..4fca8a315bb 100644 --- a/detection-rules/tax_w2_impersonation.yml +++ b/detection-rules/tax_w2_impersonation.yml @@ -63,6 +63,7 @@ source: | ) + attack_types: - "BEC/Fraud" tactics_and_techniques: From 5e27ee8e6d3285b614045f6644ce7a5018f5858f Mon Sep 17 00:00:00 2001 From: cw-sublime Date: Fri, 5 Jun 2026 17:51:02 -0400 Subject: [PATCH 5/7] Refine W2 impersonation detection rule conditions --- detection-rules/tax_w2_impersonation.yml | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/detection-rules/tax_w2_impersonation.yml b/detection-rules/tax_w2_impersonation.yml index 4fca8a315bb..fc86550f618 100644 --- a/detection-rules/tax_w2_impersonation.yml +++ b/detection-rules/tax_w2_impersonation.yml @@ -4,7 +4,6 @@ type: "rule" severity: "medium" source: | type.inbound - and length(body.current_thread.text) < 500 and sender.email.local_part in~ ( "contact", "no-reply", @@ -35,10 +34,17 @@ source: | ) // body text containing variations of "W2" and ( - strings.icontains(body.current_thread.text, "w2") - or strings.icontains(body.current_thread.text, "W-2") - or strings.icontains(body.current_thread.text, "Ẇ-2's") - or strings.icontains(body.current_thread.text, "wage") + ( + strings.icontains(body.current_thread.text, "w2") + or strings.icontains(body.current_thread.text, "W-2") + or strings.icontains(body.current_thread.text, "Ẇ-2") + or strings.icontains(body.current_thread.text, "wage statements") + ) + or ( + length(headers.reply_to) > 0 + and all(headers.reply_to, network.whois(.email.domain).days_old <= 60) + and strings.icontains(body.current_thread.text, "W-2") + ) ) and any(ml.nlu_classifier(body.current_thread.text).entities, .name == "request" From dd1ec92b5fc9a085b539a31a69f48db601d69c79 Mon Sep 17 00:00:00 2001 From: cw-sublime Date: Tue, 16 Jun 2026 18:23:37 -0400 Subject: [PATCH 6/7] Refactor W2 impersonation detection rule conditions Removed conditions checking for specific Microsoft product names and domains in the W2 impersonation detection rule. --- detection-rules/tax_w2_impersonation.yml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/detection-rules/tax_w2_impersonation.yml b/detection-rules/tax_w2_impersonation.yml index fc86550f618..98d6f147a6a 100644 --- a/detection-rules/tax_w2_impersonation.yml +++ b/detection-rules/tax_w2_impersonation.yml @@ -49,14 +49,13 @@ source: | and any(ml.nlu_classifier(body.current_thread.text).entities, .name == "request" ) - and not ( - strings.ilike(sender.display_name, - "*Excel*", - "*SharePoint*", - "*PowerPoint*", - "*OneNote*", - "*Microsoft*" - ) + and sender.email.domain.root_domain not in ( + "excel.com", + "sharepoint.com", + "sharepointonline.com", + "powerpoint.com", + "onenote.com", + "microsoft.com" ) and not any(ml.nlu_classifier(body.current_thread.text).intents, .name == "benign" and .confidence == "high" From 457c33fe4cc96a0b833a4c5f2b9f489817cc099f Mon Sep 17 00:00:00 2001 From: cw-sublime Date: Wed, 17 Jun 2026 13:03:02 -0400 Subject: [PATCH 7/7] Update W-2 detection rules --- detection-rules/tax_w2_impersonation.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/detection-rules/tax_w2_impersonation.yml b/detection-rules/tax_w2_impersonation.yml index 98d6f147a6a..4fa8d7d64e9 100644 --- a/detection-rules/tax_w2_impersonation.yml +++ b/detection-rules/tax_w2_impersonation.yml @@ -36,14 +36,15 @@ source: | and ( ( strings.icontains(body.current_thread.text, "w2") - or strings.icontains(body.current_thread.text, "W-2") + or regex.icontains(body.current_thread.text, 'w-2\b') or strings.icontains(body.current_thread.text, "Ẇ-2") + or regex.icontains(body.current_thread.text, 'w-2[a-z]?\b') or strings.icontains(body.current_thread.text, "wage statements") ) or ( length(headers.reply_to) > 0 and all(headers.reply_to, network.whois(.email.domain).days_old <= 60) - and strings.icontains(body.current_thread.text, "W-2") + and strings.icontains(body.current_thread.text, "w-2") ) ) and any(ml.nlu_classifier(body.current_thread.text).entities,