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