diff --git a/.github/workflows/clear-old-test-rules.yml b/.github/workflows/clear-old-test-rules.yml index f0cefca1ccb..a5a06aef464 100644 --- a/.github/workflows/clear-old-test-rules.yml +++ b/.github/workflows/clear-old-test-rules.yml @@ -26,12 +26,15 @@ jobs: uses: actions/github-script@v4 with: script: | - github.pulls.list({ - owner: context.repo.owner, - repo: context.repo.repo, - state: 'open' - }).then((result) => { - const openPRs = result.data.map(pr => pr.number); + github.paginate( + github.pulls.list, + { + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + }, + (response) => response.data.map((pr) => pr.number) + ).then((openPRs) => { console.log(`::set-output name=open_prs::${openPRs.join(',')}`); }); @@ -43,6 +46,8 @@ jobs: echo "This is a forked repository. Skipping the job." exit 0 fi + + echo "Open PRs: [$OPEN_PRS]" echo "Scheduled cleanup" > message.txt echo "" >> message.txt @@ -61,6 +66,7 @@ jobs: fi done + echo "$file is in open PR: $in_open_pr. File PR num: $file_pr_num" if [[ "$in_open_pr" = "false" ]]; then rm $file echo "Removed $file_pr_num" >> ../message.txt diff --git a/detection-rules/abuse_docsend_new_domain.yml b/detection-rules/abuse_docsend_new_domain.yml new file mode 100644 index 00000000000..48d295da824 --- /dev/null +++ b/detection-rules/abuse_docsend_new_domain.yml @@ -0,0 +1,37 @@ +name: "Service Abuse: DocSend Share From Newly Registered Domain" +description: "This Attack Surface Reduction (ASR) rule matches on DocSend notifications with recently registered reply-to domains." +type: "rule" +severity: "high" +source: | + type.inbound + + // Legitimate DocSend sending infratructure + and sender.email.email == "no-reply@docsend.com" + and headers.auth_summary.spf.pass + and headers.auth_summary.dmarc.pass + + // the message needs to have a reply-to address + and length(headers.reply_to) > 0 + + // reply-to email address has never received an email from your org + and not any(headers.reply_to, .email.email in $recipient_emails) + + // new reply-to + and any(headers.reply_to, + network.whois(.email.domain).days_old < 30 + ) +tags: + - "Attack surface reduction" +attack_types: + - "BEC/Fraud" + - "Credential Phishing" +tactics_and_techniques: + - "Evasion" + - "Free file host" + - "Impersonation: Brand" + - "Social engineering" +detection_methods: + - "Content analysis" + - "Header analysis" + - "Sender analysis" +id: "3bc152f2-6722-57be-b924-055c35fa1e60" diff --git a/detection-rules/abuse_docsend_unsolicited_reply-to.yml b/detection-rules/abuse_docsend_unsolicited_reply-to.yml new file mode 100644 index 00000000000..f4e6bc0ab78 --- /dev/null +++ b/detection-rules/abuse_docsend_unsolicited_reply-to.yml @@ -0,0 +1,56 @@ +name: "Service Abuse: DocSend Share From an Unsolicited Reply-To Address" +description: "DocSend shares which contain a reply-to address or domain that has not been previously observed by the recipient organization." +type: "rule" +severity: "high" +source: | + type.inbound + + // Legitimate DocSend sending infratructure + and sender.email.email == "no-reply@docsend.com" + and headers.auth_summary.spf.pass + and headers.auth_summary.dmarc.pass + + // the message needs to have a reply-to address + and length(headers.reply_to) > 0 + + // reply-to email address has never been sent an email by the org + and not ( + any(headers.reply_to, .email.email in $recipient_emails) + // if the reply-to email address is NOT in free_email_providers, check the domain in recipient_domains + or any(filter(headers.reply_to, + // filter the list to only emails that are not in free_email_providers + ( + .email.domain.domain not in $free_email_providers + or .email.domain.root_domain not in $free_email_providers + ) + ), + .email.domain.domain in $recipient_domains + ) + ) + // reply-to address has never sent an email to the org + and not ( + any(headers.reply_to, .email.email in $sender_emails) + // if the reply-to address is NOT in free_email_providers, check the domain in sender_domains + or any(filter(headers.reply_to, + // filter the list to only emails that are not in free_email_providers + ( + .email.domain.domain not in $free_email_providers + or .email.domain.root_domain not in $free_email_providers + ) + ), + .email.domain.domain in $sender_domains + ) + ) +tags: + - "Attack surface reduction" +attack_types: + - "Credential Phishing" +tactics_and_techniques: + - "Evasion" + - "Free file host" + - "Social engineering" +detection_methods: + - "Content analysis" + - "Header analysis" + - "Sender analysis" +id: "b377e64c-21bd-5040-86ec-534e545a42db" diff --git a/detection-rules/abuse_docusign_sus_names.yml b/detection-rules/abuse_docusign_sus_names.yml index 1e0aee78607..96b8507f448 100644 --- a/detection-rules/abuse_docusign_sus_names.yml +++ b/detection-rules/abuse_docusign_sus_names.yml @@ -42,7 +42,7 @@ source: | or .email.domain.domain not in $free_email_providers ) ), - .email.domain.root_domain in $sender_domains + .email.domain.domain in $sender_domains ) ) diff --git a/detection-rules/abuse_docusign_unsolicited_reply-to.yml b/detection-rules/abuse_docusign_unsolicited_reply-to.yml index 07fe308cdf3..a796593ddc4 100644 --- a/detection-rules/abuse_docusign_unsolicited_reply-to.yml +++ b/detection-rules/abuse_docusign_unsolicited_reply-to.yml @@ -41,10 +41,10 @@ source: | // filter the list to only emails that are not in free_email_providers ( .email.domain.domain not in $free_email_providers - or .email.domain.domain not in $free_email_providers + or .email.domain.root_domain not in $free_email_providers ) ), - .email.domain.root_domain in $sender_domains + .email.domain.domain in $sender_domains ) ) tags: diff --git a/detection-rules/abuse_dropbox_sus_names.yml b/detection-rules/abuse_dropbox_sus_names.yml index 982458a9a1c..51a683658fd 100644 --- a/detection-rules/abuse_dropbox_sus_names.yml +++ b/detection-rules/abuse_dropbox_sus_names.yml @@ -58,8 +58,10 @@ source: | // the filename is also contianed in the subject line or ( + // untitled.paper + regex.icontains(subject.subject, 'shared.*\"Untitled.paper') // scanner themed - regex.icontains(subject.subject, 'shared.*\".*scanne[rd]') + or regex.icontains(subject.subject, 'shared.*\".*scanne[rd]') // image theme or regex.icontains(subject.subject, 'shared.*\".*_IMG_') or regex.icontains(subject.subject, 'shared.*\".*IMG[_-](?:\d|\W)+\"') diff --git a/detection-rules/abuse_dropbox_unsolicited_reply-to.yml b/detection-rules/abuse_dropbox_unsolicited_reply-to.yml new file mode 100644 index 00000000000..92811c145ff --- /dev/null +++ b/detection-rules/abuse_dropbox_unsolicited_reply-to.yml @@ -0,0 +1,59 @@ +name: "Service Abuse: Dropbox Share From an Unsolicited Reply-To Address" +description: "This rule detects Dropbox share notifications which contain a reply-to address or domain that has not been previously observed sending messages to or receiving messages from the recipient organization." +type: "rule" +severity: "medium" +source: | + type.inbound + + // Legitimate Dropbox sending infratructure + and sender.email.email == "no-reply@dropbox.com" + and headers.auth_summary.spf.pass + and headers.auth_summary.dmarc.pass + and strings.ends_with(headers.auth_summary.spf.details.designator, + '.dropbox.com' + ) + and strings.icontains(subject.subject, 'shared') + and strings.icontains(subject.subject, 'with you') + + and length(headers.reply_to) > 0 + // reply-to email address has never been sent an email by the org + and not ( + any(headers.reply_to, .email.email in $recipient_emails) + // if the reply-to email address is NOT in free_email_providers, check the domain in recipient_domains + or any(filter(headers.reply_to, + // filter the list to only emails that are not in free_email_providers + ( + .email.domain.domain not in $free_email_providers + or .email.domain.root_domain not in $free_email_providers + ) + ), + .email.domain.domain in $recipient_domains + ) + ) + // reply-to address has never sent an email to the org + and not ( + any(headers.reply_to, .email.email in $sender_emails) + // if the reply-to address is NOT in free_email_providers, check the domain in sender_domains + or any(filter(headers.reply_to, + // filter the list to only emails that are not in free_email_providers + ( + .email.domain.domain not in $free_email_providers + or .email.domain.root_domain not in $free_email_providers + ) + ), + .email.domain.domain in $sender_domains + ) + ) +tags: + - "Attack surface reduction" +attack_types: + - "Callback Phishing" + - "BEC/Fraud" +tactics_and_techniques: + - "Evasion" + - "Social engineering" +detection_methods: + - "Sender analysis" + - "Header analysis" + - "Content analysis" +id: "50a1499f-bb59-5ee0-b4f4-e3cc84a5c41e" diff --git a/detection-rules/abuse_google_drive_unsolicited_reply-to.yml b/detection-rules/abuse_google_drive_unsolicited_reply-to.yml new file mode 100644 index 00000000000..fc1dfe73eab --- /dev/null +++ b/detection-rules/abuse_google_drive_unsolicited_reply-to.yml @@ -0,0 +1,57 @@ +name: "Service Abuse: Google Drive Share From an Unsolicited Reply-To Address" +description: "Identifies messages appearing to come from Google Drive sharing notifications that contain a reply-to address not previously seen in organizational communications. This tactic exploits trust in legitimate Google services while attempting to establish unauthorized communication channels." +type: "rule" +severity: "medium" +source: | + type.inbound + and sender.email.email in ( + 'drive-shares-dm-noreply@google.com', + 'drive-shares-noreply@google.com', + ) + and not any(headers.reply_to, .email.domain.domain in $org_domains) + + // the message needs to have a reply-to address + and length(headers.reply_to) > 0 + + // reply-to email address has never been sent an email by the org + and not ( + any(headers.reply_to, .email.email in $recipient_emails) + // if the reply-to email address is NOT in free_email_providers, check the domain in recipient_domains + or any(filter(headers.reply_to, + // filter the list to only emails that are not in free_email_providers + ( + .email.domain.domain not in $free_email_providers + or .email.domain.root_domain not in $free_email_providers + ) + ), + .email.domain.domain in $recipient_domains + ) + ) + // reply-to address has never sent an email to the org + and not ( + any(headers.reply_to, .email.email in $sender_emails) + // if the reply-to address is NOT in free_email_providers, check the domain in sender_domains + or any(filter(headers.reply_to, + // filter the list to only emails that are not in free_email_providers + ( + .email.domain.domain not in $free_email_providers + or .email.domain.root_domain not in $free_email_providers + ) + ), + .email.domain.root_domain in $sender_domains + ) + ) +tags: + - "Attack surface reduction" +attack_types: + - "BEC/Fraud" + - "Callback Phishing" + - "Credential Phishing" +tactics_and_techniques: + - "Free email provider" + - "Social engineering" + - "Free file host" +detection_methods: + - "Header analysis" + - "Sender analysis" +id: "4581ec0c-aed2-50ed-8e16-2c9ca1d350ff" diff --git a/detection-rules/abuse_quickbooks_impersonation_intuit.yml b/detection-rules/abuse_quickbooks_impersonation_intuit.yml new file mode 100644 index 00000000000..f2cbfede2ed --- /dev/null +++ b/detection-rules/abuse_quickbooks_impersonation_intuit.yml @@ -0,0 +1,43 @@ +name: "Brand Impersonation: QuickBooks Notification From Intuit Themed Company Name" +description: "This detection rule matches on QuickBooks notifications that feature company names impersonating Intuit and QuickBooks." +type: "rule" +severity: "medium" +source: | + type.inbound + + // Legitimate Intuit sending infratructure + and sender.email.email == "quickbooks@notification.intuit.com" + and headers.auth_summary.spf.pass + and headers.auth_summary.dmarc.pass + and strings.ends_with(headers.auth_summary.spf.details.designator, + '.intuit.com' + ) + and ( + // subject also contains the company name + strings.icontains(subject.subject, "Quickbooks") + or strings.icontains(subject.subject, "Intuit") + // the reply-to contains Inuit Themes + or any(headers.reply_to, + ( + strings.icontains(.email.email, 'intuit') + or strings.icontains(.email.domain.domain, 'quickbooks') + ) + and not (.email.domain.root_domain in ('intuit.com', 'quickbooks.com')) + ) + // the "company" part of the message + or regex.icontains(body.html.raw, + '

.*(?:Intuit|Quickbooks).*

' + ) + ) +attack_types: + - "Callback Phishing" + - "Credential Phishing" + - "BEC/Fraud" +tactics_and_techniques: + - "Evasion" + - "Social engineering" +detection_methods: + - "Content analysis" + - "Sender analysis" + - "Header analysis" +id: "42058fc4-d700-5bc3-9ee9-91641d9343c2" diff --git a/detection-rules/abuse_quickbooks_new_domain.yml b/detection-rules/abuse_quickbooks_new_domain.yml new file mode 100644 index 00000000000..c05a552f12e --- /dev/null +++ b/detection-rules/abuse_quickbooks_new_domain.yml @@ -0,0 +1,45 @@ +name: "Service Abuse: QuickBooks Notification From New Domain" +description: "This Attack Surface Reduction (ASR) rule matches on QuickBooks notifications with recently registered reply-to domains." +type: "rule" +severity: "medium" +source: | + type.inbound + + // Legitimate Intuit sending infratructure + and sender.email.email == "quickbooks@notification.intuit.com" + and headers.auth_summary.spf.pass + and headers.auth_summary.dmarc.pass + and strings.ends_with(headers.auth_summary.spf.details.designator, + '.intuit.com' + ) + + // remove payment confirmation messages + and not strings.starts_with(subject.subject, 'Payment confirmation:') + + // the message needs to have a reply-to address + and length(headers.reply_to) > 0 + + // reply-to email address has never received an email from your org + and not any(headers.reply_to, .email.email in $recipient_emails) + + // new reply-to + and any(filter(headers.reply_to, + // negate .com.au which doesn't provide created date for domains + .email.domain.tld not in ('com.au') + ), + network.whois(.email.domain).days_old < 30 + ) +tags: + - "Attack surface reduction" +attack_types: + - "Callback Phishing" + - "Credential Phishing" + - "BEC/Fraud" +tactics_and_techniques: + - "Evasion" + - "Social engineering" +detection_methods: + - "Content analysis" + - "Sender analysis" + - "Header analysis" +id: "c4f46473-0f5a-56d6-bb7e-489460bdb20f" diff --git a/detection-rules/abuse_quickbooks_suspicious_comments.yml b/detection-rules/abuse_quickbooks_suspicious_comments.yml new file mode 100644 index 00000000000..ec436e55852 --- /dev/null +++ b/detection-rules/abuse_quickbooks_suspicious_comments.yml @@ -0,0 +1,39 @@ +name: "Service Abuse: QuickBooks Notification with Suspicious Comments" +description: "This detection rule matches QuickBooks notifications that contain suspicious keywords within the comments section of the notification" +type: "rule" +severity: "medium" +source: | + type.inbound + + // Legitimate Intuit sending infratructure + and sender.email.email == "quickbooks@notification.intuit.com" + and headers.auth_summary.spf.pass + and headers.auth_summary.dmarc.pass + and strings.ends_with(headers.auth_summary.spf.details.designator, + '.intuit.com' + ) + + // remove payment confirmation messages + and not strings.starts_with(subject.subject, 'Payment confirmation:') + + and body.html.raw is not null + // Comments contains suspicious phrases + and ( + // three different templates where commonly observed, on regex for each template + // this could optionally be converted into a "2 of" logic against current_thread if FN are discovered + regex.icontains(body.html.raw, '
\s*.*\b(?:your subscription renewal|couldn.?t be processed|trouble renewing subscription|update your details|just update your|continue your subscription|prefer to use EFT|change payment method|verify your account|suspended due to issue|payment declined notice|account needs verification|confirm your billing|immediate action required|failed payment notification|billing information update|service interruption warning|unable to process payment|subscription payment failed|action needed now|update banking information|subscription expiration notice|payment method change)\b.*
') + or regex.icontains(body.html.raw, '
.*\b(?:your subscription renewal|couldn.?t be processed|trouble renewing subscription|update your details|just update your|continue your subscription|prefer to use EFT|change payment method|verify your account|suspended due to issue|payment declined notice|account needs verification|confirm your billing|immediate action required|failed payment notification|billing information update|service interruption warning|unable to process payment|subscription payment failed|action needed now|update banking information|subscription expiration notice|payment method change)\b.*
') + or regex.icontains(body.html.raw, '(?:\s*)?\s*\s*') + ) +attack_types: + - "Callback Phishing" + - "Credential Phishing" + - "BEC/Fraud" +tactics_and_techniques: + - "Evasion" + - "Social engineering" +detection_methods: + - "Content analysis" + - "Sender analysis" + - "Header analysis" +id: "a23d0950-9117-5199-bc74-7192217b80ff" diff --git a/detection-rules/attachment_callback_phish_with_pdf.yml b/detection-rules/attachment_callback_phish_with_pdf.yml index 73fecdfc794..942e53bcce7 100644 --- a/detection-rules/attachment_callback_phish_with_pdf.yml +++ b/detection-rules/attachment_callback_phish_with_pdf.yml @@ -74,6 +74,10 @@ source: | strings.icontains(.scan.ocr.raw, "norton"), strings.icontains(.scan.ocr.raw, "ebay"), strings.icontains(.scan.ocr.raw, "paypal"), + // suspicious attachment name from the attachment object not file.explode() output + ( + regex.icontains(..file_name, 'INV(?:_|\s)?\d+(.pdf)$') + ) ) // Negate bank statements and not ( diff --git a/detection-rules/attachment_docusign_suspicious_links.yml b/detection-rules/attachment_docusign_suspicious_links.yml index 58ec4ac0d39..0b532459c9a 100644 --- a/detection-rules/attachment_docusign_suspicious_links.yml +++ b/detection-rules/attachment_docusign_suspicious_links.yml @@ -65,56 +65,58 @@ source: | ) ) ) - + // accomidate truncated pngs and GIF files which can cause logodetect/OCR failures - or any(attachments, - ( - .file_type =~ "gif" - or any(file.explode(.), - any(.scan.exiftool.fields, - .key == "Warning" and .value == "Truncated PNG image" - ) - ) - ) - and ( - any(ml.logo_detect(beta.message_screenshot()).brands, - ( - .name == "DocuSign" - or any(file.explode(beta.message_screenshot()), - strings.ilike(.scan.ocr.raw, "*DocuSign*") - ) - ) - ) - and ( - any(file.explode(beta.message_screenshot()), - ( - any(ml.nlu_classifier(.scan.ocr.raw).intents, - .name == "cred_theft" and .confidence != "low" - ) - or regex.icontains(.scan.ocr.raw, - "((re)?view|access|complete(d)?) document(s)?", - "[^d][^o][^c][^u]sign", - "important edocs", - // German (Document (check|check|sign|sent)) - "Dokument (überprüfen|prüfen|unterschreiben|geschickt)", - // German (important|urgent|immediate) - "(wichtig|dringend|sofort)" - ) + or ( + any(attachments, + ( + .file_type =~ "gif" + or any(file.explode(.), + any(.scan.exiftool.fields, + .key == "Warning" and .value == "Truncated PNG image" ) - ) - ) - and not any(file.explode(beta.message_screenshot()), - ( - strings.ilike(.scan.ocr.raw, "*DocuSigned By*") - and not strings.ilike(.scan.ocr.raw, - "*DocuSign Envelope ID*" - ) - and not strings.ilike(.scan.ocr.raw, - "*Certificate Of Completion*" - ) - ) - ) - ) + ) + ) + ) + and ( + any(ml.logo_detect(beta.message_screenshot()).brands, + ( + .name == "DocuSign" + or any(file.explode(beta.message_screenshot()), + strings.ilike(.scan.ocr.raw, "*DocuSign*") + ) + ) + ) + and ( + any(file.explode(beta.message_screenshot()), + ( + any(ml.nlu_classifier(.scan.ocr.raw).intents, + .name == "cred_theft" and .confidence != "low" + ) + or regex.icontains(.scan.ocr.raw, + "((re)?view|access|complete(d)?) document(s)?", + "[^d][^o][^c][^u]sign", + "important edocs", + // German (Document (check|check|sign|sent)) + "Dokument (überprüfen|prüfen|unterschreiben|geschickt)", + // German (important|urgent|immediate) + "(wichtig|dringend|sofort)" + ) + ) + ) + ) + and not any(file.explode(beta.message_screenshot()), + ( + strings.ilike(.scan.ocr.raw, "*DocuSigned By*") + and not strings.ilike(.scan.ocr.raw, + "*DocuSign Envelope ID*" + ) + and not strings.ilike(.scan.ocr.raw, + "*Certificate Of Completion*" + ) + ) + ) + ) ) ) and ( @@ -125,7 +127,7 @@ source: | ) ) and not profile.by_sender().any_false_positives - + // negate docusign 'via' messages and not ( any(headers.hops, diff --git a/detection-rules/attachment_extortion.yml b/detection-rules/attachment_extortion.yml index 144ac7ef31b..02878275278 100644 --- a/detection-rules/attachment_extortion.yml +++ b/detection-rules/attachment_extortion.yml @@ -15,68 +15,150 @@ source: | ) ) and any(attachments, - (.file_type in $file_types_images or .file_type == "pdf") - and any(filter(file.explode(.), .scan.ocr.raw is not null), - ( - any(ml.nlu_classifier(.scan.ocr.raw).intents, - .name == "extortion" and .confidence == "high" - ) - and any(ml.nlu_classifier(.scan.ocr.raw).entities, - .name == "financial" + // use ocr output from file.explode on pdfs/images + ( + (.file_type in $file_types_images or .file_type == "pdf") + and any(filter(file.explode(.), .scan.ocr.raw is not null), + ( + any(ml.nlu_classifier(.scan.ocr.raw).intents, + .name == "extortion" and .confidence == "high" + ) + and any(ml.nlu_classifier(.scan.ocr.raw).entities, + .name == "financial" + ) ) - ) - or 3 of ( - // malware terms - regex.icontains(.scan.ocr.raw, "((spy|mal)ware|trojan|remote control)"), - // actions recorded - regex.icontains(.scan.ocr.raw, - "porn|adult (web)?site|webcam|masturbating|jerking off|pleasuring yourself|getting off" - ), - regex.icontains(.scan.ocr.raw, - "pervert|perversion|masturbat" - ), - // a timeframe to pay - regex.icontains(.scan.ocr.raw, '\d\d hours', '(?:one|two|three) days?'), - // a promise from the actor - regex.icontains(.scan.ocr.raw, + or 3 of ( + // malware terms + regex.icontains(.scan.ocr.raw, + "((spy|mal)ware|trojan|remote control)" + ), + // actions recorded + regex.icontains(.scan.ocr.raw, + "porn|adult (web)?site|webcam|masturbating|jerking off|pleasuring yourself|getting off" + ), + regex.icontains(.scan.ocr.raw, + "pervert|perversion|masturbat" + ), + // a timeframe to pay + regex.icontains(.scan.ocr.raw, + '\d\d hours', + '(?:one|two|three) days?' + ), + // a promise from the actor + regex.icontains(.scan.ocr.raw, 'permanently delete|destroy (?:\w+\s*){0,4} (?:data|evidence|videos?)' - ), - // a threat from the actor - regex.icontains(.scan.ocr.raw, - 'sen[dt]\s*(?:\w+\s*){0,2}\s*to\s*(?:\w+\s*){0,3}\s*your contacts'), - // bitcoin - ( + ), + // a threat from the actor regex.icontains(.scan.ocr.raw, - 'bitcoin|\bbtc\b|blockchain' - ) - // negate cryptocurrency newsletters - and not ( - any(body.links, - strings.icontains(.display_text, "unsubscribe") - and ( - strings.icontains(.href_url.path, "unsubscribe") - // handle mimecast URL rewrites - or ( - .href_url.domain.root_domain == 'mimecastprotect.com' - and strings.icontains(.href_url.query_params, - sender.email.domain.root_domain + 'sen[dt]\s*(?:\w+\s*){0,2}\s*to\s*(?:\w+\s*){0,3}\s*your contacts' + ), + // bitcoin + ( + regex.icontains(.scan.ocr.raw, + 'bitcoin|\bbtc\b|blockchain' + ) + // negate cryptocurrency newsletters + and not ( + any(body.links, + strings.icontains(.display_text, "unsubscribe") + and ( + strings.icontains(.href_url.path, "unsubscribe") + // handle mimecast URL rewrites + or ( + .href_url.domain.root_domain == 'mimecastprotect.com' + and strings.icontains(.href_url.query_params, + sender.email.domain.root_domain + ) ) ) - ) + ) ) - ) - ), - // bitcoin wallet address + threat - ( - strings.icontains(.scan.ocr.raw, - "contact the police" - ) - and regex.icontains(.scan.ocr.raw, - '(\b[13][a-km-zA-HJ-NP-Z0-9]{24,33}\b)|\bX[1-9A-HJ-NP-Za-km-z]{33}\b|\b(0x[a-fA-F0-9]{40})\b|\b[LM3][a-km-zA-HJ-NP-Z1-9]{26,33}\b|\b[48][0-9AB][1-9A-HJ-NP-Za-km-z]{93}\b' - ) - ), - regex.icontains(.scan.ocr.raw, 'bc1q.{0,50}\b') + ), + // bitcoin wallet address + threat + ( + strings.icontains(.scan.ocr.raw, "contact the police") + and regex.icontains(.scan.ocr.raw, + '(\b[13][a-km-zA-HJ-NP-Z0-9]{24,33}\b)|\bX[1-9A-HJ-NP-Za-km-z]{33}\b|\b(0x[a-fA-F0-9]{40})\b|\b[LM3][a-km-zA-HJ-NP-Z1-9]{26,33}\b|\b[48][0-9AB][1-9A-HJ-NP-Za-km-z]{93}\b' + ) + ), + regex.icontains(.scan.ocr.raw, 'bc1q.{0,50}\b') + ) + ) + ) + or + // use beta.parse_text on plain text files + ( + ( + .file_extension in ("txt") + and ( + ( + any(ml.nlu_classifier(file.parse_text(.).text).intents, + .name == "extortion" and .confidence == "high" ) + and any(ml.nlu_classifier(file.parse_text(.).text).entities, + .name == "financial" + ) + ) + or 3 of ( + // malware terms + regex.icontains(beta.parse_text(.).text, + "((spy|mal)ware|trojan|remote control)" + ), + // actions recorded + regex.icontains(beta.parse_text(.).text, + "porn|adult (web)?site|webcam|masturbating|jerking off|pleasuring yourself|getting off" + ), + regex.icontains(beta.parse_text(.).text, + "pervert|perversion|masturbat" + ), + // a timeframe to pay + regex.icontains(beta.parse_text(.).text, + '\d\d hours', + '(?:one|two|three) days?' + ), + // a promise from the actor + regex.icontains(beta.parse_text(.).text, + 'permanently delete|destroy (?:\w+\s*){0,4} (?:data|evidence|videos?)' + ), + // a threat from the actor + regex.icontains(beta.parse_text(.).text, + 'sen[dt]\s*(?:\w+\s*){0,2}\s*to\s*(?:\w+\s*){0,3}\s*your contacts' + ), + // bitcoin + ( + regex.icontains(beta.parse_text(.).text, + 'bitcoin|\bbtc\b|blockchain' + ) + // negate cryptocurrency newsletters + and not ( + any(body.links, + strings.icontains(.display_text, "unsubscribe") + and ( + strings.icontains(.href_url.path, "unsubscribe") + // handle mimecast URL rewrites + or ( + .href_url.domain.root_domain == 'mimecastprotect.com' + and strings.icontains(.href_url.query_params, + sender.email.domain.root_domain + ) + ) + ) + ) + ) + ), + // bitcoin wallet address + threat + ( + strings.icontains(beta.parse_text(.).text, + "contact the police" + ) + and regex.icontains(beta.parse_text(.).text, + '(\b[13][a-km-zA-HJ-NP-Z0-9]{24,33}\b)|\bX[1-9A-HJ-NP-Za-km-z]{33}\b|\b(0x[a-fA-F0-9]{40})\b|\b[LM3][a-km-zA-HJ-NP-Z1-9]{26,33}\b|\b[48][0-9AB][1-9A-HJ-NP-Za-km-z]{93}\b' + ) + ), + regex.icontains(beta.parse_text(.).text, 'bc1q.{0,50}\b') + ) + ) + ) ) ) and ( diff --git a/detection-rules/attachment_file_scheme_link_to_executable_filetype.yml b/detection-rules/attachment_file_scheme_link_to_executable_filetype.yml index d50ce32802b..32c32f8b4cb 100644 --- a/detection-rules/attachment_file_scheme_link_to_executable_filetype.yml +++ b/detection-rules/attachment_file_scheme_link_to_executable_filetype.yml @@ -15,6 +15,7 @@ source: | and .size < 100000000 ) ) + and length(file.oletools(.).relationships) < 500 and any(file.oletools(.).relationships, .target_url.scheme == "file" and regex.icontains(.target_url.path, diff --git a/detection-rules/attachment_free_subdomain_suspicious_link_language.yml b/detection-rules/attachment_free_subdomain_suspicious_link_language.yml index 80600bb8977..b3436acbefc 100644 --- a/detection-rules/attachment_free_subdomain_suspicious_link_language.yml +++ b/detection-rules/attachment_free_subdomain_suspicious_link_language.yml @@ -10,12 +10,30 @@ source: | and .href_url.domain.subdomain is not null and .href_url.domain.subdomain != "www" ) - and (length(recipients.to) == 0 or all(recipients.to, .display_name == "Undisclosed recipients")) - and length(recipients.cc) == 0 - and length(recipients.bcc) == 0 + and ( + ( + ( + length(recipients.to) == 0 + or all(recipients.to, .display_name == "Undisclosed recipients") + ) + and length(recipients.cc) == 0 + and length(recipients.bcc) == 0 + ) + or ( + length(recipients.to) == 1 + and any(recipients.to, .email.email == sender.email.email) + ) + or ( + length(recipients.to) == 0 + and length(recipients.cc) == 0 + and length(recipients.bcc) > 0 + ) + ) and any(body.links, any(file.explode(ml.link_analysis(.).screenshot), - any(ml.nlu_classifier(.scan.ocr.raw).intents, .name == "cred_theft" and .confidence != "low") + any(ml.nlu_classifier(.scan.ocr.raw).intents, + .name == "cred_theft" and .confidence != "low" + ) ) ) tags: diff --git a/detection-rules/attachment_office_file_relationship_cred_theft.yml b/detection-rules/attachment_office_file_relationship_cred_theft.yml index 8945b4622bc..9d0dc17d383 100644 --- a/detection-rules/attachment_office_file_relationship_cred_theft.yml +++ b/detection-rules/attachment_office_file_relationship_cred_theft.yml @@ -15,6 +15,7 @@ source: | and .size < 100000000 ) ) + and length(file.oletools(.).relationships) < 500 and any(file.oletools(.).relationships, ( any(ml.nlu_classifier(ml.link_analysis(.target_url).final_dom.display_text diff --git a/detection-rules/body_extortion.yml b/detection-rules/body_extortion.yml index a37e4abde09..ebeb51ed28f 100644 --- a/detection-rules/body_extortion.yml +++ b/detection-rules/body_extortion.yml @@ -9,34 +9,34 @@ source: | type.inbound and ( ( - any(ml.nlu_classifier(body.current_thread.text).intents, + any(ml.nlu_classifier(strings.replace_confusables(body.current_thread.text)).intents, .name == "extortion" and .confidence == "high" ) - and any(ml.nlu_classifier(body.current_thread.text).entities, + and any(ml.nlu_classifier(strings.replace_confusables(body.current_thread.text)).entities, .name == "financial" ) ) // manual indicators failsafe or 3 of ( // malware terms - regex.icontains(body.current_thread.text, "((spy|mal)ware|trojan|remote control)"), + regex.icontains(strings.replace_confusables(body.current_thread.text), "((spy|mal)ware|trojan|remote control)"), // actions recorded - regex.icontains(body.current_thread.text, + regex.icontains(strings.replace_confusables(body.current_thread.text), "porn|adult (web)?site|webcam|masturbating|jerking off|pleasuring yourself|getting off" ), - regex.icontains(body.current_thread.text, "pervert|perversion|masturbat"), + regex.icontains(strings.replace_confusables(body.current_thread.text), "pervert|perversion|masturbat"), // a timeframe to pay - regex.icontains(body.current_thread.text, '\d\d hours', '(?:one|two|three|\d) days?'), + regex.icontains(strings.replace_confusables(body.current_thread.text), '\d\d hours', '(?:one|two|three|\d) days?'), // a promise from the actor - regex.icontains(body.current_thread.text, + regex.icontains(strings.replace_confusables(body.current_thread.text), 'permanently delete|(remove|destroy) (?:\w+\s*){0,4} (?:data|evidence|videos?)' ), // a threat from the actor - regex.icontains(body.current_thread.text, + regex.icontains(strings.replace_confusables(body.current_thread.text), 'sen[dt]\s*(?:\w+\s*){0,2}\s*to\s*(?:\w+\s*){0,3}\s*your contacts'), // bitcoin language (excluding newsletters) ( - regex.icontains(body.current_thread.text, 'bitcoin|\bbtc\b|blockchain') + regex.icontains(strings.replace_confusables(body.current_thread.text), 'bitcoin|\bbtc\b|blockchain') // negate cryptocurrency newsletters and not ( any(body.links, @@ -56,14 +56,14 @@ source: | ), // bitcoin wallet address + threat ( - strings.icontains(body.current_thread.text, + strings.icontains(strings.replace_confusables(body.current_thread.text), "contact the police" ) - and regex.icontains(body.current_thread.text, + and regex.icontains(strings.replace_confusables(body.current_thread.text), '(\b[13][a-km-zA-HJ-NP-Z0-9]{24,33}\b)|\bX[1-9A-HJ-NP-Za-km-z]{33}\b|\b(0x[a-fA-F0-9]{40})\b|\b[LM3][a-km-zA-HJ-NP-Z1-9]{26,33}\b|\b[48][0-9AB][1-9A-HJ-NP-Za-km-z]{93}\b' ) ), - regex.icontains(body.current_thread.text, 'bc1q.{0,50}\b') + regex.icontains(strings.replace_confusables(body.current_thread.text), 'bc1q.{0,50}\b') ) ) and ( diff --git a/detection-rules/header_onmicrosoft_traversal.yml b/detection-rules/header_onmicrosoft_traversal.yml new file mode 100644 index 00000000000..c6ba1546391 --- /dev/null +++ b/detection-rules/header_onmicrosoft_traversal.yml @@ -0,0 +1,37 @@ +name: "Message Traversed Multiple onmicrosoft.com Tenants" +description: "This detection rule identifies messages that have traversed multiple distinct onmicrosoft.com tenants. This technique has been observed as an evasion tactic to distribute a single message across a list of targeted recipients." +type: "rule" +severity: "medium" +source: | + type.inbound + and length(recipients.to) == 1 + and all(recipients.to, + .email.domain.root_domain == "onmicrosoft.com" + and not .email.domain.domain in $org_domains + ) + // the message has traversed two or more different "onmicrosoft.com" subdomains + and length(distinct(map(filter(headers.hops, + strings.icontains(.authentication_results.spf_details.designator, + '.onmicrosoft.com' + ) + and not strings.contains(.authentication_results.spf_details.designator, + "@" + ) + ), + .authentication_results.spf_details.designator + ), + . + ) + ) > 1 + + and all(recipients.to, .email.domain.domain != headers.return_path.domain.domain) +attack_types: + - "Callback Phishing" +tactics_and_techniques: + - "Evasion" + - "Free email provider" + - "Free subdomain host" +detection_methods: + - "Sender analysis" + - "Header analysis" +id: "9cf01c0d-95d5-5ea6-8150-cf5879834e06" diff --git a/detection-rules/impersonation_employee_payroll_fraud.yml b/detection-rules/impersonation_employee_payroll_fraud.yml index a2bbff19da2..b7c8409840e 100644 --- a/detection-rules/impersonation_employee_payroll_fraud.yml +++ b/detection-rules/impersonation_employee_payroll_fraud.yml @@ -26,7 +26,7 @@ source: | ) ) and ( - not profile.by_sender().solicited + not profile.by_sender_email().solicited or ( profile.by_sender().any_messages_malicious_or_spam and not profile.by_sender().any_false_positives diff --git a/detection-rules/impersonation_facebook.yml b/detection-rules/impersonation_facebook.yml index f1db3377bf2..a74ad1b8b1e 100644 --- a/detection-rules/impersonation_facebook.yml +++ b/detection-rules/impersonation_facebook.yml @@ -8,6 +8,8 @@ severity: "low" source: | type.inbound and ( + // sender display name is a strong enough indicator + // that it can be used without any other impersonation logic ( strings.ilike(sender.display_name, '*facebook ads*', @@ -21,10 +23,16 @@ source: | or ( strings.ilevenshtein(sender.display_name, 'meta support') <= 2 // negation for Zeta Support - and not (sender.display_name == "Zeta Support" and sender.email.domain.root_domain == 'zetaglobal.net') + and not ( + sender.display_name == "Zeta Support" + and sender.email.domain.root_domain == 'zetaglobal.net' + ) ) or strings.ilike(sender.email.domain.domain, '*facebook*') ) + // the use of these keywords (facebook, meta, meta.*support) + // or the levenshtien distance to facebook + // are less strong and thus need to be combined with logo detection or nlu or ( ( ( @@ -51,6 +59,17 @@ source: | ) ) ) + // salesforce sender combined with logo detection and nlu is enough + or ( + sender.email.domain.root_domain == "salesforce.com" + and any(ml.logo_detect(beta.message_screenshot()).brands, + .name in ("Facebook", "Meta") + ) + and any(ml.nlu_classifier(body.current_thread.text).intents, + .name in ("cred_theft", "callback_scam", "steal_pii") + and .confidence in ("high") + ) + ) or // or the body contains a facebook/meta footer with the address citing "community support" ( @@ -92,7 +111,8 @@ source: | profile.by_sender().any_messages_malicious_or_spam and not profile.by_sender().any_false_positives ) - or sender.email.email == "noreply@salesforce.com" + // if saleforce is being abused, sender profiles aren't very useful + or sender.email.email in ("noreply@salesforce.com", "support@salesforce.com") // sent via Google group or any(headers.hops, any(.fields, .name == "X-Google-Group-Id")) ) diff --git a/detection-rules/impersonation_microsoft_credential_theft.yml b/detection-rules/impersonation_microsoft_credential_theft.yml index 36f64134c9e..6874acdcd09 100644 --- a/detection-rules/impersonation_microsoft_credential_theft.yml +++ b/detection-rules/impersonation_microsoft_credential_theft.yml @@ -52,7 +52,18 @@ source: | and not profile.by_sender().any_false_positives ) ) + and not ( + sender.email.domain.domain == "planner.office365.com" + and headers.return_path.email == "noreply@planner.office365.com" + and headers.auth_summary.dmarc.details.from.domain == "planner.office365.com" + ) + // message is not from sharepoint actual (additional check in case DMARC check above fails to bail out) + and not ( + strings.ilike(headers.message_id, '') + ) + // negate highly trusted sender domains unless they fail DMARC authentication and ( ( diff --git a/detection-rules/impersonation_paypal.yml b/detection-rules/impersonation_paypal.yml index eb9a0edaa07..3f02037438b 100644 --- a/detection-rules/impersonation_paypal.yml +++ b/detection-rules/impersonation_paypal.yml @@ -8,9 +8,9 @@ severity: "medium" source: | type.inbound and ( - sender.display_name =~ "paypal" - or strings.ilevenshtein(sender.display_name, 'paypal') <= 1 - or strings.ilike(sender.email.domain.domain, '*paypal*') + strings.replace_confusables(sender.display_name) =~ "paypal" + or strings.ilevenshtein(strings.replace_confusables(sender.display_name), 'paypal') <= 1 + or strings.ilike(strings.replace_confusables(sender.display_name), '*paypal*') or any(attachments, (.file_type in $file_types_images or .file_type == "pdf") and any(ml.logo_detect(.).brands, .name == "PayPal") diff --git a/detection-rules/impersonation_sharepoint_fake_file_share.yml b/detection-rules/impersonation_sharepoint_fake_file_share.yml index b24422a89ef..024f1f19389 100644 --- a/detection-rules/impersonation_sharepoint_fake_file_share.yml +++ b/detection-rules/impersonation_sharepoint_fake_file_share.yml @@ -15,7 +15,9 @@ source: | "*shared a file with you*", "*shared with you*", "*invited you to access a file*", - "*you've received a document*" + "*received a document*", + "*shared a document*", + "*shared this document*" ) ) or any(file.explode(beta.message_screenshot()), @@ -23,7 +25,9 @@ source: | "*shared a file with you*", "*shared with you*", "*invited you to access a file*", - "*you've received a document*" + "*received a document*", + "*shared a document*", + "*shared this document*" ) ) ) @@ -40,8 +44,14 @@ source: | "*PowerPoint*", "*OneNote*" ) + or any(body.links, strings.icontains(.display_text, "OPEN DOCUMENT")) or subject.subject is null or subject.subject == "" + // the org as determined by NLU is in the subject + or any(ml.nlu_classifier(body.current_thread.text).entities, + .name == "org" and strings.icontains(subject.subject, .text) + ) + ) ) or any([ @@ -210,7 +220,7 @@ source: | regex.icontains(body.html.raw, 'rgb\((25[0-5]),\s?(20[0-2]),\s?([0-7])\)') ) or regex.icontains(body.html.raw, - ']+style="[^"]*background-color:\s*#[0-9A-F]{2}[5-9A-F]{2}[0-9A-F]{2}[^"]*"[^>]*>[^<]*(?:open)[^<]*' // blue button containing the word "open" + ']+style="[^"]*background-color:\s*#[0-9A-F]{2}[5-9A-F]{2}[0-9A-F]{2}[^"]*"[^>]*>[^<]*(?:open)[^<]*' // blue button containing the word "open" ) or ( any(recipients.to, @@ -264,7 +274,8 @@ source: | or sender.email.domain.root_domain not in $high_trust_sender_root_domains ) and ( - (not profile.by_sender().solicited) + profile.by_sender().solicited == false + or profile.by_sender_email().prevalence == "new" or ( profile.by_sender().any_messages_malicious_or_spam and not profile.by_sender().any_false_positives diff --git a/detection-rules/impersonation_usps.yml b/detection-rules/impersonation_usps.yml index 2f6b310cc6e..d730a013bdd 100644 --- a/detection-rules/impersonation_usps.yml +++ b/detection-rules/impersonation_usps.yml @@ -26,11 +26,16 @@ source: | // no links go to usps.com all(body.links, .href_url.domain.root_domain != "usps.com") ) - and ( - sender.email.domain.root_domain not in ("usps.com") + sender.email.domain.root_domain not in ( + "usps.com", + "opinions-inmoment.com" // https://faq.usps.com/s/article/USPS-Customer-Experience-Surveys + ) or ( - sender.email.domain.root_domain in ("usps.com") + sender.email.domain.root_domain in ( + "usps.com", + "opinions-inmoment.com" // https://faq.usps.com/s/article/USPS-Customer-Experience-Surveys + ) and not headers.auth_summary.dmarc.pass ) ) diff --git a/detection-rules/impersonation_vip_urgent_request.yml b/detection-rules/impersonation_vip_urgent_request.yml index bad63d6041b..e955fde6c2b 100644 --- a/detection-rules/impersonation_vip_urgent_request.yml +++ b/detection-rules/impersonation_vip_urgent_request.yml @@ -31,7 +31,12 @@ source: | and not profile.by_sender().any_false_positives ) ) - + // negate sharepoint notifications originating from within the org + and not ( + sender.email.email in ('no-reply@sharepointonline.com') + and length(headers.reply_to) > 0 + and all(headers.reply_to, .email.domain.root_domain in $org_domains) + ) // negate highly trusted sender domains unless they fail DMARC authentication and ( ( diff --git a/detection-rules/impersonation_x_with_credphish_nlu.yml b/detection-rules/impersonation_x_with_credphish_nlu.yml index 3ebc1eda1d1..37523123103 100644 --- a/detection-rules/impersonation_x_with_credphish_nlu.yml +++ b/detection-rules/impersonation_x_with_credphish_nlu.yml @@ -30,6 +30,7 @@ source: | profile.by_sender().any_messages_malicious_or_spam and not profile.by_sender().any_false_positives ) + or sender.email.email in ("noreply@salesforce.com", "support@salesforce.com") ) // negate highly trusted sender domains unless they fail DMARC authentication @@ -39,6 +40,9 @@ source: | and not headers.auth_summary.dmarc.pass ) or sender.email.domain.root_domain not in $high_trust_sender_root_domains + + // salesforce has been abused for x/twitter phishing campaigns repeatedly + or sender.email.domain.root_domain == "salesforce.com" ) attack_types: - "Credential Phishing" diff --git a/detection-rules/link_cyrillic_substitutions_unsolicited.yml b/detection-rules/link_cyrillic_substitutions_unsolicited.yml index df81e883528..353772b6628 100644 --- a/detection-rules/link_cyrillic_substitutions_unsolicited.yml +++ b/detection-rules/link_cyrillic_substitutions_unsolicited.yml @@ -6,7 +6,14 @@ source: | type.inbound // message contains between 1 and 9 links - and 0 < length(body.links) < 10 + and ( + 0 < length(body.links) < 10 + or ( + length(body.links) == 0 + and length(attachments) > 0 + and body.current_thread.text == "" + ) + ) // display name or subject contains Cyrillic vowels in addition to standard letters and any([subject.subject, sender.display_name], diff --git a/detection-rules/link_fake_password_expiration.yml b/detection-rules/link_fake_password_expiration.yml index b9d1bc57bc2..fa96f7828c7 100644 --- a/detection-rules/link_fake_password_expiration.yml +++ b/detection-rules/link_fake_password_expiration.yml @@ -5,8 +5,8 @@ severity: "medium" source: | type.inbound - // few links - and 0 < length(body.links) < 10 + // few links which are not in $org_domains + and 0 < length(filter(body.links, .href_url.domain.domain not in $org_domains)) <= 10 // no attachments or suspicious attachment and ( @@ -17,6 +17,18 @@ source: | .scan.entropy.entropy > 7 and length(.scan.ocr.raw) < 20 ) ) + // or there are duplicate pdfs in name + or ( + length(filter(attachments, .file_type == "pdf")) > length(distinct(filter(attachments, + .file_type == "pdf" + ), + .file_name + ) + ) + or + // all PDFs are the same MD5 + length(distinct(filter(attachments, .file_type == "pdf"), .md5)) == 1 + ) ) // body contains expire, expiration, loose, lose @@ -93,11 +105,13 @@ source: | ) or regex.icontains(body.html.raw, '(?:

