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"
to-top