Detection rules › Sublime MQL

Attachment: HTML attachment with login portal indicators

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

Recursively scans files and archives to detect indicators of login portals implemented in HTML files. This is a known credential theft technique used by threat actors.

Threat classification

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

CategoryValues
Attack typesCredential Phishing
Tactics and techniquesHTML smuggling, Scripting

Event coverage

Rule body MQL

type.inbound
and any(attachments,
        (
          .file_extension in~ ("html", "htm", "shtml", "dhtml")
          or .file_extension in~ $file_extensions_common_archives
          or .file_type == "html"
        )
        and any(file.explode(.),
                // suspicious strings found in javascript
                (
                  length(filter(.scan.javascript.strings,
                                strings.ilike(., "*password*", )
                         )
                  ) >= 2
                  and 2 of (
                    any(.scan.javascript.strings,
                        strings.ilike(., "*incorrect*")
                    ),
                    any(.scan.javascript.strings, strings.ilike(., "*invalid*")),
                    any(.scan.javascript.strings, strings.ilike(., "*login*")),
                    any(.scan.javascript.strings, regex.icontains(., "sign.in")),
                  )
                )
                or (
                  // suspicious strings found outside of javascript, but binexplode'd file still of HTML type
                  length(filter(.scan.strings.strings,
                                strings.ilike(., "*password*", )
                         )
                  ) >= 2
                  and 2 of (
                    any(.scan.strings.strings, strings.ilike(., "*incorrect*")),
                    any(.scan.strings.strings, strings.ilike(., "*invalid*")),
                    any(.scan.strings.strings, strings.ilike(., "*login*")),
                    any(.scan.strings.strings, strings.ilike(., "*<script>*")),
                    any(.scan.strings.strings, regex.icontains(., "sign.in")),
                    any(.scan.strings.strings,
                        regex.icontains(.,
                                        '<title>.[^<]+(Payment|Invoice|Statement|Login|Microsoft|Email|Excel)'
                        )
                    )
                  )
                )
                or 
                // Known phishing obfuscation
                2 of (
                  // Enter password
                  any(.scan.strings.strings,
                      strings.ilike(.,
                                    "*&#69;&#110;&#116;&#101;&#114;&#32;&#112;&#97;&#115;&#115;&#119;&#111;&#114;&#100*"
                      )
                  ),

                  // Forgotten my password
                  any(.scan.strings.strings,
                      strings.ilike(.,
                                    "*&#70;&#111;&#114;&#103;&#111;&#116;&#116;&#101;&#110;&#32;&#109;&#121;&#32;&#112;&#97;&#115;&#115;&#119;&#111;&#114;&#100*"
                      )
                  ),

                  // Sign in
                  any(.scan.strings.strings,
                      strings.ilike(.,
                                    "*&#83;&#105;&#103;&#110;&#32;&#105;&#110*"
                      )
                  )
                )
        )
)
and (
  (
    // exclude internal mailers where there is no SPF configured.
    // if the sender's root domain is an org domain, we
    // ensure there's an SPF pass
    // we use root_domain because it's typically subdomains that are misconfigured
    sender.email.domain.root_domain in $org_domains
    and headers.auth_summary.spf.pass
  )
  or sender.email.domain.root_domain not in $org_domains
)

// negate highly trusted sender domains unless they fail DMARC authentication
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 (
  (
    not profile.by_sender().solicited
    and profile.by_sender().prevalence in ("new", "outlier")
  )
  or (
    profile.by_sender().any_messages_malicious_or_spam
    and not profile.by_sender().any_messages_benign
  )
)

Detection logic

Scope: inbound message.

