Skip to content

Commit 4a53eff

Browse files
[Shared Samples] [PR #4687] added rule: PR# 4687 - Request for Quote or Purchase (RFQ|RFP) with suspicious sender or recipient pattern
1 parent dce22d4 commit 4a53eff

1 file changed

Lines changed: 241 additions & 0 deletions

File tree

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
name: "PR# 4687 - Request for Quote or Purchase (RFQ|RFP) with suspicious sender or recipient pattern"
2+
description: |
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.
5+
type: "rule"
6+
severity: "medium"
7+
source: |
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+
)
33+
)
34+
or (
35+
length(recipients.to) == 1
36+
and all(recipients.to, .email.email == sender.email.email)
37+
and (length(recipients.cc) > 0 or length(recipients.bcc) > 0)
38+
)
39+
or (
40+
length(recipients.to) == 0
41+
and length(recipients.cc) == 1
42+
and sender.email.email == recipients.cc[0].email.email
43+
)
44+
or (
45+
length(recipients.to) == 1
46+
and length(recipients.cc) == 0
47+
and sender.email.email == recipients.to[0].email.email
48+
)
49+
)
50+
and (
51+
// Group the keyword patterns that specifically indicate RFQ/RFP
52+
(
53+
(
54+
// RFQ/RFP specific language patterns
55+
regex.icontains(body.current_thread.text,
56+
'(discuss.{0,15}purchas(e|ing))'
57+
)
58+
or regex.icontains(body.current_thread.text,
59+
'(sign(ed?)|view).{0,10}(purchase order)|Request for (a Quot(e|ation)|Proposal)'
60+
)
61+
or regex.icontains(body.current_thread.text,
62+
'(please|kindly).{0,30}(?:proposal|quot(e|ation))'
63+
)
64+
or regex.icontains(subject.subject,
65+
'(request for (purchase|quot(e|ation))|\bRFQ\b|\bRFP\b|bid invit(e|ation))'
66+
)
67+
or any(attachments,
68+
regex.icontains(.file_name, "(purchase.?order|Quot(e|ation))")
69+
)
70+
or any(ml.nlu_classifier(body.current_thread.text).tags,
71+
.name == "purchase_order" and .confidence != "low"
72+
)
73+
or any(ml.nlu_classifier(body.current_thread.text).entities,
74+
.name == "financial" and regex.imatch(.text, "rfp|rfq")
75+
)
76+
or any(ml.nlu_classifier(body.current_thread.text).entities,
77+
.name == "request" and strings.icontains(.text, 'submit bid')
78+
)
79+
)
80+
// Required: at least one RFQ/RFP keyword pattern
81+
82+
// Optional: at least one additional indicator (can be another keyword pattern or a non-keyword indicator)
83+
and (
84+
2 of (
85+
// RFQ/RFP keyword patterns (same as above)
86+
regex.icontains(body.current_thread.text,
87+
'(discuss.{0,15}purchas(e|ing))'
88+
),
89+
regex.icontains(body.current_thread.text,
90+
'(sign(ed?)|view).{0,10}(purchase order)|Request for a Quot(e|ation)'
91+
),
92+
regex.icontains(body.current_thread.text,
93+
'(please|kindly).{0,30}(?:proposal|quot(e|ation))'
94+
),
95+
regex.icontains(body.current_thread.text,
96+
'(?:invitation|intent) to bid'
97+
),
98+
regex.icontains(subject.subject,
99+
'(request for (purchase|quot(e|ation))|\bRFQ\b|\bRFP\b|bid invit(e|ation))'
100+
),
101+
any(attachments,
102+
regex.icontains(.file_name, "(purchase.?order|Quot(e|ation))")
103+
),
104+
any(ml.nlu_classifier(body.current_thread.text).tags,
105+
.name == "purchase_order" and .confidence == "high"
106+
),
107+
any(ml.nlu_classifier(body.current_thread.text).entities,
108+
.name == "financial" and regex.imatch(.text, "rfp|rfq")
109+
),
110+
111+
// Non-keyword indicators
112+
(
113+
any(ml.nlu_classifier(body.current_thread.text).entities,
114+
.name == "request"
115+
)
116+
and any(ml.nlu_classifier(body.current_thread.text).entities,
117+
.name == "urgency"
118+
)
119+
and not any(ml.nlu_classifier(body.current_thread.text).topics,
120+
.name == "Advertising and Promotions"
121+
and .confidence == "high"
122+
)
123+
),
124+
(
125+
0 < length(filter(body.links,
126+
(
127+
.href_url.domain.domain in $free_subdomain_hosts
128+
or .href_url.domain.domain in $free_file_hosts
129+
or network.whois(.href_url.domain).days_old < 30
130+
)
131+
and (
132+
regex.match(.display_text, '[A-Z ]+')
133+
or any(ml.nlu_classifier(.display_text).entities,
134+
.name in ("request", "urgency")
135+
)
136+
or any(ml.nlu_classifier(.display_text).intents,
137+
.name in ("cred_theft")
138+
)
139+
)
140+
)
141+
) < 3
142+
),
143+
// mentions an attachment that does not exist
144+
(
145+
length(attachments) == 0
146+
and strings.icontains(body.current_thread.text, "attached")
147+
),
148+
any(body.current_thread.links, regex.icontains(.href_url.url, 'RFP'))
149+
)
150+
)
151+
)
152+
or (
153+
length(attachments) == 1
154+
and length(body.current_thread.text) < 100
155+
and all(attachments,
156+
.file_type in $file_types_images
157+
and any(file.explode(.),
158+
2 of (
159+
regex.icontains(.scan.ocr.raw,
160+
'(discuss.{0,15}purchas(e|ing))'
161+
),
162+
regex.icontains(.scan.ocr.raw,
163+
'(sign(ed?)|view).{0,10}(purchase order)|Request for a Quot(e|ation)'
164+
),
165+
regex.icontains(.scan.ocr.raw,
166+
'(please|kindly).{0,30}quote'
167+
),
168+
(
169+
any(ml.nlu_classifier(.scan.ocr.raw).entities,
170+
.name == "request"
171+
)
172+
and any(ml.nlu_classifier(.scan.ocr.raw).entities,
173+
.name == "urgency"
174+
)
175+
),
176+
any(ml.nlu_classifier(.scan.ocr.raw).tags,
177+
.name == "purchase_order" and .confidence == "high"
178+
),
179+
any(ml.nlu_classifier(.scan.ocr.raw).entities,
180+
.name == "financial"
181+
and regex.imatch(.text, "rfp|rfq")
182+
),
183+
)
184+
)
185+
)
186+
)
187+
// fake PDF file icon used as a link lure with bid solicitation language
188+
or (
189+
regex.icontains(subject.subject, 'project\s+summary')
190+
and any(html.xpath(body.html, '//div[.//img[contains(@src,"pdf")]]//a').nodes,
191+
regex.icontains(.display_text, 'project\s+summary')
192+
)
193+
and regex.icontains(body.current_thread.text,
194+
'(put a bid|\bbid\s+(for|on)\b|submit.{1,20}(bid|quot(e|ation))|request for (purchase|quot(e|ation))|\bRFQ\b|\bRFP\b|project\s+summary)'
195+
)
196+
)
197+
)
198+
// wetransfer includes user specific reply-to's & link display text which triggers NLU logic further within the rule
199+
and not (
200+
sender.email.domain.root_domain == "wetransfer.com"
201+
and coalesce(headers.auth_summary.dmarc.pass, false)
202+
)
203+
204+
// negate highly trusted sender domains unless they fail DMARC authentication
205+
and (
206+
(
207+
sender.email.domain.root_domain in $high_trust_sender_root_domains
208+
and not headers.auth_summary.dmarc.pass
209+
)
210+
or sender.email.domain.root_domain not in $high_trust_sender_root_domains
211+
)
212+
and (
213+
(
214+
(
215+
not profile.by_sender().solicited
216+
or profile.by_sender().days_since.last_contact > 30
217+
)
218+
and not profile.by_sender().any_messages_benign
219+
)
220+
// sender address listed as a recipient
221+
or (
222+
length(recipients.to) == 1
223+
and sender.email.email in map(recipients.to, .email.email)
224+
)
225+
)
226+
attack_types:
227+
- "BEC/Fraud"
228+
tactics_and_techniques:
229+
- "Evasion"
230+
- "Free email provider"
231+
detection_methods:
232+
- "Content analysis"
233+
- "Natural Language Understanding"
234+
- "URL analysis"
235+
id: "3e99fe16-b030-5b6f-a269-77d378795ae7"
236+
tags:
237+
- created_from_open_prs
238+
- rule_status_modified
239+
- pr_author_D-Bolton
240+
references:
241+
- https://github.com/sublime-security/sublime-rules/pull/4687

0 commit comments

Comments
 (0)