Detection rules › Sublime MQL

Sublime MQL rules: evasion

Evasion: Hidden content divs from freemail sender

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

Detects inbound messages from freemail senders containing multiple hidden HTML div elements with specific styling properties (display:none, opacity:0, zero dimensions) that are commonly used to evade content filtering and detection systems.

Threat classification

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

CategoryValues
Attack typesCredential Phishing
Tactics and techniquesEvasion, Free email provider

Telemetry coverage

PlatformRecord / event type
SublimeInbound email message

Message attributes

  • body
  • body.html
  • sender.email
  • type

Rule body

type.inbound
and sender.email.domain.root_domain in $free_email_providers
and strings.count(body.html.raw,
                  '<div style="display:none;opacity:0;width:0;height:0;overflow:hidden" aria-hidden="true">'
) >= 3

Detection logic

Scope: inbound message.

Detects inbound messages from freemail senders containing multiple hidden HTML div elements with specific styling properties (display:none, opacity:0, zero dimensions) that are commonly used to evade content filtering and detection systems.

  1. inbound message
  2. sender.email.domain.root_domain in $free_email_providers
  3. strings.count(body.html.raw, '<div style="display:none;opacity:0;width:0;height:0;overflow:hidden" aria-hidden="true">') ≥ 3

Inspects: body.html.raw, sender.email.domain.root_domain, type.inbound. Sensors: strings.count. Reference lists: $free_email_providers.

Stages and Predicates

Stage 1: mql_rule

and
  strings.count func_call "strings.count(body.html.raw, \"<div style=\"display:none;opacity:0;width:0;height:0;overflow:hidden\" aria-hidden=\"true\">\") >= 3"
  type.inbound eq "true"
   macro "sender.email.domain.root_domain in free_email_providers"

Indicators

These rows show field, operator, and value matches.

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

Link: Credential harvesting with excess padding evasion

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

Detects inbound messages containing credential-related action links with tall screenshot images and HTML padding techniques used to evade detection. The rule identifies messages with excessive empty div tags, non-breaking spaces, or large margin-top values that artificially increase content height while hiding malicious intent.

Threat classification

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

CategoryValues
Attack typesCredential Phishing
Tactics and techniquesEvasion, Social engineering

Telemetry coverage

PlatformRecord / event type
SublimeInbound email message

Message attributes

  • body
  • body.current_thread
  • body.html
  • sender.email
  • type

Rule body

type.inbound
// CTA link with action-oriented display text pointing to a different domain than the sender
and any(body.current_thread.links,
        regex.icontains(.display_text,
                        '(?:open|sign.?in|log.?in|retain|credential|secure|confirm|accept|release|review|document)'
        )
        and .href_url.domain.root_domain != sender.email.domain.root_domain
        and not regex.icontains(.display_text, 'open source')
)
// tall rendered email with low word density
and beta.parse_exif(file.message_screenshot()).image_height > 1500
and beta.parse_exif(file.message_screenshot()).image_height * 100 / regex.count(body.html.display_text,
                                                                                '\S+'
) > 500
// html whitespace stuffing patterns
and (
  // bare div-br blocks repeated 30+ times
  regex.icontains(body.html.raw, '(?:<div>\s*<br\s*/?\s*>\s*</div>\s*){30,}')
  // style div-br blocks repeated 20+ times
  or regex.icontains(body.html.raw,
                     '(?:<div\s+style="[^"]+"\s*[^>]*>\s*<br\s*/?\s*>\s*</div>\s*){20,}'
  )
  // attributed empty div-nbsp blocks repeated 20+ times (styled/classed empty divs, e.g. Outlook Aptos)
  // requires an attribute to avoid bare <div>&nbsp;</div> newsletter spacers
  or (
    regex.icontains(body.html.raw,
                    '(?:<div\s+[^>]+>\s*(?:&nbsp;|&#160;)\s*</div>\s*){20,}'
    )
    // exclude collapsed/hidden empty divs (display:none, font-size:0, line-height:0)
    // these render to zero height and are ESP preheader artifacts, not visible stuffing
    and not regex.icontains(body.html.raw,
                            '(?:<div\s+[^>]*(?:display\s*:\s*none|font-size\s*:\s*0|line-height\s*:\s*0)[^>]*>\s*(?:&nbsp;|&#160;)\s*</div>\s*){20,}'
    )
  )
  // p-nbsp blocks repeated 25+ times
  or regex.icontains(body.html.raw,
                     '(?:<p>\s*(?:&nbsp;|&#160;)\s*</p>\s*){25,}'
  )
  // css margin-top pushdown >= 1500px
  or (
    regex.icontains(body.html.raw,
                    'margin-top\s*:\s*(?:1[5-9]\d{2}|[2-9]\d{3}|\d{5,})px'
    )
    and not regex.icontains(body.html.raw,
                            'position\s*:\s*absolute[^"]*margin-top\s*:\s*(?:1[5-9]\d{2}|[2-9]\d{3}|\d{5,})px'
    )
    and not regex.icontains(body.html.raw,
                            'margin-left\s*:\s*\d{3,}px[^"]*margin-top\s*:\s*(?:1[5-9]\d{2}|[2-9]\d{3}|\d{5,})px'
    )
  )
)

Detection logic

Scope: inbound message.

