Detection rules › Sublime MQL

Sublime MQL rules: recon

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

Telemetry coverage

PlatformRecord / event type
SublimeInbound email message

Message attributes

  • body
  • body.current_thread
  • body.html
  • body.links
  • headers
  • headers.auth_summary
  • recipients
  • recipients.to
  • sender.email
  • subject
  • type

Rule body

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 (9)

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

Stages and Predicates

Stage 1: mql_rule

and
  or
    and
      any(ml.nlu_classifier(body.current_thread.text).intents)
        and
          ml.nlu_classifier(body.current_thread.text).intents.confidence in ["high", "medium"]
          ml.nlu_classifier(body.current_thread.text).intents.name eq "bec"
      not
        body.html.raw regex_match "(?:<div data-smartmail=|gmail_signature(?:_[^\"]*)?)"
      body.current_thread.text length_compare "200"
      body.previous_threads length_compare "0"
    and
      body.current_thread.text length_compare "900"
      body.current_thread.text regex_match ".*(hi|hello)"
    any(map(...))
       macro "(length(body.current_thread.text) - length(map(...)[])) < 50"
    body.current_thread.text is_null
    body.current_thread.text length_compare "50"
  or
    and
      not
        headers.auth_summary.dmarc.pass eq "true"
       macro "sender.email.domain.root_domain in high_trust_sender_root_domains"
     macro "sender.email.domain.root_domain not in high_trust_sender_root_domains"
  or
    and
      or
        subject.base cross_field_compare "body.current_thread.text"
        subject.base match "checking"
        subject.base match "testing"
      subject.base length_compare "25"
    subject.base length_compare "10"
  or
    any(recipients.to)
      recipients.to.display_name eq "undisclosed?recipients"
    recipients.bcc length_compare "0"
    recipients.cc length_compare "0"
  or
    body.links length_compare "0"
    length func_call "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)"
  not
    profile.by_sender func_call "profile.by_sender().any_messages_benign"
  not
    profile.by_sender func_call "profile.by_sender().solicited"
  attachments length_compare "0"
  profile.by_sender func_call "profile.by_sender().prevalence != common"
  type.inbound eq "true"

Indicators

These rows show field, operator, and value matches.

Reconnaissance: Email address harvesting attempt

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

Detects potential email harvesting or credential phishing attempts where short messages contain email addresses in the body text. These messages often try to extract contact information or validate email addresses for future attacks.

Threat classification

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

CategoryValues
Attack typesBEC/Fraud, Credential Phishing, Spam
Tactics and techniquesFree email provider, Social engineering

Telemetry coverage

PlatformRecord / event type
SublimeInbound email message

Message attributes

  • body
  • body.current_thread
  • sender.email
  • subject
  • type

Rule body

type.inbound
and length(subject.base) <= 15
// detect email addresses in body text
and (
  regex.imatch(body.current_thread.text,
               '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}'
  )
)
// external freemail sender
and sender.email.domain.root_domain in $free_email_providers
// no attachments but allow one link
and length(attachments) == 0
and length(body.current_thread.links) == 1

Detection logic

Scope: inbound message.

Detects potential email harvesting or credential phishing attempts where short messages contain email addresses in the body text. These messages often try to extract contact information or validate email addresses for future attacks.

  1. inbound message
  2. length(subject.base) ≤ 15
  3. body.current_thread.text matches '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}'
  4. sender.email.domain.root_domain in $free_email_providers
  5. length(attachments) is 0
  6. length(body.current_thread.links) is 1

Inspects: body.current_thread.links, body.current_thread.text, sender.email.domain.root_domain, subject.base, type.inbound. Sensors: regex.imatch. Reference lists: $free_email_providers.

Indicators matched (1)

FieldMatchValue
regex.imatchregex[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}

Stages and Predicates

Stage 1: mql_rule

and
  attachments length_compare "0"
  body.current_thread.links length_compare "1"
  body.current_thread.text regex_match "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}"
  subject.base length_compare "15"
  type.inbound eq "true"
   macro "sender.email.domain.root_domain in free_email_providers"

Indicators

These rows show field, operator, and value matches.

Reconnaissance: Empty message from uncommon sender

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

