Fake voicemail notification (untrusted sender)
This rule detects a common credential phishing vector enticing the user to engage with links under the premise that they have a voicemail to retrieve. The rule looks for voicemail verbiage in the display name, body, subject or a combination of those elements with emojis or a medium to high credential theft NLU Intent from first-time + unsolicited sender.
Sublime rule (View on GitHub)
1name: "Fake voicemail notification (untrusted sender)"
2description: |
3 This rule detects a common credential phishing vector enticing the user to engage with links under the premise that they have a voicemail to retrieve.
4 The rule looks for voicemail verbiage in the display name, body, subject or a combination of those elements with emojis or a medium to high credential theft NLU Intent from first-time + unsolicited sender.
5type: "rule"
6severity: "medium"
7source: |
8 type.inbound
9 // contains links or attachments
10 and (
11 (0 < length(body.links) <= 25 or 0 < length(distinct(attachments, .md5)) <= 3)
12 and 0 <= length(distinct(attachments, .md5)) <= 8
13 )
14
15 // the subject or display_name need some keywords which are voicemail related
16 and (
17 any([subject.subject, sender.display_name],
18 regex.icontains(.,
19 // split phrases that occur within 3 words between or only punctuation between them
20 '(?:v[nm](\b|[[:punct:]])?|\bvoice(?:mail|message)?|audi[o0]|incoming|missed(?:\sa\s)?|left( a)?|wireless)(?:\w+(\s\w+)?|[[:punct:]]+|\s+){0,3}(?:mail|message|msg|recording|received|notif|support|ca[li1][li1]\d*\b|ca[il1][il1](?:er)?|log|transcript(?:ion)?)\b',
21 // regex specific to v-mail, v_msg, v,mail, etc
22 // list of "secondary" words synced with regex above this one
23 'v[[:punct:]](?:mail|message|msg|recording|received|notif|support|ca[li1][li1]\d*\b|ca[il1][il1](?:er)?|log|transcript(?:ion)?\b)',
24 // split phrases that start with "caller" that occur within 3 words between or only punctation
25 'ca[li1][li1](?:er)?(?:\w+(\s\w+)?|[[:punct:]]+|\s+){0,3}(?:v[nm](\b|[[:punct:]])?|\bvoice(?:mail|message)?|audi[o0]|missed(?:\sa\s)?|left( a)?)',
26 // strong phrases
27 '(?:open mp3|audi[o0] note|\.wav|left a vm|[^\s]+voip[^\s]*|unanswered.*ca[li1][li1]|incoming.vm|left msg|wireless ca[li1][li1]er|VM Service|voice message|missed.ca[li1][li1](?:e[rd])?|\bca[li1][li1].(?:support|service)(?: for| log)?|missed.{0,10} VM|new voicemail from|new.v.m.from.\+?\d+|new voicemail?(?:\w+(\s\w+)?|[[:punct:]]+|\s+){0,3}transcript(s|ion)?|message received|incoming transmission)',
28 // starts in the format of `(4)` and contains some voicemail keywords
29 '^\(\d\)\s(?:\w+(\s\w+)?|[[:punct:]]+|\s+){0,3}(?:message|voip|voice|unread|call)',
30 'ca[li1][li1](?:er)?(?:\w+(\s\w+)?|[[:punct:]]+|\s+){0,3}(?:playback|transcript)',
31
32 // obfuscated phone number with at least one digit in the area code and at least one obfuscated number in the last group
33 // 555-555-555X, 555-555-XXXX, 555-5XX-XXXX
34 '\b1?\(?(\d{3}|\d{2}[\*X]|\d[\*X]{2})\)?[^a-z0-9]{0,2}(\d{2,3}|\d{2}[\*X]|\d[\*X]{2}|[\*X]{2,3})[^a-z0-9]{0,4}(\d{3}[\*X]|\d{2}[\*X]{2}|\d[\*X]{3}|[\*X]{3,4})[^0-9]',
35 // obfuscated phone number with at least one digit in the prefix
36 // XXX-555-5555, XXX-5XX-XXXX
37 '\b1?\(?(\d{2}[\*X]|\d[\*X]{2}|[\*X]{2,3})\)?[^a-z0-9]{0,2}(\d{2,3}|\d{2}[\*X]|\d[\*X]{2})[^a-z0-9]{0,4}(\d{4}|\d{3}[\*X]|\d{2}[\*X]{2}|\d[\*X]{3}|[\*X]{3,4})\b',
38 )
39 )
40 // body.current_thread.text inspection should be very specific to avoid FP
41 or regex.icontains(strings.replace_confusables(body.current_thread.text),
42 // body.current_thread.text,
43 'sent (?:from|by) (?:your )?voice (?:mail )?system',
44 '(?:new|this) (?:voice(?:mail)?|audi[o0]) (?:message|notification|record)',
45 'voicemail (is )?attached',
46 'an? (?:new )?encrypted voicemail',
47 'a (?:new )?pending message',
48 'Your? have (?: an?)?incoming voiceRec',
49 "you(?:\'ve| have) a (?:new )?missed ca[li1][li1]",
50 'New Voicemail Received',
51 'New missed ca[li1][li1] record',
52 '\bvoicemail transcript\b',
53 'Listen to VoiceMail',
54 'New voicemail from'
55 )
56 // pull out two regexes that could benefit from negations
57 or (
58 regex.icontains(body.current_thread.text,
59 // body.current_thread.text,
60 '(?:you|we) (?:have |received )+(?:\w+(\s\w+)?|[[:punct:]]+|\s+){0,3}(?:\b|\s+)voice\s?(?:mail|audi[o0]|message|notification)',
61 'left you a (?:\w+(\s\w+)?|[[:punct:]]+|\s+){0,3}(?:voice(?:mail)?|audi[o0])(?: message|notification)?',
62 )
63 and not regex.icontains(body.current_thread.text,
64 '(?:I(?:\sjust)?|just(?: called you at (?:\d+[[:punct:]])+) and)? left you a (?:\w+(\s\w+)?|[[:punct:]]+|\s+){0,3}(?:voice(?:mail)?|audio)(?: message)?'
65 )
66 and not regex.icontains(body.current_thread.text,
67 'you (?:have |received )my voice\s?(?:mail|audio|message)'
68 )
69 )
70 // Reuse the body.current_thread.text logic against the OCR output of the message screenshot
71 or (
72 length(attachments) > 0
73 and (
74 all(attachments, .file_type in $file_types_images)
75 or (
76 // there is a mix of fake audio attachments and images
77 length(filter(filter(attachments,
78 strings.starts_with(.content_type, "audio")
79 ),
80 // confirm the content type with file.explode
81 // we have seen attachments claim to be audio/* files, only to be exploded as something else
82 any(file.explode(.),
83 not strings.starts_with(.flavors.mime, "audio")
84 )
85 )
86 // the total # of fake audio attachments + the total # of image attachments = the total # of attachments
87 // meaning, all attachments that are NOT fake audio attachments MUST be images
88 ) + length(filter(attachments, .file_type in $file_types_images)) == length(attachments)
89 )
90 )
91 and any((filter(file.explode(beta.message_screenshot()), .depth == 0)),
92 regex.icontains(.scan.ocr.raw,
93 // body.current_thread.text,
94 'sent (?:from|by) (?:your )?voice (?:mail )?system',
95 'new (?:voice(?:mail)?|audio) (?:message|notification|record)',
96 'voicemail (is )?attached',
97 'an? (?:new )?encrypted voicemail',
98 'a (?:new )?pending message',
99 'Your? have (?: an?)?incoming voiceRec',
100 "you(?:\'ve| have) a (?:new )?missed ca[li1][li1]",
101 'New Voicemail Received',
102 'New missed ca[li1][li1] record',
103 'voicemail transcript(?:ion)?',
104 'Listen to VoiceMail',
105 'New voicemail from'
106 )
107 or (
108 regex.icontains(.scan.ocr.raw,
109 // body.current_thread.text,
110 'you (?:have |received )*(?:\w+(\s\w+)?|[[:punct:]]+|\s+){0,3}\bvoice\s?(?:mail|audi[o0]|message)',
111 'left you a (?:\w+(\s\w+)?|[[:punct:]]+|\s+){0,3}(?:voice(?:mail)?|audi[o0])(?: message)?',
112 )
113 and not regex.icontains(body.current_thread.text,
114 '(?:I(?:\sjust)?|just) left you a (?:\w+(\s\w+)?|[[:punct:]]+|\s+){0,3}(?:voice(?:mail)?|audio)(?: message)?'
115 )
116 and not regex.icontains(body.current_thread.text,
117 'you (?:have |received )my voice\s?(?:mail|audio|message)'
118 )
119 )
120 )
121 )
122 or strings.icontains(body.html.raw, '<title>Voicemail Notification</title>')
123 or strings.icontains(body.html.raw, '<!-- Voicemail phone logo')
124 )
125 and 2 of (
126 (
127 // the sender is a freemail
128 sender.email.domain.root_domain in $free_email_providers
129 ),
130 (
131 any(ml.nlu_classifier(body.current_thread.text).intents,
132 .name in ("cred_theft") and .confidence in ("medium", "high")
133 )
134 or
135 // use the OCR from the message screenshot
136 any(filter(file.explode(beta.message_screenshot()), .depth == 0),
137 any(ml.nlu_classifier(.scan.ocr.raw).intents,
138 .name in ("cred_theft") and .confidence in ("medium", "high")
139 )
140 )
141 ),
142 (
143 any(attachments,
144 .content_type in ("html", "text", "text/html")
145 and any(ml.logo_detect(file.html_screenshot(.)).brands,
146 .name in ("Microsoft") and .confidence in ("medium", "high")
147 )
148 )
149 ),
150 (
151 regex.icontains(sender.display_name,
152 '(voice|audi[o0]|call|missed|caii)(\s?|-)(mail|message|recording|call|caii)|(transcription|Caller.?ID)'
153 )
154 ),
155 // attachment names are often HTML and voice mail related
156 (
157 any(attachments,
158 // this logic is reused below for eml attachments
159 // ensure updates occur both places
160 (
161 .content_type in ("html", "text", "text/html")
162 or .file_type in ("html", "unknown")
163 or .file_type == "pdf"
164 )
165 and (
166 regex.icontains(.file_name,
167 '(?:voice|aud[i1l][o0]|call|missed|caii|mail|message|recording|call|caii|transcr[il1]ption|v[nm]|audi[o0]|play|listen|unheard|msg)',
168 // contains a time
169 // 01min , 60secs
170 '0?[1-9]\s*min(?:(?:ute)?s)?',
171 '\d{1,2}\s*s(?:ec(?:ond)?s)?',
172 // (00:50s)
173 // 3:26 seconds
174 '[\(\[]?(?:\d{1,2}[\:\s-])\d{1,2}[\)\]]?\s*(?:s(?:(?:ecs?)onds)?)[\)\]]?',
175 // 03min25secs
176 '0?[1-9]\s*min(?:(?:ute)?s)?\d{1,2}\s*s(?:ec(?:ond)?s)?',
177 // [0:39]
178 // (0:39)
179 '[\(\[](?:\d{1,2}[\:\s-])\d{1,2}[\)\]]\s',
180 // contains an emoji
181 '[\x{1F300}-\x{1F5FF}\x{1F600}-\x{1F64F}\x{1F680}-\x{1F6FF}\x{1F700}-\x{1F77F}\x{1F780}-\x{1F7FF}\x{1F900}-\x{1F9FF}\x{2600}-\x{26FF}\x{2700}-\x{27BF}\x{2300}-\x{23FF}]'
182 )
183 // somtimes there is no name, it's just the extension which is also strange
184 or .file_name in~ (".htm", ".html")
185 // or sometimes it has no name....
186 or .file_name is null
187 )
188 )
189 ),
190 // attachment contains javascript
191 (
192 any(attachments,
193 (
194 .content_type in ("html", "text", "text/html")
195 or .file_type in ("html", "unknown")
196 )
197 and (
198 (
199 .size < 1500
200 and any(file.explode(.), length(.scan.html.scripts) > 0)
201 )
202 // bypass the size requirement under these conditions
203 or (
204 // sync with https://github.com/sublime-security/sublime-rules/blob/main/detection-rules/attachment_svg_embedded_js.yml
205 strings.ilike(file.parse_text(.,
206 encodings=[
207 "ascii",
208 "utf8",
209 "utf16-le"
210 ]
211 ).text,
212 "*onload*",
213 "*window.location.href*",
214 "*onerror*",
215 "*CDATA*",
216 "*<script*",
217 "*</script*",
218 "*atob*",
219 "*location.assign*",
220 "*decodeURIComponent*"
221 )
222 )
223 )
224 )
225 ),
226 (
227 any(attachments,
228 (
229 .content_type in ("html", "text", "text/html")
230 or .file_type in ("html", "unknown")
231 )
232 and any(recipients.to,
233 // the html attachment contains a receipient email address
234 strings.contains(file.parse_html(..).raw, .email.email)
235 // the sld of the domain is in the attachment name
236 or strings.contains(..file_name, .email.domain.sld)
237 )
238 )
239 ),
240 // eml attachments
241 (
242 any(filter(attachments, .content_type == "message/rfc822"),
243 // which contain attachments
244 // this is the same logic as above
245 any(file.parse_eml(.).attachments,
246 (
247 .content_type in ("html", "text", "text/html")
248 or .file_type in ("html", "unknown")
249 or .file_type == "pdf"
250 )
251 and (
252 regex.icontains(.file_name,
253 '(?:voice|aud[il1][o0]|call|missed|caii|mail|message|recording|call|caii|transcr[il1]ption|v[nm]|audi[o0]|play|listen|unheard|msg)',
254 // contains a time
255 // 01min , 60secs
256 '0?[1-9]\s*min(?:(?:ute)?s)?',
257 '\d{1,2}\s*s(?:ec(?:ond)?s)?',
258 // (00:50s)
259 // 3:26 seconds
260 '[\(\[]?(?:\d{1,2}[\:\s-])\d{1,2}[\)\]]?\s*(?:s(?:(?:ecs?)onds)?)[\)\]]?',
261 // 03min25secs
262 '0?[1-9]\s*min(?:(?:ute)?s)?\d{1,2}\s*s(?:ec(?:ond)?s)?',
263 // [0:39]
264 // (0:39)
265 '[\(\[](?:\d{1,2}[\:\s-])\d{1,2}[\)\]]\s',
266 // contains an emoji
267 '[\x{1F300}-\x{1F5FF}\x{1F600}-\x{1F64F}\x{1F680}-\x{1F6FF}\x{1F700}-\x{1F77F}\x{1F780}-\x{1F7FF}\x{1F900}-\x{1F9FF}\x{2600}-\x{26FF}\x{2700}-\x{27BF}\x{2300}-\x{23FF}]'
268 )
269 // somtimes there is no name, it's just the extension which is also strange
270 or .file_name in~ (".htm", ".html")
271 // or sometimes it has no name....
272 or .file_name is null
273 )
274 )
275 )
276 ),
277 // attached eml sender/recipeient/subject are all the same as the outer
278 // and have an attachment or body links
279 (
280 any(filter(attachments, .content_type == "message/rfc822"),
281 // which contain attachments
282 // this is the same logic as above
283 file.parse_eml(.).subject.subject == subject.subject
284 and file.parse_eml(.).sender.email.email == sender.email.email
285 and (
286 length(file.parse_eml(.).recipients.to) == length(recipients.to)
287 and all(recipients.to,
288 .email.email in map(file.parse_eml(..).recipients.to,
289 .email.email
290 )
291 )
292 )
293 and (
294 // there are attachments
295 length(file.parse_eml(.).attachments) > 0
296 // or body links
297 or length(filter(file.parse_eml(.).body.links,
298 .href_url.domain.domain not in $org_domains
299 and .href_url.domain.root_domain not in $org_domains
300 )
301 ) > 0
302 )
303 )
304 ),
305 // the body links contain the recipients email
306 (
307 length(filter(recipients.to, .email.email != "" or .email.domain.valid)) > 0
308 and any(body.links,
309 any(recipients.to,
310 strings.icontains(..href_url.url, .email.email)
311 or strings.icontains(..href_url.url, .email.local_part)
312 )
313 )
314 ),
315 (
316 length(body.current_thread.text) < 700
317 and regex.icontains(body.current_thread.text,
318 'Méssãge|Méssage|Recéived|Addréss'
319 )
320 ),
321 (
322 // sender domain matches no body domains
323 // only inspect "links" that have a display_text and display_url is null to remove "plain text" email address from being caught
324 length(filter(body.links,
325 .display_text is not null
326 and .display_url.url is null
327 and .href_url.domain.valid
328 )
329 ) > 0
330 and all(filter(body.links,
331 .display_text is not null
332 and .display_url.url is null
333 and .href_url.domain.valid
334 ),
335 .href_url.domain.root_domain != sender.email.domain.root_domain
336 and .href_url.domain.root_domain not in $org_domains
337 and .href_url.domain.root_domain not in ("aka.ms")
338 and .href_url.domain.root_domain not in (
339 "unitelvoice.com",
340 "googleapis.com",
341 "dialmycalls.com",
342 "ringcentral.biz",
343 "google.com"
344 )
345 )
346 ),
347 // the body links contain vm related phrases
348 (
349 any(body.links,
350 regex.contains(.display_text, '[^a-z]*[A-Z][^a-z]*')
351 and regex.icontains(.display_text,
352 '(v[nm]|voice|audi[o0]|call|missed|caii)(\s?|-)(mail|message|recording|call|caii)|transcription|open mp3|audi[o0] note|listen|playback|\(?(?:\*\*\*|[0-9]{3})?.(?:\*\*\*|[0-9]{3})[^a-z]{0,2}(?:\*{4}|\d+\*+)|play'
353 )
354 // negate FP terms in link display texts
355 and not strings.icontains(.display_text, 'voice call center')
356 )
357 ),
358 (
359 any(body.links,
360 .href_url.path == "/ctt"
361 and regex.icontains(.display_text,
362 '(v[nm]|voice|audi[o0]|call|missed|caii)(\s?|-)(mail|message|recording|call|caii)|transcription|open mp3|audi[o0] note|listen|playback|\(?(?:\*\*\*|[0-9]{3})?.(?:\*\*\*|[0-9]{3})[^a-z]{0,2}(?:\*{4}|\d+\*+)|play'
363 )
364 // negate FP terms in link display texts
365 and not strings.icontains(.display_text, 'voice call center')
366 )
367 ),
368 // new domains
369 (
370 any(body.links,
371 network.whois(.href_url.domain).days_old < 10
372 and not strings.icontains(.href_url.path, "unsubscribe")
373 )
374 ),
375 // sld use in sender/subject selements
376 (
377 any(recipients.to,
378 // recipient's SLD is in the sender's display name
379 strings.icontains(sender.display_name, .email.domain.sld)
380 // recipient's SLD is in the sender's display name
381 or strings.icontains(subject.subject, .email.domain.sld)
382 // recipient's SLD is in the senders local_part
383 or strings.icontains(sender.email.local_part, .email.domain.sld)
384 )
385 ),
386 // often times the subject or sender display name will contain time references
387 (
388 any([sender.display_name, subject.subject, body.current_thread.text],
389 regex.icontains(.,
390 // 01min , 60secs
391 '0?[1-9]\s*min(?:(?:ute)?s)?\b',
392 '\d{1,2}\s*s(?:ec(?:ond)?s)?\b',
393 // (00:50s)
394 // 3:26 seconds
395 '[\(\[]?(?:\d{1,2}[\:\s-])\d{1,2}[\)\]]?\s*(?:s(?:(?:ecs?)onds)?)[\)\]]?',
396 // 03min25secs
397 '0?[1-9]\s*min(?:(?:ute)?s)?\d{1,2}\s*s(?:ec(?:ond)?s)?',
398 // [0:39]
399 // (0:39)
400 '[\(\[](?:\d{1,2}[\:\s-])\d{1,2}[\)\]]\s'
401 )
402 )
403 // resuse the same logic against ORC output of message_screenshot
404 or any(filter(file.explode(beta.message_screenshot()), .depth == 0),
405 regex.icontains(.scan.ocr.raw,
406 // 01min , 60secs
407 '0?[1-9]\s*min(?:(?:ute)?s)?\b',
408 '\d{1,2}\s*s(?:ec(?:ond)?s)?\b',
409 // (00:50s)
410 // 3:26 seconds
411 '[\(\[]?(?:\d{1,2}[\:\s-])\d{1,2}[\)\]]?\s*(?:s(?:(?:ecs?)onds)?)[\)\]]?',
412 // 03min25secs
413 '0?[1-9]\s*min(?:(?:ute)?s)?\d{1,2}\s*s(?:ec(?:ond)?s)?',
414 // [0:39]
415 // (0:39)
416 '[\(\[](?:\d{1,2}[\:\s-])\d{1,2}[\)\]]\s'
417 )
418 )
419 ),
420 // often times the subject or sender display name will contain dates
421 (
422 any([sender.display_name, subject.subject],
423 // days of week
424 any([
425 'monday',
426 'tuesday',
427 'wednesday',
428 'thursday',
429 'friday',
430 'saturday',
431 'sunday'
432 ],
433 strings.icontains(.., .)
434 )
435 // months
436 // may is problematic for words like "Mayor", "Maybe", "MayFlower", etc
437 or any([
438 "January",
439 "February",
440 "March",
441 "April",
442 "June",
443 "July",
444 "August",
445 "September",
446 "October",
447 "November",
448 "December"
449 ],
450 strings.icontains(.., .)
451 )
452 // use a regex for May
453 or regex.icontains(., '\bmay\b')
454 // common date formats
455 or regex.contains(.,
456 // YYYY-MM-DD or YY-MM-DD (ISO 8601 format)
457 '\d{2}(\d{2})?-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])',
458 // MM/DD/YYYY or MM/DD/YY (US format)
459 '(0[1-9]|1[0-2])/(0[1-9]|[12]\d|3[01])/\d{2}(\d{2})?',
460 // DD/MM/YYYY or DD/MM/YY (European format)
461 '(0[1-9]|[12]\d|3[01])/(0[1-9]|1[0-2])/\d{2}(\d{2})?',
462 // Month DD, YYYY or Month DD, YY (e.g., March 15, 2024 or March 15, 24)
463 '(January|February|March|April|May|June|July|August|September|October|November|December) (0[1-9]|[12]\d|3[01]), \d{2}(\d{2})?'
464 )
465 // common time formats
466 or regex.contains(.,
467 // Example: 23:45, 08:30
468 '([01]\d|2[0-3]):([0-5]\d)',
469 // Example: 23:45:59, 08:30:12
470 '([01]\d|2[0-3]):([0-5]\d):([0-5]\d)',
471 // Example: 08:30 AM, 12:45 pm
472 '(0[1-9]|1[0-2]):([0-5]\d)\s?([AaPp][Mm])',
473 // Example: 08:30 AM, 12:45 pm
474 '(0[1-9]|1[0-2]):([0-5]\d):([0-5]\d) ?([AaPp][Mm])'
475 )
476 )
477 // or use the OCR results from beta.message_screenshot
478 or any(filter(file.explode(beta.message_screenshot()), .depth == 0),
479 // days of week
480 any([
481 'monday',
482 'tuesday',
483 'wednesday',
484 'thursday',
485 'friday',
486 'saturday',
487 'sunday'
488 ],
489 strings.icontains(..scan.ocr.raw, .)
490 )
491 // months
492 // may is problematic for words like "Mayor", "Maybe", "MayFlower", etc
493 or any([
494 "January",
495 "February",
496 "March",
497 "April",
498 "June",
499 "July",
500 "August",
501 "September",
502 "October",
503 "November",
504 "December"
505 ],
506 strings.icontains(..scan.ocr.raw, .)
507 )
508 // use a regex for May
509 or regex.contains(.scan.ocr.raw, '\bMay\b')
510 // common date formats
511 or regex.contains(.scan.ocr.raw,
512 // YYYY-MM-DD or YY-MM-DD (ISO 8601 format)
513 '\d{2}(\d{2})?-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])',
514 // MM/DD/YYYY or MM/DD/YY (US format)
515 '(0[1-9]|1[0-2])/(0[1-9]|[12]\d|3[01])/\d{2}(\d{2})?',
516 // DD/MM/YYYY or DD/MM/YY (European format)
517 '(0[1-9]|[12]\d|3[01])/(0[1-9]|1[0-2])/\d{2}(\d{2})?',
518 // Month DD, YYYY or Month DD, YY (e.g., March 15, 2024 or March 15, 24)
519 '(January|February|March|April|May|June|July|August|September|October|November|December) (0[1-9]|[12]\d|3[01]), \d{2}(\d{2})?'
520 )
521 // common time formats
522 or regex.contains(.scan.ocr.raw,
523 // Example: 23:45, 08:30
524 '([01]\d|2[0-3]):([0-5]\d)',
525 // Example: 23:45:59, 08:30:12
526 '([01]\d|2[0-3]):([0-5]\d):([0-5]\d)',
527 // Example: 08:30 AM, 12:45 pm
528 '(0[1-9]|1[0-2]):([0-5]\d)\s?([AaPp][Mm])',
529 // Example: 08:30 AM, 12:45 pm
530 '(0[1-9]|1[0-2]):([0-5]\d):([0-5]\d) ?([AaPp][Mm])'
531 )
532 )
533 ),
534 // there are often emoji in the sender display name
535 (
536 any([sender.display_name, subject.subject],
537 // contains an emoji
538 regex.contains(.,
539 '[\x{1F300}-\x{1F5FF}\x{1F600}-\x{1F64F}\x{1F680}-\x{1F6FF}\x{1F700}-\x{1F77F}\x{1F780}-\x{1F7FF}\x{1F900}-\x{1F9FF}\x{2600}-\x{26FF}\x{2700}-\x{27BF}\x{2300}-\x{23FF}]'
540 )
541 // negate where the emoji occur in tags
542 and not regex.contains(.,
543 '^(?:\[[^\]]*\]\s*)*\[[^\]]*[\x{1F300}-\x{1F5FF}\x{1F600}-\x{1F64F}\x{1F680}-\x{1F6FF}\x{1F700}-\x{1F77F}\x{1F780}-\x{1F7FF}\x{1F900}-\x{1F9FF}\x{2600}-\x{26FF}\x{2700}-\x{27BF}\x{2300}-\x{23FF}][^\]]*\]'
544 )
545 )
546 ),
547 // an attachment is a pdf, image, or document that contains a url
548 (
549 1 <= length(attachments) <= 2
550 and any(attachments,
551 (
552 .file_type in $file_types_images
553 or .file_type == "pdf"
554 or .file_extension in $file_extensions_macros
555 )
556 and any(file.explode(.),
557 .scan.qr.type == "url"
558 or strings.icontains(.scan.qr.data, 'http')
559 or any(recipients.to,
560 strings.icontains(..scan.qr.data, .email.local_part)
561 or strings.icontains(..scan.qr.data, .email.email)
562 )
563 )
564 )
565 )
566 )
567
568 // negating legit replies and legitimate audio file attachments and known voicemail senders
569 and not (
570 sender.email.domain.valid
571 and sender.email.domain.root_domain in (
572 "magicjack.com",
573 "unitelvoice.com",
574 "voipinterface.net",
575 "ringcentral.biz",
576 "verizonwireless.com",
577 "t-mobile.com",
578 "justcall.io",
579 "airtel.com"
580 )
581 )
582 and not (
583 any(filter(attachments, strings.starts_with(.content_type, "audio")),
584 // confirm the content type with file.explode
585 // we have seen attachments claim to be audio/* files, only to be exploded as something else
586 any(file.explode(.), strings.starts_with(.flavors.mime, "audio"))
587 )
588 )
589 and not (
590 (
591 strings.istarts_with(subject.subject, "RE:")
592 // out of office auto-reply
593 // the NLU model will handle these better natively soon
594 or strings.istarts_with(subject.subject, "Automatic reply:")
595 )
596 and (
597 length(headers.references) > 0
598 or any(headers.hops, any(.fields, strings.ilike(.name, "In-Reply-To")))
599 )
600 )
601 // negate highly trusted sender domains unless they fail DMARC authentication
602 and (
603 (
604 sender.email.domain.root_domain in $high_trust_sender_root_domains
605 and not headers.auth_summary.dmarc.pass
606 )
607 or sender.email.domain.root_domain not in $high_trust_sender_root_domains
608 )
609 // bounce-back negations
610 and not any(attachments,
611 any(file.parse_eml(.).attachments,
612 .content_type == "message/delivery-status"
613 )
614 )
615 // bounce-back negations
616 and not (
617 any(attachments,
618 .content_type in ("message/delivery-status", "text/calendar")
619 )
620 )
621 // negate bouncebacks from proofpoint
622 and not (
623 sender.display_name == "Mail Delivery Subsystem"
624 and strings.ends_with(headers.message_id, "pphosted.com>")
625 and any(headers.hops,
626 .index == 0 and strings.contains(.received.server.raw, "pphosted.com")
627 )
628 and any(attachments, .content_type == "message/rfc822")
629 )
630 // an impersonated high trust domain
631 and (
632 (
633 sender.email.domain.root_domain in $high_trust_sender_root_domains
634 and not headers.auth_summary.dmarc.pass
635 )
636
637 // sender profile
638 or (
639 (
640 not sender.email.domain.root_domain in $org_domains
641 and (profile.by_sender().prevalence not in ("common"))
642 and not profile.by_sender().solicited
643 )
644 or (
645 profile.by_sender().any_messages_malicious_or_spam
646 and not profile.by_sender().any_false_positives
647 )
648 // match if the sender is in org domains but failed auth
649 or (
650 sender.email.domain.domain in $org_domains
651 and not headers.auth_summary.dmarc.pass
652 )
653 )
654 )
655attack_types:
656 - "Credential Phishing"
657tactics_and_techniques:
658 - "Social engineering"
659detection_methods:
660 - "Content analysis"
661 - "Natural Language Understanding"
662 - "Sender analysis"
663 - "URL analysis"
664id: "74ba7787-e543-5ce8-b6eb-e1ecdb8f1d67"