Detection rules › Sublime MQL

Suspected cross-site scripting (XSS) found in subject

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

This rule detects Cross-Site Scripting (XSS) attempts within email subjects. It bypasses messages from highly trusted domains unless they fail authentication. However, the rule remains flexible, triggering even for trusted domains when emails are sent from Google Groups, ensuring thorough protection against potential threats while minimizing false positives.

Threat classification

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

CategoryValues
Attack typesCredential Phishing
Tactics and techniquesEvasion, Scripting

Event coverage

Rule body MQL

type.inbound
// subject contains suspected cross site scripting
and regex.icontains(subject.subject,
                    '(?:[<]|%(25)?3c|\\u003c|\\x3c|&[lg]t;|&n[vw][lg]t;)\/?(?:script(?:\s*/?\s*src\s*=)?|div|iframe|embed|object|style|form|meta|link|svg|img|audio|video|source|body|input|textarea|select|noscript|(?:/|%(25)?2f)?title|(?:/|%(25)?2f)?textarea|(?:/|%(25)?2f)?style|(?:/|%(25)?2f)?template|(?:/|%(25)?2f)?noembed)',
                    // constructor chain pattern
                    'constructor\.constructor|\[\s*constructor\s*\]|\.__proto__|\[constructor\]'
)

// and contains html or url encoded strings, hex escaped strings, opening or closing html tags, or escaped non word characters
// subject contains common event handlers
and regex.icontains(subject.subject,
                    '(?:\b|%[a-f0-9]{2})(?:on(?:abort|blur|change|click|dblclick|error|focus|load|mouse(?:over|out|move|down|up)|key(?:down|up|press)|reset|select|submit|unload)|srcdoc|src\s*(?:=|%(?:25)?3d))',
                    // subject contains javascript funcitons
                    '(?:\b|%(?:25)?[a-f0-9]{2})(?:javascript|eval|settimeout|setinterval|document\.(?:cookie|write|location|createElement|body|appendChild)|fetch|constructor|__proto__|prototype|atob|getScript|script(?:\s|%(?:25)?20)src)(?:\b|%(?:25)?[a-f0-9]{2})',
                    // url encoded forms of script src
                    '(?:\b|%(?:25)?[a-f0-9]{2})script(?:\s|%(?:25)?20)src(?:\b|%(?:25)?[a-f0-9]{2})'
)
and regex.icontains(subject.subject,
                    // Pattern 1: Quote followed by various special characters in encoded/literal forms:
                    // - < > angle brackets (%3C, %3E, literal, &lt;, &gt;)
                    // - quotes (&quot;, &apos;)
                    // - parentheses (&lpar;, &rpar;)
                    // - curly braces (&lcub;, &rcub;)
                    // - square brackets (&lsqb;, &rsqb;)
                    // - equals sign (&equals;)
                    // - forward/backward slashes (&sol;, &bsol;)
                    // - colon (&colon;)
                    // - semicolon (&semi;)
                    '[\x27\x22](?:%3[CE]|[<>]|&(?:lt|gt|quot|apos|[lr](?:par|cub|sqb)|equals|[bs]ol|colon|semi)\x3b)',
                    // Pattern 2: Encoded/special characters followed by quote
                    // Same as above but in reverse order - special chars followed by quote
                    '(?:%3[CE]|[<>]|&(?:lt|gt|quot|apos|[lr](?:par|cub|sqb)|equals|[bs]ol|colon|semi)\x3b)[\x27\x22]',

                    // Pattern 3: Hexadecimal or decimal HTML entities with semicolon
                    // e.g., &#x27; (hex) or &#39; (decimal)
                    '&#[xX]?[a-f0-9]+;',

                    // Pattern 4: Raw decimal HTML entities
                    // e.g., &#60 (without semicolon)
                    '&#\d+',

                    // Pattern 5: URL encoded characters
                    // e.g., %3C for <, %3E for >, %22 for ", %27 for '
                    '%[a-f0-9]{2}',

                    // Pattern 6: Unicode/hex escapes
                    // e.g., \u003C for <, \x3C for
                    '\\[xXuU][a-f0-9]{4}',
                    // New patterns for this type of payload
                    '</[^>]+/[^>]+>', // Matches closing tags with slash delimiters
                    '//[^"\x27>\s]+', // Matches protocol-relative URLs
                    'xss\.report', // Specific known XSS domains
                    '/\*|\*/|\-\->', // comment chars (/*, */, -->)
)

// negate highly trusted sender domains unless they fail DMARC authentication
and (
  (
    sender.email.domain.root_domain in $high_trust_sender_root_domains
    and (
      any(distinct(headers.hops, .authentication_results.dmarc is not null),
          strings.ilike(.authentication_results.dmarc, "*fail")
      )
      or (
        strings.icontains(sender.display_name, "via")
        and any(headers.hops,
                any(.fields,
                    .name == "List-ID"
                    and strings.ends_with(.value,
                                          strings.concat(sender.email.domain.domain,
                                                         ">"
                                          )
                    )
                )
        )
      )
    )
  )
  or sender.email.domain.root_domain not in $high_trust_sender_root_domains
)

Detection logic

Scope: inbound message.

