• Sublime Core Feed
Low Severity

Spam: Image as content with Hidden HTML Element

Labels

Spam
Evasion
Image as content
Content analysis
HTML analysis
Sender analysis

Description

This has been observed in the delivery of emails containing account/membership expiration lure themes of popular online services or delivery notifications.

References

No references.

Sublime Security
Created Jul 9th, 2024 • Last updated Jul 17th, 2025
Feed Source
Sublime Core Feed
Source
GitHub
type.inbound
and (not profile.by_sender().solicited or sender.email.email == "")
// not high trust sender domains
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
)
and (
  // find the template - a link that is a centered image
  (
    // at the start of a center
    regex.contains(body.html.raw,
                   'center(?:\x22[^\>]*)?\>\s*<a href=\"https?:\/\/[^\x22]+\x22(?:\s[a-z]+=\x22[^\x22]+\x22)*>\s*[^\n]*?(?:\<img src=\x22[^\x22]+\x22>(?:<[a-z]+>\s*)*){1,}<\/a>'
    )
    // method two for the start of a center but includes an header line
    or regex.contains(body.html.raw,
                      'center(?:\x22[^\>]*)?\>\s*<a href=\"https?:\/\/[^\x22]+\x22(?:\s[a-z]+=\x22[^\x22]+\x22)*>\s*[^\n]*?<h\d>[^\<]+<\/h\d+>\s*(?:\<img src=\x22[^\x22]+\x22>(?:<[a-z]+>\s*)*){1,}<\/a>(?:<[a-z]+>\s*)*<\/'
    )
    // method three for the start of a center, but includes words not in a header line
    or regex.contains(body.html.raw,
                      'center(?:[\x22\x3b][^\>]*)?\>\s*<a href=\"https?:\/\/[^\x22]+\x22(?:\s[a-z]+=\x22[^\x22]+\x22)*>\s*[^\<][^\/][^\a]+\s*<\/a>\s*(?:<[a-z]+>\s*)*\s*<a href=\"https?:\/\/[^\x22]+\x22(?:\s[a-z]+=\x22[^\x22]+\x22)*>\s*(?:<img src=\x22[^\x22]+\x22>(?:<[a-z]+>\s*)*){1,}<\/a>'
    )
    // method four - the body starts with a centered div which is not visable, which contains a link and img within the link
    or regex.contains(body.html.raw,
                      '<body(?:\x22[^\>]+)?\>\s*<center>\s*<(?:span|div)[^\>]*style=\x22[^\x22]*\s*(?:display\s*\x3a\s*none|visibility\s*\x3a\s*hidden)\x3b[^\x22]*\x22(?:\s*\w+=\"\w+\")*>[^\<]+</div>\s*<a[^\>]*href="[^\x22]+\x22(?:\s[a-z]+=\x22[^\x22]+\x22)*>\s*(?:<img src=\x22[^\x22]+\x22(?:\s[a-z]+=\x22[^\x22]+\x22)*>\s*){1,}\<\/a>'
    )
    // or at the end of the center
    or regex.contains(body.html.raw,
                      '<a href=\"https?:\/\/[^\x22]+\x22(?:\s[a-z]+=\x22[^\x22]+\x22)*>\s*(?:\<img src=\x22[^\x22]+\x22>(?:<\/a>|(?:<[a-z]+>\s*))*){1,}<\/center>'
    )
    // at the start of the body
    or regex.contains(body.html.raw,
                      'body(?:\x22[^\>]+)?\>\s*<a href=\"https?:\/\/[^\x22]+\x22(?:\s[a-z]+=\x22[^\x22]+\x22)*>\s*[^\n]*?(?:\<img src=\x22[^\x22]+\x22>(?:<[a-z]+>\s*)*){1,}<\/a>'
    )
    // a href with background url which is centered very early on in the body.html.raw
    or regex.contains(body.html.raw,
                      '^(?:<[^\>]+>\s*){0,6}<a[^\>]*href=\x22[^\>]+\>\s*<(?:div|span)[^\>]*style=\x22[^\x22]*background:url\([^\)]+\)[^\x22]*center;[^\>]*\>\s*<\/(?:div|span)>\s*</a>'
    )
    or regex.contains(body.html.raw,
                      '<(?:div|span)[^\>]*style="[^\"]*center;[^\"]*\"[^\>]*>\s*<a href=\"[^\>]+\>(?:.|\W)*<img src="[^\"]+">\s*</a>\s*<br>\s*<(?:div|span)[^\>]*style="[^\"]*display\s*:\s*none;[^\"]*\">'
    )
  )
  and (
    // where there is a span/div that is hidden with either &nbsp\x3b\x200c? or underscores repeating multiple times OR followed by a new metatag
    regex.contains(body.html.raw,
                   '<(?:span|div)[^\>]*style=\x22[^\x22]*\s*(?:display\s*\x3a\s*none|visibility\s*\x3a\s*hidden)\x3b[^\x22]*\x22(?:\s*\w+=\"\w+\")*>\s*(?:<\/?[^\>]+>\s*)*(?:(?:_|[\pCc\pCf\pCs]*&nbsp\x3b\s*[\pCc\pCf\pCs]*){3,}|\s+\<meta |\s+\<center )'
    )
    or 
    // a custom css value is used to hide the body
    // unable to use capture groups to capture the custom html tag to apply the hidden style
    // instead we use [A-Za-z] to catch a single char. 
    regex.contains(body.html.raw,
                   'style\s+[^\>]*type\s*\x3d\s*\"text/css\"[^\>]*>\s*[^\<]*[A-Za-z]\s*\{[^\}]*(?:display\s*\x3a\s*none|visibility\s*\x3a\s*hidden)[^\}]*\}[^\<]+\</style><[A-Za-z]>'
    )
    or 
    // the hidden span/div is before the body/meta
    regex.contains(body.html.raw,
                   '<(?:span|div)[^\>]*style=\x22[^\x22]*\s*(?:display\s*\x3a\s*none|visibility\s*\x3a\s*hidden)\x3b[^\x22]*\x22(?:\s*\w+=\"\w+\")*>\s*\<(?:body|meta|head|(?:<?div[^\>]+\>\s*(?:[^\<]*|<[a-z]+>\s*)<\/div>\s*){2,})'
    )
    // the length of the inner text is greather than or equal to 10x more than the display text
    // this attempts to generically cover multiple methods of hiding text
    or (
      length(body.html.inner_text) > 0
      and (
        length(body.html.inner_text) >= (length(body.html.display_text) * 10)
      )
    )
    // used to push down or move content out of view
    or (
      sum([
            regex.count(body.html.display_text, '[\r\n].?[\r\n]'),
            regex.icount(body.html.raw, '(?:<br>(?:[^\<]|\s){0,2}){3}'),
            regex.icount(body.html.raw, '(?:<blockquote>(?:[^\<]|\s){0,2}){3}'),
            regex.icount(body.html.raw, '<div>(?:.|\s){0,2}<\/div>'),
            regex.icount(body.html.raw, '<span>(?:.|\s){0,2}<\/span>'),
          ]
      ) > 40
    )
  )
)
MQL Rule Console
DocsLearning Labs

Playground

Test against your own EMLs or sample data.

Share

Post about this on your socials.

Get Started. Today.

Managed or self-managed. No MX changes.

Get Started