Detection rules › Sublime MQL

Spam: Fake photo share

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

Message contains pretexting language about sharing photos ("found these photos and thought you'd like them", "remember these photos?") and a link with a newly registered domain. Fake threads and plain text bodies have been seen in the wild, indicating active evasion techniques.

Threat classification

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

CategoryValues
Attack typesSpam
Tactics and techniquesEvasion, Social engineering

Event coverage

Rule body MQL

type.inbound
and length(attachments) == 0
and (
  (
    (
      (
        (length(body.plain.raw) < 500 and length(body.current_thread.text) == 0)
        or (
          length(body.html.display_text) < 500
          and length(body.current_thread.text) == 0
        )
        or length(body.current_thread.text) < 500
        or any(map(filter(ml.nlu_classifier(body.current_thread.text).entities,
                          .name == "disclaimer"
                   ),
                   .text
               ),
               (length(body.current_thread.text) - length(.)) < 500
        )
      )
      and strings.ilike(subject.subject,
                        "*picture*",
                        "*photo*",
                        "*image*",
                        "*sad news*",
                        "*sad announcement*",
                        "*sad update*",
                        "*new pics*",
                        "*Reunion*",
                        "*planing*"
      )
    )
    or (
      (
        (
          length(body.html.display_text) < 500
          and length(body.current_thread.text) == 0
        )
        and strings.ilike(body.html.display_text,
                          "*picture*",
                          "*photo*",
                          "*image*",
                          "*sad news*",
                          "*sad announcement*",
                          "*sad update*",
                          "*new pics*"
        )
      )
      or (
        (length(body.plain.raw) < 500 and length(body.current_thread.text) == 0)
        and strings.ilike(body.plain.raw,
                          "*picture*",
                          "*photo*",
                          "*image*",
                          "*sad news*",
                          "*sad announcement*",
                          "*sad update*",
                          "*new pics*"
        )
        and not strings.icontains(body.plain.raw, "[cid:image")
      )
      or (
        length(body.current_thread.text) < 500
        and strings.ilike(body.current_thread.text,
                          "*picture*",
                          "*photo*",
                          "*image*",
                          "*sad news*",
                          "*sad announcement*",
                          "*sad update*",
                          "*new pics*"
        )
      )
    )
    or (
      body.plain.raw is not null
      and body.html.display_text is null
      and (
        length(body.current_thread.text) == 0
        or (
          length(body.current_thread.text) < 500
          // fake forward indicator in the plain text body
          and (
            regex.contains(body.plain.raw,
                           'On (Sunday|Monday|Tuesday|Wednesday|Thursday|Friday|Saturday).{0,50} wrote'
            )
            or strings.icontains(body.plain.raw, 'Original Message')
          )
          and not regex.contains(body.current_thread.text,
                                 'On (Sunday|Monday|Tuesday|Wednesday|Thursday|Friday|Saturday).{0,50} wrote'
          )
          and not strings.icontains(body.current_thread.text,
                                    'Original Message'
          )
          and not any(headers.hops, any(.fields, .name == "Resent-From"))
        )
      )
      and strings.ilike(body.plain.raw,
                        "*picture*",
                        "*photo*",
                        "*image*",
                        "*sad news*",
                        "*sad announcement*",
                        "*sad update*",
                        "*pics*"
      )
      and not strings.istarts_with(body.plain.raw, "[cid:image")
      and strings.icontains(subject.subject, sender.display_name)
    )
    or (
      strings.icontains(subject.subject, sender.display_name)
      and sender.email.domain.root_domain in $free_email_providers
      and length(body.links) == 2
      and length(filter(body.links, .display_text == "h")) == 1
      and length(filter(body.links, .display_url.scheme == "ttp")) == 1
    )
  )
  and length(body.links) < 5
  and any(body.links,
          (
            (
              network.whois(.href_url.domain).days_old < 30
              or not network.whois(.href_url.domain).found
              or network.whois(.href_url.domain).found is null
            )
            and .href_url.domain.root_domain != sender.email.domain.root_domain
          )
          or (
            length(.display_text) == 1
            and .href_url.domain.root_domain in ("facebook.com", "youtube.com")
          )
          or (
            // random 5-character subdomain
            regex.icontains(.href_url.domain.domain,
                            '^[a-z]{5}\.[a-z]{5,}\.[a-z]+'
            )
            // subdomain contains 3+ consecutive consonants
            and regex.icontains(.href_url.domain.domain,
                                '^[a-z]*[b-df-hj-np-tv-z]{3,}[a-z]*\.'
            )
            and network.whois(.href_url.domain).days_old < 365
          )
  )
)
and (
  (
    (length(headers.references) > 0 or headers.in_reply_to is null)
    and not (
      (
        strings.istarts_with(subject.subject, "RE:")
        or strings.istarts_with(subject.subject, "R:")
        or strings.istarts_with(subject.subject, "ODG:")
        or strings.istarts_with(subject.subject, "答复:")
        or strings.istarts_with(subject.subject, "AW:")
        or strings.istarts_with(subject.subject, "TR:")
        or strings.istarts_with(subject.subject, "FWD:")
        or regex.imatch(subject.subject, '(\[[^\]]+\]\s?){0,3}(re|fwd?)\s?:')
      )
    )
  )
  or length(headers.references) == 0
)
// 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.