This rule detects Cross-Site Scripting (XSS) attempts within email subjects. It bypasses messages from highly trusted domains unless they fail authentication. However, the rule remains flexible, triggering even for trusted domains when emails are sent from Google Groups, ensuring thorough protection against potential threats while minimizing false positives.

  1. inbound message
  2. subject.subject matches any of 2 patterns
    • (?:[<]|%(25)?3c|\\u003c|\\x3c|&[lg]t;|&n[vw][lg]t;)\/?(?:script(?:\s*/?\s*src\s*=)?|div|iframe|embed|object|style|form|meta|link|svg|img|audio|video|source|body|input|textarea|select|noscript|(?:/|%(25)?2f)?title|(?:/|%(25)?2f)?textarea|(?:/|%(25)?2f)?style|(?:/|%(25)?2f)?template|(?:/|%(25)?2f)?noembed)
    • constructor\.constructor|\[\s*constructor\s*\]|\.__proto__|\[constructor\]
  3. subject.subject matches any of 3 patterns
    • (?:\b|%[a-f0-9]{2})(?:on(?:abort|blur|change|click|dblclick|error|focus|load|mouse(?:over|out|move|down|up)|key(?:down|up|press)|reset|select|submit|unload)|srcdoc|src\s*(?:=|%(?:25)?3d))
    • (?:\b|%(?:25)?[a-f0-9]{2})(?:javascript|eval|settimeout|setinterval|document\.(?:cookie|write|location|createElement|body|appendChild)|fetch|constructor|__proto__|prototype|atob|getScript|script(?:\s|%(?:25)?20)src)(?:\b|%(?:25)?[a-f0-9]{2})
    • (?:\b|%(?:25)?[a-f0-9]{2})script(?:\s|%(?:25)?20)src(?:\b|%(?:25)?[a-f0-9]{2})
  4. subject.subject matches any of 10 patterns
    • [\x27\x22](?:%3[CE]|[<>]|&(?:lt|gt|quot|apos|[lr](?:par|cub|sqb)|equals|[bs]ol|colon|semi)\x3b)
    • (?:%3[CE]|[<>]|&(?:lt|gt|quot|apos|[lr](?:par|cub|sqb)|equals|[bs]ol|colon|semi)\x3b)[\x27\x22]
    • &#[xX]?[a-f0-9]+;
    • &#\d+
    • %[a-f0-9]{2}
    • \\[xXuU][a-f0-9]{4}
    • </[^>]+/[^>]+>
    • //[^"\x27>\s]+
    • xss\.report
    • /\*|\*/|\-\->
  5. any of:
    • all of:
      • sender.email.domain.root_domain in $high_trust_sender_root_domains
      • any of:
        • any of distinct(headers.hops) where:
          • .authentication_results.dmarc matches '*fail'
        • all of:
          • sender.display_name contains 'via'
          • any of headers.hops where:
            • any of .fields where all hold:
              • .name is 'List-ID'
              • strings.ends_with(.value)
    • sender.email.domain.root_domain not in $high_trust_sender_root_domains

Inspects: headers.hops, headers.hops[].authentication_results.dmarc, headers.hops[].fields, headers.hops[].fields[].name, headers.hops[].fields[].value, sender.display_name, sender.email.domain.domain, sender.email.domain.root_domain, subject.subject, type.inbound. Sensors: regex.icontains, strings.concat, strings.ends_with, strings.icontains, strings.ilike. Reference lists: $high_trust_sender_root_domains.

Indicators matched (18)

FieldMatchValue
regex.icontainsregex(?:[<]|%(25)?3c|\\u003c|\\x3c|&[lg]t;|&n[vw][lg]t;)\/?(?:script(?:\s*/?\s*src\s*=)?|div|iframe|embed|object|style|form|meta|link|svg|img|audio|video|source|body|input|textarea|select|noscript|(?:/|%(25)?2f)?title|(?:/|%(25)?2f)?textarea|(?:/|%(25)?2f)?style|(?:/|%(25)?2f)?template|(?:/|%(25)?2f)?noembed)
regex.icontainsregexconstructor\.constructor|\[\s*constructor\s*\]|\.__proto__|\[constructor\]
regex.icontainsregex(?:\b|%[a-f0-9]{2})(?:on(?:abort|blur|change|click|dblclick|error|focus|load|mouse(?:over|out|move|down|up)|key(?:down|up|press)|reset|select|submit|unload)|srcdoc|src\s*(?:=|%(?:25)?3d))
regex.icontainsregex(?:\b|%(?:25)?[a-f0-9]{2})(?:javascript|eval|settimeout|setinterval|document\.(?:cookie|write|location|createElement|body|appendChild)|fetch|constructor|__proto__|prototype|atob|getScript|script(?:\s|%(?:25)?20)src)(?:\b|%(?:25)?[a-f0-9]{2})
regex.icontainsregex(?:\b|%(?:25)?[a-f0-9]{2})script(?:\s|%(?:25)?20)src(?:\b|%(?:25)?[a-f0-9]{2})
regex.icontainsregex[\x27\x22](?:%3[CE]|[<>]|&(?:lt|gt|quot|apos|[lr](?:par|cub|sqb)|equals|[bs]ol|colon|semi)\x3b)
regex.icontainsregex(?:%3[CE]|[<>]|&(?:lt|gt|quot|apos|[lr](?:par|cub|sqb)|equals|[bs]ol|colon|semi)\x3b)[\x27\x22]
regex.icontainsregex&#[xX]?[a-f0-9]+;
regex.icontainsregex&#\d+
regex.icontainsregex%[a-f0-9]{2}
regex.icontainsregex\\[xXuU][a-f0-9]{4}
regex.icontainsregex</[^>]+/[^>]+>
6 more
regex.icontainsregex//[^"\x27>\s]+
regex.icontainsregexxss\.report
regex.icontainsregex/\*|\*/|\-\->
strings.ilikesubstring*fail
strings.icontainssubstringvia
headers.hops[].fields[].nameequalsList-ID