From a8707e410087e0ace4f2a90bec7811694f66a402 Mon Sep 17 00:00:00 2001 From: John Farina Date: Fri, 29 May 2026 17:00:03 -0400 Subject: [PATCH 1/2] Update impersonation_docusign.yml --- detection-rules/impersonation_docusign.yml | 44 +++++++++++++++------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/detection-rules/impersonation_docusign.yml b/detection-rules/impersonation_docusign.yml index 07654a57f89..fad6a7c01c3 100644 --- a/detection-rules/impersonation_docusign.yml +++ b/detection-rules/impersonation_docusign.yml @@ -10,10 +10,10 @@ source: | and ( // orgs can have docusign.company.com strings.ilike(sender.email.email, '*docusign.net*', '*docusign.com*') - + // if the above is true, you'll see a "via Docusign" or strings.ilike(sender.display_name, '*docusign*') - + // detects 1 character variations, // such as DocuSlgn (with an "L" instead of an "I") or strings.ilevenshtein(sender.display_name, "docusign") == 1 @@ -53,7 +53,9 @@ source: | 'Please use the link above to Docusign' ) or strings.icontains(body.current_thread.text, 'Review on Docusign') - or strings.icontains(body.current_thread.text, 'Completed with Docusign') + or strings.icontains(body.current_thread.text, + 'Completed with Docusign' + ) or strings.icontains(body.current_thread.text, 'Completed on Docusign') or strings.icontains(body.current_thread.text, 'Complete with Docusign') or strings.icontains(body.current_thread.text, @@ -81,7 +83,9 @@ source: | or strings.icontains(body.current_thread.text, 'review via DocuSign Electronic Signature' ) - or strings.icontains(body.current_thread.text, 'sent to you by DocuSign') + or strings.icontains(body.current_thread.text, + 'sent to you by DocuSign' + ) or strings.icontains(body.current_thread.text, 'Processed by DocuSign') or strings.icontains(body.current_thread.text, 'Please read and sign the document' @@ -113,7 +117,7 @@ source: | 'Sign\s*(?:and\s*|&\s*)Return.{0,40}docusign', 'Sign\s*(?:and\s*|&\s*)Return.docusign.{0,40}' ) - + // additional context from subject.subject or strings.icontains(subject.subject, 'complete with docusign') or strings.icontains(subject.subject, 'signature request') @@ -145,7 +149,9 @@ source: | regex.icontains(body.html.raw, 'Powered by.{0,6}(?:\s*<\/?[^\>]+\>\s*)+]+(?:src="https:\/\/docucdn-a\.akamaihd\.net\/[^\"]+email-logo.png"|alt="DocuSign")' ) - or regex.icontains(body.current_thread.text, 'Powered by\s*DocuSign') + or regex.icontains(body.current_thread.text, + 'Powered by\s*DocuSign' + ) ) // limit it to where the powered by is within the current thread and strings.icontains(body.current_thread.text, 'Powered by') @@ -194,7 +200,7 @@ source: | or strings.icontains(body.current_thread.text, 'a secure link to DocuSign' ) - + // footer links or ( length(filter(body.links, @@ -259,7 +265,7 @@ source: | ) // image contains an alt text of docusign or any(html.xpath(body.html, '//img/@alt').nodes, .raw =~ "docusign") - + // Basic variations with HTML encoding // use of regex extract allows or any(regex.iextract(body.html.raw, @@ -273,14 +279,14 @@ source: | ), .full_match !~ "docusign" ) - + // Look for HTML entities for each letter in sequence or any(regex.iextract(body.html.raw, '(?:D|D|D)(?:o|о|o|o|о|о|ο|ο)(?:c|с|c|c|с|с|ϲ|ç|g|ĉ|ĉ)(?:u|u|u|у|у|υ|υ)(?:s|s|s|ѕ|ѕ)(?:i|і|i|i|і|і|ı|ı)(?:g|g|g|ɡ|ɡ|ğ|ğ)(?:n|n|n|н|н|η|η)' ), .full_match !~ "docusign" ) - + // Handle repeated HTML entities and variation selectors (using Unicode class) or any(regex.iextract(body.html.raw, 'D(?:&#[0-9]{1,7};)*\p{Mn}*o(?:&#[0-9]{1,7};)*\p{Mn}*c(?:&#[0-9]{1,7};)*\p{Mn}*u(?:&#[0-9]{1,7};)*\p{Mn}*[Ss](?:&#[0-9]{1,7};)*\p{Mn}*i(?:&#[0-9]{1,7};)*\p{Mn}*g(?:&#[0-9]{1,7};)*\p{Mn}*n' @@ -301,8 +307,16 @@ source: | ) ) ) + or ( + strings.icontains(body.current_thread.text, 'Docusign') + and ( + regex.icontains(body.html.raw, '[^<]*Easearch[^<]*') + or regex.icontains(body.html.raw, '[^<]*(?:Docusign|Document)') + or regex.icontains(body.html.raw, '{(?:domain|randomNumber\d?)}') + ) + ) ) - + // identifies the main CTA in the email, eg "Review now" or "Review document" // this should always be a known docusign domain, // even with branded docusign subdomains @@ -347,6 +361,8 @@ source: | or strings.icontains(.display_text, "Document") ) ) + or strings.icontains(.display_text, "complete tasks") + or strings.icontains(.display_text, "View and complete") ) ), // ensure those links aren't legit @@ -399,13 +415,13 @@ source: | ) ) ) - + // negate highly trusted sender domains if they pass DMARC authentication and not ( sender.email.domain.root_domain in $high_trust_sender_root_domains and coalesce(headers.auth_summary.dmarc.pass, false) ) - + // negation for messages traversing docusign.net // happens with custom sender domains and not ( @@ -413,7 +429,7 @@ source: | and headers.auth_summary.spf.pass and headers.auth_summary.dmarc.pass ) - + // adding negation for messages originating from docusigns api // and the sender.display.name contains "via" and not ( From 637d2d07f8770e6b003485325ac10e838e02121d Mon Sep 17 00:00:00 2001 From: CI Bot Date: Fri, 29 May 2026 21:03:20 +0000 Subject: [PATCH 2/2] Auto-format MQL and add rule IDs --- detection-rules/impersonation_docusign.yml | 34 +++++++++------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/detection-rules/impersonation_docusign.yml b/detection-rules/impersonation_docusign.yml index fad6a7c01c3..5d526b0924f 100644 --- a/detection-rules/impersonation_docusign.yml +++ b/detection-rules/impersonation_docusign.yml @@ -10,10 +10,10 @@ source: | and ( // orgs can have docusign.company.com strings.ilike(sender.email.email, '*docusign.net*', '*docusign.com*') - + // if the above is true, you'll see a "via Docusign" or strings.ilike(sender.display_name, '*docusign*') - + // detects 1 character variations, // such as DocuSlgn (with an "L" instead of an "I") or strings.ilevenshtein(sender.display_name, "docusign") == 1 @@ -53,9 +53,7 @@ source: | 'Please use the link above to Docusign' ) or strings.icontains(body.current_thread.text, 'Review on Docusign') - or strings.icontains(body.current_thread.text, - 'Completed with Docusign' - ) + or strings.icontains(body.current_thread.text, 'Completed with Docusign') or strings.icontains(body.current_thread.text, 'Completed on Docusign') or strings.icontains(body.current_thread.text, 'Complete with Docusign') or strings.icontains(body.current_thread.text, @@ -83,9 +81,7 @@ source: | or strings.icontains(body.current_thread.text, 'review via DocuSign Electronic Signature' ) - or strings.icontains(body.current_thread.text, - 'sent to you by DocuSign' - ) + or strings.icontains(body.current_thread.text, 'sent to you by DocuSign') or strings.icontains(body.current_thread.text, 'Processed by DocuSign') or strings.icontains(body.current_thread.text, 'Please read and sign the document' @@ -117,7 +113,7 @@ source: | 'Sign\s*(?:and\s*|&\s*)Return.{0,40}docusign', 'Sign\s*(?:and\s*|&\s*)Return.docusign.{0,40}' ) - + // additional context from subject.subject or strings.icontains(subject.subject, 'complete with docusign') or strings.icontains(subject.subject, 'signature request') @@ -149,9 +145,7 @@ source: | regex.icontains(body.html.raw, 'Powered by.{0,6}(?:\s*<\/?[^\>]+\>\s*)+]+(?:src="https:\/\/docucdn-a\.akamaihd\.net\/[^\"]+email-logo.png"|alt="DocuSign")' ) - or regex.icontains(body.current_thread.text, - 'Powered by\s*DocuSign' - ) + or regex.icontains(body.current_thread.text, 'Powered by\s*DocuSign') ) // limit it to where the powered by is within the current thread and strings.icontains(body.current_thread.text, 'Powered by') @@ -200,7 +194,7 @@ source: | or strings.icontains(body.current_thread.text, 'a secure link to DocuSign' ) - + // footer links or ( length(filter(body.links, @@ -265,7 +259,7 @@ source: | ) // image contains an alt text of docusign or any(html.xpath(body.html, '//img/@alt').nodes, .raw =~ "docusign") - + // Basic variations with HTML encoding // use of regex extract allows or any(regex.iextract(body.html.raw, @@ -279,14 +273,14 @@ source: | ), .full_match !~ "docusign" ) - + // Look for HTML entities for each letter in sequence or any(regex.iextract(body.html.raw, '(?:D|D|D)(?:o|о|o|o|о|о|ο|ο)(?:c|с|c|c|с|с|ϲ|ç|g|ĉ|ĉ)(?:u|u|u|у|у|υ|υ)(?:s|s|s|ѕ|ѕ)(?:i|і|i|i|і|і|ı|ı)(?:g|g|g|ɡ|ɡ|ğ|ğ)(?:n|n|n|н|н|η|η)' ), .full_match !~ "docusign" ) - + // Handle repeated HTML entities and variation selectors (using Unicode class) or any(regex.iextract(body.html.raw, 'D(?:&#[0-9]{1,7};)*\p{Mn}*o(?:&#[0-9]{1,7};)*\p{Mn}*c(?:&#[0-9]{1,7};)*\p{Mn}*u(?:&#[0-9]{1,7};)*\p{Mn}*[Ss](?:&#[0-9]{1,7};)*\p{Mn}*i(?:&#[0-9]{1,7};)*\p{Mn}*g(?:&#[0-9]{1,7};)*\p{Mn}*n' @@ -316,7 +310,7 @@ source: | ) ) ) - + // identifies the main CTA in the email, eg "Review now" or "Review document" // this should always be a known docusign domain, // even with branded docusign subdomains @@ -415,13 +409,13 @@ source: | ) ) ) - + // negate highly trusted sender domains if they pass DMARC authentication and not ( sender.email.domain.root_domain in $high_trust_sender_root_domains and coalesce(headers.auth_summary.dmarc.pass, false) ) - + // negation for messages traversing docusign.net // happens with custom sender domains and not ( @@ -429,7 +423,7 @@ source: | and headers.auth_summary.spf.pass and headers.auth_summary.dmarc.pass ) - + // adding negation for messages originating from docusigns api // and the sender.display.name contains "via" and not (