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}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 )
66 // Required: at least one RFQ/RFP keyword pattern
67
68 // Optional: at least one additional indicator (can be another keyword pattern or a non-keyword indicator)
69 and (
70 2 of (
71 // RFQ/RFP keyword patterns (same as above)
72 regex.icontains(body.current_thread.text,
73 '(discuss.{0,15}purchas(e|ing))'
74 ),
75 regex.icontains(body.current_thread.text,
76 '(sign(ed?)|view).{0,10}(purchase order)|Request for a Quot(e|ation)'
77 ),
78 regex.icontains(body.current_thread.text,
79 '(please|kindly).{0,30}quot(e|ation)'
80 ),
81 regex.icontains(subject.subject,
82 '(request for (purchase|quot(e|ation))|\bRFQ\b|\bRFP\b|bid invit(e|ation))'
83 ),
84 any(attachments,
85 regex.icontains(.file_name, "(purchase.?order|Quot(e|ation))")
86 ),
87 any(ml.nlu_classifier(body.current_thread.text).tags,
88 .name == "purchase_order" and .confidence == "high"
89 ),
90
91 // Non-keyword indicators
92 (
93 any(ml.nlu_classifier(body.current_thread.text).entities,
94 .name == "request"
95 )
96 and any(ml.nlu_classifier(body.current_thread.text).entities,
97 .name == "urgency"
98 )
99 and not any(beta.ml_topic(body.current_thread.text).topics,
100 .name == "Advertising and Promotions"
101 and .confidence == "high"
102 )
103 ),
104 (
105 0 < length(filter(body.links,
106 (
107 .href_url.domain.domain in $free_subdomain_hosts
108 or .href_url.domain.domain in $free_file_hosts
109 or network.whois(.href_url.domain).days_old < 30
110 )
111 and (
112 regex.match(.display_text, '[A-Z ]+')
113 or any(ml.nlu_classifier(.display_text).entities,
114 .name in ("request", "urgency")
115 )
116 or any(ml.nlu_classifier(.display_text).intents,
117 .name in ("cred_theft")
118 )
119 )
120 )
121 ) < 3
122 )
123 )
124 )
125 )
126 or (
127 length(attachments) == 1
128 and length(body.current_thread.text) < 100
129 and all(attachments,
130 .file_type in $file_types_images
131 and any(file.explode(.),
132 2 of (
133 regex.icontains(.scan.ocr.raw,
134 '(discuss.{0,15}purchas(e|ing))'
135 ),
136 regex.icontains(.scan.ocr.raw,
137 '(sign(ed?)|view).{0,10}(purchase order)|Request for a Quot(e|ation)'
138 ),
139 regex.icontains(.scan.ocr.raw,
140 '(please|kindly).{0,30}quote'
141 ),
142 (
143 any(ml.nlu_classifier(.scan.ocr.raw).entities,
144 .name == "request"
145 )
146 and any(ml.nlu_classifier(.scan.ocr.raw).entities,
147 .name == "urgency"
148 )
149 ),
150 any(ml.nlu_classifier(.scan.ocr.raw).tags,
151 .name == "purchase_order" and .confidence == "high"
152 )
153 )
154 )
155 )
156 )
157 )
158
159 // negate highly trusted sender domains unless they fail DMARC authentication
160 and (
161 (
162 sender.email.domain.root_domain in $high_trust_sender_root_domains
163 and not headers.auth_summary.dmarc.pass
164 )
165 or sender.email.domain.root_domain not in $high_trust_sender_root_domains
166 )
167 and (
168 not profile.by_sender().solicited
169 or profile.by_sender().days_since.last_contact > 30
170 )
171 and not profile.by_sender().any_messages_benign
172attack_types:
173 - "BEC/Fraud"
174tactics_and_techniques:
175 - "Evasion"
176 - "Free email provider"
177detection_methods:
178 - "Content analysis"
179 - "Natural Language Understanding"
180 - "URL analysis"
181id: "2ac0d329-c1fb-5c87-98dd-ea3e5b85377a"