Detects incoming messages that are completely empty, containing no subject line, message body content, or file attachments. Such messages may be used for reconnaissance, delivery confirmation, or as part of multi-stage attacks.

Threat classification

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

CategoryValues
Attack typesSpam
Tactics and techniquesEvasion, Social engineering

Telemetry coverage

PlatformRecord / event type
SublimeInbound email message

Message attributes

  • body
  • body.html
  • body.plain
  • subject
  • type

Rule body

type.inbound
and subject.base == ""
and (
  body.plain.raw is null
  or body.plain.raw == ""
  or regex.imatch(body.plain.raw, '^\s*$')
)
and (
  body.html.raw is null
  or body.html.raw == ""
  or regex.imatch(body.html.raw, '^\s*$')
)
and length(attachments) == 0
and profile.by_sender().prevalence != "common"

Detection logic

Scope: inbound message.

Detects incoming messages that are completely empty, containing no subject line, message body content, or file attachments. Such messages may be used for reconnaissance, delivery confirmation, or as part of multi-stage attacks.

  1. inbound message
  2. subject.base is ''
  3. any of:
    • body.plain.raw is missing
    • body.plain.raw is ''
    • body.plain.raw matches '^\\s*$'
  4. any of:
    • body.html.raw is missing
    • body.html.raw is ''
    • body.html.raw matches '^\\s*$'
  5. length(attachments) is 0
  6. profile.by_sender().prevalence is not 'common'

Inspects: body.html.raw, body.plain.raw, subject.base, type.inbound. Sensors: profile.by_sender, regex.imatch.

Indicators matched (4)

FieldMatchValue
subject.baseequals
body.plain.rawequals
regex.imatchregex^\s*$
body.html.rawequals

Stages and Predicates

Stage 1: mql_rule

and
  or
    body.html.raw eq ""
    body.html.raw is_null
    body.html.raw regex_match "^\\s*$"
  or
    body.plain.raw eq ""
    body.plain.raw is_null
    body.plain.raw regex_match "^\\s*$"
  attachments length_compare "0"
  profile.by_sender func_call "profile.by_sender().prevalence != common"
  subject.base eq ""
  type.inbound eq "true"

Indicators

These rows show field, operator, and value matches.

Reconnaissance: Empty subject with mismatched reply-to from new sender

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

Message with no subject line from a new sender where the reply-to address differs from the sender address, potentially indicating header manipulation or impersonation tactics.

Threat classification

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

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

Telemetry coverage

PlatformRecord / event type
SublimeInbound email message

Message attributes

  • headers
  • headers.reply_to
  • sender.email
  • subject
  • type

Rule body

type.inbound
and profile.by_sender_email().prevalence == "new"
and subject.base == ""
and length(headers.reply_to) > 0
and sender.email.email not in map(headers.reply_to, .email.email)

Detection logic

Scope: inbound message.

Message with no subject line from a new sender where the reply-to address differs from the sender address, potentially indicating header manipulation or impersonation tactics.

  1. inbound message
  2. profile.by_sender_email().prevalence is 'new'
  3. subject.base is ''
  4. length(headers.reply_to) > 0
  5. sender.email.email not in map(headers.reply_to, .email.email)

Inspects: headers.reply_to, headers.reply_to[].email.email, sender.email.email, subject.base, type.inbound. Sensors: profile.by_sender_email.

Indicators matched (1)

FieldMatchValue
subject.baseequals

Stages and Predicates

Stage 1: mql_rule

and
  headers.reply_to length_compare "0"
  profile.by_sender_email func_call "profile.by_sender_email().prevalence == new"
  subject.base eq ""
  type.inbound eq "true"
   macro "sender.email.email not in map(headers.reply_to, .email.email)"

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
type.inboundeq
  • true transforms: boolean
field:"type.inbound" kind:eq value:"true"

Reconnaissance: Fake real estate inquiry with empty body

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

Detects inbound messages with no body content — neither plain text nor HTML — but an unusually long subject line containing real estate inquiry language. The subject lines impersonate prospective home buyers asking about specific property listings or seeking a trusted local real estate agent, often mentioning relocation. These messages are designed to establish contact with real estate professionals as a precursor to fraud.

Threat classification

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

CategoryValues
Attack typesBEC/Fraud
Tactics and techniquesSocial engineering

