QR Code with suspicious indicators

This rule flags messages with QR codes in attachments when there are three or fewer attachments. If no attachments are present, the rule captures a screenshot of the message for analysis. Additional triggers include: sender's name containing the recipient's SLD, recipient's email mentioned in the body, an empty message body, a suspicious subject, or undisclosed recipients.

Sublime rule (View on GitHub)

  1name: "QR Code with suspicious indicators"
  2description: |
  3    This rule flags messages with QR codes in attachments when there are three or fewer attachments. If no attachments are present, the rule captures a screenshot of the message for analysis. Additional triggers include: sender's name containing the recipient's SLD, recipient's email mentioned in the body, an empty message body, a suspicious subject, or undisclosed recipients.
  4type: "rule"
  5severity: "high"
  6source: |
  7  type.inbound
  8  and (
  9    (
 10      length(attachments) <= 3
 11      or (
 12        any(attachments, length(ml.logo_detect(.).brands) > 0)
 13        and length(attachments) <= 10
 14      )
 15    )
 16    and (
 17      any(attachments,
 18          (
 19            .file_type in $file_types_images
 20            or .file_extension in $file_extensions_macros
 21            or .file_type == "pdf"
 22          )
 23          and (
 24            any(file.explode(.),
 25                (
 26                  .scan.qr.type is not null
 27                  and regex.contains(.scan.qr.data, '\.')
 28                  and not strings.starts_with(.scan.qr.data,
 29                                              "https://qr.skyqr.co.za/"
 30                  )
 31                  and not (
 32                    strings.icontains(.scan.qr.data, ',')
 33                    and .scan.qr.type == 'undefined'
 34                  )
 35                  // not a json string
 36                  and not (
 37                    strings.starts_with(.scan.qr.data, '{')
 38                    and strings.ends_with(.scan.qr.data, '}')
 39                  )
 40                  // exclude images taken with mobile cameras and screenshots from android
 41                  and not any(.scan.exiftool.fields,
 42                              .key == "Model"
 43                              or (
 44                                .key == "Software"
 45                                and strings.starts_with(.value, "Android")
 46                              )
 47                  )
 48                  // exclude images taken with mobile cameras and screenshots from Apple
 49                  and not any(.scan.exiftool.fields,
 50                              .key == "DeviceManufacturer"
 51                              and .value == "Apple Computer Inc."
 52                  )
 53                  // exclude images from WhatsApp (mobile)
 54                  and not regex.match(.file_name,
 55                                      'WhatsApp Image \d\d\d\d-\d\d-\d\d at.*.jpe?g'
 56                  )
 57                  and not (
 58                    (
 59                      .scan.exiftool.image_height > 3000
 60                      and .scan.exiftool.image_height is not null
 61                    )
 62                    or (
 63                      .scan.exiftool.image_width > 3000
 64                      and .scan.exiftool.image_width is not null
 65                    )
 66                  )
 67                  // exclude contact cards
 68                  and not strings.istarts_with(.scan.qr.data, "BEGIN:VCARD")
 69  
 70                  // negate QR codes to legit Servicio de Administración Tributaria (SAT) Gov links
 71                  and not (
 72                    .scan.qr.url.domain.root_domain is not null
 73                    and .scan.qr.url.domain.root_domain in ('sat.gob.mx')
 74                  )
 75                  and not (
 76                    .scan.qr.data is not null
 77                    and strings.icontains(.scan.qr.data, 'sat.gob.mx')
 78                  )
 79                )
 80                or (
 81                  regex.icontains(.scan.ocr.raw, 'scan|camera')
 82                  and regex.icontains(.scan.ocr.raw, '\bQR\b|Q\.R\.|barcode')
 83                )
 84            )
 85          )
 86      )
 87      or (
 88        length(attachments) == 0
 89        //
 90        // This rule makes use of a beta feature and is subject to change without notice
 91        // using the beta feature in custom rules is not suggested until it has been formally released
 92        //
 93        and (
 94          (
 95            beta.parse_exif(file.message_screenshot()).image_height < 2000
 96            and beta.parse_exif(file.message_screenshot()).image_width < 2000
 97          )
 98          // ignore image height/width if there is excessive whitespace padding
 99          or regex.contains(body.html.raw,
100                            '\"padding:0px 0px \d{3,4}px 0px'
101          )
102        )
103        and any(beta.scan_qr(file.message_screenshot()).items,
104                .type is not null
105                and regex.contains(.data, '\.')
106                // exclude contact cards
107                and not strings.istarts_with(.data, "BEGIN:VCARD")
108        )
109      )
110    )
111    and (
112      any(recipients.to,
113          strings.icontains(sender.display_name, .email.domain.sld)
114      )
115      or length(body.current_thread.text) is null
116      or (
117        body.current_thread.text == ""
118        and (
119          (
120            (length(headers.references) > 0 or headers.in_reply_to is null)
121            and not (
122              (
123                strings.istarts_with(subject.subject, "RE:")
124                or strings.istarts_with(subject.subject, "RES:")
125                or strings.istarts_with(subject.subject, "R:")
126                or strings.istarts_with(subject.subject, "ODG:")
127                or strings.istarts_with(subject.subject, "答复:")
128                or strings.istarts_with(subject.subject, "AW:")
129                or strings.istarts_with(subject.subject, "TR:")
130                or strings.istarts_with(subject.subject, "FWD:")
131                or regex.imatch(subject.subject,
132                                '(\[[^\]]+\]\s?){0,3}(re|fwd?|automat.*)\s?:.*'
133                )
134              )
135            )
136          )
137          or length(headers.references) == 0
138        )
139      )
140      or regex.contains(subject.subject,
141                        "(Authenticat(e|or|ion)|2fa|Multi.Factor|(qr|bar).code|action.require|alert|Att(n|ention):)"
142      )
143      or (any(recipients.to, strings.icontains(subject.subject, .display_name)))
144      or (
145        regex.icontains(subject.subject,
146                        "termination.*notice",
147                        "38417",
148                        ":completed",
149                        "[il1]{2}mit.*ma[il1]{2} ?bo?x",
150                        "[il][il][il]egai[ -]",
151                        "[li][li][li]ega[li] attempt",
152                        "[ng]-?[io]n .*block",
153                        "[ng]-?[io]n .*cancel",
154                        "[ng]-?[io]n .*deactiv",
155                        "[ng]-?[io]n .*disabl",
156                        "action.*required",
157                        "abandon.*package",
158                        "about.your.account",
159                        "acc(ou)?n?t (is )?on ho[li]d",
160                        "acc(ou)?n?t.*terminat",
161                        "acc(oun)?t.*[il1]{2}mitation",
162                        "access.*limitation",
163                        "account (will be )?block",
164                        "account.*de-?activat",
165                        "account.*locked",
166                        "account.*re-verification",
167                        "account.*security",
168                        "account.*suspension",
169                        "account.has.been",
170                        "account.has.expired",
171                        "account.will.be.blocked",
172                        "account v[il]o[li]at",
173                        "activity.*acc(oun)?t",
174                        "almost.full",
175                        "app[li]e.[il]d",
176                        "authenticate.*account",
177                        "been.*suspend",
178                        "bonus",
179                        "clos.*of.*account.*processed",
180                        "confirm.your.account",
181                        "courier.*able",
182                        "crediential.*notif",
183                        "deactivation.*in.*progress",
184                        "delivery.*attempt.*failed",
185                        "document.received",
186                        "documented.*shared.*with.*you",
187                        "dropbox.*document",
188                        "e-?ma[il1]+ .{010}suspen",
189                        "e-?ma[il1]{1} user",
190                        "e-?ma[il1]{2} acc",
191                        "e-?ma[il1]{2}.*up.?grade",
192                        "e.?ma[il1]{2}.*server",
193                        "e.?ma[il1]{2}.*suspend",
194                        "email.update",
195                        "faxed you",
196                        "fraud(ulent)?.*charge",
197                        "from.helpdesk",
198                        "fu[il1]{2}.*ma[il1]+[ -]?box",
199                        "has.been.*suspended",
200                        "has.been.limited",
201                        "have.locked",
202                        "he[li]p ?desk upgrade",
203                        "heipdesk",
204                        "i[il]iega[il]",
205                        "ii[il]ega[il]",
206                        "incoming e?mail",
207                        "incoming.*fax",
208                        "lock.*security",
209                        "ma[il1]{1}[ -]?box.*quo",
210                        "ma[il1]{2}[ -]?box.*fu[il1]",
211                        "ma[il1]{2}box.*[il1]{2}mit",
212                        "ma[il1]{2}box stor",
213                        "mail on.?hold",
214                        "mail.*box.*migration",
215                        "mail.*de-?activat",
216                        "mail.update.required",
217                        "mails.*pending",
218                        "messages.*pending",
219                        "missed.*shipping.*notification",
220                        "missed.shipment.notification",
221                        "must.update.your.account",
222                        "new [sl][io]g?[nig][ -]?in from",
223                        "new voice ?-?mail",
224                        "notifications.*pending",
225                        "office.*3.*6.*5.*suspend",
226                        "office365",
227                        "on google docs with you",
228                        "online doc",
229                        "password.*compromised",
230                        "payment advice",
231                        "periodic maintenance",
232                        "potential(ly)? unauthorized",
233                        "refund not approved",
234                        "report",
235                        "revised.*policy",
236                        "scam",
237                        "scanned.?invoice",
238                        "secured?.update",
239                        "security breach",
240                        "securlty",
241                        "seguranca",
242                        "signed.*delivery",
243                        "status of your .{314}? ?delivery",
244                        "susp[il1]+c[il1]+ous.*act[il1]+v[il1]+ty",
245                        "suspicious.*sign.*[io]n",
246                        "suspicious.activit",
247                        "temporar(il)?y deactivate",
248                        "temporar[il1]{2}y disab[li]ed",
249                        "temporarily.*lock",
250                        "un-?usua[li].activity",
251                        "unable.*deliver",
252                        "unauthorized.*activit",
253                        "unauthorized.device",
254                        "undelivered message",
255                        "unread.*doc",
256                        "unusual.activity",
257                        "upgrade.*account",
258                        "upgrade.notice",
259                        "urgent message",
260                        "urgent.verification",
261                        "v[il1]o[li1]at[il1]on security",
262                        "va[il1]{1}date.*ma[il1]{2}[ -]?box",
263                        "verification ?-?require",
264                        "verification( )?-?need",
265                        "verify.your?.account",
266                        "web ?-?ma[il1]{2}",
267                        "web[ -]?ma[il1]{2}",
268                        "will.be.suspended",
269                        "your (customer )?account .as",
270                        "your.office.365",
271                        "your.online.access"
272        )
273        or any($suspicious_subjects, strings.icontains(subject.subject, .))
274        or regex.icontains(sender.display_name,
275                           "Accounts.?Payable",
276                           "Admin",
277                           "Administrator",
278                           "Alert",
279                           "Assistant",
280                           "Billing",
281                           "Benefits",
282                           "Bonus",
283                           "CEO",
284                           "CFO",
285                           "CIO",
286                           "CTO",
287                           "Chairman",
288                           "Claim",
289                           "Confirm",
290                           "Critical",
291                           "Customer Service",
292                           "Deal",
293                           "Discount",
294                           "Director",
295                           "Exclusive",
296                           "Executive",
297                           "Fax",
298                           "Free",
299                           "Gift",
300                           "/bHR/b",
301                           "Helpdesk",
302                           "Human Resources",
303                           "Immediate",
304                           "Important",
305                           "Info",
306                           "Information",
307                           "Invoice",
308                           '\bIT\b',
309                           "Legal",
310                           "Lottery",
311                           "Management",
312                           "Manager",
313                           "Member Services",
314                           "Notification",
315                           "Offer",
316                           "Operations",
317                           "Order",
318                           "Partner",
319                           "Payment",
320                           "Payroll",
321                           "President",
322                           "Premium",
323                           "Prize",
324                           "Receipt",
325                           "Refund",
326                           "Registrar",
327                           "Required",
328                           "Reward",
329                           "Sales",
330                           "Secretary",
331                           "Security",
332                           "Service",
333                           "Signature",
334                           "Storage",
335                           "Support",
336                           "Sweepstakes",
337                           "System",
338                           "Tax",
339                           "Tech Support",
340                           "Update",
341                           "Upgrade",
342                           "Urgent",
343                           "Validate",
344                           "Verify",
345                           "VIP",
346                           "Webmaster",
347                           "Winner",
348        )
349      )
350      or (
351        (
352          length(recipients.to) == 0
353          or all(recipients.to, .display_name == "Undisclosed recipients")
354        )
355        and length(recipients.cc) == 0
356        and length(recipients.bcc) == 0
357      )
358      or any(beta.scan_qr(file.message_screenshot()).items,
359             (
360               .url.domain.tld in $suspicious_tlds
361               and .url.domain.root_domain != "app.link"
362             )
363             // linkanalysis phishing disposition
364             or ml.link_analysis(.url).credphish.disposition == "phishing"
365      )
366      or any(attachments,
367             (
368               .file_type in $file_types_images
369               or .file_extension in $file_extensions_macros
370               or .file_type == "pdf"
371             )
372             and any(file.explode(.),
373                     (
374                       .scan.qr.url.domain.tld in $suspicious_tlds
375                       and .scan.qr.url.domain.root_domain != "app.link"
376                       and .scan.qr.url.domain.root_domain != "qr.link"
377                       and .scan.qr.url.domain.root_domain != "skyqr.co.za"
378                     )
379                     and .scan.qr.url.domain.root_domain not in $org_domains
380             )
381      )
382      or sender.email.domain.tld in $suspicious_tlds
383    )
384  )
385  
386  // sender profile is new or outlier
387  and (
388    profile.by_sender_email().any_messages_malicious_or_spam
389    or (
390      sender.email.domain.domain in $org_domains
391      and not coalesce(headers.auth_summary.dmarc.pass, false)
392    )
393    or (
394      profile.by_sender_email().prevalence in ("new", "outlier")
395      and not profile.by_sender_email().solicited
396    )
397  )
398  and not profile.by_sender_email().any_messages_benign
399  // negate highly trusted sender domains unless they fail DMARC authentication
400  and not (
401    sender.email.domain.root_domain in $high_trust_sender_root_domains
402    and coalesce(headers.auth_summary.dmarc.pass, false)
403  )  
404attack_types:
405  - "Credential Phishing"
406tactics_and_techniques:
407  - "QR code"
408  - "Social engineering"
409detection_methods:
410  - "Content analysis"
411  - "Header analysis"
412  - "Computer Vision"
413  - "Natural Language Understanding"
414  - "QR code analysis"
415  - "Sender analysis"
416  - "URL analysis"
417id: "04f5c34f-6518-512d-916c-4c2c2827c6a9"
to-top