Medium Severity

Credential phishing: Suspicious e-sign agreement document notification

Description

Detects phishing attempts disguised as e-signature requests, characterized by common document sharing phrases, unusual HTML padding, and suspicious link text.

References

No references.

Sublime Security
Created May 15th, 2024 • Last updated Sep 4th, 2026
Source
type.inbound
and (
  any([subject.subject, sender.display_name],
      regex.icontains(strings.replace_confusables(.),
                      "D[0o]cuLink",
                      "Agreement",
                      "Access.&.Appr[0o]ved",
                      "Agreement.{0,5}Review",
                      "Attend.and.Review",
                      "action.re?quired",
                      "AuthentiSign",
                      "Completed.File",
                      "D[0o]chsared",
                      "D[0o]cshared",
                      "D[0o]csPoint",
                      "D[0o]cument.Shared",
                      "D[0o]cuCentre",
                      "D[0o]cuCenter",
                      "D[0o]cCenter",
                      "D[0o]csOnline",
                      "D[0o]cSend",
                      "D[0o]cu?Send",
                      "d[0o]csign",
                      "D[0o]cu-eSin",
                      "D[0o]cu-management",
                      "\\beSign",
                      "e\\.sign",
                      "esign.[0o]nline",
                      "[SsZz][lL][GgSs][Nn].*D[0o]c",
                      "e-d[0o]c",
                      "e-signature",
                      "e-Verify Doc",
                      "eSignature",
                      "eSign&Return",
                      "eSign[0o]nline",
                      "Fileshare",
                      "Review.and.C[0o]mplete",
                      "Review.&.Sign",
                      "Sign[0o]nline",
                      "Signature.Request",
                      "Shared.C[0o]mpleted",
                      "Sign.and.Seal",
                      "viaSign",
                      "D[0o]cuSign",
                      "D[0o]csID",
                      "Complete.{0,10}D[0o]cuSign",
                      "Enroll & Sign",
                      "Review and Sign",
                      "Sign(?:Report|Now)",
                      "SignD[0o]c",
                      "D[0o]cxxx",
                      "d[0o]cufile",
                      'E\x{00AD}-\x{00AD}S\x{00AD}i\x{00AD}g\x{00AD}n\x{00AD}&Return',
                      "d[0o]cument.signature",
                      "Electr[0o]nic.?Signature",
                      "Complete: ",
                      "Please (?:Review|Sign)",
                      "^REVIEW$",
                      "requests your signature",
                      "signature on.*contract",
                      "Independent Contract",
                      "Contract.*signature",
                      "add your signature",
                      "signature needed",
                      "attn_task",
                      "DocReq\\b"
      )
      or (
        regex.icontains(strings.replace_confusables(.), "action.re?quired")
        and not (
          sender.email.domain.root_domain == "sharepointonline.com"
          and headers.auth_summary.dmarc.pass
          and strings.icontains(subject.subject, "asked to edit")
        )
      )
  )
  // lure CTA injected into the body (not subject/display) above a forwarded thread
  or (
    regex.icontains(strings.replace_confusables(body.current_thread.text),
                    "requests? your signature",
                    "review, sign, and return",
                    "signature on the attached"
    )
    // legit "please sign & return" mail attaches the actual document; this
    // campaign references an "attached" doc but delivers a fake one via an
    // off-domain link, with no real document attached
    and not any(attachments,
                .file_extension in~ ("pdf", "doc", "docx", "xls", "xlsx", "rtf")
    )
    // legit notification platforms (e.g. ParentSquare, Panopto) self-host their
    // "review/sign/view" CTA on the sender's own root domain; this campaign points
    // the CTA at an off-domain credential page. require a suspicious link whose
    // display text is a document CTA AND that leaves the sender domain, so
    // self-hosted legit notifications (and incidental links like the aka.ms
    // first-contact banner, which carries no CTA verb) don't match.
    and any(body.links,
            .href_url.domain.root_domain is not null
            and .href_url.domain.root_domain != sender.email.domain.root_domain
            and regex.icontains(strings.replace_confusables(.display_text),
                                "view",
                                "review",
                                "sign",
                                "document",
                                "download",
                                "open",
                                "complete"
            )
    )
  )
)
and (
  // unusual repeated patterns in HTML
  regex.icontains(body.html.raw, '((<br\s*/?>\s*){20,}|\n{20,})')
  or regex.icontains(body.html.raw, '(<p[^>]*>\s*<br\s*/?>\s*</p>\s*){30,}')
  or regex.icontains(body.html.raw,
                     '(<p class=".*?"><span style=".*?"><o:p>&nbsp;</o:p></span></p>\s*){30,}'
  )
  or regex.icontains(body.html.raw, '(<p>&nbsp;</p>\s*){7,}')
  or regex.icontains(body.html.raw, '(<p[^>]*>\s*&nbsp;<br>\s*</p>\s*){5,}')
  or regex.icontains(body.html.raw, '(<p[^>]*>&nbsp;</p>\s*){7,}')
  or strings.count(body.html.raw, "&nbsp;\u{200C}&nbsp;\u{200C}&nbsp") > 50
  or regex.count(body.html.raw,
                 '<span\s*class\s*=\s*"[^\"]+"\s*>\s*[a-z]\s*<\/span><span\s*class\s*=\s*"[^\"]+"\s*>\s*[a-z]+\s*<\/span>'
  ) > 50
  // lookalike docusign
  or regex.icontains(body.html.raw, '>Docus[1l]gn<')
  or strings.icontains(body.current_thread.text, 'completed by all parties')
  or (
    regex.icontains(body.html.inner_text, 'Document')
    and length(body.html.inner_text) < 500
  )
  // common greetings via email.local_part
  or any(recipients.to,
         // use count to ensure the email address is not part of a disclaimer
         strings.icount(body.current_thread.text, .email.local_part) > 
         // sum allows us to add more logic as needed
         sum([
               strings.icount(body.current_thread.text,
                              strings.concat('was sent to ', .email.email)
               ),
               strings.icount(body.current_thread.text,
                              strings.concat('intended for ', .email.email)
               )
             ]
         )
  )
  // common greetings via mailbox display name
  or strings.icount(body.current_thread.text, mailbox.display_name) > 
  // sum allows us to add more logic as needed
  sum([
        strings.icount(body.current_thread.text,
                       strings.concat('was sent to ', mailbox.display_name)
        ),
        strings.icount(body.current_thread.text,
                       strings.concat('intended for ', mailbox.display_name)
        )
      ]
  )
  // Abnormally high count of mailto links in raw html
  or regex.count(body.html.raw,
                 'mailto:[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}'
  ) > 50

  // High count of empty elements (padding)
  or regex.count(body.html.raw,
                 '<(?:p|div|span|td)[^>]*>\s*(?:&nbsp;|\s)*\s*</(?:p|div|span|td)>'
  ) > 30

  // HR impersonation
  or strings.ilike(sender.display_name, "HR", "H?R", "*Human Resources*")

  // Sender display name contains a phone number
  or regex.icontains(sender.display_name,
                     '\+?([ilo0-9]{1}.)?\(?[ilo0-9]{3}?\)?.[ilo0-9]{3}.?[ilo0-9]{4}'
  )

  // short CTA grafted above a forwarded legitimate thread to borrow its credibility
  or (
    length(body.previous_threads) > 0 and length(body.current_thread.text) < 600
  )
)
and (
  any(body.links,

      // suspicious content within link display_text
      regex.icontains(strings.replace_confusables(.display_text),
                      "activate",
                      "re-auth",
                      "verify",
                      "acknowledg",
                      "(keep|change).{0,20}(active|password|access)",
                      '((verify|view|click|download|goto|keep|Vιew|release).{0,15}(attachment|current|download|fax|file|document|message|same)s?)',
                      'use.same.pass',
                      'validate.{0,15}account',
                      'recover.{0,15}messages',
                      '(retry|update).{0,10}payment',
                      'check activity',
                      '(listen|play).{0,10}(vm|voice)',
                      'clarify.{0,20}(deposit|wallet|funds)',
                      'enter.{0,15}teams',
                      'Review and sign',
                      'REVIEW.*DOCUMENT',
                      'Open Document',
                      'Sign Now',
                      'complete tasks?'
      )
      // check that the display_text is all lowercase
      or (
        regex.contains(.display_text,
                       "\\bVIEW",
                       "DOWNLOAD",
                       "CHECK",
                       "KEEP.(SAME|MY)",
                       "VERIFY",
                       "ACCESS\\b",
                       "SIGN\\b",
                       "ENABLE\\b",
                       "RETAIN",
                       "PLAY",
                       "LISTEN",
        )
        and regex.match(.display_text, "^[^a-z]*[A-Z][^a-z]*$")
      )

      // the display text is _exactly_
      or .display_text in~ ("Open")

      // URL fragment containing recipient's address
      or .href_url.fragment in map(recipients.to, .email.email)
  )
  // one hyperlinked image that's not a tracking pixel
  or (
    length(html.xpath(body.html,
                      "//a//img[(number(@width) > 5 or not(@width)) and (number(@height) > 5 or not(@height))]"
           ).nodes
    ) == 1
    and length(body.current_thread.text) < 500
  )
  or (
    length(attachments) > 0
    and any(attachments,
            (
              regex.icontains(beta.ocr(.).text,
                              "activate",
                              "re-auth",
                              "verify",
                              "acknowledg",
                              "(keep|change).{0,20}(active|password|access)",
                              '((verify|view|click|download|goto|keep|Vιew|release).{0,15}(attachment|current|download|fax|file|document|message|same)s?)',
                              'use.same.pass',
                              'validate.{0,15}account',
                              'recover.{0,15}messages',
                              '(retry|update).{0,10}payment',
                              'check activity',
                              '(listen|play).{0,10}(vm|voice)',
                              'clarify.{0,20}(deposit|wallet|funds)',
                              'enter.{0,15}teams',
                              'Review and sign'
              )
            )
            or (
              any(file.explode(.),
                  regex.icontains(.scan.ocr.raw,
                                  "activate",
                                  "re-auth",
                                  "verify",
                                  "acknowledg",
                                  "(keep|change).{0,20}(active|password|access)",
                                  '((verify|view|click|download|goto|keep|Vιew|release).{0,15}(attachment|current|download|fax|file|document|message|same)s?)',
                                  'use.same.pass',
                                  'validate.{0,15}account',
                                  'recover.{0,15}messages',
                                  '(retry|update).{0,10}payment',
                                  'check activity',
                                  '(listen|play).{0,10}(vm|voice)',
                                  'clarify.{0,20}(deposit|wallet|funds)',
                                  'enter.{0,15}teams',
                                  'Review and sign'
                  )
              )
            )
    )
  )
)
// the message is unsolicited and no false positives
and (
  not profile.by_sender_email().solicited
  or profile.by_sender_email().prevalence == "new"
  or (
    profile.by_sender_email().any_messages_malicious_or_spam
    and not profile.by_sender_email().any_messages_benign
  )
  or (
    profile.by_sender_email().any_messages_malicious_or_spam
    and profile.by_sender_email().any_messages_benign
    and (
      not headers.auth_summary.dmarc.pass or not headers.auth_summary.spf.pass
    )
  )
)

// negate replies/fowards containing legitimate docs
and not (length(headers.references) > 0 or headers.in_reply_to is not null)

// negate highly trusted sender domains unless they fail DMARC authentication
and (
  (
    sender.email.domain.root_domain in $high_trust_sender_root_domains
    and (
      any(distinct(headers.hops, .authentication_results.dmarc is not null),
          strings.ilike(.authentication_results.dmarc, "*fail")
      )
    )
  )
  or sender.email.domain.root_domain not in $high_trust_sender_root_domains
)
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