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