Detection rules › Sublime MQL
Sublime MQL rules: appsheet
| Rule | Severity |
|---|---|
| Service abuse: AppSheet infrastructure with suspicious indicators | medium |
Service abuse: AppSheet infrastructure with suspicious indicators
#Identifies messages that resemble credential theft, originating from AppSheet. AppSheet 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 == "noreply@appsheet.com"
and (
// recently registered or suspicious links
(
any(filter(body.links, .href_url.domain.root_domain != "appsheet.com"),
network.whois(.href_url.domain).days_old <= 10
or .href_url.domain.root_domain in $free_file_hosts
or .href_url.domain.domain in $free_subdomain_hosts
or .href_url.domain.root_domain in $url_shorteners
// account for URL rewrites
or (
any(.href_url.query_params_decoded['domain'],
(
. in $url_shorteners
or . in $free_subdomain_hosts
or . in $free_file_hosts
)
)
)
)
)
// suspicious display name
or (
regex.icontains(sender.display_name,
'(?:legal|misuse|compliance|violation|enforcement)',
// unicode blank character confusables in display name
'\x{00A0}|\x{1680}|\x{2000}|\x{200A}|\x{200B}|\x{202F}|\x{205F}|\x{3000}'
)
// commonly impersonated brands
or strings.ilike(strings.replace_confusables(sender.display_name),
'*Apple*',
'*Amazon*',
'*Binance*',
'*Facebook*',
'*Meta*',
'*Google*',
'*LinkedIn*'
)
)
// suspicious pattern in body
or regex.icontains(body.current_thread.text,
'(?:(Copyright|Advertising|Content|Data|Intellectual Property|I\.?\s?P\.?\b) (?:Polic(y|ies))|Violation|Contravention|Complaint|Misuse)|(?:(Enforce(ment)?|Required|Mandatory|Immediate) (?:Action|Response))|Cease (\&|and) Desist'
)
// NLU failsafe
or (
any(ml.nlu_classifier(body.current_thread.text).intents,
.name in~ ("cred_theft", "steal_pii", "job_scam")
and .confidence in~ ("medium", "high")
)
// negate the NLU result if there is only a single link leading back to AppSheet (likely benign)
and not (
length(body.links) == 1
and any(body.links,
.display_text == "Powered by AppSheet"
and .href_url.domain.root_domain == "appsheet.com"
)
)
)
)
// negate legitimate use of AppSheet within the org
and not (
length(headers.reply_to) is not null
and any(filter(headers.reply_to, .email.domain.root_domain != "appsheet.com"),
.email.domain.root_domain in~ $org_domains
or .email.domain.root_domain in~ $recipient_domains
)
)
Detection logic
Scope: inbound message.
Identifies messages that resemble credential theft, originating from AppSheet. AppSheet infrastrcture abuse has been observed recently to send phishing attacks.
- inbound message
- sender.email.email is 'noreply@appsheet.com'
any of:
any of
filter(body.links)where any holds:- network.whois(.href_url.domain).days_old ≤ 10
- .href_url.domain.root_domain in $free_file_hosts
- .href_url.domain.domain in $free_subdomain_hosts
- .href_url.domain.root_domain in $url_shorteners
any of
.href_url.query_params_decoded['domain']where any holds:- . in $url_shorteners
- . in $free_subdomain_hosts
- . in $free_file_hosts
any of:
sender.display_name matches any of 2 patterns
(?:legal|misuse|compliance|violation|enforcement)\x{00A0}|\x{1680}|\x{2000}|\x{200A}|\x{200B}|\x{202F}|\x{205F}|\x{3000}
strings.replace_confusables(sender.display_name) matches any of 7 patterns
*Apple**Amazon**Binance**Facebook**Meta**Google**LinkedIn*
- body.current_thread.text matches '(?:(Copyright|Advertising|Content|Data|Intellectual Property|I\\.?\\s?P\\.?\\b) (?:Polic(y|ies))|Violation|Contravention|Complaint|Misuse)|(?:(Enforce(ment)?|Required|Mandatory|Immediate) (?:Action|Response))|Cease (\\&|and) Desist'
all of:
any of
ml.nlu_classifier(body.current_thread.text).intentswhere all hold:- .name in ('cred_theft', 'steal_pii', 'job_scam')
- .confidence in ('medium', 'high')
not:
all of:
- length(body.links) is 1
any of
body.linkswhere all hold:- .display_text is 'Powered by AppSheet'
- .href_url.domain.root_domain is 'appsheet.com'
not:
all of:
- length(headers.reply_to) is set
any of
filter(headers.reply_to)where any holds:- .email.domain.root_domain in $org_domains
- .email.domain.root_domain in $recipient_domains
Inspects: body.current_thread.text, body.links, body.links[].display_text, body.links[].href_url.domain.root_domain, headers.reply_to, headers.reply_to[].email.domain.root_domain, sender.display_name, sender.email.email, type.inbound. Sensors: ml.nlu_classifier, network.whois, regex.icontains, strings.ilike, strings.replace_confusables. Reference lists: $free_file_hosts, $free_subdomain_hosts, $org_domains, $recipient_domains, $url_shorteners.
Indicators matched (16)
| Field | Match | Value |
|---|---|---|
sender.email.email | equals | noreply@appsheet.com |
regex.icontains | regex | (?:legal|misuse|compliance|violation|enforcement) |
regex.icontains | regex | \x{00A0}|\x{1680}|\x{2000}|\x{200A}|\x{200B}|\x{202F}|\x{205F}|\x{3000} |
strings.ilike | substring | *Apple* |
strings.ilike | substring | *Amazon* |
strings.ilike | substring | *Binance* |
strings.ilike | substring | *Facebook* |
strings.ilike | substring | *Meta* |
strings.ilike | substring | *Google* |
strings.ilike | substring | *LinkedIn* |
regex.icontains | regex | (?:(Copyright|Advertising|Content|Data|Intellectual Property|I\.?\s?P\.?\b) (?:Polic(y|ies))|Violation|Contravention|Complaint|Misuse)|(?:(Enforce(ment)?|Required|Mandatory|Immediate) (?:Action|Response))|Cease (\&|and) Desist |
ml.nlu_classifier(body.current_thread.text).intents[].name | member | cred_theft |
4 more
ml.nlu_classifier(body.current_thread.text).intents[].name | member | steal_pii |
ml.nlu_classifier(body.current_thread.text).intents[].name | member | job_scam |
ml.nlu_classifier(body.current_thread.text).intents[].confidence | member | medium |
ml.nlu_classifier(body.current_thread.text).intents[].confidence | member | high |
Stages and Predicates
Stage 1: mql_rule
and
or
and
not
and
any(body.links)
and
body.links.display_text eq "Powered by AppSheet"
body.links.href_url.domain.root_domain eq "appsheet.com"
body.links length_compare "1"
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 in ["cred_theft", "job_scam", "steal_pii"]
any(filter(body.links))
or
any(filter(body.links).href_url.query_params_decoded['domain'])
or
macro "filter(body.links)[].href_url.query_params_decoded['domain'][] in free_file_hosts"
macro "filter(body.links)[].href_url.query_params_decoded['domain'][] in free_subdomain_hosts"
macro "filter(body.links)[].href_url.query_params_decoded['domain'][] in url_shorteners"
network.whois func_call "network.whois(filter(body.links)[].href_url.domain).days_old <= 10"
macro "filter(body.links)[].href_url.domain.domain in free_subdomain_hosts"
macro "filter(body.links)[].href_url.domain.root_domain in free_file_hosts"
macro "filter(body.links)[].href_url.domain.root_domain in url_shorteners"
body.current_thread.text regex_match "(?:(Copyright|Advertising|Content|Data|Intellectual Property|I\\.?\\s?P\\.?\\b) (?:Polic(y|ies))|Violation|Contravention|Complaint|Misuse)|(?:(Enforce(ment)?|Required|Mandatory|Immediate) (?:Action|Response))|Cease (\\&|and) Desist"
sender.display_name regex_match "(?:legal|misuse|compliance|violation|enforcement)"
sender.display_name regex_match "\\x{00A0}|\\x{1680}|\\x{2000}|\\x{200A}|\\x{200B}|\\x{202F}|\\x{205F}|\\x{3000}"
strings.replace_confusables(sender.display_name) match "Amazon"
strings.replace_confusables(sender.display_name) match "Apple"
strings.replace_confusables(sender.display_name) match "Binance"
strings.replace_confusables(sender.display_name) match "Facebook"
strings.replace_confusables(sender.display_name) match "Google"
strings.replace_confusables(sender.display_name) match "LinkedIn"
strings.replace_confusables(sender.display_name) match "Meta"
not
and
any(filter(headers.reply_to))
or
macro "filter(headers.reply_to)[].email.domain.root_domain in org_domains"
macro "filter(headers.reply_to)[].email.domain.root_domain in recipient_domains"
length(headers.reply_to) is_not_null
sender.email.email eq "noreply@appsheet.com"
type.inbound eq "true"Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
filter(headers.reply_to) | array_any | excludes:filter(headers.reply_to) | |
length(headers.reply_to) | is_not_null | excludes:length(headers.reply_to) |
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 |
sender.display_name | regex_match |
| field:"sender.display_name" kind:regex_match |
sender.email.email | eq |
| field:"sender.email.email" kind:eq value:"noreply@appsheet.com" |
strings.replace_confusables(sender.display_name) | wildcard |
| field:"strings.replace_confusables(sender.display_name)" kind:wildcard |
type.inbound | eq |
| field:"type.inbound" kind:eq value:"true" |