Telemetry coverage

PlatformRecord / event type
SublimeInbound email message

Message attributes

  • body
  • body.html
  • body.plain
  • subject
  • type

Rule body

type.inbound
and (
  body.plain.raw is null
  or body.plain.raw == ""
  or regex.imatch(body.plain.raw, '^\s*$')
)
and (
  body.html.raw is null
  or body.html.raw == ""
  or regex.imatch(body.html.raw, '^\s*$')
)
and length(subject.base) > 150
and (
  strings.icontains(subject.base, 'purchase a home')
  or regex.icontains(subject.base,
                     'the listing (?:on|at)',
                     '(?:interested in|regarding) (?:this|the) property',
                     '(?:will be|are) relocating'
  )
)

Detection logic

Scope: inbound message.

Detects inbound messages with no body content — neither plain text nor HTML — but an unusually long subject line containing real estate inquiry language. The subject lines impersonate prospective home buyers asking about specific property listings or seeking a trusted local real estate agent, often mentioning relocation. These messages are designed to establish contact with real estate professionals as a precursor to fraud.

  1. inbound message
  2. any of:
    • body.plain.raw is missing
    • body.plain.raw is ''
    • body.plain.raw matches '^\\s*$'
  3. any of:
    • body.html.raw is missing
    • body.html.raw is ''
    • body.html.raw matches '^\\s*$'
  4. length(subject.base) > 150
  5. any of:
    • subject.base contains 'purchase a home'
    • subject.base matches any of 3 patterns
      • the listing (?:on|at)
      • (?:interested in|regarding) (?:this|the) property
      • (?:will be|are) relocating

Inspects: body.html.raw, body.plain.raw, subject.base, type.inbound. Sensors: regex.icontains, regex.imatch, strings.icontains.

Indicators matched (7)

FieldMatchValue
body.plain.rawequals
regex.imatchregex^\s*$
body.html.rawequals
strings.icontainssubstringpurchase a home
regex.icontainsregexthe listing (?:on|at)
regex.icontainsregex(?:interested in|regarding) (?:this|the) property
regex.icontainsregex(?:will be|are) relocating

Stages and Predicates

Stage 1: mql_rule

and
  or
    body.html.raw eq ""
    body.html.raw is_null
    body.html.raw regex_match "^\\s*$"
  or
    body.plain.raw eq ""
    body.plain.raw is_null
    body.plain.raw regex_match "^\\s*$"
  or
    subject.base contains "purchase a home"
    subject.base regex_match "(?:interested in|regarding) (?:this|the) property"
    subject.base regex_match "(?:will be|are) relocating"
    subject.base regex_match "the listing (?:on|at)"
  subject.base length_compare "150"
  type.inbound eq "true"

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
body.html.rawis_null
  • (no value, null check)
field:"body.html.raw" kind:is_null
body.html.rawregex_match
  • ^\s*$
field:"body.html.raw" kind:regex_match value:"^\s*$"
body.plain.rawis_null
  • (no value, null check)
field:"body.plain.raw" kind:is_null
body.plain.rawregex_match
  • ^\s*$
field:"body.plain.raw" kind:regex_match value:"^\s*$"
subject.basecontains
  • purchase a home
field:"subject.base" kind:contains value:"purchase a home"
subject.baseregex_match
  • (?:interested in|regarding) (?:this|the) property
  • (?:will be|are) relocating
  • the listing (?:on|at)
field:"subject.base" kind:regex_match
type.inboundeq
  • true transforms: boolean
field:"type.inbound" kind:eq value:"true"

Reconnaissance: Hotel booking reply-to redirect

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

Detects messages impersonating hotel booking inquiries by identifying common hotel-related language patterns from senders where the reply-to is a free email provider and differs from the sender domain in an effort to validate whether a recipient address is valid or not, potentially preceding an attack.

Threat classification

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

CategoryValues
Attack typesBEC/Fraud
Tactics and techniquesFree email provider, Social engineering

Telemetry coverage

PlatformRecord / event type
SublimeInbound email message

Message attributes

  • body
  • body.current_thread
  • headers
  • headers.reply_to
  • recipients
  • recipients.to
  • sender.email
  • subject
  • type

