Request for Quote or Purchase (RFQ|RFP) with suspicious sender or recipient pattern
RFQ/RFP scams involve fraudulent emails posing as legitimate requests for quotations or purchases, often sent by scammers impersonating reputable organizations. These scams aim to deceive recipients into providing sensitive information or conducting unauthorized transactions, often leading to financial loss, or data leakage.
Sublime rule (View on GitHub)
1name: "Request for Quote or Purchase (RFQ|RFP) with suspicious sender or recipient pattern"
2description: |
3 RFQ/RFP scams involve fraudulent emails posing as legitimate requests for quotations or purchases, often sent by scammers impersonating reputable organizations.
4 These scams aim to deceive recipients into providing sensitive information or conducting unauthorized transactions, often leading to financial loss, or data leakage.
5type: "rule"
6severity: "medium"
7source: |
8 type.inbound
9 and (
10 (
11 (
12 length(recipients.to) == 0
13 or all(recipients.to,
14 .display_name in (
15 "Undisclosed recipients",
16 "undisclosed-recipients"
17 )
18 )
19 )
20 and length(recipients.cc) == 0
21 )
22 or
23 (
24 sender.email.domain.root_domain in $free_email_providers
25 and any(headers.reply_to, .email.email != sender.email.email)
26 and any(headers.reply_to, .email.email not in $recipient_emails)
27 )
28 or
29 (
30 length(headers.reply_to) > 0
31 and all(headers.reply_to,
32 .email.domain.root_domain != sender.email.domain.root_domain
33 and not .email.domain.root_domain in $org_domains
34 // wetransfer includes user specific reply-to's & link display text which triggers NLU logic further within the rule
35 and not sender.email.domain.root_domain == "wetransfer.com"
36 )
37 )
38 )
39 and (
40 // Group the language patterns that specifically indicate RFQ/RFP
41 (
42 1 of (
43 // RFQ/RFP specific language patterns
44 regex.icontains(body.current_thread.text, '(discuss.{0,15}purchas(e|ing))'),
45 regex.icontains(body.current_thread.text, '(sign(ed?)|view).{0,10}(purchase order)|Request for a Quot(e|ation)'),
46 regex.icontains(body.current_thread.text, '(please|kindly).{0,30}quot(e|ation)'),
47 regex.icontains(subject.subject, '(request for (purchase|quot(e|ation))|\bRFQ\b|\bRFP\b|bid invit(e|ation))'),
48 any(attachments, regex.icontains(.file_name, "(purchase.?order|Quot(e|ation))")),
49 any(ml.nlu_classifier(body.current_thread.text).tags, .name == "purchase_order" and .confidence == "high")
50 )
51 // Required: at least one RFQ/RFP language pattern
52
53 // Optional: at least one additional indicator (can be another language pattern or a non-language indicator)
54 and (
55 2 of (
56 // RFQ/RFP language patterns (same as above)
57 regex.icontains(body.current_thread.text, '(discuss.{0,15}purchas(e|ing))'),
58 regex.icontains(body.current_thread.text, '(sign(ed?)|view).{0,10}(purchase order)|Request for a Quot(e|ation)'),
59 regex.icontains(body.current_thread.text, '(please|kindly).{0,30}quot(e|ation)'),
60 regex.icontains(subject.subject, '(request for (purchase|quot(e|ation))|\bRFQ\b|\bRFP\b|bid invit(e|ation))'),
61 any(attachments, regex.icontains(.file_name, "(purchase.?order|Quot(e|ation))")),
62 any(ml.nlu_classifier(body.current_thread.text).tags, .name == "purchase_order" and .confidence == "high"),
63
64 // Non-language indicators
65 (
66 any(ml.nlu_classifier(body.current_thread.text).entities, .name == "request")
67 and any(ml.nlu_classifier(body.current_thread.text).entities, .name == "urgency")
68 ),
69 (
70 0 < length(filter(body.links,
71 (
72 .href_url.domain.domain in $free_subdomain_hosts
73 or .href_url.domain.domain in $free_file_hosts
74 or network.whois(.href_url.domain).days_old < 30
75 )
76 and (
77 regex.match(.display_text, '[A-Z ]+')
78 or any(ml.nlu_classifier(.display_text).entities,
79 .name in ("request", "urgency")
80 )
81 )
82 )
83 ) < 3
84 )
85 )
86 )
87 )
88 or (
89 length(attachments) == 1
90 and length(body.current_thread.text) < 100
91 and all(attachments,
92 .file_type in $file_types_images
93 and any(file.explode(.),
94 2 of (
95 regex.icontains(.scan.ocr.raw,
96 '(discuss.{0,15}purchas(e|ing))'
97 ),
98 regex.icontains(.scan.ocr.raw,
99 '(sign(ed?)|view).{0,10}(purchase order)|Request for a Quot(e|ation)'
100 ),
101 regex.icontains(.scan.ocr.raw,
102 '(please|kindly).{0,30}quote'
103 ),
104 (
105 any(ml.nlu_classifier(.scan.ocr.raw).entities,
106 .name == "request"
107 )
108 and any(ml.nlu_classifier(.scan.ocr.raw).entities,
109 .name == "urgency"
110 )
111 ),
112 any(ml.nlu_classifier(.scan.ocr.raw).tags,
113 .name == "purchase_order" and .confidence == "high"
114 )
115 )
116 )
117 )
118 )
119 )
120
121 // negate highly trusted sender domains unless they fail DMARC authentication
122 and (
123 (
124 sender.email.domain.root_domain in $high_trust_sender_root_domains
125 and not headers.auth_summary.dmarc.pass
126 )
127 or sender.email.domain.root_domain not in $high_trust_sender_root_domains
128 )
129 and not profile.by_sender().solicited
130 and not profile.by_sender().any_false_positives
131attack_types:
132 - "BEC/Fraud"
133tactics_and_techniques:
134 - "Evasion"
135 - "Free email provider"
136detection_methods:
137 - "Content analysis"
138 - "Natural Language Understanding"
139 - "URL analysis"
140id: "2ac0d329-c1fb-5c87-98dd-ea3e5b85377a"