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"
to-top