Rule body

type.inbound
and any(headers.reply_to,
        .email.domain.root_domain in $free_email_providers
        and .email.domain.root_domain != sender.email.domain.root_domain
)
and all(recipients.to,
        .email.domain.root_domain != sender.email.domain.root_domain
)
and length(body.links) == 0
and length(attachments) == 0
and length(body.current_thread.text) < 600
and not (
  (subject.is_reply or subject.is_forward)
  and (length(headers.references) > 0 or headers.in_reply_to is not null)
)
and (
  (
    strings.ilike(body.current_thread.text,
                  '*hotel*',
                  '*your property*',
                  '*accommodation*',
                  '*guest services*',
                  '*reception*',
                  '*front desk*'
    )
    and strings.ilike(body.current_thread.text,
                      '*booking*',
                      '*to book*',
                      '*book a *',
                      '*reserv*',
                      '*room*',
                      '*suite*',
                      '*availability*',
                      '*check-in*',
                      '*available dates*',
                      '*family trip*',
                      '*deluxe accommodation*',
                      '*two children*',
                      '*hotel manager*'
    )
  )
  or strings.ilike(subject.base,
                   '*hotel*',
                   '*room reserv*',
                   '*room inquiry*',
                   '*room availability*',
                   '*suite*',
                   '*accommodation*'
  )
)

Detection logic

Scope: inbound message.

Detects messages impersonating hotel booking inquiries by identifying common hotel-related language patterns from senders where the reply-to is a free email provider and differs from the sender domain in an effort to validate whether a recipient address is valid or not, potentially preceding an attack.

  1. inbound message
  2. any of headers.reply_to where all hold:
    • .email.domain.root_domain in $free_email_providers
    • .email.domain.root_domain is not sender.email.domain.root_domain
  3. all of recipients.to where:
    • .email.domain.root_domain is not sender.email.domain.root_domain
  4. length(body.links) is 0
  5. length(attachments) is 0
  6. length(body.current_thread.text) < 600
  7. not:
    • all of:
      • any of:
        • subject.is_reply
        • subject.is_forward
      • any of:
        • length(headers.references) > 0
        • headers.in_reply_to is set
  8. any of:
    • all of:
      • body.current_thread.text matches any of 6 patterns
        • *hotel*
        • *your property*
        • *accommodation*
        • *guest services*
        • *reception*
        • *front desk*
      • body.current_thread.text matches any of 13 patterns
        • *booking*
        • *to book*
        • *book a *
        • *reserv*
        • *room*
        • *suite*
        • *availability*
        • *check-in*
        • *available dates*
        • *family trip*
        • *deluxe accommodation*
        • *two children*
        • *hotel manager*
    • subject.base matches any of 6 patterns
      • *hotel*
      • *room reserv*
      • *room inquiry*
      • *room availability*
      • *suite*
      • *accommodation*

Inspects: body.current_thread.text, body.links, headers.in_reply_to, headers.references, headers.reply_to, headers.reply_to[].email.domain.root_domain, recipients.to, recipients.to[].email.domain.root_domain, sender.email.domain.root_domain, subject.base, subject.is_forward, subject.is_reply, type.inbound. Sensors: strings.ilike. Reference lists: $free_email_providers.

Indicators matched (22)

FieldMatchValue
strings.ilikesubstring*hotel*
strings.ilikesubstring*your property*
strings.ilikesubstring*accommodation*
strings.ilikesubstring*guest services*
strings.ilikesubstring*reception*
strings.ilikesubstring*front desk*
strings.ilikesubstring*booking*
strings.ilikesubstring*to book*
strings.ilikesubstring*book a *
strings.ilikesubstring*reserv*
strings.ilikesubstring*room*
strings.ilikesubstring*suite*
10 more
strings.ilikesubstring*availability*
strings.ilikesubstring*check-in*
strings.ilikesubstring*available dates*
strings.ilikesubstring*family trip*
strings.ilikesubstring*deluxe accommodation*
strings.ilikesubstring*two children*
strings.ilikesubstring*hotel manager*
strings.ilikesubstring*room reserv*
strings.ilikesubstring*room inquiry*
strings.ilikesubstring*room availability*

Stages and Predicates

Stage 1: mql_rule