\s* \s*

\s*){7,}') or regex.icontains(body.html.raw, '(?:

\s* \s*

\s*
\s*){7,}') - or regex.icontains(body.html.raw, '(?:]*>\s* \s*
\s*

\s*){5,}') + or regex.icontains(body.html.raw, + '(?:]*>\s* \s*
\s*

\s*){5,}' + ) or regex.icontains(body.html.raw, '(?:]*> 

\s*){7,}') ) ) - + // a body link does not match the sender domain and any(body.links, .href_url.domain.root_domain != sender.email.domain.root_domain diff --git a/detection-rules/link_google_presentation_open_redirect.yml b/detection-rules/link_google_presentation_open_redirect.yml index f06fe291a15..65e5d89c90c 100644 --- a/detection-rules/link_google_presentation_open_redirect.yml +++ b/detection-rules/link_google_presentation_open_redirect.yml @@ -85,7 +85,7 @@ source: | or ( sender.email.email in ( 'comments-noreply@docs.google.com', - 'drive-shares-dm-noreaply@google.com', + 'drive-shares-dm-noreply@google.com', 'drive-shares-noreply@google.com', 'calendar-notification@google.com' ) diff --git a/detection-rules/link_microsoft_low_reputation.yml b/detection-rules/link_microsoft_low_reputation.yml index af2dc0dd9ca..67b83e08151 100644 --- a/detection-rules/link_microsoft_low_reputation.yml +++ b/detection-rules/link_microsoft_low_reputation.yml @@ -4,7 +4,7 @@ type: "rule" severity: "medium" source: | type.inbound - and 0 < length(body.links) < 100 + and 0 < length(body.links) < 50 // suspicious link and any(body.links, ( @@ -70,7 +70,8 @@ source: | .file_type in $file_types_images and any(ml.logo_detect(.).brands, strings.starts_with(.name, "Microsoft")) ) - or strings.istarts_with(body.current_thread.text, "Microsoft ") + or strings.istarts_with(strings.replace_confusables(body.current_thread.text), "Microsoft ") + or regex.imatch(strings.replace_confusables(body.current_thread.text), '[\n\s]*[o0O]ff[il1]ce\b.*') or any(ml.logo_detect(beta.message_screenshot()).brands, strings.starts_with(.name, "Microsoft") ) @@ -331,7 +332,7 @@ source: | "sharepointonline.com", "yammer.com", ) - + // negate legitimate Office 365 bouncebacks and not ( length(attachments) > 0 @@ -340,7 +341,7 @@ source: | ) and (sender.email.local_part in ('postmaster', 'mailer-daemon')) ) - + // negate Microsoft "welcome to the X group" notifications and not ( headers.auth_summary.dmarc.pass diff --git a/detection-rules/link_multistage_adobe_express.yml b/detection-rules/link_multistage_adobe_express.yml index 696391ccaf7..3578e588fdc 100644 --- a/detection-rules/link_multistage_adobe_express.yml +++ b/detection-rules/link_multistage_adobe_express.yml @@ -4,12 +4,25 @@ type: "rule" severity: "high" source: | type.inbound - and any(body.links, - // it is a new.express.adobe.com page - .href_url.domain.domain == "new.express.adobe.com" - and strings.starts_with(.href_url.path, "/webpage/") - - // filter down the links on express.adobe.com page to those that are external to adobe + and any(filter(body.links, + // the link is a new.express.adobe.com page + .href_url.domain.domain == "new.express.adobe.com" + and strings.starts_with(.href_url.path, "/webpage/") + ), + // filter down the links on express.adobe.com page to those that are external to adobe + // check that the length of external links is reasonable + length(distinct(filter(ml.link_analysis(., mode="aggressive").final_dom.links, + // filter any links on the adobe express page which are + // on express.adobe.com + .href_url.domain.domain != 'new.express.adobe.com' + // or www.adobe.com (privacy page/report abuse/etc) + and .href_url.domain.domain != 'www.adobe.com' + // relative links (no domains) + and .href_url.domain.domain is not null + ), + .href_url.domain.domain + ) + ) <= 10 and any(filter(ml.link_analysis(., mode="aggressive").final_dom.links, // filter any links on the adobe express page which are // on express.adobe.com diff --git a/detection-rules/paypal_invoice_abuse.yml b/detection-rules/paypal_invoice_abuse.yml index 87e2d188b3c..61de2bd572b 100644 --- a/detection-rules/paypal_invoice_abuse.yml +++ b/detection-rules/paypal_invoice_abuse.yml @@ -20,6 +20,41 @@ source: | and ( strings.ilike(body.html.display_text, "*seller note*") or strings.ilike(body.html.display_text, "*Note from *") + // phone number in subject + // the subject contains the seller's "name", attacks have been seen with the entire callback text in the seller's name + or ( + regex.icontains(strings.replace_confusables(subject.subject), + '.*\+?([lo0-9]{1}.)?\(?[lo0-9]{3}?\)?.[lo0-9]{3}.?[lo0-9]{4}.*' + ) + or regex.icontains(strings.replace_confusables(subject.subject), + '.*\+[lo0-9]{1,3}[lo0-9]{10}.*' + ) + or // +12028001238 + regex.icontains(strings.replace_confusables(subject.subject), + '.*[lo0-9]{3}\.[lo0-9]{3}\.[lo0-9]{4}.*' + ) + or // 202-800-1238 + regex.icontains(strings.replace_confusables(subject.subject), + '.*[lo0-9]{3}-[lo0-9]{3}-[lo0-9]{4}.*' + ) + or // (202) 800-1238 + regex.icontains(strings.replace_confusables(subject.subject), + '.*\([lo0-9]{3}\)\s[lo0-9]{3}-[lo0-9]{4}.*' + ) + or // (202)-800-1238 + regex.icontains(strings.replace_confusables(subject.subject), + '.*\([lo0-9]{3}\)-[lo0-9]{3}-[lo0-9]{4}.*' + ) + or ( // 8123456789 + regex.icontains(strings.replace_confusables(subject.subject), + '.*8[lo0-9]{9}.*' + ) + and regex.icontains(strings.replace_confusables(subject.subject + ), + '\+[1l]' + ) + ) + ) ) and ( ( diff --git a/detection-rules/suspicious_request_for_quote_or_purchase.yml b/detection-rules/suspicious_request_for_quote_or_purchase.yml index cb5738d04f6..1f1476624df 100644 --- a/detection-rules/suspicious_request_for_quote_or_purchase.yml +++ b/detection-rules/suspicious_request_for_quote_or_purchase.yml @@ -10,7 +10,12 @@ source: | ( ( length(recipients.to) == 0 - or all(recipients.to, .display_name == "Undisclosed recipients") + or all(recipients.to, + .display_name in ( + "Undisclosed recipients", + "undisclosed-recipients" + ) + ) ) and length(recipients.cc) == 0 and length(recipients.bcc) == 0 @@ -39,10 +44,14 @@ source: | '(sign(ed?)|view).{0,10}(purchase order)|Request for a Quot(e|ation)' ) ), - (regex.icontains(body.current_thread.text, '(please|kindly).{0,30}quote')), + ( + regex.icontains(body.current_thread.text, + '(please|kindly).{0,30}quot(e|ation)' + ) + ), ( regex.icontains(subject.subject, - '(request for (purchase|quot(e|ation))|\bRFQ\b|\bRFP\b)' + '(request for (purchase|quot(e|ation))|\bRFQ\b|\bRFP\b|bid invit(e|ation))' ) ), ( @@ -63,6 +72,22 @@ source: | .name == "purchase_order" and .confidence == "high" ) ), + ( + 0 < length(filter(body.links, + ( + .href_url.domain.domain in $free_subdomain_hosts + or .href_url.domain.domain in $free_file_hosts + or network.whois(.href_url.domain).days_old < 30 + ) + and ( + regex.match(.display_text, '[A-Z ]+') + or any(ml.nlu_classifier(.display_text).entities, + .name in ("request", "urgency") + ) + ) + ) + ) < 3 + ) ) or ( length(attachments) == 1 diff --git a/detection-rules/suspicious_sharepoint_file_shared.yml b/detection-rules/suspicious_sharepoint_file_shared.yml index 2b0e481a4ff..3a21411caaa 100644 --- a/detection-rules/suspicious_sharepoint_file_shared.yml +++ b/detection-rules/suspicious_sharepoint_file_shared.yml @@ -1,5 +1,5 @@ name: "Suspicious SharePoint File Sharing" -description: "This rule detect potential credential phishing leveraging SharePoint file sharing to deliver a PDF or OneNote file using indicators such as suspicious sender analysis and link characteristics." +description: "This rule detect potential credential phishing leveraging SharePoint file sharing to deliver a PDF, OneNote, or Unknown file type file using indicators such as suspicious sender analysis and link characteristics." type: "rule" severity: "medium" source: | @@ -61,6 +61,7 @@ source: | and ( strings.icontains(.href_url.path, '/:o:/') or strings.icontains(.href_url.path, '/:b:/') + or strings.icontains(.href_url.path, '/:u:/') ) ) diff --git a/insights/sender/sender_domain_similar_to_cc.yml b/insights/sender/sender_domain_similar_to_cc.yml new file mode 100644 index 00000000000..2ea3b0f4346 --- /dev/null +++ b/insights/sender/sender_domain_similar_to_cc.yml @@ -0,0 +1,15 @@ +name: "CC'd domains similar to sender domain" +type: "query" +source: | + distinct(map(filter(recipients.cc, + any(recipients.cc, + 0 < strings.ilevenshtein(sender.email.domain.sld, + .email.domain.sld + ) < 4 + ) + ), + .email.domain.domain + ), + . + ) +severity: "medium"
\s*.*\b(?:your subscription renewal|couldn.?t be processed|trouble renewing subscription|update your details|just update your|continue your subscription|prefer to use EFT|change payment method|verify your account|suspended due to issue|payment declined notice|account needs verification|confirm your billing|immediate action required|failed payment notification|billing information update|service interruption warning|unable to process payment|subscription payment failed|action needed now|update banking information|subscription expiration notice|payment method change)\b.*