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