and
  or
    and
      or
        body.current_thread.text match "accommodation"
        body.current_thread.text match "front desk"
        body.current_thread.text match "guest services"
        body.current_thread.text match "hotel"
        body.current_thread.text match "reception"
        body.current_thread.text match "your property"
      or
        body.current_thread.text match "availability"
        body.current_thread.text match "available dates"
        body.current_thread.text match "book a "
        body.current_thread.text match "booking"
        body.current_thread.text match "check-in"
        body.current_thread.text match "deluxe accommodation"
        body.current_thread.text match "family trip"
        body.current_thread.text match "hotel manager"
        body.current_thread.text match "reserv"
        body.current_thread.text match "room"
        body.current_thread.text match "suite"
        body.current_thread.text match "to book"
        body.current_thread.text match "two children"
    subject.base match "accommodation"
    subject.base match "hotel"
    subject.base match "room availability"
    subject.base match "room inquiry"
    subject.base match "room reserv"
    subject.base match "suite"
  not
    and
      or
        headers.in_reply_to is_not_null
        headers.references length_compare "0"
      or
        subject.is_forward eq "true"
        subject.is_reply eq "true"
  any(headers.reply_to)
    and
      headers.reply_to.email.domain.root_domain cross_field_compare "sender.email.domain.root_domain"
       macro "headers.reply_to[].email.domain.root_domain in free_email_providers"
  attachments length_compare "0"
  body.current_thread.text length_compare "600"
  body.links length_compare "0"
  type.inbound eq "true"
   macro "all(recipients.to)"

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
headers.in_reply_tois_not_null(no value, null check)excludes:headers.in_reply_to
headers.referenceslength_compare0excludes:headers.references field:"headers.references" value:"0"
subject.is_forwardeqtrueexcludes:subject.is_forward field:"subject.is_forward" value:"true"
subject.is_replyeqtrueexcludes:subject.is_reply field:"subject.is_reply" value:"true"

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
body.current_thread.textwildcard
  • *accommodation*
  • *availability*
  • *available dates*
  • *book a *
  • *booking*
  • *check-in*
  • *deluxe accommodation*
  • *family trip*
  • *front desk*
  • *guest services*
  • *hotel manager*
  • *hotel*
  • *reception*
  • *reserv*
  • *room*
  • *suite*
  • *to book*
  • *two children*
  • *your property*
field:"body.current_thread.text" kind:wildcard
subject.basewildcard
  • *accommodation*
  • *hotel*
  • *room availability*
  • *room inquiry*
  • *room reserv*
  • *suite*
field:"subject.base" kind:wildcard
type.inboundeq
  • true transforms: boolean
field:"type.inbound" kind:eq value:"true"

Reconnaissance: Large unknown recipient list

#
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. There's a large number of recipients that are unknown to the organization, 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

Telemetry coverage

PlatformRecord / event type
SublimeInbound email message

Message attributes

  • attachments
  • body
  • body.current_thread
  • body.links
  • headers
  • headers.auth_summary
  • recipients
  • recipients.to
  • sender.email
  • subject
  • type

Rule body

