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