Detects inbound messages containing credential-related action links with tall screenshot images and HTML padding techniques used to evade detection. The rule identifies messages with excessive empty div tags, non-breaking spaces, or large margin-top values that artificially increase content height while hiding malicious intent.

  1. inbound message
  2. any of body.current_thread.links where all hold:
    • .display_text matches '(?:open|sign.?in|log.?in|retain|credential|secure|confirm|accept|release|review|document)'
    • .href_url.domain.root_domain is not sender.email.domain.root_domain
    • not:
      • .display_text matches 'open source'
  3. beta.parse_exif(file.message_screenshot()).image_height > 1500
  4. beta.parse_exif(file.message_screenshot()).image_height * 100 / regex.count(body.html.display_text) > 500
  5. any of:
    • body.html.raw matches '(?:<div>\\s*<br\\s*/?\\s*>\\s*</div>\\s*){30,}'
    • body.html.raw matches '(?:<div\\s+style="[^"]+"\\s*[^>]*>\\s*<br\\s*/?\\s*>\\s*</div>\\s*){20,}'
    • all of:
      • body.html.raw matches '(?:<div\\s+[^>]+>\\s*(?:&nbsp;|&#160;)\\s*</div>\\s*){20,}'
      • not:
        • body.html.raw matches '(?:<div\\s+[^>]*(?:display\\s*:\\s*none|font-size\\s*:\\s*0|line-height\\s*:\\s*0)[^>]*>\\s*(?:&nbsp;|&#160;)\\s*</div>\\s*){20,}'
    • body.html.raw matches '(?:<p>\\s*(?:&nbsp;|&#160;)\\s*</p>\\s*){25,}'
    • all of:
      • body.html.raw matches 'margin-top\\s*:\\s*(?:1[5-9]\\d{2}|[2-9]\\d{3}|\\d{5,})px'
      • not:
        • body.html.raw matches 'position\\s*:\\s*absolute[^"]*margin-top\\s*:\\s*(?:1[5-9]\\d{2}|[2-9]\\d{3}|\\d{5,})px'
      • not:
        • body.html.raw matches 'margin-left\\s*:\\s*\\d{3,}px[^"]*margin-top\\s*:\\s*(?:1[5-9]\\d{2}|[2-9]\\d{3}|\\d{5,})px'

Inspects: body.current_thread.links, body.current_thread.links[].display_text, body.current_thread.links[].href_url.domain.root_domain, body.html.raw, sender.email.domain.root_domain, type.inbound. Sensors: beta.parse_exif, file.message_screenshot, regex.icontains.

Indicators matched (6)

FieldMatchValue
regex.icontainsregex(?:open|sign.?in|log.?in|retain|credential|secure|confirm|accept|release|review|document)
regex.icontainsregex(?:<div>\s*<br\s*/?\s*>\s*</div>\s*){30,}
regex.icontainsregex(?:<div\s+style="[^"]+"\s*[^>]*>\s*<br\s*/?\s*>\s*</div>\s*){20,}
regex.icontainsregex(?:<div\s+[^>]+>\s*(?:&nbsp;|&#160;)\s*</div>\s*){20,}
regex.icontainsregex(?:<p>\s*(?:&nbsp;|&#160;)\s*</p>\s*){25,}
regex.icontainsregexmargin-top\s*:\s*(?:1[5-9]\d{2}|[2-9]\d{3}|\d{5,})px

Stages and Predicates

Stage 1: mql_rule

and
  any(body.current_thread.links)
    and
      not
        body.current_thread.links.display_text regex_match "open source"
      body.current_thread.links.display_text regex_match "(?:open|sign.?in|log.?in|retain|credential|secure|confirm|accept|release|review|document)"
      body.current_thread.links.href_url.domain.root_domain cross_field_compare "sender.email.domain.root_domain"
  or
    and
      not
        body.html.raw regex_match "(?:<div\\s+[^>]*(?:display\\s*:\\s*none|font-size\\s*:\\s*0|line-height\\s*:\\s*0)[^>]*>\\s*(?:&nbsp;|&#160;)\\s*</div>\\s*){20,}"
      body.html.raw regex_match "(?:<div\\s+[^>]+>\\s*(?:&nbsp;|&#160;)\\s*</div>\\s*){20,}"
    and
      not
        body.html.raw regex_match "margin-left\\s*:\\s*\\d{3,}px[^\"]*margin-top\\s*:\\s*(?:1[5-9]\\d{2}|[2-9]\\d{3}|\\d{5,})px"
      not
        body.html.raw regex_match "position\\s*:\\s*absolute[^\"]*margin-top\\s*:\\s*(?:1[5-9]\\d{2}|[2-9]\\d{3}|\\d{5,})px"
      body.html.raw regex_match "margin-top\\s*:\\s*(?:1[5-9]\\d{2}|[2-9]\\d{3}|\\d{5,})px"
    body.html.raw regex_match "(?:<div>\\s*<br\\s*/?\\s*>\\s*</div>\\s*){30,}"
    body.html.raw regex_match "(?:<div\\s+style=\"[^\"]+\"\\s*[^>]*>\\s*<br\\s*/?\\s*>\\s*</div>\\s*){20,}"
    body.html.raw regex_match "(?:<p>\\s*(?:&nbsp;|&#160;)\\s*</p>\\s*){25,}"
  beta.parse_exif func_call "beta.parse_exif(file.message_screenshot()).image_height > 1500"
  type.inbound eq "true"
   macro "((beta.parse_exif(file.message_screenshot()).image_height * 100) / regex.count(body.html.display_text)) > 500"

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
body.html.rawregex_match
  • (?:<div>\s*<br\s*/?\s*>\s*</div>\s*){30,}
  • (?:<div\s+[^>]+>\s*(?: | )\s*</div>\s*){20,}
  • (?:<div\s+style="[^"]+"\s*[^>]*>\s*<br\s*/?\s*>\s*</div>\s*){20,}
  • (?:<p>\s*(?: | )\s*</p>\s*){25,}
  • margin-top\s*:\s*(?:1[5-9]\d{2}|[2-9]\d{3}|\d{5,})px
field:"body.html.raw" kind:regex_match
type.inboundeq
  • true transforms: boolean
field:"type.inbound" kind:eq value:"true"