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