Attachment: QR code with recipient targeting and special characters

Detects messages with QR code in attachments containing special characters in the path that include the recipient's email address in either the URL path or fragment, potentially encoded in base64. The URLs have a simple path structure and may end with suspicious patterns.

Sublime rule (View on GitHub)

  1name: "Attachment: QR code with recipient targeting and special characters"
  2description: "Detects messages with QR code in attachments containing special characters in the path that include the recipient's email address in either the URL path or fragment, potentially encoded in base64. The URLs have a simple path structure and may end with suspicious patterns."
  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          (
 11            // Office documents
 12            (
 13              .file_extension in $file_extensions_macros
 14              // Email Attachments
 15              or .file_extension == "eml"
 16            )
 17            and any(file.explode(.),
 18                    .scan.qr.type == "url"
 19                    // QR code URL contains recipient's email (targeting indicator)
 20                    and any(recipients.to,
 21                            .email.domain.valid
 22                            and (
 23                              // Plaintext email address in URL
 24                              strings.icontains(..scan.qr.url.url, .email.email)
 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            // pdf or images
 79            (
 80              .file_type == "pdf" or .file_type in $file_types_images
 81            )
 82            //
 83            // This rule makes use of a beta feature and is subject to change without notice
 84            // using the beta feature in custom rules is not suggested until it has been formally released
 85            //
 86            and any(beta.scan_qr(.).items,
 87                    .type is not null
 88                    // a single path
 89                    and strings.count(.url.path, '/') == 2
 90                    and (
 91                      (
 92                        (
 93                          strings.contains(.url.path, '/$')
 94                          or strings.contains(.url.path, '/*')
 95                          or strings.contains(.url.path, '/#')
 96                        )
 97                        // subdomain should contain num{3}alpha or alphanum{3}
 98                        and regex.icontains(.url.domain.subdomain,
 99                                            '^(?:[a-z]+[0-9]{3}|[0-9]{3}[a-z]+)$'
100                        )
101                        // url path should contain num{3}alpha or alphanum{3}
102                        and regex.icontains(.url.path,
103                                            '\/(?:[a-z]+[0-9]{3}|[0-9]{3}[a-z]+)\/'
104                        )
105                      )
106                      or (
107                        // special char in the path
108                        (
109                          strings.contains(.url.path, '!')
110                          or strings.contains(.url.path, '@')
111                        )
112                        and (
113                          strings.contains(.url.path, '/$')
114                          or strings.contains(.url.path, '/*')
115                          or strings.contains(.url.path, '/#')
116                          // hex dollar sign
117                          or strings.icontains(.url.path, '%24')
118                          // hex star
119                          or strings.icontains(.url.path, '%2A')
120                          // hex pound
121                          or strings.icontains(.url.path, '%23')
122                        )
123                        // ensure expected ordering
124                        and regex.icontains(.url.url, '[!@].*(?:[$*]|%2[A43])')
125                      )
126                    )
127            )
128          )
129  )  
130
131attack_types:
132  - "Credential Phishing"
133tactics_and_techniques:
134  - "QR code"
135  - "Social engineering"
136  - "Evasion"
137detection_methods:
138  - "File analysis"
139  - "QR code analysis"
140  - "URL analysis"
141  - "Computer Vision"
142id: "fc9e1c09-4691-5cde-94d1-ccd953f1b63a"
to-top