Detection rules › Sublime MQL

Link: Multiple HTTP protocols in single URL

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

Detects messages containing links with 5 or more HTTP protocol declarations within a single URL, indicating potential URL manipulation or obfuscation techniques.

Threat classification

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

CategoryValues
Attack typesCredential Phishing, Malware/Ransomware
Tactics and techniquesEvasion

Event coverage

Rule body MQL

type.inbound
and 0 < length(body.current_thread.links) < 10
and any(body.current_thread.links,
        .visible
        // no ability to loop query_params_decoded, so create the non-decoded equivlent 
        and not strings.icontains(.href_url.url, 'unsubscribe')
        and not strings.icontains(.display_text, 'unsubscribe')
        and any(regex.extract(.href_url.query_params,
                              '[?&](?P<name>[^=&]+)(?:=(?P<value>[^&]*))?'
                ),

                // filter down to query params that start with a url
                regex.contains(.named_groups['value'],
                               '^(?:https?(?:%253[Aa]|%3[Aa]|:))?(?:%252[Ff]|%2[Ff]|/)(?:%252[Ff]|%2[Ff]|/)'
                )
                // the number of unique domains in the URL query param is greater or equal to three
                and length(distinct(map(filter(regex.iextract(.named_groups['value'],
                                                              '(?:https?(?:%253[Aa]|%3[Aa]|:))?(?:%252[Ff]|%2[Ff]|/)(?:%252[Ff]|%2[Ff]|/)(?P<domain>[^/\s&%]+)'
                                               ),
                                               // sometimes URLs have // and produce entries we want to skip
                                               // so ensure it's a valid domain first
                                               strings.parse_domain(.named_groups['domain']
                                               ).error is null
                                               and strings.parse_domain(.named_groups['domain']
                                               ).valid
                                               // remove domain that are the same as the sender root domain
                                               and strings.parse_domain(.named_groups['domain']
                                               ).root_domain != sender.email.domain.root_domain
                                        ),
                                        // return just the root domian
                                        strings.parse_domain(.named_groups['domain']
                                        ).root_domain
                                    ),
                                    .
                           )
                ) >= 3

                // there are three or more total URLs in that query param
                and regex.count(.named_groups['value'],
                                '(?:https?(?:%253[Aa]|%3[Aa]|:))?(?:%252[Ff]|%2[Ff]|/)(?:%252[Ff]|%2[Ff]|/)'
                ) >= 3
        )
)

Detection logic

Scope: inbound message.

Detects messages containing links with 5 or more HTTP protocol declarations within a single URL, indicating potential URL manipulation or obfuscation techniques.

  1. inbound message
  2. all of:
    • length(body.current_thread.links) > 0
    • length(body.current_thread.links) < 10
  3. any of body.current_thread.links where all hold:
    • .visible
    • not:
      • .href_url.url contains 'unsubscribe'
    • not:
      • .display_text contains 'unsubscribe'
    • any of regex.extract(.href_url.query_params) where all hold:
      • .named_groups['value'] matches '^(?:https?(?:%253[Aa]|%3[Aa]|:))?(?:%252[Ff]|%2[Ff]|/)(?:%252[Ff]|%2[Ff]|/)'
      • length(distinct(map(filter(regex.iextract(.named_groups['value'], '(?:https?(?:%253[Aa]|%3[Aa]|:))?(?:%252[Ff]|%2[Ff]|/)(?:%252[Ff]|%2[Ff]|/)(?P<domain>[^/\\s&%]+)'), strings.parse_domain(.named_groups['domain']).error is null and strings.parse_domain(.named_groups['domain']).valid and strings.parse_domain(.named_groups['domain']).root_domain != sender.email.domain.root_domain), strings.parse_domain(.named_groups['domain']).root_domain), .)) ≥ 3
      • regex.count(.named_groups['value'], '(?:https?(?:%253[Aa]|%3[Aa]|:))?(?:%252[Ff]|%2[Ff]|/)(?:%252[Ff]|%2[Ff]|/)') ≥ 3

Inspects: body.current_thread.links, body.current_thread.links[].display_text, body.current_thread.links[].href_url.query_params, body.current_thread.links[].href_url.url, body.current_thread.links[].visible, sender.email.domain.root_domain, type.inbound. Sensors: regex.contains, regex.count, regex.extract, regex.iextract, strings.icontains, strings.parse_domain.

Indicators matched (5)

FieldMatchValue
strings.icontainssubstringunsubscribe
regex.extractregex[?&](?P<name>[^=&]+)(?:=(?P<value>[^&]*))?
regex.containsregex^(?:https?(?:%253[Aa]|%3[Aa]|:))?(?:%252[Ff]|%2[Ff]|/)(?:%252[Ff]|%2[Ff]|/)
regex.iextractregex(?:https?(?:%253[Aa]|%3[Aa]|:))?(?:%252[Ff]|%2[Ff]|/)(?:%252[Ff]|%2[Ff]|/)(?P<domain>[^/\s&%]+)
regex.countregex(?:https?(?:%253[Aa]|%3[Aa]|:))?(?:%252[Ff]|%2[Ff]|/)(?:%252[Ff]|%2[Ff]|/)