Message contains pretexting language about sharing photos ("found these photos and thought you'd like them", "remember these photos?") and a link with a newly registered domain. Fake threads and plain text bodies have been seen in the wild, indicating active evasion techniques.

  1. inbound message
  2. length(attachments) is 0
  3. all of:
    • any of:
      • all of:
        • any of:
          • all of:
            • length(body.plain.raw) < 500
            • length(body.current_thread.text) is 0
          • all of:
            • length(body.html.display_text) < 500
            • length(body.current_thread.text) is 0
          • length(body.current_thread.text) < 500
          • any of map(...) where:
            • length(body.current_thread.text) - length(.) < 500
        • subject.subject matches any of 9 patterns
          • *picture*
          • *photo*
          • *image*
          • *sad news*
          • *sad announcement*
          • *sad update*
          • *new pics*
          • *Reunion*
          • *planing*
      • any of:
        • all of:
          • all of:
            • length(body.html.display_text) < 500
            • length(body.current_thread.text) is 0
          • body.html.display_text matches any of 7 patterns
            • *picture*
            • *photo*
            • *image*
            • *sad news*
            • *sad announcement*
            • *sad update*
            • *new pics*
        • all of:
          • all of:
            • length(body.plain.raw) < 500
            • length(body.current_thread.text) is 0
          • body.plain.raw matches any of 7 patterns
            • *picture*
            • *photo*
            • *image*
            • *sad news*
            • *sad announcement*
            • *sad update*
            • *new pics*
          • not:
            • body.plain.raw contains '[cid:image'
        • all of:
          • length(body.current_thread.text) < 500
          • body.current_thread.text matches any of 7 patterns
            • *picture*
            • *photo*
            • *image*
            • *sad news*
            • *sad announcement*
            • *sad update*
            • *new pics*
      • all of:
        • body.plain.raw is set
        • body.html.display_text is missing
        • any of:
          • length(body.current_thread.text) is 0
          • all of:
            • length(body.current_thread.text) < 500
            • any of:
              • body.plain.raw matches 'On (Sunday|Monday|Tuesday|Wednesday|Thursday|Friday|Saturday).{0,50} wrote'
              • body.plain.raw contains 'Original Message'
            • not:
              • body.current_thread.text matches 'On (Sunday|Monday|Tuesday|Wednesday|Thursday|Friday|Saturday).{0,50} wrote'
            • not:
              • body.current_thread.text contains 'Original Message'
            • not:
              • any of headers.hops where:
                • any of .fields where:
                  • .name is 'Resent-From'
        • body.plain.raw matches any of 7 patterns
          • *picture*
          • *photo*
          • *image*
          • *sad news*
          • *sad announcement*
          • *sad update*
          • *pics*
        • not:
          • body.plain.raw starts with '[cid:image'
        • strings.icontains(subject.subject)
      • all of:
        • strings.icontains(subject.subject)
        • sender.email.domain.root_domain in $free_email_providers
        • length(body.links) is 2
        • length(filter(body.links, .display_text == 'h')) is 1
        • length(filter(body.links, .display_url.scheme == 'ttp')) is 1
    • length(body.links) < 5
    • any of body.links where any holds:
      • all of:
        • any of:
          • network.whois(.href_url.domain).days_old < 30
          • not:
            • network.whois(.href_url.domain).found
          • network.whois(.href_url.domain).found is missing
        • .href_url.domain.root_domain is not sender.email.domain.root_domain
      • all of:
        • length(.display_text) is 1
        • .href_url.domain.root_domain in ('facebook.com', 'youtube.com')
      • all of:
        • .href_url.domain.domain matches '^[a-z]{5}\\.[a-z]{5,}\\.[a-z]+'
        • .href_url.domain.domain matches '^[a-z]*[b-df-hj-np-tv-z]{3,}[a-z]*\\.'
        • network.whois(.href_url.domain).days_old < 365
  4. any of:
    • all of:
      • any of:
        • length(headers.references) > 0
        • headers.in_reply_to is missing
      • none of:
        • subject.subject starts with 'RE:'
        • subject.subject starts with 'R:'
        • subject.subject starts with 'ODG:'
        • subject.subject starts with '答复:'
        • subject.subject starts with 'AW:'
        • subject.subject starts with 'TR:'
        • subject.subject starts with 'FWD:'
        • subject.subject matches '(\\[[^\\]]+\\]\\s?){0,3}(re|fwd?)\\s?:'
    • length(headers.references) is 0
  5. 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.display_text, body.links, body.links[].display_text, body.links[].display_url.scheme, body.links[].href_url.domain, body.links[].href_url.domain.domain, body.links[].href_url.domain.root_domain, body.plain.raw, headers.auth_summary.dmarc.pass, headers.hops, headers.hops[].fields, headers.hops[].fields[].name, headers.in_reply_to, headers.references, sender.display_name, sender.email.domain.root_domain, subject.subject, type.inbound. Sensors: ml.nlu_classifier, network.whois, regex.contains, regex.icontains, regex.imatch, strings.icontains, strings.ilike, strings.istarts_with. Reference lists: $free_email_providers, $high_trust_sender_root_domains.

