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