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,
 82                                  '(?:scan|camera|review and sign)'
 83                  )
 84                  and regex.icontains(.scan.ocr.raw, '(?:\bQR\b|Q\.R\.|barcode)')
 85                )
 86            )
 87          )
 88      )
 89      or (
 90        length(attachments) == 0
 91        //
 92        // This rule makes use of a beta feature and is subject to change without notice
 93        // using the beta feature in custom rules is not suggested until it has been formally released
 94        //
 95        and (
 96          (
 97            beta.parse_exif(file.message_screenshot()).image_height < 2000
 98            and beta.parse_exif(file.message_screenshot()).image_width < 2000
 99          )
100          // ignore image height/width if there is excessive whitespace padding
101          or regex.contains(body.html.raw, '\"padding:0px 0px \d{3,4}px 0px')
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                        "all.?staff",
175                        "almost.full",
176                        "app[li]e.[il]d",
177                        "authenticate.*account",
178                        "been.*suspend",
179                        "bonus",
180                        "clos.*of.*account.*processed",
181                        "confirm.your.account",
182                        "courier.*able",
183                        "crediential.*notif",
184                        "deactivation.*in.*progress",
185                        "delivery.*attempt.*failed",
186                        "document.received",
187                        "documented.*shared.*with.*you",
188                        "dropbox.*document",
189                        "e-?ma[il1]+ .{010}suspen",
190                        "e-?ma[il1]{1} user",
191                        "e-?ma[il1]{2} acc",
192                        "e-?ma[il1]{2}.*up.?grade",
193                        "e.?ma[il1]{2}.*server",
194                        "e.?ma[il1]{2}.*suspend",
195                        "email.update",
196                        "faxed you",
197                        "fraud(ulent)?.*charge",
198                        "from.helpdesk",
199                        "fu[il1]{2}.*ma[il1]+[ -]?box",
200                        "has.been.*suspended",
201                        "has.been.limited",
202                        "have.locked",
203                        "he[li]p ?desk upgrade",
204                        "heipdesk",
205                        "i[il]iega[il]",
206                        "ii[il]ega[il]",
207                        "incoming e?mail",
208                        "incoming.*fax",
209                        "lock.*security",
210                        "ma[il1]{1}[ -]?box.*quo",
211                        "ma[il1]{2}[ -]?box.*fu[il1]",
212                        "ma[il1]{2}box.*[il1]{2}mit",
213                        "ma[il1]{2}box stor",
214                        "mail on.?hold",
215                        "mail.*box.*migration",
216                        "mail.*de-?activat",
217                        "mail.update.required",
218                        "mails.*pending",
219                        "messages.*pending",
220                        "missed.*shipping.*notification",
221                        "missed.shipment.notification",
222                        "must.update.your.account",
223                        "new [sl][io]g?[nig][ -]?in from",
224                        "new voice ?-?mail",
225                        "notifications.*pending",
226                        "office.*3.*6.*5.*suspend",
227                        "office365",
228                        "on google docs with you",
229                        "online doc",
230                        "password.*compromised",
231                        "payment advice",
232                        "periodic maintenance",
233                        "potential(ly)? unauthorized",
234                        "refund not approved",
235                        "report",
236                        "revised.*policy",
237                        "scam",
238                        "scanned.?invoice",
239                        "secured?.update",
240                        "security breach",
241                        "securlty",
242                        "seguranca",
243                        "signed.*delivery",
244                        "status of your .{314}? ?delivery",
245                        "susp[il1]+c[il1]+ous.*act[il1]+v[il1]+ty",
246                        "suspicious.*sign.*[io]n",
247                        "suspicious.activit",
248                        "temporar(il)?y deactivate",
249                        "temporar[il1]{2}y disab[li]ed",
250                        "temporarily.*lock",
251                        "un-?usua[li].activity",
252                        "unable.*deliver",
253                        "unauthorized.*activit",
254                        "unauthorized.device",
255                        "undelivered message",
256                        "unread.*doc",
257                        "unusual.activity",
258                        "upgrade.*account",
259                        "upgrade.notice",
260                        "urgent message",
261                        "urgent.verification",
262                        "v[il1]o[li1]at[il1]on security",
263                        "va[il1]{1}date.*ma[il1]{2}[ -]?box",
264                        "verification ?-?require",
265                        "verification( )?-?need",
266                        "verify.your?.account",
267                        "web ?-?ma[il1]{2}",
268                        "web[ -]?ma[il1]{2}",
269                        "will.be.suspended",
270                        "your (customer )?account .as",
271                        "your.office.365",
272                        "your.online.access"
273        )
274        or any($suspicious_subjects, strings.icontains(subject.subject, .))
275        or regex.icontains(sender.display_name,
276                           "Accounts.?Payable",
277                           "Admin",
278                           "Administrator",
279                           "Alert",
280                           "Assistant",
281                           "Billing",
282                           "Benefits",
283                           "Bonus",
284                           "CEO",
285                           "CFO",
286                           "CIO",
287                           "CTO",
288                           "Chairman",
289                           "Claim",
290                           "Confirm",
291                           "Critical",
292                           "Customer Service",
293                           "Deal",
294                           "Discount",
295                           "Director",
296                           "Exclusive",
297                           "Executive",
298                           "Fax",
299                           "Free",
300                           "Gift",
301                           "/bHR/b",
302                           "Helpdesk",
303                           "Human Resources",
304                           "Immediate",
305                           "Important",
306                           "Info",
307                           "Information",
308                           "Invoice",
309                           '\bIT\b',
310                           "Legal",
311                           "Lottery",
312                           "Management",
313                           "Manager",
314                           "Member Services",
315                           "Notification",
316                           "Offer",
317                           "Operations",
318                           "Order",
319                           "Partner",
320                           "Payment",
321                           "Payroll",
322                           "President",
323                           "Premium",
324                           "Prize",
325                           "Receipt",
326                           "Refund",
327                           "Registrar",
328                           "Required",
329                           "Reward",
330                           "Sales",
331                           "Secretary",
332                           "Security",
333                           "Service",
334                           "Signature",
335                           "StaffAnnouncement",
336                           "Storage",
337                           "Support",
338                           "Sweepstakes",
339                           "System",
340                           "Tax",
341                           "Tech Support",
342                           "Update",
343                           "Upgrade",
344                           "Urgent",
345                           "Validate",
346                           "Verify",
347                           "VIP",
348                           "Webmaster",
349                           "Winner",
350        )
351      )
352      or (
353        (
354          length(recipients.to) == 0
355          or all(recipients.to, .display_name == "Undisclosed recipients")
356        )
357        and length(recipients.cc) == 0
358        and length(recipients.bcc) == 0
359      )
360      or any(beta.scan_qr(file.message_screenshot()).items,
361             (
362               .url.domain.tld in $suspicious_tlds
363               and .url.domain.root_domain != "app.link"
364             )
365             // linkanalysis phishing disposition
366             or ml.link_analysis(.url).credphish.disposition == "phishing"
367      )
368      or any(attachments,
369             (
370               .file_type in $file_types_images
371               or .file_extension in $file_extensions_macros
372               or .file_type == "pdf"
373             )
374             and any(file.explode(.),
375                     (
376                       .scan.qr.url.domain.tld in $suspicious_tlds
377                       and .scan.qr.url.domain.root_domain != "app.link"
378                       and .scan.qr.url.domain.root_domain != "qr.link"
379                       and .scan.qr.url.domain.root_domain != "skyqr.co.za"
380                     )
381                     and .scan.qr.url.domain.root_domain not in $org_domains
382             )
383      )
384      or sender.email.domain.tld in $suspicious_tlds
385    )
386  )
387  
388  // sender profile is new or outlier
389  and (
390    profile.by_sender_email().any_messages_malicious_or_spam
391    or (
392      sender.email.domain.domain in $org_domains
393      and not coalesce(headers.auth_summary.dmarc.pass, false)
394    )
395    or (
396      profile.by_sender_email().prevalence in ("new", "outlier")
397      and not profile.by_sender_email().solicited
398    )
399  )
400  and not profile.by_sender_email().any_messages_benign
401  // negate highly trusted sender domains unless they fail DMARC authentication
402  and not (
403    sender.email.domain.root_domain in $high_trust_sender_root_domains
404    and coalesce(headers.auth_summary.dmarc.pass, false)
405  )  
406attack_types:
407  - "Credential Phishing"
408tactics_and_techniques:
409  - "QR code"
410  - "Social engineering"
411detection_methods:
412  - "Content analysis"
413  - "Header analysis"
414  - "Computer Vision"
415  - "Natural Language Understanding"
416  - "QR code analysis"
417  - "Sender analysis"
418  - "URL analysis"
419id: "04f5c34f-6518-512d-916c-4c2c2827c6a9"
to-top