Detection rules › Sublime MQL

Fake thread with suspicious indicators

Severity
medium
Type
rule
Source
github.com/sublime-security/sublime-rules

Fake thread contains suspicious indicators, which can lead to BEC, credential phishing, and other undesirable outcomes.

Threat classification

Sublime's own taxonomy (not MITRE ATT&CK).

CategoryValues
Attack typesBEC/Fraud, Credential Phishing, Spam
Tactics and techniquesEvasion, Social engineering

Event coverage

Rule body MQL

type.inbound
// fake thread check
and (length(headers.references) == 0 or headers.in_reply_to is null)
and (
  subject.is_reply
  or subject.is_forward
  // fake thread, but no indication in the subject line
  // current_thread pulls the recent thread, but the full body contains the fake "original" email
  or (
    not (subject.is_reply or subject.is_forward)
    and any([body.current_thread.text, body.html.display_text, body.plain.raw],
            3 of (
              strings.icontains(., "from:"),
              strings.icontains(., "to:"),
              strings.icontains(., "sent:"),
              strings.icontains(., "date:"),
              strings.icontains(., "cc:"),
              strings.icontains(., "subject:")
            )
    )
    and length(body.current_thread.text) + 100 < length(coalesce(body.html.display_text,
                                                                 body.plain.raw
                                                        )
    )
  )
)

// negating bouncebacks
and not any(attachments,
            .content_type in ("message/delivery-status", "message/rfc822")
)
// negating Google Calendar invites
and (
  (
    headers.return_path.domain.domain is not null
    and headers.return_path.domain.domain != 'calendar-server.bounces.google.com'
  )
  or headers.return_path.domain.domain is null
)
// not mimecast secure message from internal source
and not (
  strings.istarts_with(headers.message_id, '<Mimecast.')
  and strings.iends_with(headers.message_id, '.mimecast.lan>')
  and headers.hops[0].received.server.raw == "relay.mimecast.com"
  and strings.icontains(headers.hops[0].received.source.raw, 'mimecast.lan')
)

// and not solicited
and not profile.by_sender().solicited
and 4 of (
  // language attempting to engage
  (
    any(ml.nlu_classifier(body.current_thread.text).entities,
        .name == "request"
    )
    and any(ml.nlu_classifier(body.current_thread.text).entities,
            .name == "financial"
    )
  ),

  // invoicing language
  (
    any(ml.nlu_classifier(body.current_thread.text).tags, .name == "invoice")
    or any(ml.nlu_classifier(body.current_thread.text).entities,
           .text == "invoice"
    )
  ),

  // urgency request
  any(ml.nlu_classifier(body.current_thread.text).entities, .name == "urgency"),

  // cred_theft detection
  any(ml.nlu_classifier(body.current_thread.text).intents,
      .name == "cred_theft" and .confidence in~ ("medium", "high")
  ),

  // commonly abused sender TLD
  strings.ilike(sender.email.domain.tld, "*.jp"),

  // headers traverse abused TLD
  any(headers.domains, strings.ilike(.tld, "*.jp")),

  // known suspicious pattern in the URL path
  any(body.links, regex.match(.href_url.path, '\/[a-z]{3}\d[a-z]')),

  // link display text is in all caps
  any(body.links, regex.match(.display_text, '[A-Z ]+')),

  // link display text contains invisible characters (U+200F)
  any(body.links, strings.contains(.display_text, "\u{200F}")),

  // Low reputation link with display text ending in a document extension
  any(body.links,
      .href_url.domain.root_domain not in $tranco_1m
      and .href_url.domain.valid
      and .href_url.domain.root_domain not in $org_domains
      and .href_url.domain.root_domain not in $high_trust_sender_root_domains
      and (
        any($file_extensions_macros, strings.ends_with(..display_text, .))
        or strings.ends_with(.display_text, 'pdf')
      )
  ),

  // display name contains an email
  regex.contains(sender.display_name, '[a-z0-9]+@[a-z]+'),

  // Sender domain is empty
  sender.email.domain.domain == "",

  // sender domain matches no body domains
  all(body.links,
      .href_url.domain.root_domain != sender.email.domain.root_domain
  ),

  // body contains name of VIP
  (
    any($org_vips, strings.icontains(body.html.inner_text, .display_name))
    or any($org_vips, strings.icontains(body.plain.raw, .display_name))
  ),

  // new body domain
  any(body.links, network.whois(.href_url.domain).days_old < 30),

  // new sender domain
  network.whois(sender.email.domain).days_old < 30,

  // new sender
  profile.by_sender().days_known < 7,

  // excessive whitespace
  (
    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>&nbsp;</p><br>\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,}')
  ),

  // body contains recipient SLD
  any(recipients.to,
      strings.icontains(body.current_thread.text, .email.domain.sld)
  ),
  (
    // bec
    any(ml.nlu_classifier(body.current_thread.text).intents,
        .name == "bec" and .confidence != "low"
    )
    // previous thread contains matching domain but mismatch local part in current thread
    and any(body.previous_threads,
            .sender.email.domain.root_domain == recipients.to[0].email.domain.root_domain
            and .sender.email.email != recipients.to[0].email.email
    )
  )
)

