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