From d7a4ee0ae95183e21292065693ff93ae9fc52d91 Mon Sep 17 00:00:00 2001 From: Aiden Mitchell Date: Thu, 16 Jan 2025 12:06:30 -0800 Subject: [PATCH 1/2] Update link_sharepoint_sus_name.yml --- detection-rules/link_sharepoint_sus_name.yml | 82 +++++++++++++++++--- 1 file changed, 71 insertions(+), 11 deletions(-) diff --git a/detection-rules/link_sharepoint_sus_name.yml b/detection-rules/link_sharepoint_sus_name.yml index 7e780eef6f3..479f720a01a 100644 --- a/detection-rules/link_sharepoint_sus_name.yml +++ b/detection-rules/link_sharepoint_sus_name.yml @@ -89,14 +89,16 @@ source: | or strings.icontains(.display_text, 'contract agreement') or regex.icontains(.display_text, 'Pr[0o]p[0o]sal') or strings.icontains(.display_text, 'contract doc') - + // generic document name AND additional suspicious indicator or ( - regex.imatch(.display_text, 'documents?') - and ( - // Find the share comment in the HTML and check for reply/forward "impersonation" - regex.icontains(body.html.raw, '

(re|fwd?)') + regex.imatch(.display_text, 'documents?') + and ( + // Find the share comment in the HTML and check for reply/forward "impersonation" + regex.icontains(body.html.raw, + '

