Detection rules › Sublime MQL
Sublime MQL rules: xero
| Rule | Severity |
|---|---|
| Xero infrastructure abuse | medium |
| Xero invoice abuse | medium |
Xero infrastructure abuse
#Identifies messages that resemble credential theft, originating from Xero. Xero infrastrcture abuse has been observed recently to send phishing attacks.
Threat classification
Sublime's own taxonomy (not MITRE ATT&CK).
| Category | Values |
|---|---|
| Attack types | Credential Phishing |
| Tactics and techniques | Evasion, Social engineering |
Telemetry coverage
| Platform | Record / event type |
|---|---|
| Sublime | Inbound email message |
Message attributes
Rule body
type.inbound
and sender.email.email == "messaging-service@post.xero.com"
and
// there are external links (not org or xero domains)
length(filter(body.links,
.href_url.domain.domain not in $org_domains
and .href_url.domain.root_domain not in ("xero.com", )
)
) > 0
and (
any(ml.nlu_classifier(body.current_thread.text).intents,
.name == "cred_theft" and .confidence == "high"
)
// subject match when cred_theft doesn't match
// high confidence observed subject intros in the format of "Urgent Thing: ..."
or regex.icontains(subject.subject,
'^(?:(?:Final|Last)?\s*Warning|(?:Final|Last|Legal|Critical|Content Violation)?\s*(?:Alert|Noti(?:ce|fication))|Appeal Required|Time.Sensitive|Critical.Alert|Important|Copyright Issue)\s*:\s*'
)
or any(ml.logo_detect(file.message_screenshot()).brands,
.name in ("Facebook", "Meta", "Instagram")
and .confidence in ("medium", "high")
)
// any of the links are for newly registered domains
or any(filter(body.links,
.href_url.domain.domain not in $org_domains
and .href_url.domain.root_domain not in ("xero.com")
),
network.whois(.href_url.domain).days_old < 30
)
or (
any(ml.nlu_classifier(body.current_thread.text).topics,
.name in ("B2B Cold Outreach", "Professional and Career Development")
and .confidence != "low"
)
)
// sender display name or subject contains confusables
or (
sender.display_name != strings.replace_confusables(sender.display_name)
or subject.subject != strings.replace_confusables(subject.subject)
)
// IP pool appears to be tagged by Xero via Mailgun
// https://help.mailgun.com/hc/en-us/articles/360052184214-IP-Pools
or any(headers.hops,
any(.fields,
.name == "X-Mailgun-Sending-Ip-Pool-Name"
and .value == "High Risk Pool"
)
)
)
and (
( // sender domain matches no body domains
length(body.links) > 0
and all(body.links,
.href_url.domain.root_domain not in ("xero.com", )
or .href_url.domain.root_domain is null
)
)
// link contains email address
or any(recipients.to,
.email.domain.valid
and any(body.links,
strings.icontains(.href_url.url, ..email.email)
or any(beta.scan_base64(.href_url.url,
format="url",
ignore_padding=true
),
strings.icontains(., ...email.email)
)
or any(beta.scan_base64(.href_url.fragment,
ignore_padding=true
),
strings.icontains(., ...email.email)
)
// cloudflare turnstile or phishing warning page
or strings.icontains(ml.link_analysis(., mode="aggressive").final_dom.display_text,
"cloudflare"
)
)
)
or regex.icontains(subject.subject, "termination.*notice")
or any(ml.nlu_classifier(body.current_thread.text).entities,
.name in ("sender", "org")
and regex.icontains(.text, 'Recruitment|staffing|\bhr\b')
)
)
Detection logic
Scope: inbound message.
Identifies messages that resemble credential theft, originating from Xero. Xero infrastrcture abuse has been observed recently to send phishing attacks.
- inbound message
- sender.email.email is 'messaging-service@post.xero.com'
- length(filter(body.links, .href_url.domain.domain not in $org_domains and .href_url.domain.root_domain not in ('xero.com'))) > 0
any of:
any of
ml.nlu_classifier(body.current_thread.text).intentswhere all hold:- .name is 'cred_theft'
- .confidence is 'high'
- subject.subject matches '^(?:(?:Final|Last)?\\s*Warning|(?:Final|Last|Legal|Critical|Content Violation)?\\s*(?:Alert|Noti(?:ce|fication))|Appeal Required|Time.Sensitive|Critical.Alert|Important|Copyright Issue)\\s*:\\s*'
any of
ml.logo_detect(file.message_screenshot()).brandswhere all hold:- .name in ('Facebook', 'Meta', 'Instagram')
- .confidence in ('medium', 'high')
any of
filter(body.links)where:- network.whois(.href_url.domain).days_old < 30
any of
ml.nlu_classifier(body.current_thread.text).topicswhere all hold:- .name in ('B2B Cold Outreach', 'Professional and Career Development')
- .confidence is not 'low'
any of:
- sender.display_name is not strings.replace_confusables(sender.display_name)
- subject.subject is not strings.replace_confusables(subject.subject)
any of
headers.hopswhere:any of
.fieldswhere all hold:- .name is 'X-Mailgun-Sending-Ip-Pool-Name'
- .value is 'High Risk Pool'
any of:
all of:
- length(body.links) > 0
all of
body.linkswhere any holds:- .href_url.domain.root_domain not in ('xero.com')
- .href_url.domain.root_domain is missing
any of
recipients.towhere all hold:- .email.domain.valid
any of
body.linkswhere any holds:- strings.icontains(.href_url.url)
any of
beta.scan_base64(.href_url.url)where:- strings.icontains(.)
any of
beta.scan_base64(.href_url.fragment)where:- strings.icontains(.)
- ml.link_analysis(., mode='aggressive').final_dom.display_text contains 'cloudflare'
- subject.subject matches 'termination.*notice'
any of
ml.nlu_classifier(body.current_thread.text).entitieswhere all hold:- .name in ('sender', 'org')
- .text matches 'Recruitment|staffing|\\bhr\\b'
Inspects: body.current_thread.text, body.links, body.links[].href_url.domain.domain, body.links[].href_url.domain.root_domain, body.links[].href_url.fragment, body.links[].href_url.url, headers.hops, headers.hops[].fields, headers.hops[].fields[].name, headers.hops[].fields[].value, recipients.to, recipients.to[].email.domain.valid, recipients.to[].email.email, sender.display_name, sender.email.email, subject.subject, type.inbound. Sensors: beta.scan_base64, file.message_screenshot, ml.link_analysis, ml.logo_detect, ml.nlu_classifier, network.whois, regex.icontains, strings.icontains, strings.replace_confusables. Reference lists: $org_domains.
Indicators matched (18)
| Field | Match | Value |
|---|---|---|
sender.email.email | equals | messaging-service@post.xero.com |
ml.nlu_classifier(body.current_thread.text).intents[].name | equals | cred_theft |
ml.nlu_classifier(body.current_thread.text).intents[].confidence | equals | high |
regex.icontains | regex | ^(?:(?:Final|Last)?\s*Warning|(?:Final|Last|Legal|Critical|Content Violation)?\s*(?:Alert|Noti(?:ce|fication))|Appeal Required|Time.Sensitive|Critical.Alert|Important|Copyright Issue)\s*:\s* |
ml.logo_detect(file.message_screenshot()).brands[].name | member | Facebook |
ml.logo_detect(file.message_screenshot()).brands[].name | member | Meta |
ml.logo_detect(file.message_screenshot()).brands[].name | member | Instagram |
ml.logo_detect(file.message_screenshot()).brands[].confidence | member | medium |
ml.logo_detect(file.message_screenshot()).brands[].confidence | member | high |
ml.nlu_classifier(body.current_thread.text).topics[].name | member | B2B Cold Outreach |
ml.nlu_classifier(body.current_thread.text).topics[].name | member | Professional and Career Development |
headers.hops[].fields[].name | equals | X-Mailgun-Sending-Ip-Pool-Name |
6 more
headers.hops[].fields[].value | equals | High Risk Pool |
strings.icontains | substring | cloudflare |
regex.icontains | regex | termination.*notice |
ml.nlu_classifier(body.current_thread.text).entities[].name | member | sender |
ml.nlu_classifier(body.current_thread.text).entities[].name | member | org |
regex.icontains | regex | Recruitment|staffing|\bhr\b |
Stages and Predicates
Stage 1: mql_rule
and
or
any(recipients.to)
and
any(body.links)
or
any(beta.scan_base64(body.links.href_url.fragment))
strings.icontains func_call "strings.icontains(beta.scan_base64(body.links[].href_url.fragment)[])"
any(beta.scan_base64(body.links.href_url.url))
strings.icontains func_call "strings.icontains(beta.scan_base64(body.links[].href_url.url)[])"
ml.link_analysis(body.links[], mode='aggressive').final_dom.display_text contains "cloudflare"
strings.icontains func_call "strings.icontains(body.links[].href_url.url)"
recipients.to.email.domain.valid eq "true"
any(ml.nlu_classifier(body.current_thread.text).entities)
and
ml.nlu_classifier(body.current_thread.text).entities.name in ["org", "sender"]
ml.nlu_classifier(body.current_thread.text).entities.text regex_match "Recruitment|staffing|\\bhr\\b"
and
body.links length_compare "0"
macro "all(body.links)"
subject.subject regex_match "termination.*notice"
or
any(headers.hops)
any(headers.hops.fields)
and
headers.hops.fields[].name eq "X-Mailgun-Sending-Ip-Pool-Name"
headers.hops.fields[].value eq "High Risk Pool"
any(ml.logo_detect(file.message_screenshot()).brands)
and
ml.logo_detect(file.message_screenshot()).brands.confidence in ["high", "medium"]
ml.logo_detect(file.message_screenshot()).brands.name in ["Facebook", "Instagram", "Meta"]
any(ml.nlu_classifier(body.current_thread.text).intents)
and
ml.nlu_classifier(body.current_thread.text).intents.confidence eq "high"
ml.nlu_classifier(body.current_thread.text).intents.name eq "cred_theft"
any(ml.nlu_classifier(body.current_thread.text).topics)
and
ml.nlu_classifier(body.current_thread.text).topics.confidence ne "low"
ml.nlu_classifier(body.current_thread.text).topics.name in ["B2B Cold Outreach", "Professional and Career Development"]
any(filter(body.links))
network.whois func_call "network.whois(filter(body.links)[].href_url.domain).days_old < 30"
subject.subject regex_match "^(?:(?:Final|Last)?\\s*Warning|(?:Final|Last|Legal|Critical|Content Violation)?\\s*(?:Alert|Noti(?:ce|fication))|Appeal Required|Time.Sensitive|Critical.Alert|Important|Copyright Issue)\\s*:\\s*"
macro "sender.display_name != strings.replace_confusables(sender.display_name)"
macro "subject.subject != strings.replace_confusables(subject.subject)"
filter(body.links, .href_url.domain.domain not in $org_domains and .href_url.domain.root_domain not in ('xero.com')) length_compare "0"
sender.email.email eq "messaging-service@post.xero.com"
type.inbound eq "true"Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
sender.email.email | eq |
| field:"sender.email.email" kind:eq value:"messaging-service@post.xero.com" |
subject.subject | regex_match |
| field:"subject.subject" kind:regex_match |
type.inbound | eq |
| field:"type.inbound" kind:eq value:"true" |
Xero invoice abuse
#Detects suspicious Xero invoice communications containing urgent payment requests where the sender's display name contains either confusable characters or impersonates internal services like HR or IT support.
Threat classification
Sublime's own taxonomy (not MITRE ATT&CK).
| Category | Values |
|---|---|
| Attack types | BEC/Fraud, Credential Phishing |
| Tactics and techniques | Impersonation: Brand, Impersonation: Employee, Social engineering |
Telemetry coverage
| Platform | Record / event type |
|---|---|
| Sublime | Inbound email message |
Message attributes
Rule body
type.inbound
and sender.email.domain.root_domain == "xero.com"
and (
// contains legitimate xero invoice links
any(body.links,
.href_url.domain.domain == "in.xero.com"
or (
.href_url.domain.root_domain == "mimecastprotect.com"
and .href_url.query_params == "domain=in.xero.com"
)
)
// or financial communications with invoice content and urgency
and (
any(beta.ml_topic(body.current_thread.text).topics,
.name == "Financial Communications" and .confidence != "low"
)
and any(ml.nlu_classifier(body.current_thread.text).tags,
.name == "invoice" and .confidence in ("medium", "high")
)
and any(ml.nlu_classifier(body.current_thread.text).entities,
.name == "urgency"
)
and any(ml.nlu_classifier(body.current_thread.text).entities,
.name == "request"
)
)
)
and (
// display name contains confusables (brand impersonation)
sender.display_name != strings.replace_confusables(sender.display_name)
// or HR/recruitment/employment/internal service impersonation
or regex.icontains(sender.display_name,
'\bhr\b|human resources|staffing|recruiting|recruitment|employment|payroll|it support|help ?desk|admin|administrator'
)
)
Detection logic
Scope: inbound message.
Detects suspicious Xero invoice communications containing urgent payment requests where the sender's display name contains either confusable characters or impersonates internal services like HR or IT support.
- inbound message
- sender.email.domain.root_domain is 'xero.com'
all of:
any of
body.linkswhere any holds:- .href_url.domain.domain is 'in.xero.com'
all of:
- .href_url.domain.root_domain is 'mimecastprotect.com'
- .href_url.query_params is 'domain=in.xero.com'
all of:
any of
beta.ml_topic(body.current_thread.text).topicswhere all hold:- .name is 'Financial Communications'
- .confidence is not 'low'
any of
ml.nlu_classifier(body.current_thread.text).tagswhere all hold:- .name is 'invoice'
- .confidence in ('medium', 'high')
any of
ml.nlu_classifier(body.current_thread.text).entitieswhere:- .name is 'urgency'
any of
ml.nlu_classifier(body.current_thread.text).entitieswhere:- .name is 'request'
any of:
- sender.display_name is not strings.replace_confusables(sender.display_name)
- sender.display_name matches '\\bhr\\b|human resources|staffing|recruiting|recruitment|employment|payroll|it support|help ?desk|admin|administrator'
Inspects: body.current_thread.text, body.links, body.links[].href_url.domain.domain, body.links[].href_url.domain.root_domain, body.links[].href_url.query_params, sender.display_name, sender.email.domain.root_domain, type.inbound. Sensors: beta.ml_topic, ml.nlu_classifier, regex.icontains, strings.replace_confusables.
Indicators matched (11)
| Field | Match | Value |
|---|---|---|
sender.email.domain.root_domain | equals | xero.com |
body.links[].href_url.domain.domain | equals | in.xero.com |
body.links[].href_url.domain.root_domain | equals | mimecastprotect.com |
body.links[].href_url.query_params | equals | domain=in.xero.com |
beta.ml_topic(body.current_thread.text).topics[].name | equals | Financial Communications |
ml.nlu_classifier(body.current_thread.text).tags[].name | equals | invoice |
ml.nlu_classifier(body.current_thread.text).tags[].confidence | member | medium |
ml.nlu_classifier(body.current_thread.text).tags[].confidence | member | high |
ml.nlu_classifier(body.current_thread.text).entities[].name | equals | urgency |
ml.nlu_classifier(body.current_thread.text).entities[].name | equals | request |
regex.icontains | regex | \bhr\b|human resources|staffing|recruiting|recruitment|employment|payroll|it support|help ?desk|admin|administrator |
Stages and Predicates
Stage 1: mql_rule
and
any(body.links)
or
and
body.links.href_url.domain.root_domain eq "mimecastprotect.com"
body.links.href_url.query_params eq "domain=in.xero.com"
body.links.href_url.domain.domain eq "in.xero.com"
any(beta.ml_topic(body.current_thread.text).topics)
and
beta.ml_topic(body.current_thread.text).topics.confidence ne "low"
beta.ml_topic(body.current_thread.text).topics.name eq "Financial Communications"
any(ml.nlu_classifier(body.current_thread.text).tags)
and
ml.nlu_classifier(body.current_thread.text).tags.confidence in ["high", "medium"]
ml.nlu_classifier(body.current_thread.text).tags.name eq "invoice"
any(ml.nlu_classifier(body.current_thread.text).entities)
ml.nlu_classifier(body.current_thread.text).entities.name eq "request"
any(ml.nlu_classifier(body.current_thread.text).entities)
ml.nlu_classifier(body.current_thread.text).entities.name eq "urgency"
or
sender.display_name regex_match "\\bhr\\b|human resources|staffing|recruiting|recruitment|employment|payroll|it support|help ?desk|admin|administrator"
macro "sender.display_name != strings.replace_confusables(sender.display_name)"
sender.email.domain.root_domain eq "xero.com"
type.inbound eq "true"Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
sender.display_name | regex_match |
| field:"sender.display_name" kind:regex_match |
sender.email.domain.root_domain | eq |
| field:"sender.email.domain.root_domain" kind:eq value:"xero.com" |
type.inbound | eq |
| field:"type.inbound" kind:eq value:"true" |