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"