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 (
94 (
95 beta.parse_exif(file.message_screenshot()).image_height < 2000
96 and beta.parse_exif(file.message_screenshot()).image_width < 2000
97 )
98 // ignore image height/width if there is excessive whitespace padding
99 or regex.contains(body.html.raw,
100 '\"padding:0px 0px \d{3,4}px 0px'
101 )
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 "almost.full",
175 "app[li]e.[il]d",
176 "authenticate.*account",
177 "been.*suspend",
178 "bonus",
179 "clos.*of.*account.*processed",
180 "confirm.your.account",
181 "courier.*able",
182 "crediential.*notif",
183 "deactivation.*in.*progress",
184 "delivery.*attempt.*failed",
185 "document.received",
186 "documented.*shared.*with.*you",
187 "dropbox.*document",
188 "e-?ma[il1]+ .{010}suspen",
189 "e-?ma[il1]{1} user",
190 "e-?ma[il1]{2} acc",
191 "e-?ma[il1]{2}.*up.?grade",
192 "e.?ma[il1]{2}.*server",
193 "e.?ma[il1]{2}.*suspend",
194 "email.update",
195 "faxed you",
196 "fraud(ulent)?.*charge",
197 "from.helpdesk",
198 "fu[il1]{2}.*ma[il1]+[ -]?box",
199 "has.been.*suspended",
200 "has.been.limited",
201 "have.locked",
202 "he[li]p ?desk upgrade",
203 "heipdesk",
204 "i[il]iega[il]",
205 "ii[il]ega[il]",
206 "incoming e?mail",
207 "incoming.*fax",
208 "lock.*security",
209 "ma[il1]{1}[ -]?box.*quo",
210 "ma[il1]{2}[ -]?box.*fu[il1]",
211 "ma[il1]{2}box.*[il1]{2}mit",
212 "ma[il1]{2}box stor",
213 "mail on.?hold",
214 "mail.*box.*migration",
215 "mail.*de-?activat",
216 "mail.update.required",
217 "mails.*pending",
218 "messages.*pending",
219 "missed.*shipping.*notification",
220 "missed.shipment.notification",
221 "must.update.your.account",
222 "new [sl][io]g?[nig][ -]?in from",
223 "new voice ?-?mail",
224 "notifications.*pending",
225 "office.*3.*6.*5.*suspend",
226 "office365",
227 "on google docs with you",
228 "online doc",
229 "password.*compromised",
230 "payment advice",
231 "periodic maintenance",
232 "potential(ly)? unauthorized",
233 "refund not approved",
234 "report",
235 "revised.*policy",
236 "scam",
237 "scanned.?invoice",
238 "secured?.update",
239 "security breach",
240 "securlty",
241 "seguranca",
242 "signed.*delivery",
243 "status of your .{314}? ?delivery",
244 "susp[il1]+c[il1]+ous.*act[il1]+v[il1]+ty",
245 "suspicious.*sign.*[io]n",
246 "suspicious.activit",
247 "temporar(il)?y deactivate",
248 "temporar[il1]{2}y disab[li]ed",
249 "temporarily.*lock",
250 "un-?usua[li].activity",
251 "unable.*deliver",
252 "unauthorized.*activit",
253 "unauthorized.device",
254 "undelivered message",
255 "unread.*doc",
256 "unusual.activity",
257 "upgrade.*account",
258 "upgrade.notice",
259 "urgent message",
260 "urgent.verification",
261 "v[il1]o[li1]at[il1]on security",
262 "va[il1]{1}date.*ma[il1]{2}[ -]?box",
263 "verification ?-?require",
264 "verification( )?-?need",
265 "verify.your?.account",
266 "web ?-?ma[il1]{2}",
267 "web[ -]?ma[il1]{2}",
268 "will.be.suspended",
269 "your (customer )?account .as",
270 "your.office.365",
271 "your.online.access"
272 )
273 or any($suspicious_subjects, strings.icontains(subject.subject, .))
274 or regex.icontains(sender.display_name,
275 "Accounts.?Payable",
276 "Admin",
277 "Administrator",
278 "Alert",
279 "Assistant",
280 "Billing",
281 "Benefits",
282 "Bonus",
283 "CEO",
284 "CFO",
285 "CIO",
286 "CTO",
287 "Chairman",
288 "Claim",
289 "Confirm",
290 "Critical",
291 "Customer Service",
292 "Deal",
293 "Discount",
294 "Director",
295 "Exclusive",
296 "Executive",
297 "Fax",
298 "Free",
299 "Gift",
300 "/bHR/b",
301 "Helpdesk",
302 "Human Resources",
303 "Immediate",
304 "Important",
305 "Info",
306 "Information",
307 "Invoice",
308 '\bIT\b',
309 "Legal",
310 "Lottery",
311 "Management",
312 "Manager",
313 "Member Services",
314 "Notification",
315 "Offer",
316 "Operations",
317 "Order",
318 "Partner",
319 "Payment",
320 "Payroll",
321 "President",
322 "Premium",
323 "Prize",
324 "Receipt",
325 "Refund",
326 "Registrar",
327 "Required",
328 "Reward",
329 "Sales",
330 "Secretary",
331 "Security",
332 "Service",
333 "Signature",
334 "Storage",
335 "Support",
336 "Sweepstakes",
337 "System",
338 "Tax",
339 "Tech Support",
340 "Update",
341 "Upgrade",
342 "Urgent",
343 "Validate",
344 "Verify",
345 "VIP",
346 "Webmaster",
347 "Winner",
348 )
349 )
350 or (
351 (
352 length(recipients.to) == 0
353 or all(recipients.to, .display_name == "Undisclosed recipients")
354 )
355 and length(recipients.cc) == 0
356 and length(recipients.bcc) == 0
357 )
358 or any(beta.scan_qr(file.message_screenshot()).items,
359 (
360 .url.domain.tld in $suspicious_tlds
361 and .url.domain.root_domain != "app.link"
362 )
363 // linkanalysis phishing disposition
364 or ml.link_analysis(.url).credphish.disposition == "phishing"
365 )
366 or any(attachments,
367 (
368 .file_type in $file_types_images
369 or .file_extension in $file_extensions_macros
370 or .file_type == "pdf"
371 )
372 and any(file.explode(.),
373 (
374 .scan.qr.url.domain.tld in $suspicious_tlds
375 and .scan.qr.url.domain.root_domain != "app.link"
376 and .scan.qr.url.domain.root_domain != "qr.link"
377 and .scan.qr.url.domain.root_domain != "skyqr.co.za"
378 )
379 and .scan.qr.url.domain.root_domain not in $org_domains
380 )
381 )
382 or sender.email.domain.tld in $suspicious_tlds
383 )
384 )
385
386 // sender profile is new or outlier
387 and (
388 profile.by_sender_email().any_messages_malicious_or_spam
389 or (
390 sender.email.domain.domain in $org_domains
391 and not coalesce(headers.auth_summary.dmarc.pass, false)
392 )
393 or (
394 profile.by_sender_email().prevalence in ("new", "outlier")
395 and not profile.by_sender_email().solicited
396 )
397 )
398 and not profile.by_sender_email().any_messages_benign
399 // negate highly trusted sender domains unless they fail DMARC authentication
400 and not (
401 sender.email.domain.root_domain in $high_trust_sender_root_domains
402 and coalesce(headers.auth_summary.dmarc.pass, false)
403 )
404attack_types:
405 - "Credential Phishing"
406tactics_and_techniques:
407 - "QR code"
408 - "Social engineering"
409detection_methods:
410 - "Content analysis"
411 - "Header analysis"
412 - "Computer Vision"
413 - "Natural Language Understanding"
414 - "QR code analysis"
415 - "Sender analysis"
416 - "URL analysis"
417id: "04f5c34f-6518-512d-916c-4c2c2827c6a9"