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 sender.email.domain.root_domain in $free_email_providers
24 and any(headers.reply_to, .email.email != sender.email.email)
25 and any(headers.reply_to, .email.email not in $recipient_emails)
26 )
27 or (
28 length(headers.reply_to) > 0
29 and all(headers.reply_to,
30 .email.domain.root_domain != sender.email.domain.root_domain
31 and not .email.domain.root_domain in $org_domains
32 // wetransfer includes user specific reply-to's & link display text which triggers NLU logic further within the rule
33 and not sender.email.domain.root_domain == "wetransfer.com"
34 )
35 )
36 or (
37 length(recipients.to) == 1
38 and all(recipients.to, .email.email == sender.email.email)
39 and (length(recipients.cc) > 0 or length(recipients.bcc) > 0)
40 )
41 )
42 and (
43 // Group the keyword patterns that specifically indicate RFQ/RFP
44 (
45 1 of (
46 // RFQ/RFP specific language patterns
47 regex.icontains(body.current_thread.text,
48 '(discuss.{0,15}purchas(e|ing))'
49 ),
50 regex.icontains(body.current_thread.text,
51 '(sign(ed?)|view).{0,10}(purchase order)|Request for (a Quot(e|ation)|Proposal)'
52 ),
53 regex.icontains(body.current_thread.text,
54 '(please|kindly).{0,30}(?:proposal|quot(e|ation))'
55 ),
56 regex.icontains(subject.subject,
57 '(request for (purchase|quot(e|ation))|\bRFQ\b|\bRFP\b|bid invit(e|ation))'
58 ),
59 any(attachments,
60 regex.icontains(.file_name, "(purchase.?order|Quot(e|ation))")
61 ),
62 any(ml.nlu_classifier(body.current_thread.text).tags,
63 .name == "purchase_order" and .confidence == "high"
64 ),
65 any(ml.nlu_classifier(body.current_thread.text).entities,
66 .name == "financial" and regex.imatch(.text, "rfp|rfq")
67 )
68 )
69 // Required: at least one RFQ/RFP keyword pattern
70
71 // Optional: at least one additional indicator (can be another keyword pattern or a non-keyword indicator)
72 and (
73 2 of (
74 // RFQ/RFP keyword patterns (same as above)
75 regex.icontains(body.current_thread.text,
76 '(discuss.{0,15}purchas(e|ing))'
77 ),
78 regex.icontains(body.current_thread.text,
79 '(sign(ed?)|view).{0,10}(purchase order)|Request for a Quot(e|ation)'
80 ),
81 regex.icontains(body.current_thread.text,
82 '(please|kindly).{0,30}(?:proposal|quot(e|ation))'
83 ),
84 regex.icontains(subject.subject,
85 '(request for (purchase|quot(e|ation))|\bRFQ\b|\bRFP\b|bid invit(e|ation))'
86 ),
87 any(attachments,
88 regex.icontains(.file_name, "(purchase.?order|Quot(e|ation))")
89 ),
90 any(ml.nlu_classifier(body.current_thread.text).tags,
91 .name == "purchase_order" and .confidence == "high"
92 ),
93 any(ml.nlu_classifier(body.current_thread.text).entities,
94 .name == "financial" and regex.imatch(.text, "rfp|rfq")
95 ),
96
97 // Non-keyword indicators
98 (
99 any(ml.nlu_classifier(body.current_thread.text).entities,
100 .name == "request"
101 )
102 and any(ml.nlu_classifier(body.current_thread.text).entities,
103 .name == "urgency"
104 )
105 and not any(beta.ml_topic(body.current_thread.text).topics,
106 .name == "Advertising and Promotions"
107 and .confidence == "high"
108 )
109 ),
110 (
111 0 < length(filter(body.links,
112 (
113 .href_url.domain.domain in $free_subdomain_hosts
114 or .href_url.domain.domain in $free_file_hosts
115 or network.whois(.href_url.domain).days_old < 30
116 )
117 and (
118 regex.match(.display_text, '[A-Z ]+')
119 or any(ml.nlu_classifier(.display_text).entities,
120 .name in ("request", "urgency")
121 )
122 or any(ml.nlu_classifier(.display_text).intents,
123 .name in ("cred_theft")
124 )
125 )
126 )
127 ) < 3
128 ),
129 // mentions an attachment that does not exist
130 (
131 length(attachments) == 0
132 and strings.icontains(body.current_thread.text, "attached")
133 )
134 )
135 )
136 )
137 or (
138 length(attachments) == 1
139 and length(body.current_thread.text) < 100
140 and all(attachments,
141 .file_type in $file_types_images
142 and any(file.explode(.),
143 2 of (
144 regex.icontains(.scan.ocr.raw,
145 '(discuss.{0,15}purchas(e|ing))'
146 ),
147 regex.icontains(.scan.ocr.raw,
148 '(sign(ed?)|view).{0,10}(purchase order)|Request for a Quot(e|ation)'
149 ),
150 regex.icontains(.scan.ocr.raw,
151 '(please|kindly).{0,30}quote'
152 ),
153 (
154 any(ml.nlu_classifier(.scan.ocr.raw).entities,
155 .name == "request"
156 )
157 and any(ml.nlu_classifier(.scan.ocr.raw).entities,
158 .name == "urgency"
159 )
160 ),
161 any(ml.nlu_classifier(.scan.ocr.raw).tags,
162 .name == "purchase_order" and .confidence == "high"
163 ),
164 any(ml.nlu_classifier(.scan.ocr.raw).entities,
165 .name == "financial"
166 and regex.imatch(.text, "rfp|rfq")
167 ),
168 )
169 )
170 )
171 )
172 )
173
174 // negate highly trusted sender domains unless they fail DMARC authentication
175 and (
176 (
177 sender.email.domain.root_domain in $high_trust_sender_root_domains
178 and not headers.auth_summary.dmarc.pass
179 )
180 or sender.email.domain.root_domain not in $high_trust_sender_root_domains
181 )
182 and (
183 not profile.by_sender().solicited
184 or profile.by_sender().days_since.last_contact > 30
185 )
186 and not profile.by_sender().any_messages_benign
187attack_types:
188 - "BEC/Fraud"
189tactics_and_techniques:
190 - "Evasion"
191 - "Free email provider"
192detection_methods:
193 - "Content analysis"
194 - "Natural Language Understanding"
195 - "URL analysis"
196id: "2ac0d329-c1fb-5c87-98dd-ea3e5b85377a"