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            .file_extension in $file_extensions_macros
 13            and any(file.explode(.),
 14                    .scan.qr.type == "url"
 15                    // QR code URL contains recipient's email (targeting indicator)
 16                    and any(recipients.to,
 17                            .email.domain.valid
 18                            and (
 19                              // Plaintext email address in URL
 20                              strings.icontains(..scan.qr.url.url, .email.email)
 21                              // OR base64 encoded email address
 22                              or any(strings.scan_base64(..scan.qr.url.url,
 23                                                         format="url",
 24                                                         ignore_padding=true
 25                                     ),
 26                                     strings.icontains(., ..email.email)
 27                              )
 28                            )
 29                    )
 30                    // a single path
 31                    and strings.count(.scan.qr.url.path, '/') == 2
 32                    and (
 33                      (
 34                        (
 35                          strings.contains(.scan.qr.url.path, '/$')
 36                          or strings.contains(.scan.qr.url.path, '/*')
 37                          or strings.contains(.scan.qr.url.path, '/#')
 38                        )
 39                        // subdomain should contain num{3}alpha or alphanum{3}
 40                        and regex.icontains(.scan.qr.url.domain.subdomain,
 41                                            '^(?:[a-z]+[0-9]{3}|[0-9]{3}[a-z]+)(?:$|\.)'
 42                        )
 43                        // url path should contain num{3}alpha or alphanum{3}
 44                        and regex.icontains(.scan.qr.url.path,
 45                                            '\/(?:[a-z]+[0-9]{3}|[0-9]{3}[a-z]+)\/'
 46                        )
 47                      )
 48                      or (
 49                        // special char in the path
 50                        (
 51                          strings.contains(.scan.qr.url.path, '!')
 52                          or strings.contains(.scan.qr.url.path, '@')
 53                        )
 54                        and (
 55                          strings.contains(.scan.qr.url.path, '/$')
 56                          or strings.contains(.scan.qr.url.path, '/*')
 57                          or strings.contains(.scan.qr.url.path, '/#')
 58                          // hex dollar sign
 59                          or strings.icontains(.scan.qr.url.path, '%24')
 60                          // hex star
 61                          or strings.icontains(.scan.qr.url.path, '%2A')
 62                          // hex pound
 63                          or strings.icontains(.scan.qr.url.path, '%23')
 64                        )
 65                        // ensure expected ordering
 66                        and regex.icontains(.scan.qr.url.url,
 67                                            '[!@].*(?:[$*]|%2[A43])'
 68                        )
 69                      )
 70                    )
 71            )
 72          )
 73          or (
 74            // pdf or images
 75            (
 76              .file_type == "pdf" or .file_type in $file_types_images
 77            )
 78            //
 79            // This rule makes use of a beta feature and is subject to change without notice
 80            // using the beta feature in custom rules is not suggested until it has been formally released
 81            //
 82            and any(beta.scan_qr(.).items,
 83                    .type is not null
 84                    // a single path
 85                    and strings.count(.url.path, '/') == 2
 86                    and (
 87                      (
 88                        (
 89                          strings.contains(.url.path, '/$')
 90                          or strings.contains(.url.path, '/*')
 91                          or strings.contains(.url.path, '/#')
 92                        )
 93                        // subdomain should contain num{3}alpha or alphanum{3}
 94                        and regex.icontains(.url.domain.subdomain,
 95                                            '^(?:[a-z]+[0-9]{3}|[0-9]{3}[a-z]+)(?:$|\.)'
 96                        )
 97                        // url path should contain num{3}alpha or alphanum{3}
 98                        and regex.icontains(.url.path,
 99                                            '\/(?:[a-z]+[0-9]{3}|[0-9]{3}[a-z]+)\/'
100                        )
101                      )
102                      or (
103                        // special char in the path
104                        (
105                          strings.contains(.url.path, '!')
106                          or strings.contains(.url.path, '@')
107                        )
108                        and (
109                          strings.contains(.url.path, '/$')
110                          or strings.contains(.url.path, '/*')
111                          or strings.contains(.url.path, '/#')
112                          // hex dollar sign
113                          or strings.icontains(.url.path, '%24')
114                          // hex star
115                          or strings.icontains(.url.path, '%2A')
116                          // hex pound
117                          or strings.icontains(.url.path, '%23')
118                        )
119                        // ensure expected ordering
120                        and regex.icontains(.url.url, '[!@].*(?:[$*]|%2[A43])')
121                      )
122                    )
123            )
124          )
125  )  
126
127attack_types:
128  - "Credential Phishing"
129tactics_and_techniques:
130  - "QR code"
131  - "Social engineering"
132  - "Evasion"
133detection_methods:
134  - "File analysis"
135  - "QR code analysis"
136  - "URL analysis"
137  - "Computer Vision"
138id: "fc9e1c09-4691-5cde-94d1-ccd953f1b63a"
to-top