Detection rules › Sublime MQL

Attachment: QR code with credential phishing indicators

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

Detects messages with between 1-3 attachments containing a QR code with suspicious credential theft indicators, such as: LinkAnalysis credential phishing conclusion, decoded QR code url traverses suspicious infrastructure, the final destination is in URLhaus, decoded URL downloads a zip or executable, leverages URL shorteners, known QR abused openredirects, and more.

Threat classification

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

CategoryValues
Attack typesCredential Phishing
Tactics and techniquesQR code, Social engineering

Event coverage

Rule body MQL

type.inbound
and (
  1 <= length(attachments) < 3
  or (
    // if there are more than three attachments
    3 <= length(attachments) < 20
    // there are only pngs and pdf/docx
    and length(distinct(map(attachments, .file_extension))) == 2
    and all(distinct(map(attachments, .file_extension)),
            . in ('png', 'pdf', 'docx')
    )
    and (
      // multiple attachments mention common brands or other common common filenames
      (
        length(filter(attachments,
                      strings.icontains(.file_name, 'adobe')
                      or strings.icontains(.file_name, 'office')
                      or strings.icontains(.file_name, 'appstore')
                      or strings.icontains(.file_name, 'google')
                      or strings.icontains(.file_name, 'padlock')
                      or regex.icontains(.file_name, '\bdoc\b')
               )
        ) > 3
      )
      // the attachment name contains the SLD of a recipient
      or any(filter(attachments, .file_extension in ('pdf', 'docx')),
             any(filter(recipients.to, .email.domain.valid),
                 strings.icontains(..file_name, .email.domain.sld)
             )
      )
    )
  )
)

// Inspects image attachments for QR codes
and any(attachments,
        (
          .file_type in $file_types_images
          or .file_type == "pdf"
          or .file_extension in $file_extensions_macros
        )
        and (
          any(file.explode(.),
              .scan.qr.type == "url"
              and not .scan.qr.url.domain.domain == "geico.app.link"
              and (
                // pass the QR URL to LinkAnalysis
                any([ml.link_analysis(.scan.qr.url)],
                    .credphish.disposition == "phishing"

                    // any routing traverses via $suspicious_tld list
                    or any(.redirect_history, .domain.tld in $suspicious_tlds)

                    // effective destination in $suspicious_tld list
                    or .effective_url.domain.tld in $suspicious_tlds

                    // or the effective destination domain is in $abuse_ch_urlhaus_domains_trusted_reporters
                    or .effective_url.domain.root_domain in $abuse_ch_urlhaus_domains_trusted_reporters

                    // or any files downloaded are zips or executables
                    or any(.files_downloaded,
                           .file_extension in $file_extensions_common_archives
                           or .file_extension in $file_extensions_executables
                    )
                )
                or (

                  // or the QR code's root domain is a url_shortener
                  .scan.qr.url.domain.root_domain in $url_shorteners
                  or .scan.qr.url.domain.root_domain in $social_landing_hosts
                  and (
                    not (
                      any(ml.nlu_classifier(body.current_thread.text).intents,
                          .name == "benign"
                      )
                      or any(ml.nlu_classifier(body.current_thread.text).entities,
                             .name == "disclaimer"
                      )
                    )
                    or not any(attachments,
                               any(file.explode(.),
                                   any(ml.nlu_classifier(.scan.ocr.raw).intents,
                                       .name == "benign"
                                   )
                               )
                    )
                    // the QR code contains the email address of a recipient
                    or (
                      any(filter(recipients.to, .email.domain.valid),
                          strings.icontains(..scan.qr.url.url, .email.email)
                      )
                    )
                  )

                  // exclude google maps
                  and not strings.starts_with(.scan.qr.url.url,
                                              'https://goo.gl/maps'
                  )
                  and not strings.starts_with(.scan.qr.url.url,
                                              'https://maps.app.goo.gl'
                  )
                )

                // the QR code url is a bing open redirect
                or (
                  .scan.qr.url.domain.root_domain == 'bing.com'
                  and .scan.qr.url.path =~ '/ck/a'
                )
                // QR code contains non ascii chars
                or regex.contains(.scan.qr.url.url, '[^\x00-\x7F]')
                or (

                  // usap-dc open redirect
                  .scan.qr.url.domain.root_domain == "usap-dc.org"
                  and .scan.qr.url.path =~ "/tracker"
                  and strings.starts_with(.scan.qr.url.query_params,
                                          "type=dataset&url=http"
                  )
                  // the QR code contains the email address of a recipient
                  // allowing for base64 encoded variants
                  or (
                    any(filter(recipients.to, .email.domain.valid),
                        strings.icontains(..scan.qr.url.url, .email.email)
                        or any(beta.scan_base64(..scan.qr.url.url,
                                                ignore_padding=true
                               ),
                               strings.icontains(., ..email.email)
                        )
                        or any(beta.scan_base64(..scan.qr.url.fragment,
                                                ignore_padding=true
                               ),
                               strings.icontains(., ..email.email)
                        )
                    )
                  )
                )
              )
          )
        )
)
and (
  (
    profile.by_sender_email().prevalence in ("new", "outlier")
    and not profile.by_sender_email().solicited
  )
  or (
    profile.by_sender_email().any_messages_malicious_or_spam
    and not profile.by_sender_email().any_messages_benign
  )
  or (
    sender.email.domain.domain in $org_domains
    and not coalesce(headers.auth_summary.dmarc.pass, false)
  )
)