Indicators matched (30)

FieldMatchValue
ml.nlu_classifier(body.current_thread.text).entities[].nameequalsdisclaimer
strings.ilikesubstring*picture*
strings.ilikesubstring*photo*
strings.ilikesubstring*image*
strings.ilikesubstring*sad news*
strings.ilikesubstring*sad announcement*
strings.ilikesubstring*sad update*
strings.ilikesubstring*new pics*
strings.ilikesubstring*Reunion*
strings.ilikesubstring*planing*
strings.icontainssubstring[cid:image
regex.containsregexOn (Sunday|Monday|Tuesday|Wednesday|Thursday|Friday|Saturday).{0,50} wrote
18 more
strings.icontainssubstringOriginal Message
headers.hops[].fields[].nameequalsResent-From
strings.ilikesubstring*pics*
strings.istarts_withprefix[cid:image
body.links[].display_textequalsh
body.links[].display_url.schemeequalsttp
body.links[].href_url.domain.root_domainmemberfacebook.com
body.links[].href_url.domain.root_domainmemberyoutube.com
regex.icontainsregex^[a-z]{5}\.[a-z]{5,}\.[a-z]+
regex.icontainsregex^[a-z]*[b-df-hj-np-tv-z]{3,}[a-z]*\.
strings.istarts_withprefixRE:
strings.istarts_withprefixR:
strings.istarts_withprefixODG:
strings.istarts_withprefix答复:
strings.istarts_withprefixAW:
strings.istarts_withprefixTR:
strings.istarts_withprefixFWD:
regex.imatchregex(\[[^\]]+\]\s?){0,3}(re|fwd?)\s?: