Medium Severity

Attachment: Duplicated header pages in fraudulent multi-page PDF Request for Quotation

Description

Detects inbound PDF attachments between 2-3 pages where the first three lines of the header text appear nearly identical (within a Levenshtein distance of 5) on multiple pages, combined with terminology commonly found in fraudulent Request for Quotation (RFQ) documents such as procurement language, payment conditions, and preference point systems. This pattern is consistent with fabricated or manipulated procurement documents used in BEC or fraud schemes.

References

No references.

Sublime Security
Created Jun 25th, 2026 • Last updated Jun 25th, 2026
Source
type.inbound
and any(attachments,
        .file_type == "pdf"
        //
        // This rule makes use of a beta feature and is subject to change without notice
        // using the beta feature in custom rules is not suggested until it has been formally released
        //
        and 1 < beta.parse_exif(.).page_count <= 3
        //
        // This rule makes use of a beta feature and is subject to change without notice
        // using the beta feature in custom rules is not suggested until it has been formally released
        //
        and beta.ocr(.).page_results[0].text != ""
        // extract the first 3 lines from the first page
        and any(regex.iextract(beta.ocr(.).page_results[0].text,
                               '^(?P<page_1_3_lines>(?:[^\r\n]+[\r\n]+){3})'
                ),
                // make sure we have something
                .named_groups["page_1_3_lines"] != ""
                // either page 2 or page 3 contain VERY close text
                // we have to do very close because sometimes the address format changes in trivial ways
                // like a comma is removed or something
                and (
                  any(regex.iextract(beta.ocr(..).page_results[1].text,
                                     '^(?P<page_2_3_lines>(?:[^\r\n]+[\r\n]+){3})'
                      ),
                      strings.levenshtein(.named_groups["page_2_3_lines"],
                                          ..named_groups["page_1_3_lines"]
                      ) <= 5
                  )
                  or any(regex.iextract(beta.ocr(..).page_results[2].text,
                                        '^(?P<page_3_3_lines>(?:[^\r\n]+[\r\n]+){3})'
                         ),
                         strings.levenshtein(.named_groups["page_3_3_lines"],
                                             ..named_groups["page_1_3_lines"]
                         ) <= 5
                  )
                )
        )
        and (
          3 of (
            strings.icontains(beta.ocr(.).text,
                              "Contact Person",
                              "Date of issue",
                              "Date Issued",
                              "Closing Time and Date",
                              "Closing Date",
                              "QUOTATIONS ARE HEREBY INVITED FOR THE SUPPLY OF",
                              "COMPULSORY BIDDERS MUST QUOTE",
                              "Method of RFQ Submission"
            ),
            strings.icontains(beta.ocr(.).text,
                              "Must be inclusive",
                              "Tax on Price Quotation",
                              "100% payment made in the form",
                              "Conditions for Release of Payment",
                              "Period of Validity of Quotes",
                              "Partial Bids",
                              "PRICING QUOTATION",
                              "Payment Terms and Conditions",
                              "freight, insurance until acceptance"
            ),
            strings.icontains(beta.ocr(.).text,
                              "80/20 preference point system",
                              "80:20",
                              "Selection of suppliers will be based on",
                              "plant upgrade and maintenance",
                              "plant maintenance",
                              "without an Official Purchase Order",
                              "If unable to quote"
            ),
            strings.icontains(beta.ocr(.).text,
                              "remain binding upon me/us",
                              "Authorized Signature",
                              "Name and Capacity",
                              "must be completed and accompanied by",
                              "This is not a Purchase Order"
            )
          )
        )
)
MQL Rule Console
DocsLearning Labs

Playground

Test against your own EMLs or sample data.

Share

Post about this on your socials.

Get Started. Today.

Managed or self-managed. No MX changes.

Deploy and integrate a free Sublime instance in minutes.
Get Started