type.inbound
and (
  length(recipients.to) > 10
  and length(filter(recipients.to,
                    .email.domain.domain not in $org_domains
                    and .email.email not in $recipient_emails
                    and (
                      .email.domain.valid
                      or strings.icontains(.display_name, "undisclosed")
                    )
             )
  ) >= 10
)
and (
  length(subject.subject) <= 10 
  or subject.subject == body.current_thread.text
  or (subject.is_reply and length(body.previous_threads) == 0)
)
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"
                   or network.whois(.display_url.domain).days_old < 30
            )
  ) == length(body.links)
)
and (
  length(attachments) == 0
  or (
    length(attachments) == 1
    and any(attachments,
            .file_type in ("pdf", "png", "jpg", "tif", "heif", "doc", "docx")
            and any(file.explode(.),
                    length(.scan.ocr.raw) < 20
                    or length(.scan.strings.strings) == 1
            )
    )
  )
)
and (
  body.current_thread.text is null
  or length(body.current_thread.text) < 50
  // 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
    )
  )
)
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. There's a large number of recipients that are unknown to the organization, no links or attachments, and a short body and subject from an unknown sender.

  1. inbound message
  2. all of:
    • length(recipients.to) > 10
    • length(filter(recipients.to, .email.domain.domain not in $org_domains and .email.email not in $recipient_emails and .email.domain.valid or strings.icontains(.display_name, 'undisclosed'))) ≥ 10
  3. any of:
    • length(subject.subject) ≤ 10
    • subject.subject is body.current_thread.text
    • all of:
      • subject.is_reply
      • length(body.previous_threads) is 0
  4. 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' or network.whois(.display_url.domain).days_old < 30)) is length(body.links)
  5. any of:
    • length(attachments) is 0
    • all of:
      • length(attachments) is 1
      • any of attachments where all hold:
        • .file_type in ('pdf', 'png', 'jpg', 'tif', 'heif', 'doc', 'docx')
        • any of file.explode(.) where any holds:
          • length(.scan.ocr.raw) < 20
          • length(.scan.strings.strings) is 1
  6. any of:
    • body.current_thread.text is missing
    • length(body.current_thread.text) < 50
    • any of map(...) where:
      • length(body.current_thread.text) - length(.) < 50
  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: attachments[].file_type, body.current_thread.text, body.links, body.links[].display_text, body.links[].display_url.domain, body.links[].display_url.url, body.links[].href_url.domain.domain, body.previous_threads, headers.auth_summary.dmarc.pass, recipients.to, recipients.to[].display_name, recipients.to[].email.domain.domain, recipients.to[].email.domain.valid, recipients.to[].email.email, sender.email.domain.root_domain, subject.is_reply, subject.subject, type.inbound. Sensors: file.explode, ml.nlu_classifier, network.whois, profile.by_sender, strings.icontains. Reference lists: $high_trust_sender_root_domains, $org_domains, $recipient_emails.

Indicators matched (10)

FieldMatchValue
strings.icontainssubstringundisclosed
body.links[].href_url.domain.domainequalsaka.ms
attachments[].file_typememberpdf
attachments[].file_typememberpng
attachments[].file_typememberjpg
attachments[].file_typemembertif
attachments[].file_typememberheif
attachments[].file_typememberdoc
attachments[].file_typememberdocx
ml.nlu_classifier(body.current_thread.text).entities[].nameequalsdisclaimer

Stages and Predicates

Stage 1: mql_rule

and
  or
    and
      any(attachments)
        and
          any(file.explode(attachments))
            or
              file.explode(attachments[])[].scan.ocr.raw length_compare "20"
              file.explode(attachments[])[].scan.strings.strings length_compare "1"
          attachments.file_type in ["doc", "docx", "heif", "jpg", "pdf", "png", "tif"]
      attachments length_compare "1"
    attachments length_compare "0"
  or
    and
      not
        headers.auth_summary.dmarc.pass eq "true"
       macro "sender.email.domain.root_domain in high_trust_sender_root_domains"
     macro "sender.email.domain.root_domain not in high_trust_sender_root_domains"
  or
    and
      body.previous_threads length_compare "0"
      subject.is_reply eq "true"
    subject.subject cross_field_compare "body.current_thread.text"
    subject.subject length_compare "10"
  or
    any(map(...))
       macro "(length(body.current_thread.text) - length(map(...)[])) < 50"
    body.current_thread.text is_null
    body.current_thread.text length_compare "50"
  or
    body.links length_compare "0"
    length func_call "length(filter(body.links, .display_text is null and .display_url.url == sender.email.domain.root_domain or .href_url.domain.domain == 'aka.ms' or network.whois(.display_url.domain).days_old < 30)) == length(body.links)"
  not
    profile.by_sender func_call "profile.by_sender().any_messages_benign"
  not
    profile.by_sender func_call "profile.by_sender().solicited"
  filter(recipients.to, .email.domain.domain not in $org_domains and .email.email not in $recipient_emails and .email.domain.valid or strings.icontains(.display_name, 'undisclosed')) length_compare "10"
  profile.by_sender func_call "profile.by_sender().prevalence != common"
  recipients.to length_compare "10"
  type.inbound eq "true"

Indicators

These rows show field, operator, and value matches.

Reconnaissance: Short generic greeting message

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