// 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
)

Detection logic

Scope: inbound message.

Detects messages with between 1-3 attachments containing a QR code with suspicious credential theft indicators, such as: LinkAnalysis credential phishing conclusion, decoded QR code url traverses suspicious infrastructure, the final destination is in URLhaus, decoded URL downloads a zip or executable, leverages URL shorteners, known QR abused openredirects, and more.

  1. inbound message
  2. any of:
    • all of:
      • length(attachments) ≥ 1
      • length(attachments) < 3
    • all of:
      • all of:
        • length(attachments) ≥ 3
        • length(attachments) < 20
      • length(distinct(map(attachments, .file_extension))) is 2
      • all of distinct(...) where:
        • . in ('png', 'pdf', 'docx')
      • any of:
        • length(filter(attachments, strings.icontains(.file_name, 'adobe') or strings.icontains(.file_name, 'office') or strings.icontains(.file_name, 'appstore') or strings.icontains(.file_name, 'google') or strings.icontains(.file_name, 'padlock') or regex.icontains(.file_name, '\\bdoc\\b'))) > 3
        • any of filter(attachments) where:
          • any of filter(recipients.to) where:
            • strings.icontains(.file_name)
  3. any of attachments where all hold:
    • any of:
      • .file_type in $file_types_images
      • .file_type is 'pdf'
      • .file_extension in $file_extensions_macros
    • any of file.explode(.) where all hold:
      • .scan.qr.type is 'url'
      • not:
        • .scan.qr.url.domain.domain is 'geico.app.link'
      • any of:
        • any of [ml.link_analysis(.scan.qr.url)] where any holds:
          • .credphish.disposition is 'phishing'
          • any of .redirect_history where:
            • .domain.tld in $suspicious_tlds
          • .effective_url.domain.tld in $suspicious_tlds
          • .effective_url.domain.root_domain in $abuse_ch_urlhaus_domains_trusted_reporters
          • any of .files_downloaded where any holds:
            • .file_extension in $file_extensions_common_archives
            • .file_extension in $file_extensions_executables
        • any of:
          • .scan.qr.url.domain.root_domain in $url_shorteners
          • all of:
            • .scan.qr.url.domain.root_domain in $social_landing_hosts
            • any of:
              • none of:
                • any of ml.nlu_classifier(body.current_thread.text).intents where:
                  • .name is 'benign'
                • any of ml.nlu_classifier(body.current_thread.text).entities where:
                  • .name is 'disclaimer'
              • not:
                • any of attachments where:
                  • any of file.explode(.) where:
                    • any of ml.nlu_classifier(.scan.ocr.raw).intents where:
                      • .name is 'benign'
              • any of filter(recipients.to) where:
                • strings.icontains(.scan.qr.url.url)
            • not:
              • .scan.qr.url.url starts with 'https://goo.gl/maps'
            • not:
              • .scan.qr.url.url starts with 'https://maps.app.goo.gl'
        • all of:
          • .scan.qr.url.domain.root_domain is 'bing.com'
          • .scan.qr.url.path is '/ck/a'
        • .scan.qr.url.url matches '[^\\x00-\\x7F]'
        • any of:
          • all of:
            • .scan.qr.url.domain.root_domain is 'usap-dc.org'
            • .scan.qr.url.path is '/tracker'
            • .scan.qr.url.query_params starts with 'type=dataset&url=http'
          • any of filter(recipients.to) where any holds:
            • strings.icontains(.scan.qr.url.url)
            • any of beta.scan_base64(.scan.qr.url.url) where:
              • strings.icontains(.)
            • any of beta.scan_base64(.scan.qr.url.fragment) where:
              • strings.icontains(.)
  4. any of:
    • all of:
      • profile.by_sender_email().prevalence in ('new', 'outlier')
      • not:
        • profile.by_sender_email().solicited
    • all of:
      • profile.by_sender_email().any_messages_malicious_or_spam
      • not:
        • profile.by_sender_email().any_messages_benign
    • all of:
      • sender.email.domain.domain in $org_domains
      • not:
        • coalesce(headers.auth_summary.dmarc.pass)
  5. 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

