Detection rules › Sublime MQL

Reconnaissance: All recipients cc/bcc'd or undisclosed

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

Recon messages, a form of deliverability testing, are used to validate whether a recipient address is valid or not, potentially preceding an attack. All recipients are bcc'd or undisclosed, with no links or attachments, and a short body and subject from an unknown sender.

Threat classification

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

CategoryValues
Attack typesReconnaissance

Event coverage

Rule body MQL

type.inbound
and (
  length(recipients.bcc) > 0
  or length(recipients.cc) > 0
  or any(recipients.to, strings.ilike(.display_name, "undisclosed?recipients"))
)
and (
  length(subject.base) <= 10
  or (
    (
      strings.ilike(subject.base, "*checking*", "*testing*")
      or subject.base == body.current_thread.text
    )
    and length(subject.base) <= 25
  )
)
and length(attachments) == 0
// and there are no links. Or all the links are to aka.ms or an extraction from a warning banner that match the senders domain
and (
  length(body.links) == 0
  or length(filter(body.links,
                   (
                     .display_text is null
                     and .display_url.url == sender.email.domain.root_domain
                   )
                   or .href_url.domain.domain == "aka.ms"
            )
  ) == length(body.links)
)
and (
  body.current_thread.text is null
  or length(body.current_thread.text) < 50
  or (
    length(body.current_thread.text) < 900
    // or body is most likely all warning banner ending with a generic greeting
    and regex.imatch(body.current_thread.text, '.*(hi|hello)')
  )
  // body length without disclaimer is shorter than 50 characters
  or (
    any(map(filter(ml.nlu_classifier(body.current_thread.text).entities,
                   .name == "disclaimer"
            ),
            .text
        ),
        (length(body.current_thread.text) - length(.)) < 50
    )
  )
  // matching nlu_classifier 'bec' to smaller messages less than 200
  or (
    any(ml.nlu_classifier(body.current_thread.text).intents,
        .name == "bec" and .confidence in ("high", "medium")
    )
    and length(body.current_thread.text) < 200
    and not regex.icontains(body.html.raw,
                            '(?:<div data-smartmail=|gmail_signature(?:_[^"]*)?)'
    ) // not condition to exclude smaller messages with a legitimate email signature
    and length(body.previous_threads) == 0 // excluding messages with simple responses to other threads
  )
)
and profile.by_sender().prevalence != "common"
and not profile.by_sender().solicited
and not profile.by_sender().any_messages_benign

// 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
)

Detection logic

Scope: inbound message.

Recon messages, a form of deliverability testing, are used to validate whether a recipient address is valid or not, potentially preceding an attack. All recipients are bcc'd or undisclosed, with no links or attachments, and a short body and subject from an unknown sender.

  1. inbound message
  2. any of:
    • length(recipients.bcc) > 0
    • length(recipients.cc) > 0
    • any of recipients.to where:
      • .display_name matches 'undisclosed?recipients'
  3. any of:
    • length(subject.base) ≤ 10
    • all of:
      • any of:
        • subject.base matches any of 2 patterns
          • *checking*
          • *testing*
        • subject.base is body.current_thread.text
      • length(subject.base) ≤ 25
  4. length(attachments) is 0
  5. any of:
    • length(body.links) is 0
    • length(filter(body.links, .display_text is null and .display_url.url == sender.email.domain.root_domain or .href_url.domain.domain == 'aka.ms')) is length(body.links)
  6. any of:
    • body.current_thread.text is missing
    • length(body.current_thread.text) < 50
    • all of:
      • length(body.current_thread.text) < 900
      • body.current_thread.text matches '.*(hi|hello)'
    • any of map(...) where:
      • length(body.current_thread.text) - length(.) < 50
    • all of:
      • any of ml.nlu_classifier(body.current_thread.text).intents where all hold:
        • .name is 'bec'
        • .confidence in ('high', 'medium')
      • length(body.current_thread.text) < 200
      • not:
        • body.html.raw matches '(?:<div data-smartmail=|gmail_signature(?:_[^"]*)?)'
      • length(body.previous_threads) is 0
  7. profile.by_sender().prevalence is not 'common'
  8. not:
    • profile.by_sender().solicited
  9. not:
    • profile.by_sender().any_messages_benign
  10. 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

Inspects: body.current_thread.text, body.html.raw, body.links, body.links[].display_text, body.links[].display_url.url, body.links[].href_url.domain.domain, body.previous_threads, headers.auth_summary.dmarc.pass, recipients.bcc, recipients.cc, recipients.to, recipients.to[].display_name, sender.email.domain.root_domain, subject.base, type.inbound. Sensors: ml.nlu_classifier, profile.by_sender, regex.icontains, regex.imatch, strings.ilike. Reference lists: $high_trust_sender_root_domains.

Indicators matched (10)

FieldMatchValue
strings.ilikesubstringundisclosed?recipients
strings.ilikesubstring*checking*
strings.ilikesubstring*testing*
body.links[].href_url.domain.domainequalsaka.ms
regex.imatchregex.*(hi|hello)
ml.nlu_classifier(body.current_thread.text).entities[].nameequalsdisclaimer
ml.nlu_classifier(body.current_thread.text).intents[].nameequalsbec
ml.nlu_classifier(body.current_thread.text).intents[].confidencememberhigh
ml.nlu_classifier(body.current_thread.text).intents[].confidencemembermedium
regex.icontainsregex(?:<div data-smartmail=|gmail_signature(?:_[^"]*)?)