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, ' ') + or 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.* | ') + ) +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, '