Inspects: attachments[].file_extension, attachments[].file_name, attachments[].file_type, body.current_thread.text, headers.auth_summary.dmarc.pass, recipients.to, recipients.to[].email.domain.valid, sender.email.domain.domain, sender.email.domain.root_domain, type.inbound. Sensors: beta.scan_base64, file.explode, ml.link_analysis, ml.nlu_classifier, profile.by_sender_email, regex.contains, regex.icontains, strings.icontains, strings.starts_with. Reference lists: $abuse_ch_urlhaus_domains_trusted_reporters, $file_extensions_common_archives, $file_extensions_executables, $file_extensions_macros, $file_types_images, $high_trust_sender_root_domains, $org_domains, $social_landing_hosts, $suspicious_tlds, $url_shorteners.

Indicators matched (26)

FieldMatchValue
distinct(...)[]memberpng
distinct(...)[]memberpdf
distinct(...)[]memberdocx
strings.icontainssubstringadobe
strings.icontainssubstringoffice
strings.icontainssubstringappstore
strings.icontainssubstringgoogle
strings.icontainssubstringpadlock
regex.icontainsregex\bdoc\b
attachments[].file_extensionmemberpdf
attachments[].file_extensionmemberdocx
attachments[].file_typeequalspdf
14 more
file.explode(attachments[])[].scan.qr.typeequalsurl
file.explode(attachments[])[].scan.qr.url.domain.domainequalsgeico.app.link
[ml.link_analysis(file.explode(attachments[])[].scan.qr.url)][].credphish.dispositionequalsphishing
ml.nlu_classifier(body.current_thread.text).intents[].nameequalsbenign
ml.nlu_classifier(body.current_thread.text).entities[].nameequalsdisclaimer
ml.nlu_classifier(file.explode(attachments[])[].scan.ocr.raw).intents[].nameequalsbenign
strings.starts_withprefixhttps://goo.gl/maps
strings.starts_withprefixhttps://maps.app.goo.gl
file.explode(attachments[])[].scan.qr.url.domain.root_domainequalsbing.com
file.explode(attachments[])[].scan.qr.url.pathequals/ck/a
regex.containsregex[^\x00-\x7F]
file.explode(attachments[])[].scan.qr.url.domain.root_domainequalsusap-dc.org
file.explode(attachments[])[].scan.qr.url.pathequals/tracker
strings.starts_withprefixtype=dataset&url=http