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