Recursively scans files and archives to detect indicators of login portals implemented in HTML files. This is a known credential theft technique used by threat actors.

  1. inbound message
  2. any of attachments where all hold:
    • any of:
      • .file_extension in ('html', 'htm', 'shtml', 'dhtml')
      • .file_extension in $file_extensions_common_archives
      • .file_type is 'html'
    • any of file.explode(.) where any holds:
      • all of:
        • length(filter(.scan.javascript.strings, strings.ilike(., '*password*'))) ≥ 2
        • at least 2 of:
          • any of .scan.javascript.strings where:
            • . matches '*incorrect*'
          • any of .scan.javascript.strings where:
            • . matches '*invalid*'
          • any of .scan.javascript.strings where:
            • . matches '*login*'
          • any of .scan.javascript.strings where:
            • . matches 'sign.in'
      • all of:
        • length(filter(.scan.strings.strings, strings.ilike(., '*password*'))) ≥ 2
        • at least 2 of:
          • any of .scan.strings.strings where:
            • . matches '*incorrect*'
          • any of .scan.strings.strings where:
            • . matches '*invalid*'
          • any of .scan.strings.strings where:
            • . matches '*login*'
          • any of .scan.strings.strings where:
            • . matches '*<script>*'
          • any of .scan.strings.strings where:
            • . matches 'sign.in'
          • any of .scan.strings.strings where:
            • . matches '<title>.[^<]+(Payment|Invoice|Statement|Login|Microsoft|Email|Excel)'
      • at least 2 of:
        • any of .scan.strings.strings where:
          • . matches '*&#69;&#110;&#116;&#101;&#114;&#32;&#112;&#97;&#115;&#115;&#119;&#111;&#114;&#100*'
        • any of .scan.strings.strings where:
          • . matches '*&#70;&#111;&#114;&#103;&#111;&#116;&#116;&#101;&#110;&#32;&#109;&#121;&#32;&#112;&#97;&#115;&#115;&#119;&#111;&#114;&#100*'
        • any of .scan.strings.strings where:
          • . matches '*&#83;&#105;&#103;&#110;&#32;&#105;&#110*'
  3. any of:
    • all of:
      • sender.email.domain.root_domain in $org_domains
      • headers.auth_summary.spf.pass
    • sender.email.domain.root_domain not in $org_domains
  4. any of:
    • all of:
      • sender.email.domain.root_domain in $high_trust_sender_root_domains
      • not:
        • headers.auth_summary.dmarc.pass
    • sender.email.domain.root_domain not in $high_trust_sender_root_domains
  5. any of:
    • all of:
      • not:
        • profile.by_sender().solicited
      • profile.by_sender().prevalence in ('new', 'outlier')
    • all of:
      • profile.by_sender().any_messages_malicious_or_spam
      • not:
        • profile.by_sender().any_messages_benign

Inspects: attachments[].file_extension, attachments[].file_type, headers.auth_summary.dmarc.pass, headers.auth_summary.spf.pass, sender.email.domain.root_domain, type.inbound. Sensors: file.explode, profile.by_sender, regex.icontains, strings.ilike. Reference lists: $file_extensions_common_archives, $high_trust_sender_root_domains, $org_domains.

Indicators matched (15)

FieldMatchValue
attachments[].file_extensionmemberhtml
attachments[].file_extensionmemberhtm
attachments[].file_extensionmembershtml
attachments[].file_extensionmemberdhtml
attachments[].file_typeequalshtml
strings.ilikesubstring*password*
strings.ilikesubstring*incorrect*
strings.ilikesubstring*invalid*
strings.ilikesubstring*login*
regex.icontainsregexsign.in
strings.ilikesubstring*<script>*
regex.icontainsregex<title>.[^<]+(Payment|Invoice|Statement|Login|Microsoft|Email|Excel)
3 more
strings.ilikesubstring*&#69;&#110;&#116;&#101;&#114;&#32;&#112;&#97;&#115;&#115;&#119;&#111;&#114;&#100*
strings.ilikesubstring*&#70;&#111;&#114;&#103;&#111;&#116;&#116;&#101;&#110;&#32;&#109;&#121;&#32;&#112;&#97;&#115;&#115;&#119;&#111;&#114;&#100*
strings.ilikesubstring*&#83;&#105;&#103;&#110;&#32;&#105;&#110*