Detection rules › Sublime MQL

Sublime MQL rules: appsheet

Service abuse: AppSheet infrastructure with suspicious indicators

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

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

CategoryValues
Attack typesCredential Phishing
Tactics and techniquesEvasion, Social engineering

Telemetry coverage

PlatformRecord / event type
SublimeInbound email message

Message attributes

  • body
  • body.current_thread
  • body.links
  • headers
  • headers.reply_to
  • sender.email
  • type

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.

  1. inbound message
  2. sender.email.email is 'noreply@appsheet.com'
  3. 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).intents where 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.links where all hold:
            • .display_text is 'Powered by AppSheet'
            • .href_url.domain.root_domain is 'appsheet.com'
  4. 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)

FieldMatchValue
sender.email.emailequalsnoreply@appsheet.com
regex.icontainsregex(?:legal|misuse|compliance|violation|enforcement)
regex.icontainsregex\x{00A0}|\x{1680}|\x{2000}|\x{200A}|\x{200B}|\x{202F}|\x{205F}|\x{3000}
strings.ilikesubstring*Apple*
strings.ilikesubstring*Amazon*
strings.ilikesubstring*Binance*
strings.ilikesubstring*Facebook*
strings.ilikesubstring*Meta*
strings.ilikesubstring*Google*
strings.ilikesubstring*LinkedIn*
regex.icontainsregex(?:(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[].namemembercred_theft
4 more
ml.nlu_classifier(body.current_thread.text).intents[].namemembersteal_pii
ml.nlu_classifier(body.current_thread.text).intents[].namememberjob_scam
ml.nlu_classifier(body.current_thread.text).intents[].confidencemembermedium
ml.nlu_classifier(body.current_thread.text).intents[].confidencememberhigh

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.

FieldKindExcluded valuesSearch
filter(headers.reply_to)array_any(no value, null check)excludes:filter(headers.reply_to)
length(headers.reply_to)is_not_null(no value, null check)excludes:length(headers.reply_to)

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
body.current_thread.textregex_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
field:"body.current_thread.text" kind:regex_match
sender.display_nameregex_match
  • (?:legal|misuse|compliance|violation|enforcement)
    • \x{00A0}
    • \x{1680}
    • \x{2000}
    • \x{200A}
    • \x{200B}
    • \x{202F}
    • \x{205F}
    • \x{3000}
field:"sender.display_name" kind:regex_match
sender.email.emaileq
  • noreply@appsheet.com
field:"sender.email.email" kind:eq value:"noreply@appsheet.com"
strings.replace_confusables(sender.display_name)wildcard
  • *Amazon*
  • *Apple*
  • *Binance*
  • *Facebook*
  • *Google*
  • *LinkedIn*
  • *Meta*
field:"strings.replace_confusables(sender.display_name)" kind:wildcard
type.inboundeq
  • true transforms: boolean
field:"type.inbound" kind:eq value:"true"