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