(re|fwd?)' ) + ) ) // the document name is the same as the org name @@ -144,16 +146,74 @@ source: | ) ) - // and it's not an internal share - and not any(headers.hops, - any(.fields, - .name == "X-MS-Exchange-CrossTenant-AuthAs" - and .value == "Internal" + // and the sharepoint link is not related to the recipient domain + and any(filter(body.links, .href_url.domain.root_domain == 'sharepoint.com'), + any(recipients.to, + // Normalize Levenshtein distance by string length (0 = identical, 0.7+ = different) + // Working with what we have in MQL, considering we dont have max() or any other forms of string distancing + ( + ( + strings.iends_with(..href_url.domain.subdomain, + '-my' + ) // common Sharepoint subdomain suffix + and ( + ( + strings.ilevenshtein(..href_url.domain.subdomain, + .email.domain.sld + ) - 3 // subtract aforementioned suffix for more accurate calculation + ) / ( + ( + (length(..href_url.domain.subdomain) - 3) + length(.email.domain.sld + ) + + ( + ( + (length(..href_url.domain.subdomain) - 3) - length(.email.domain.sld + ) + ) + ( + length(.email.domain.sld) - ( + length(..href_url.domain.subdomain) - 3 + ) + ) + ) + ) / 2.0 // to ensure we keep the result as a float + ) + ) > 0.7 // customizable threshold + ) + or ( + not strings.iends_with(..href_url.domain.subdomain, + '-my' + ) // no suffix, continue with original calculation + and ( + strings.ilevenshtein(..href_url.domain.subdomain, + .email.domain.sld + ) / ( + ( + length(..href_url.domain.subdomain) + length(.email.domain.sld + ) + + ( + ( + length(..href_url.domain.subdomain) - length(.email.domain.sld + ) + ) + ( + length(.email.domain.sld) - length(..href_url.domain.subdomain + ) + ) + ) + ) / 2.0 // to ensure we keep the result as a float + ) + ) > 0.7 // customizable threshold + ) ) + and not strings.icontains(..href_url.path, + strings.concat(.email.domain.sld, + "_", + .email.domain.tld + ) + ) // negate sharepoint links that contain recipientdomain_com + ) ) // and sender has never had email sent to them and not profile.by_sender().solicited - attack_types: - "Credential Phishing" tactics_and_techniques: From 7569d0d6851008bd8b66cec163adf4c169924964 Mon Sep 17 00:00:00 2001 From: Aiden Mitchell Date: Thu, 16 Jan 2025 12:17:42 -0800 Subject: [PATCH 2/2] Update link_sharepoint_sus_name.yml --- detection-rules/link_sharepoint_sus_name.yml | 114 ++++++++++--------- 1 file changed, 59 insertions(+), 55 deletions(-) diff --git a/detection-rules/link_sharepoint_sus_name.yml b/detection-rules/link_sharepoint_sus_name.yml index 479f720a01a..9e9eb0f3a38 100644 --- a/detection-rules/link_sharepoint_sus_name.yml +++ b/detection-rules/link_sharepoint_sus_name.yml @@ -147,70 +147,74 @@ source: | ) // and the sharepoint link is not related to the recipient domain - and any(filter(body.links, .href_url.domain.root_domain == 'sharepoint.com'), - any(recipients.to, - // Normalize Levenshtein distance by string length (0 = identical, 0.7+ = different) - // Working with what we have in MQL, considering we dont have max() or any other forms of string distancing + and ( + any(filter(body.links, .href_url.domain.root_domain == 'sharepoint.com'), + any(recipients.to, + // Normalize Levenshtein distance by string length (0 = identical, 0.7+ = different) + // Working with what we have in MQL, considering we dont have max() or any other forms of string distancing + ( ( - ( - strings.iends_with(..href_url.domain.subdomain, - '-my' - ) // common Sharepoint subdomain suffix - and ( + strings.iends_with(..href_url.domain.subdomain, + '-my' + ) // common Sharepoint subdomain suffix + and ( + ( + strings.ilevenshtein(..href_url.domain.subdomain, + .email.domain.sld + ) - 3 // subtract aforementioned suffix for more accurate calculation + ) / ( ( - strings.ilevenshtein(..href_url.domain.subdomain, - .email.domain.sld - ) - 3 // subtract aforementioned suffix for more accurate calculation - ) / ( - ( - (length(..href_url.domain.subdomain) - 3) + length(.email.domain.sld - ) + (length(..href_url.domain.subdomain) - 3) + length(.email.domain.sld + ) + ( - ( - (length(..href_url.domain.subdomain) - 3) - length(.email.domain.sld - ) - ) + ( - length(.email.domain.sld) - ( - length(..href_url.domain.subdomain) - 3 - ) + ( + (length(..href_url.domain.subdomain) - 3) - length(.email.domain.sld + ) + ) + ( + length(.email.domain.sld) - ( + length(..href_url.domain.subdomain) - 3 ) ) - ) / 2.0 // to ensure we keep the result as a float - ) - ) > 0.7 // customizable threshold - ) - or ( - not strings.iends_with(..href_url.domain.subdomain, - '-my' - ) // no suffix, continue with original calculation - and ( - strings.ilevenshtein(..href_url.domain.subdomain, - .email.domain.sld - ) / ( - ( - length(..href_url.domain.subdomain) + length(.email.domain.sld - ) + ) + ) / 2.0 // to ensure we keep the result as a float + ) + ) > 0.7 // customizable threshold + ) + or ( + not strings.iends_with(..href_url.domain.subdomain, + '-my' + ) // no suffix, continue with original calculation + and ( + strings.ilevenshtein(..href_url.domain.subdomain, + .email.domain.sld + ) / ( + ( + length(..href_url.domain.subdomain) + length(.email.domain.sld + ) + ( - ( - length(..href_url.domain.subdomain) - length(.email.domain.sld - ) - ) + ( - length(.email.domain.sld) - length(..href_url.domain.subdomain - ) + ( + length(..href_url.domain.subdomain) - length(.email.domain.sld + ) + ) + ( + length(.email.domain.sld) - length(..href_url.domain.subdomain ) ) - ) / 2.0 // to ensure we keep the result as a float - ) - ) > 0.7 // customizable threshold - ) + ) + ) / 2.0 // to ensure we keep the result as a float + ) + ) > 0.7 // customizable threshold ) - and not strings.icontains(..href_url.path, - strings.concat(.email.domain.sld, - "_", - .email.domain.tld - ) - ) // negate sharepoint links that contain recipientdomain_com - ) + ) + and not strings.icontains(..href_url.path, + strings.concat(.email.domain.sld, + "_", + .email.domain.tld + ) + ) // negate sharepoint links that contain recipientdomain_com + ) + ) + or length(filter(body.links, .href_url.domain.root_domain == 'sharepoint.com') + ) == 0 // ignore this logic if it's not an explicit sharepoint.com share ) // and sender has never had email sent to them and not profile.by_sender().solicited