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