// negate highly trusted sender domains unless they fail DMARC authentication
and (
  (
    sender.email.domain.root_domain in $high_trust_sender_root_domains
    and not headers.auth_summary.dmarc.pass
  )
  or sender.email.domain.root_domain not in $high_trust_sender_root_domains
)
and not profile.by_sender().any_messages_benign

Detection logic

Scope: inbound message.

Fake thread contains suspicious indicators, which can lead to BEC, credential phishing, and other undesirable outcomes.

  1. inbound message
  2. any of:
    • length(headers.references) is 0
    • headers.in_reply_to is missing
  3. any of:
    • subject.is_reply
    • subject.is_forward
    • all of:
      • none of:
        • subject.is_reply
        • subject.is_forward
      • any of [body.current_thread.text, body.html.display_text, body.plain.raw] where:
        • at least 3 of 6: . contains any of 6 patterns
          • from:
          • to:
          • sent:
          • date:
          • cc:
          • subject:
      • length(body.current_thread.text) + 100 < length(coalesce(body.html.display_text, body.plain.raw))
  4. not:
    • any of attachments where:
      • .content_type in ('message/delivery-status', 'message/rfc822')
  5. any of:
    • all of:
      • headers.return_path.domain.domain is set
      • headers.return_path.domain.domain is not 'calendar-server.bounces.google.com'
    • headers.return_path.domain.domain is missing
  6. not:
    • all of:
      • headers.message_id starts with '<Mimecast.'
      • headers.message_id ends with '.mimecast.lan>'
      • headers.hops[0].received.server.raw is 'relay.mimecast.com'
      • headers.hops[0].received.source.raw contains 'mimecast.lan'
  7. not:
    • profile.by_sender().solicited
  8. at least 4 of:
    • all of:
      • any of ml.nlu_classifier(body.current_thread.text).entities where:
        • .name is 'request'
      • any of ml.nlu_classifier(body.current_thread.text).entities where:
        • .name is 'financial'
    • any of:
      • any of ml.nlu_classifier(body.current_thread.text).tags where:
        • .name is 'invoice'
      • any of ml.nlu_classifier(body.current_thread.text).entities where:
        • .text is 'invoice'
    • any of ml.nlu_classifier(body.current_thread.text).entities where:
      • .name is 'urgency'
    • any of ml.nlu_classifier(body.current_thread.text).intents where all hold:
      • .name is 'cred_theft'
      • .confidence in ('medium', 'high')
    • sender.email.domain.tld matches '*.jp'
    • any of headers.domains where:
      • .tld matches '*.jp'
    • any of body.links where:
      • .href_url.path matches '\\/[a-z]{3}\\d[a-z]'
    • any of body.links where:
      • .display_text matches '[A-Z ]+'
    • any of body.links where:
      • .display_text contains '\\u{200F}'
    • any of body.links where all hold:
      • .href_url.domain.root_domain not in $tranco_1m
      • .href_url.domain.valid
      • .href_url.domain.root_domain not in $org_domains
      • .href_url.domain.root_domain not in $high_trust_sender_root_domains
      • any of:
        • any of $file_extensions_macros where:
          • strings.ends_with(.display_text)
        • .display_text ends with 'pdf'
    • sender.display_name matches '[a-z0-9]+@[a-z]+'
    • sender.email.domain.domain is ''
    • all of body.links where:
      • .href_url.domain.root_domain is not sender.email.domain.root_domain
    • any of:
      • any of $org_vips where:
        • strings.icontains(body.html.inner_text)
      • any of $org_vips where:
        • strings.icontains(body.plain.raw)
    • any of body.links where:
      • network.whois(.href_url.domain).days_old < 30
    • network.whois(sender.email.domain).days_old < 30
    • profile.by_sender().days_known < 7
    • body.html.raw matches any of 7 patterns
      • ((<br\s*/?>\s*){20,}|\n{20,})
      • (<p[^>]*>\s*<br\s*/?>\s*</p>\s*){30,}
      • (<p class=".*?"><span style=".*?"><o:p>&nbsp;</o:p></span></p>\s*){30,}
      • (<p>&nbsp;</p>\s*){7,}
      • (<p>&nbsp;</p><br>\s*){7,}
      • (<p[^>]*>\s*&nbsp;<br>\s*</p>\s*){5,}
      • (<p[^>]*>&nbsp;</p>\s*){7,}
    • any of recipients.to where:
      • strings.icontains(body.current_thread.text)
    • all of:
      • any of ml.nlu_classifier(body.current_thread.text).intents where all hold:
        • .name is 'bec'
        • .confidence is not 'low'
      • any of body.previous_threads where all hold:
        • .sender.email.domain.root_domain is recipients.to[0].email.domain.root_domain
        • .sender.email.email is not recipients.to[0].email.email
  9. any of:
    • all of:
      • sender.email.domain.root_domain in $high_trust_sender_root_domains
      • not:
        • headers.auth_summary.dmarc.pass
    • sender.email.domain.root_domain not in $high_trust_sender_root_domains
  10. not:
    • profile.by_sender().any_messages_benign

