Detection rules › Sublime MQL
Sublime MQL rules: recon
Reconnaissance: All recipients cc/bcc'd or undisclosed
#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).
| Category | Values |
|---|---|
| Attack types | Reconnaissance |
Telemetry coverage
| Platform | Record / event type |
|---|---|
| Sublime | Inbound email message |
Message attributes
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.
- inbound message
any of:
- length(recipients.bcc) > 0
- length(recipients.cc) > 0
any of
recipients.towhere:- .display_name matches 'undisclosed?recipients'
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
- length(attachments) is 0
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)
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).intentswhere 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
- profile.by_sender().prevalence is not 'common'
not:
- profile.by_sender().solicited
not:
- profile.by_sender().any_messages_benign
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)
| Field | Match | Value |
|---|---|---|
strings.ilike | substring | undisclosed?recipients |
strings.ilike | substring | *checking* |
strings.ilike | substring | *testing* |
body.links[].href_url.domain.domain | equals | aka.ms |
regex.imatch | regex | .*(hi|hello) |
ml.nlu_classifier(body.current_thread.text).entities[].name | equals | disclaimer |
ml.nlu_classifier(body.current_thread.text).intents[].name | equals | bec |
ml.nlu_classifier(body.current_thread.text).intents[].confidence | member | high |
ml.nlu_classifier(body.current_thread.text).intents[].confidence | member | medium |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
body.current_thread.text | is_null | field:"body.current_thread.text" kind:is_null | |
body.current_thread.text | regex_match |
| field:"body.current_thread.text" kind:regex_match value:".*(hi|hello)" |
subject.base | cross_field_compare |
| field:"subject.base" kind:cross_field_compare value:"body.current_thread.text" |
subject.base | wildcard |
| field:"subject.base" kind:wildcard |
type.inbound | eq |
| field:"type.inbound" kind:eq value:"true" |
Reconnaissance: Email address harvesting attempt
#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).
| Category | Values |
|---|---|
| Attack types | BEC/Fraud, Credential Phishing, Spam |
| Tactics and techniques | Free email provider, Social engineering |
Telemetry coverage
| Platform | Record / event type |
|---|---|
| Sublime | Inbound email message |
Message attributes
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.
- inbound message
- length(subject.base) ≤ 15
- body.current_thread.text matches '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}'
- sender.email.domain.root_domain in $free_email_providers
- length(attachments) is 0
- 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)
| Field | Match | Value |
|---|---|---|
regex.imatch | regex | [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.
| Field | Kind | Values | Search |
|---|---|---|---|
body.current_thread.text | regex_match |
| field:"body.current_thread.text" kind:regex_match value:"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}" |
type.inbound | eq |
| field:"type.inbound" kind:eq value:"true" |
Reconnaissance: Empty message from uncommon sender
#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).
| Category | Values |
|---|---|
| Attack types | Spam |
| Tactics and techniques | Evasion, Social engineering |
Telemetry coverage
| Platform | Record / event type |
|---|---|
| Sublime | Inbound email message |
Message attributes
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.
- inbound message
- subject.base is ''
any of:
- body.plain.raw is missing
- body.plain.raw is ''
- body.plain.raw matches '^\\s*$'
any of:
- body.html.raw is missing
- body.html.raw is ''
- body.html.raw matches '^\\s*$'
- length(attachments) is 0
- 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)
| Field | Match | Value |
|---|---|---|
subject.base | equals | |
body.plain.raw | equals | |
regex.imatch | regex | ^\s*$ |
body.html.raw | equals | |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
body.html.raw | is_null | field:"body.html.raw" kind:is_null | |
body.html.raw | regex_match |
| field:"body.html.raw" kind:regex_match value:"^\s*$" |
body.plain.raw | is_null | field:"body.plain.raw" kind:is_null | |
body.plain.raw | regex_match |
| field:"body.plain.raw" kind:regex_match value:"^\s*$" |
type.inbound | eq |
| field:"type.inbound" kind:eq value:"true" |
Reconnaissance: Empty subject with mismatched reply-to from new sender
#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).
| Category | Values |
|---|---|
| Attack types | BEC/Fraud, Credential Phishing |
| Tactics and techniques | Evasion, Social engineering, Spoofing |
Telemetry coverage
| Platform | Record / event type |
|---|---|
| Sublime | Inbound email message |
Message attributes
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.
- inbound message
- profile.by_sender_email().prevalence is 'new'
- subject.base is ''
- length(headers.reply_to) > 0
- 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)
| Field | Match | Value |
|---|---|---|
subject.base | equals | |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
type.inbound | eq |
| field:"type.inbound" kind:eq value:"true" |
Reconnaissance: Fake real estate inquiry with empty body
#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).
| Category | Values |
|---|---|
| Attack types | BEC/Fraud |
| Tactics and techniques | Social engineering |
Telemetry coverage
| Platform | Record / event type |
|---|---|
| Sublime | Inbound email message |
Message attributes
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.
- inbound message
any of:
- body.plain.raw is missing
- body.plain.raw is ''
- body.plain.raw matches '^\\s*$'
any of:
- body.html.raw is missing
- body.html.raw is ''
- body.html.raw matches '^\\s*$'
- length(subject.base) > 150
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)
| Field | Match | Value |
|---|---|---|
body.plain.raw | equals | |
regex.imatch | regex | ^\s*$ |
body.html.raw | equals | |
strings.icontains | substring | purchase a home |
regex.icontains | regex | the listing (?:on|at) |
regex.icontains | regex | (?:interested in|regarding) (?:this|the) property |
regex.icontains | regex | (?: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.
| Field | Kind | Values | Search |
|---|---|---|---|
body.html.raw | is_null | field:"body.html.raw" kind:is_null | |
body.html.raw | regex_match |
| field:"body.html.raw" kind:regex_match value:"^\s*$" |
body.plain.raw | is_null | field:"body.plain.raw" kind:is_null | |
body.plain.raw | regex_match |
| field:"body.plain.raw" kind:regex_match value:"^\s*$" |
subject.base | contains |
| field:"subject.base" kind:contains value:"purchase a home" |
subject.base | regex_match |
| field:"subject.base" kind:regex_match |
type.inbound | eq |
| field:"type.inbound" kind:eq value:"true" |
Reconnaissance: Hotel booking reply-to redirect
#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).
| Category | Values |
|---|---|
| Attack types | BEC/Fraud |
| Tactics and techniques | Free email provider, Social engineering |
Telemetry coverage
| Platform | Record / event type |
|---|---|
| Sublime | Inbound email message |
Message attributes
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.
- inbound message
any of
headers.reply_towhere all hold:- .email.domain.root_domain in $free_email_providers
- .email.domain.root_domain is not sender.email.domain.root_domain
all of
recipients.towhere:- .email.domain.root_domain is not sender.email.domain.root_domain
- length(body.links) is 0
- length(attachments) is 0
- length(body.current_thread.text) < 600
not:
all of:
any of:
- subject.is_reply
- subject.is_forward
any of:
- length(headers.references) > 0
- headers.in_reply_to is set
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)
| Field | Match | Value |
|---|---|---|
strings.ilike | substring | *hotel* |
strings.ilike | substring | *your property* |
strings.ilike | substring | *accommodation* |
strings.ilike | substring | *guest services* |
strings.ilike | substring | *reception* |
strings.ilike | substring | *front desk* |
strings.ilike | substring | *booking* |
strings.ilike | substring | *to book* |
strings.ilike | substring | *book a * |
strings.ilike | substring | *reserv* |
strings.ilike | substring | *room* |
strings.ilike | substring | *suite* |
10 more
strings.ilike | substring | *availability* |
strings.ilike | substring | *check-in* |
strings.ilike | substring | *available dates* |
strings.ilike | substring | *family trip* |
strings.ilike | substring | *deluxe accommodation* |
strings.ilike | substring | *two children* |
strings.ilike | substring | *hotel manager* |
strings.ilike | substring | *room reserv* |
strings.ilike | substring | *room inquiry* |
strings.ilike | substring | *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.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
headers.in_reply_to | is_not_null | excludes:headers.in_reply_to | |
headers.references | length_compare | 0 | excludes:headers.references field:"headers.references" value:"0" |
subject.is_forward | eq | true | excludes:subject.is_forward field:"subject.is_forward" value:"true" |
subject.is_reply | eq | true | excludes:subject.is_reply field:"subject.is_reply" value:"true" |
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
body.current_thread.text | wildcard |
| field:"body.current_thread.text" kind:wildcard |
subject.base | wildcard |
| field:"subject.base" kind:wildcard |
type.inbound | eq |
| field:"type.inbound" kind:eq value:"true" |
Reconnaissance: Large unknown recipient list
#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).
| Category | Values |
|---|---|
| Attack types | Reconnaissance |
Telemetry coverage
| Platform | Record / event type |
|---|---|
| Sublime | Inbound email message |
Message attributes
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.
- inbound message
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
any of:
- length(subject.subject) ≤ 10
- subject.subject is body.current_thread.text
all of:
- subject.is_reply
- length(body.previous_threads) is 0
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)
any of:
- length(attachments) is 0
all of:
- length(attachments) is 1
any of
attachmentswhere 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
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
- profile.by_sender().prevalence is not 'common'
not:
- profile.by_sender().solicited
not:
- profile.by_sender().any_messages_benign
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)
| Field | Match | Value |
|---|---|---|
strings.icontains | substring | undisclosed |
body.links[].href_url.domain.domain | equals | aka.ms |
attachments[].file_type | member | pdf |
attachments[].file_type | member | png |
attachments[].file_type | member | jpg |
attachments[].file_type | member | tif |
attachments[].file_type | member | heif |
attachments[].file_type | member | doc |
attachments[].file_type | member | docx |
ml.nlu_classifier(body.current_thread.text).entities[].name | equals | disclaimer |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
body.current_thread.text | is_null | field:"body.current_thread.text" kind:is_null | |
subject.is_reply | eq |
| field:"subject.is_reply" kind:eq value:"true" |
subject.subject | cross_field_compare |
| field:"subject.subject" kind:cross_field_compare value:"body.current_thread.text" |
type.inbound | eq |
| field:"type.inbound" kind:eq value:"true" |
Reconnaissance: Short generic greeting 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.
Threat classification
Sublime's own taxonomy (not MITRE ATT&CK).
| Category | Values |
|---|---|
| Attack types | BEC/Fraud, Callback Phishing |
| Tactics and techniques | Social engineering, Free email provider |
Telemetry coverage
| Platform | Record / event type |
|---|---|
| Sublime | Inbound email message |
Message attributes
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.
- inbound message
- length(body.current_thread.text) ≤ 20
- length(subject.base) ≤ 15
- length(body.previous_threads) is 0
any of:
any of
ml.nlu_classifier(body.current_thread.text).entitieswhere:- .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+'
- sender.email.domain.root_domain in $free_email_providers
any of:
- sender.email.domain.root_domain not in (recipients.to[0].email.domain.root_domain)
any of:
all of:
all of
recipients.towhere:- .email.domain.valid is False
all of
recipients.ccwhere:- .email.domain.valid is False
- length(recipients.to) is 0
any of:
- length(recipients.cc) is 0
all of:
- length(recipients.cc) > 0
all of
recipients.ccwhere:- .email.domain.root_domain is not sender.email.domain.root_domain
any of:
- length(recipients.bcc) is 0
all of:
- length(recipients.bcc) > 0
all of
recipients.bccwhere:- .email.domain.root_domain is not sender.email.domain.root_domain
- length(attachments) is 0
- length(body.current_thread.links) is 0
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))
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)
| Field | Match | Value |
|---|---|---|
ml.nlu_classifier(body.current_thread.text).entities[].name | equals | greeting |
strings.ilike | substring | *hi* |
strings.ilike | substring | *hello* |
strings.ilike | substring | *hey* |
regex.match | regex | \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.
| Field | Kind | Values | Search |
|---|---|---|---|
body.current_thread.text | regex_match |
| field:"body.current_thread.text" kind:regex_match value:"\d+" |
body.current_thread.text | wildcard |
| field:"body.current_thread.text" kind:wildcard |
type.inbound | eq |
| field:"type.inbound" kind:eq value:"true" |