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
Feed Source
Sublime Core Feed
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  \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]* \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
)
)
)
Playground
Test against your own EMLs or sample data.