Attachment: QR code with suspicious URL patterns in EML file

Detects EML attachments containing QR codes that link to URLs with suspicious patterns, including specific alphanumeric combinations in subdomains and paths, or special characters followed by encoded terminators. These patterns are commonly used to evade detection in credential theft attacks.

Sublime rule (View on GitHub)

  1name: "Attachment: QR code with suspicious URL patterns in EML file"
  2description: "Detects EML attachments containing QR codes that link to URLs with suspicious patterns, including specific alphanumeric combinations in subdomains and paths, or special characters followed by encoded terminators. These patterns are commonly used to evade detection in credential theft attacks."
  3type: "rule"
  4severity: "high"
  5source: |
  6  type.inbound
  7  and length(recipients.to) == 1
  8  and recipients.to[0].email.domain.valid
  9  and any(attachments,
 10          // Email Attachments
 11          any(file.parse_eml(.).attachments,
 12              (
 13                // looks for office docs in the attached eml
 14                .file_extension in $file_extensions_macros
 15                and any(file.explode(.),
 16                        .scan.qr.type == "url"
 17                        // QR code URL contains recipient's email (targeting indicator)
 18                        and any(recipients.to,
 19                                .email.domain.valid
 20                                and (
 21                                  // Plaintext email address in URL
 22                                  strings.icontains(..scan.qr.url.url,
 23                                                    .email.email
 24                                  )
 25                                  // OR base64 encoded email address
 26                                  or any(strings.scan_base64(..scan.qr.url.url,
 27                                                             format="url",
 28                                                             ignore_padding=true
 29                                         ),
 30                                         strings.icontains(., ..email.email)
 31                                  )
 32                                )
 33                        )
 34                        // a single path
 35                        and strings.count(.scan.qr.url.path, '/') == 2
 36                        and (
 37                          (
 38                            (
 39                              strings.contains(.scan.qr.url.path, '/$')
 40                              or strings.contains(.scan.qr.url.path, '/*')
 41                              or strings.contains(.scan.qr.url.path, '/#')
 42                            )
 43                            // subdomain should contain num{3}alpha or alphanum{3}
 44                            and regex.icontains(.scan.qr.url.domain.subdomain,
 45                                                '^(?:[a-z]+[0-9]{3}|[0-9]{3}[a-z]+)(?:$|\.)'
 46                            )
 47                            // url path should contain num{3}alpha or alphanum{3}
 48                            and regex.icontains(.scan.qr.url.path,
 49                                                '\/(?:[a-z]+[0-9]{3}|[0-9]{3}[a-z]+)\/'
 50                            )
 51                          )
 52                          or (
 53                            // special char in the path
 54                            (
 55                              strings.contains(.scan.qr.url.path, '!')
 56                              or strings.contains(.scan.qr.url.path, '@')
 57                            )
 58                            and (
 59                              strings.contains(.scan.qr.url.path, '/$')
 60                              or strings.contains(.scan.qr.url.path, '/*')
 61                              or strings.contains(.scan.qr.url.path, '/#')
 62                              // hex dollar sign
 63                              or strings.icontains(.scan.qr.url.path, '%24')
 64                              // hex star
 65                              or strings.icontains(.scan.qr.url.path, '%2A')
 66                              // hex pound
 67                              or strings.icontains(.scan.qr.url.path, '%23')
 68                            )
 69                            // ensure expected ordering
 70                            and regex.icontains(.scan.qr.url.url,
 71                                                '[!@].*(?:[$*]|%2[A43])'
 72                            )
 73                          )
 74                        )
 75                )
 76              )
 77              or (
 78                // looks for pdfs and images in the attached eml
 79                //
 80                // This rule makes use of a beta feature and is subject to change without notice
 81                // using the beta feature in custom rules is not suggested until it has been formally released
 82                //
 83                any(beta.scan_qr(.).items,
 84                    .type is not null
 85                    // a single path
 86                    and strings.count(.url.path, '/') == 2
 87                    and (
 88                      (
 89                        (
 90                          strings.contains(.url.path, '/$')
 91                          or strings.contains(.url.path, '/*')
 92                          or strings.contains(.url.path, '/#')
 93                        )
 94                        // subdomain should contain num{3}alpha or alphanum{3}
 95                        and regex.icontains(.url.domain.subdomain,
 96                                            '^(?:[a-z]+[0-9]{3}|[0-9]{3}[a-z]+)(?:$|\.)'
 97                        )
 98                        // url path should contain num{3}alpha or alphanum{3}
 99                        and regex.icontains(.url.path,
100                                            '\/(?:[a-z]+[0-9]{3}|[0-9]{3}[a-z]+)\/'
101                        )
102                      )
103                      or (
104                        // special char in the path
105                        (
106                          strings.contains(.url.path, '!')
107                          or strings.contains(.url.path, '@')
108                        )
109                        and (
110                          strings.contains(.url.path, '/$')
111                          or strings.contains(.url.path, '/*')
112                          or strings.contains(.url.path, '/#')
113                          // hex dollar sign
114                          or strings.icontains(.url.path, '%24')
115                          // hex star
116                          or strings.icontains(.url.path, '%2A')
117                          // hex pound
118                          or strings.icontains(.url.path, '%23')
119                        )
120                        // ensure expected ordering
121                        and regex.icontains(.url.url, '[!@].*(?:[$*]|%2[A43])')
122                      )
123                    )
124                )
125              )
126          )
127  )  
128
129attack_types:
130  - "Credential Phishing"
131tactics_and_techniques:
132  - "QR code"
133  - "Evasion"
134  - "Social engineering"
135detection_methods:
136  - "Archive analysis"
137  - "File analysis"
138  - "QR code analysis"
139  - "URL analysis"
140id: "2289acd5-c3dc-58c2-81f6-4e1b0cc30064"
to-top