Detects potential reconnaissance messages with very short, generic content like 'Hi' or 'Hello' from external senders. These messages are often used to validate email addresses and test deliverability before launching larger attacks.

Threat classification

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

CategoryValues
Attack typesBEC/Fraud, Callback Phishing
Tactics and techniquesSocial engineering, Free email provider

Telemetry coverage

PlatformRecord / event type
SublimeInbound email message

Message attributes

  • body
  • body.current_thread
  • headers
  • headers.auth_summary
  • mailbox
  • recipients
  • recipients.bcc
  • recipients.cc
  • recipients.to
  • recipients.to[0]
  • sender.email
  • subject
  • type

Rule body

type.inbound
// detect generic greetings
and length(body.current_thread.text) <= 20
and length(subject.base) <= 15
// exclude messages with previous thread context (forwards/replies)
and length(body.previous_threads) == 0
and (
  any(ml.nlu_classifier(body.current_thread.text).entities, .name == "greeting")
  or strings.ilike(body.current_thread.text, "*hi*", "*hello*", "*hey*")
  or length(body.current_thread.text) <= 5
  or regex.match(body.current_thread.text, '\d+')
)
// external freemail sender
and sender.email.domain.root_domain in $free_email_providers
and (
  sender.email.domain.root_domain not in (
    recipients.to[0].email.domain.root_domain
  )
  or (
    (
      all(recipients.to, .email.domain.valid == false)
      and all(recipients.cc, .email.domain.valid == false)
    )
    or length(recipients.to) == 0
  )
)
and (
  length(recipients.cc) == 0
  or (
    length(recipients.cc) > 0
    and all(recipients.cc,
            .email.domain.root_domain != sender.email.domain.root_domain
    )
  )
)
and (
  length(recipients.bcc) == 0
  or (
    length(recipients.bcc) > 0
    and all(recipients.bcc,
            .email.domain.root_domain != sender.email.domain.root_domain
    )
  )
)
// no attachments or links
and length(attachments) == 0
and length(body.current_thread.links) == 0

// not where the sender and mailbox display_anames indicate this might be a personal email --> work email
// impersonation is covered by other core feed rules
and not (
  sum([length(recipients.to), length(recipients.bcc), length(recipients.cc)]) == 1
  // use coalesce to deal with either the sender.display_name or the mailbox element being null
  // if either are null, the function returns false, as it cannot be true if either is null
  and coalesce(strings.icontains(sender.display_name, mailbox.first_name),
               false
  )
  and coalesce(strings.icontains(sender.display_name, mailbox.last_name), false)
)
and (
  // auth failed (or absent) - ignore the profile
  coalesce(headers.auth_summary.dmarc.pass, false) == false
  or coalesce(headers.auth_summary.spf.pass, false) == false
  // auth passed - use the profile
  or (
    // no benign messages
    not profile.by_sender_email().any_messages_benign
    and (
      // not soliticed OR common
      not (
        profile.by_sender_email().solicited
        or profile.by_sender_email().prevalence == "common"
      )
      // or HAS been spam_malicious
      or profile.by_sender_email().any_messages_malicious_or_spam
    )
  )
)

Detection logic

Scope: inbound message.

