Detection rules › Sublime MQL

Sublime MQL rules: spoofable

Spoofable internal domain with suspicious signals

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

The sender is a known org domain and doesn't use a known org display name. SPF and DMARC verdicts are "none", which means the domain is spoofable. We then look for a combination of other suspicious signals such as a suspicious link or suspicious language. False Positives may occur with automated sending systems that send rich text emails, in which case we can add additional signals or exclude those.

Threat classification

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

CategoryValues
Attack typesCredential Phishing
Tactics and techniquesFree file host, Free subdomain host, Social engineering, Spoofing

Telemetry coverage

PlatformRecord / event type
SublimeInbound email message

Message attributes

  • body
  • body.current_thread
  • body.links
  • headers
  • headers.domains
  • headers.hops
  • sender.email
  • type

Rule body

type.inbound
and sender.email.domain.domain in $org_domains

// doesn't match an org display name (generic)
// we could make this more generic later
and sender.display_name not in $org_display_names
and any(headers.hops,

        // find the hop Authentication-results for the org domain
        .authentication_results.dmarc_details.from.domain in $org_domains

        // internal domain is spoofable
        and .authentication_results.dmarc == "none"
        and .authentication_results.spf == "none"
        and .authentication_results.compauth.verdict not in ("pass", "softpass")
)
and 3 of (
  (
    // low reputation / suspicious link
    any(body.links,
        .href_url.domain.root_domain not in $org_domains
        and (
          .href_url.domain.root_domain not in $tranco_1m
          or .href_url.domain.domain in $free_file_hosts
          or .href_url.domain.root_domain in $free_file_hosts
          or .href_url.domain.root_domain in $free_subdomain_hosts
          or .href_url.domain.domain in $url_shorteners
          or .href_url.domain.domain in $social_landing_hosts
        )
    )
  ),
  (
    // sender domain matches no body domains
    length(body.links) > 0
    and all(body.links,
            .href_url.domain.root_domain != sender.email.domain.root_domain
    )
  ),
  (
    // suspicious domain in headers
    any(headers.domains,
        // it's not an org domain
        .root_domain not in $org_domains

        // low reputation
        and .root_domain not in $alexa_1m

        // no one has sent an email to it before
        and .root_domain not in $recipient_domains
    )
  ),
  (
    // suspicious language
    any(ml.nlu_classifier(body.current_thread.text).intents,
        .name != "benign" and .confidence == "high"
    )
  ),
  (
    // suspicious language
    any(ml.nlu_classifier(body.current_thread.text).intents,
        .name != "benign" and .confidence == "high"
    )
  ),
)

Detection logic

Scope: inbound message.

The sender is a known org domain and doesn't use a known org display name. SPF and DMARC verdicts are "none", which means the domain is spoofable. We then look for a combination of other suspicious signals such as a suspicious link or suspicious language. False Positives may occur with automated sending systems that send rich text emails, in which case we can add additional signals or exclude those.

  1. inbound message
  2. sender.email.domain.domain in $org_domains
  3. sender.display_name not in $org_display_names
  4. any of headers.hops where all hold:
    • .authentication_results.dmarc_details.from.domain in $org_domains
    • .authentication_results.dmarc is 'none'
    • .authentication_results.spf is 'none'
    • .authentication_results.compauth.verdict not in ('pass', 'softpass')
  5. at least 3 of:
    • any of body.links where all hold:
      • .href_url.domain.root_domain not in $org_domains
      • any of:
        • .href_url.domain.root_domain not in $tranco_1m
        • .href_url.domain.domain in $free_file_hosts
        • .href_url.domain.root_domain in $free_file_hosts
        • .href_url.domain.root_domain in $free_subdomain_hosts
        • .href_url.domain.domain in $url_shorteners
        • .href_url.domain.domain in $social_landing_hosts
    • all of:
      • length(body.links) > 0
      • all of body.links where:
        • .href_url.domain.root_domain is not sender.email.domain.root_domain
    • any of headers.domains where all hold:
      • .root_domain not in $org_domains
      • .root_domain not in $alexa_1m
      • .root_domain not in $recipient_domains
    • any of ml.nlu_classifier(body.current_thread.text).intents where all hold:
      • .name is not 'benign'
      • .confidence is 'high'
    • any of ml.nlu_classifier(body.current_thread.text).intents where all hold:
      • .name is not 'benign'
      • .confidence is 'high'

Inspects: body.current_thread.text, body.links, body.links[].href_url.domain.domain, body.links[].href_url.domain.root_domain, headers.domains, headers.domains[].root_domain, headers.hops, headers.hops[].authentication_results.compauth.verdict, headers.hops[].authentication_results.dmarc, headers.hops[].authentication_results.dmarc_details.from.domain, headers.hops[].authentication_results.spf, sender.display_name, sender.email.domain.domain, sender.email.domain.root_domain, type.inbound. Sensors: ml.nlu_classifier. Reference lists: $alexa_1m, $free_file_hosts, $free_subdomain_hosts, $org_display_names, $org_domains, $recipient_domains, $social_landing_hosts, $tranco_1m, $url_shorteners.

Indicators matched (3)

FieldMatchValue
headers.hops[].authentication_results.dmarcequalsnone
headers.hops[].authentication_results.spfequalsnone
ml.nlu_classifier(body.current_thread.text).intents[].confidenceequalshigh

Stages and Predicates

Stage 1: mql_rule

and
  or
    any(body.links)
      and
        or
           macro "body.links[].href_url.domain.domain in free_file_hosts"
           macro "body.links[].href_url.domain.domain in social_landing_hosts"
           macro "body.links[].href_url.domain.domain in url_shorteners"
           macro "body.links[].href_url.domain.root_domain in free_file_hosts"
           macro "body.links[].href_url.domain.root_domain in free_subdomain_hosts"
           macro "body.links[].href_url.domain.root_domain not in tranco_1m"
         macro "body.links[].href_url.domain.root_domain not in org_domains"
    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 ne "benign"
    any(headers.domains)
      and
         macro "headers.domains[].root_domain not in alexa_1m"
         macro "headers.domains[].root_domain not in org_domains"
         macro "headers.domains[].root_domain not in recipient_domains"
    and
      body.links length_compare "0"
       macro "all(body.links)"
  any(headers.hops)
    and
      not
        headers.hops.authentication_results.compauth.verdict in ["pass", "softpass"]
      headers.hops.authentication_results.dmarc eq "none"
      headers.hops.authentication_results.spf eq "none"
       macro "headers.hops[].authentication_results.dmarc_details.from.domain in org_domains"
  type.inbound eq "true"
   macro "sender.display_name not in org_display_names"
   macro "sender.email.domain.domain in org_domains"

Indicators

These rows show field, operator, and value matches.

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