Low Severity

Spam: Image as content with Hidden HTML Element

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 Mar 3rd, 2025
Source
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 sytle
    // 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
•Docs•Learning 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.