Inspects: attachments[].content_type, body.current_thread.text, body.html.display_text, body.html.inner_text, body.html.raw, body.links, body.links[].display_text, body.links[].href_url.domain, body.links[].href_url.domain.root_domain, body.links[].href_url.domain.valid, body.links[].href_url.path, body.plain.raw, body.previous_threads, body.previous_threads[].sender.email.domain.root_domain, body.previous_threads[].sender.email.email, headers.auth_summary.dmarc.pass, headers.domains, headers.domains[].tld, headers.hops[0].received.server.raw, headers.hops[0].received.source.raw, headers.in_reply_to, headers.message_id, headers.references, headers.return_path.domain.domain, recipients.to, recipients.to[0].email.domain.root_domain, recipients.to[0].email.email, recipients.to[].email.domain.sld, sender.display_name, sender.email.domain, sender.email.domain.domain, sender.email.domain.root_domain, sender.email.domain.tld, subject.is_forward, subject.is_reply, type.inbound. Sensors: ml.nlu_classifier, network.whois, profile.by_sender, regex.contains, regex.icontains, regex.match, strings.contains, strings.ends_with, strings.icontains, strings.iends_with, strings.ilike, strings.istarts_with. Reference lists: $file_extensions_macros, $high_trust_sender_root_domains, $org_domains, $org_vips, $tranco_1m.

Indicators matched (35)

FieldMatchValue
strings.icontainssubstringfrom:
strings.icontainssubstringto:
strings.icontainssubstringsent:
strings.icontainssubstringdate:
strings.icontainssubstringcc:
strings.icontainssubstringsubject:
attachments[].content_typemembermessage/delivery-status
attachments[].content_typemembermessage/rfc822
strings.istarts_withprefix<Mimecast.
strings.iends_withsuffix.mimecast.lan>
headers.hops[0].received.server.rawequalsrelay.mimecast.com
strings.icontainssubstringmimecast.lan
23 more
ml.nlu_classifier(body.current_thread.text).entities[].nameequalsrequest
ml.nlu_classifier(body.current_thread.text).entities[].nameequalsfinancial
ml.nlu_classifier(body.current_thread.text).tags[].nameequalsinvoice
ml.nlu_classifier(body.current_thread.text).entities[].textequalsinvoice
ml.nlu_classifier(body.current_thread.text).entities[].nameequalsurgency
ml.nlu_classifier(body.current_thread.text).intents[].nameequalscred_theft
ml.nlu_classifier(body.current_thread.text).intents[].confidencemembermedium
ml.nlu_classifier(body.current_thread.text).intents[].confidencememberhigh
strings.ilikesubstring*.jp
regex.matchregex\/[a-z]{3}\d[a-z]
regex.matchregex[A-Z ]+
strings.containssubstring\u{200F}
strings.ends_withsuffixpdf
regex.containsregex[a-z0-9]+@[a-z]+
sender.email.domain.domainequals
regex.icontainsregex((<br\s*/?>\s*){20,}|\n{20,})
regex.icontainsregex(<p[^>]*>\s*<br\s*/?>\s*</p>\s*){30,}
regex.icontainsregex(<p class=".*?"><span style=".*?"><o:p>&nbsp;</o:p></span></p>\s*){30,}
regex.icontainsregex(<p>&nbsp;</p>\s*){7,}
regex.icontainsregex(<p>&nbsp;</p><br>\s*){7,}
regex.icontainsregex(<p[^>]*>\s*&nbsp;<br>\s*</p>\s*){5,}
regex.icontainsregex(<p[^>]*>&nbsp;</p>\s*){7,}
ml.nlu_classifier(body.current_thread.text).intents[].nameequalsbec