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