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 beta.parse_exif(file.message_screenshot()).image_height < 2000
 94        and beta.parse_exif(file.message_screenshot()).image_width < 2000
 95        and any(beta.scan_qr(file.message_screenshot()).items,
 96                .type is not null
 97                and regex.contains(.data, '\.')
 98                // exclude contact cards
 99                and not strings.istarts_with(.data, "BEGIN:VCARD")
100        )
101      )
102    )
103    and (
104      any(recipients.to,
105          strings.icontains(sender.display_name, .email.domain.sld)
106      )
107      or length(body.current_thread.text) is null
108      or (
109        body.current_thread.text == ""
110        and (
111          (
112            (length(headers.references) > 0 or headers.in_reply_to is null)
113            and not (
114              (
115                strings.istarts_with(subject.subject, "RE:")
116                or strings.istarts_with(subject.subject, "RES:")
117                or strings.istarts_with(subject.subject, "R:")
118                or strings.istarts_with(subject.subject, "ODG:")
119                or strings.istarts_with(subject.subject, "答复:")
120                or strings.istarts_with(subject.subject, "AW:")
121                or strings.istarts_with(subject.subject, "TR:")
122                or strings.istarts_with(subject.subject, "FWD:")
123                or regex.imatch(subject.subject,
124                                '(\[[^\]]+\]\s?){0,3}(re|fwd?|automat.*)\s?:.*'
125                )
126              )
127            )
128          )
129          or length(headers.references) == 0
130        )
131      )
132      or regex.contains(subject.subject,
133                        "(Authenticat(e|or|ion)|2fa|Multi.Factor|(qr|bar).code|action.require|alert|Att(n|ention):)"
134      )
135      or (any(recipients.to, strings.icontains(subject.subject, .display_name)))
136      or (
137        regex.icontains(subject.subject,
138                        "termination.*notice",
139                        "38417",
140                        ":completed",
141                        "[il1]{2}mit.*ma[il1]{2} ?bo?x",
142                        "[il][il][il]egai[ -]",
143                        "[li][li][li]ega[li] attempt",
144                        "[ng]-?[io]n .*block",
145                        "[ng]-?[io]n .*cancel",
146                        "[ng]-?[io]n .*deactiv",
147                        "[ng]-?[io]n .*disabl",
148                        "action.*required",
149                        "abandon.*package",
150                        "about.your.account",
151                        "acc(ou)?n?t (is )?on ho[li]d",
152                        "acc(ou)?n?t.*terminat",
153                        "acc(oun)?t.*[il1]{2}mitation",
154                        "access.*limitation",
155                        "account (will be )?block",
156                        "account.*de-?activat",
157                        "account.*locked",
158                        "account.*re-verification",
159                        "account.*security",
160                        "account.*suspension",
161                        "account.has.been",
162                        "account.has.expired",
163                        "account.will.be.blocked",
164                        "account v[il]o[li]at",
165                        "activity.*acc(oun)?t",
166                        "almost.full",
167                        "app[li]e.[il]d",
168                        "authenticate.*account",
169                        "been.*suspend",
170                        "bonus",
171                        "clos.*of.*account.*processed",
172                        "confirm.your.account",
173                        "courier.*able",
174                        "crediential.*notif",
175                        "deactivation.*in.*progress",
176                        "delivery.*attempt.*failed",
177                        "document.received",
178                        "documented.*shared.*with.*you",
179                        "dropbox.*document",
180                        "e-?ma[il1]+ .{010}suspen",
181                        "e-?ma[il1]{1} user",
182                        "e-?ma[il1]{2} acc",
183                        "e-?ma[il1]{2}.*up.?grade",
184                        "e.?ma[il1]{2}.*server",
185                        "e.?ma[il1]{2}.*suspend",
186                        "email.update",
187                        "faxed you",
188                        "fraud(ulent)?.*charge",
189                        "from.helpdesk",
190                        "fu[il1]{2}.*ma[il1]+[ -]?box",
191                        "has.been.*suspended",
192                        "has.been.limited",
193                        "have.locked",
194                        "he[li]p ?desk upgrade",
195                        "heipdesk",
196                        "i[il]iega[il]",
197                        "ii[il]ega[il]",
198                        "incoming e?mail",
199                        "incoming.*fax",
200                        "lock.*security",
201                        "ma[il1]{1}[ -]?box.*quo",
202                        "ma[il1]{2}[ -]?box.*fu[il1]",
203                        "ma[il1]{2}box.*[il1]{2}mit",
204                        "ma[il1]{2}box stor",
205                        "mail on.?hold",
206                        "mail.*box.*migration",
207                        "mail.*de-?activat",
208                        "mail.update.required",
209                        "mails.*pending",
210                        "messages.*pending",
211                        "missed.*shipping.*notification",
212                        "missed.shipment.notification",
213                        "must.update.your.account",
214                        "new [sl][io]g?[nig][ -]?in from",
215                        "new voice ?-?mail",
216                        "notifications.*pending",
217                        "office.*3.*6.*5.*suspend",
218                        "office365",
219                        "on google docs with you",
220                        "online doc",
221                        "password.*compromised",
222                        "payment advice",
223                        "periodic maintenance",
224                        "potential(ly)? unauthorized",
225                        "refund not approved",
226                        "report",
227                        "revised.*policy",
228                        "scam",
229                        "scanned.?invoice",
230                        "secured?.update",
231                        "security breach",
232                        "securlty",
233                        "seguranca",
234                        "signed.*delivery",
235                        "status of your .{314}? ?delivery",
236                        "susp[il1]+c[il1]+ous.*act[il1]+v[il1]+ty",
237                        "suspicious.*sign.*[io]n",
238                        "suspicious.activit",
239                        "temporar(il)?y deactivate",
240                        "temporar[il1]{2}y disab[li]ed",
241                        "temporarily.*lock",
242                        "un-?usua[li].activity",
243                        "unable.*deliver",
244                        "unauthorized.*activit",
245                        "unauthorized.device",
246                        "undelivered message",
247                        "unread.*doc",
248                        "unusual.activity",
249                        "upgrade.*account",
250                        "upgrade.notice",
251                        "urgent message",
252                        "urgent.verification",
253                        "v[il1]o[li1]at[il1]on security",
254                        "va[il1]{1}date.*ma[il1]{2}[ -]?box",
255                        "verification ?-?require",
256                        "verification( )?-?need",
257                        "verify.your?.account",
258                        "web ?-?ma[il1]{2}",
259                        "web[ -]?ma[il1]{2}",
260                        "will.be.suspended",
261                        "your (customer )?account .as",
262                        "your.office.365",
263                        "your.online.access"
264        )
265        or any($suspicious_subjects, strings.icontains(subject.subject, .))
266        or regex.icontains(sender.display_name,
267                           "Accounts.?Payable",
268                           "Admin",
269                           "Administrator",
270                           "Alert",
271                           "Assistant",
272                           "Billing",
273                           "Benefits",
274                           "Bonus",
275                           "CEO",
276                           "CFO",
277                           "CIO",
278                           "CTO",
279                           "Chairman",
280                           "Claim",
281                           "Confirm",
282                           "Critical",
283                           "Customer Service",
284                           "Deal",
285                           "Discount",
286                           "Director",
287                           "Exclusive",
288                           "Executive",
289                           "Fax",
290                           "Free",
291                           "Gift",
292                           "/bHR/b",
293                           "Helpdesk",
294                           "Human Resources",
295                           "Immediate",
296                           "Important",
297                           "Info",
298                           "Information",
299                           "Invoice",
300                           '\bIT\b',
301                           "Legal",
302                           "Lottery",
303                           "Management",
304                           "Manager",
305                           "Member Services",
306                           "Notification",
307                           "Offer",
308                           "Operations",
309                           "Order",
310                           "Partner",
311                           "Payment",
312                           "Payroll",
313                           "President",
314                           "Premium",
315                           "Prize",
316                           "Receipt",
317                           "Refund",
318                           "Registrar",
319                           "Required",
320                           "Reward",
321                           "Sales",
322                           "Secretary",
323                           "Security",
324                           "Service",
325                           "Signature",
326                           "Storage",
327                           "Support",
328                           "Sweepstakes",
329                           "System",
330                           "Tax",
331                           "Tech Support",
332                           "Update",
333                           "Upgrade",
334                           "Urgent",
335                           "Validate",
336                           "Verify",
337                           "VIP",
338                           "Webmaster",
339                           "Winner",
340        )
341      )
342      or (
343        (
344          length(recipients.to) == 0
345          or all(recipients.to, .display_name == "Undisclosed recipients")
346        )
347        and length(recipients.cc) == 0
348        and length(recipients.bcc) == 0
349      )
350      or any(beta.scan_qr(file.message_screenshot()).items,
351             (
352               .url.domain.tld in $suspicious_tlds
353               and .url.domain.root_domain != "app.link"
354             )
355             // linkanalysis phishing disposition
356             or ml.link_analysis(.url).credphish.disposition == "phishing"
357      )
358      or any(attachments,
359             (
360               .file_type in $file_types_images
361               or .file_extension in $file_extensions_macros
362               or .file_type == "pdf"
363             )
364             and any(file.explode(.),
365                     (
366                       .scan.qr.url.domain.tld in $suspicious_tlds
367                       and .scan.qr.url.domain.root_domain != "app.link"
368                       and .scan.qr.url.domain.root_domain != "qr.link"
369                       and .scan.qr.url.domain.root_domain != "skyqr.co.za"
370                     )
371                     and .scan.qr.url.domain.root_domain not in $org_domains
372             )
373      )
374      or sender.email.domain.tld in $suspicious_tlds
375    )
376  )
377  
378  // sender profile is new or outlier
379  and (
380    profile.by_sender_email().any_messages_malicious_or_spam
381    or (
382      sender.email.domain.domain in $org_domains
383      and not coalesce(headers.auth_summary.dmarc.pass, false)
384    )
385    or (
386      profile.by_sender_email().prevalence in ("new", "outlier")
387      and not profile.by_sender_email().solicited
388    )
389  )
390  and not profile.by_sender_email().any_messages_benign
391  // negate highly trusted sender domains unless they fail DMARC authentication
392  and not (
393    sender.email.domain.root_domain in $high_trust_sender_root_domains
394    and coalesce(headers.auth_summary.dmarc.pass, false)
395  )  
396attack_types:
397  - "Credential Phishing"
398tactics_and_techniques:
399  - "QR code"
400  - "Social engineering"
401detection_methods:
402  - "Content analysis"
403  - "Header analysis"
404  - "Computer Vision"
405  - "Natural Language Understanding"
406  - "QR code analysis"
407  - "Sender analysis"
408  - "URL analysis"
409id: "04f5c34f-6518-512d-916c-4c2c2827c6a9"
to-top