Detects potential reconnaissance messages with very short, generic content like 'Hi' or 'Hello' from external senders. These messages are often used to validate email addresses and test deliverability before launching larger attacks.

  1. inbound message
  2. length(body.current_thread.text) ≤ 20
  3. length(subject.base) ≤ 15
  4. length(body.previous_threads) is 0
  5. any of:
    • any of ml.nlu_classifier(body.current_thread.text).entities where:
      • .name is 'greeting'
    • body.current_thread.text matches any of 3 patterns
      • *hi*
      • *hello*
      • *hey*
    • length(body.current_thread.text) ≤ 5
    • body.current_thread.text matches '\\d+'
  6. sender.email.domain.root_domain in $free_email_providers
  7. any of:
    • sender.email.domain.root_domain not in (recipients.to[0].email.domain.root_domain)
    • any of:
      • all of:
        • all of recipients.to where:
          • .email.domain.valid is False
        • all of recipients.cc where:
          • .email.domain.valid is False
      • length(recipients.to) is 0
  8. any of:
    • length(recipients.cc) is 0
    • all of:
      • length(recipients.cc) > 0
      • all of recipients.cc where:
        • .email.domain.root_domain is not sender.email.domain.root_domain
  9. any of:
    • length(recipients.bcc) is 0
    • all of:
      • length(recipients.bcc) > 0
      • all of recipients.bcc where:
        • .email.domain.root_domain is not sender.email.domain.root_domain
  10. length(attachments) is 0
  11. length(body.current_thread.links) is 0
  12. not:
    • all of:
      • sum([length(recipients.to), length(recipients.bcc), length(recipients.cc)]) is 1
      • coalesce(strings.icontains(sender.display_name, mailbox.first_name))
      • coalesce(strings.icontains(sender.display_name, mailbox.last_name))
  13. any of:
    • coalesce(headers.auth_summary.dmarc.pass) is False
    • coalesce(headers.auth_summary.spf.pass) is False
    • all of:
      • not:
        • profile.by_sender_email().any_messages_benign
      • any of:
        • none of:
          • profile.by_sender_email().solicited
          • profile.by_sender_email().prevalence is 'common'
        • profile.by_sender_email().any_messages_malicious_or_spam

Inspects: body.current_thread.links, body.current_thread.text, body.previous_threads, headers.auth_summary.dmarc.pass, headers.auth_summary.spf.pass, mailbox.first_name, mailbox.last_name, recipients.bcc, recipients.bcc[].email.domain.root_domain, recipients.cc, recipients.cc[].email.domain.root_domain, recipients.cc[].email.domain.valid, recipients.to, recipients.to[0].email.domain.root_domain, recipients.to[].email.domain.valid, sender.display_name, sender.email.domain.root_domain, subject.base, type.inbound. Sensors: ml.nlu_classifier, profile.by_sender_email, regex.match, strings.icontains, strings.ilike. Reference lists: $free_email_providers.

Indicators matched (5)

FieldMatchValue
ml.nlu_classifier(body.current_thread.text).entities[].nameequalsgreeting
strings.ilikesubstring*hi*
strings.ilikesubstring*hello*
strings.ilikesubstring*hey*
regex.matchregex\d+

Stages and Predicates

Stage 1: mql_rule

and
  or
    and
      or
        not
          or
            profile.by_sender_email func_call "profile.by_sender_email().prevalence == common"
            profile.by_sender_email func_call "profile.by_sender_email().solicited"
        profile.by_sender_email func_call "profile.by_sender_email().any_messages_malicious_or_spam"
      not
        profile.by_sender_email func_call "profile.by_sender_email().any_messages_benign"
    coalesce func_call "coalesce(headers.auth_summary.dmarc.pass) == false"
    coalesce func_call "coalesce(headers.auth_summary.spf.pass) == false"
  not
    and
      coalesce func_call "coalesce(strings.icontains(sender.display_name, mailbox.first_name))"
      coalesce func_call "coalesce(strings.icontains(sender.display_name, mailbox.last_name))"
      sum func_call "sum([length(recipients.to), length(recipients.bcc), length(recipients.cc)]) == 1"
  or
    any(ml.nlu_classifier(body.current_thread.text).entities)
      ml.nlu_classifier(body.current_thread.text).entities.name eq "greeting"
    body.current_thread.text length_compare "5"
    body.current_thread.text match "hello"
    body.current_thread.text match "hey"
    body.current_thread.text match "hi"
    body.current_thread.text regex_match "\\d+"
  or
    and
      recipients.bcc length_compare "0"
       macro "all(recipients.bcc)"
    recipients.bcc length_compare "0"
  or
    and
      recipients.cc length_compare "0"
       macro "all(recipients.cc)"
    recipients.cc length_compare "0"
  or
    and
       macro "all(recipients.cc)"
       macro "all(recipients.to)"
    recipients.to length_compare "0"
     macro "sender.email.domain.root_domain not in (recipients.to[0].email.domain.root_domain)"
  attachments length_compare "0"
  body.current_thread.links length_compare "0"
  body.current_thread.text length_compare "20"
  body.previous_threads length_compare "0"
  subject.base length_compare "15"
  type.inbound eq "true"
   macro "sender.email.domain.root_domain in free_email_providers"

Indicators

These rows show field, operator, and value matches.