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 (0 < length(body.links) <= 15 or 0 < length(attachments) <= 3)
 11  // the subject or display_name need some keywords which are voicemail related
 12  and (
 13    any([subject.subject, sender.display_name],
 14        regex.icontains(.,
 15                        // split phrases that occur within 3 words between or only punctuation between them
 16                        '(?:v[nm](\b|[[:punct:]])?|\bvoice(?:mail|message)?|audio|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)',
 17                        // split phrases that start with "caller" that occur within 3 words between or only punctation 
 18                        'ca[li1][li1](?:er)?(?:\w+(\s\w+)?|[[:punct:]]+|\s+){0,3}(?:v[nm](\b|[[:punct:]])?|\bvoice(?:mail|message)?|audio|missed(?:\sa\s)?|left( a)?)',
 19                        // strong phrases
 20                        '(?:open mp3|audio 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)',
 21                        // starts in the format of `(4)` and contains some voicemail keywords
 22                        '^\(\d\)\s(?:\w+(\s\w+)?|[[:punct:]]+|\s+){0,3}(?:message|voip|voice|unread|call)',
 23                        'ca[li1][li1](?:er)?(?:\w+(\s\w+)?|[[:punct:]]+|\s+){0,3}(?:playback|transcript)',
 24
 25                        // obfuscated phone number with at least one digit in the area code and at least one obfuscated number in the last group
 26                        // 555-555-555X, 555-555-XXXX, 555-5XX-XXXX
 27                        '\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]',
 28                        // obfuscated phone number with at least one digit in the prefix
 29                        // XXX-555-5555, XXX-5XX-XXXX
 30                        '\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',
 31        )
 32    )
 33    // body.current_thread.text inspection should be very specific to avoid FP
 34    or regex.icontains(
 35                       strings.replace_confusables(body.current_thread.text),
 36                       //body.current_thread.text,
 37                       'you (?:have |received )*(?:\w+(\s\w+)?|[[:punct:]]+|\s+){0,3}\bvoice\s?(?:mail|audio|message)',
 38                       'sent (?:from|by) (?:your )?voice (?:mail )?system',
 39                       'new (?:voice(?:mail)?|audio) (?:message|notification|record)',
 40                       'voicemail (is )?attached',
 41                       'an? (?:new )?encrypted voicemail',
 42                       'a (?:new )?pending message',
 43                       'Your? have (?: an?)?incoming voiceRec',
 44                       "you(?:\'ve| have) a (?:new )?missed ca[li1][li1]",
 45                       'New Voicemail Received',
 46                       'left you a (?:\w+(\s\w+)?|[[:punct:]]+|\s+){0,3}(?:voice(?:mail)?|audio)(?: message)?',
 47                       'New missed ca[li1][li1] record',
 48                       'voicemail transcript(?:ion)?',
 49                       'Listen to VoiceMail',
 50                       'New voicemail from'
 51    )
 52    // phishing template observed https://platform.sublime.security/messages/341eed2be003036cdd3eeee575202df8a7472b6567d0dfa0f99c3b3fb42a8e7f
 53    or strings.icontains(body.html.raw, '<title>Voicemail Notification</title>')
 54    or strings.icontains(body.html.raw, '<!-- Voicemail phone logo')
 55  )
 56
 57  and 2 of (
 58    (
 59      // the sender is a freemail
 60      sender.email.domain.root_domain in $free_email_providers
 61    ),
 62    (
 63      any(ml.nlu_classifier(body.current_thread.text).intents,
 64          .name in ("cred_theft") and .confidence in ("medium", "high")
 65      )
 66    ),
 67    (
 68      any(attachments,
 69          .content_type in ("html", "text", "text/html")
 70          and any(ml.logo_detect(file.html_screenshot(.)).brands,
 71                  .name in ("Microsoft") and .confidence in ("medium", "high")
 72          )
 73      )
 74    ),
 75    (
 76      regex.icontains(sender.display_name,
 77                      '(voice|audio|call|missed|caii)(\s?|-)(mail|message|recording|call|caii)|(transcription|Caller.?ID)'
 78      )
 79    ),
 80    // attachment names are often HTML and voice mail related
 81    (
 82      any(attachments,
 83          (
 84            .content_type in ("html", "text", "text/html")
 85            or .file_type in ("html", "unknown")
 86            or .file_type == "pdf"
 87          )
 88          and (
 89            regex.icontains(.file_name,
 90                            '(?:voice|audio|call|missed|caii|mail|message|recording|call|caii|transcription|v[nm]|audio|play|listen|unheard|msg)',
 91                            // contains a time
 92                            // 01min , 60secs
 93                            '0?[1-9]\s*min(?:(?:ute)?s)?',
 94                            '\d{1,2}\s*s(?:ec(?:ond)?s)?',
 95                            // (00:50s)
 96                            // 3:26 seconds
 97                            '[\(\[]?(?:\d{1,2}[\:\s-])\d{1,2}[\)\]]?\s*(?:s(?:(?:ecs?)onds)?)[\)\]]?',
 98                            // 03min25secs
 99                            '0?[1-9]\s*min(?:(?:ute)?s)?\d{1,2}\s*s(?:ec(?:ond)?s)?',
100                            // [0:39] 
101                            // (0:39) 
102                            '[\(\[](?:\d{1,2}[\:\s-])\d{1,2}[\)\]]\s',
103                            // contains an emoji
104                            '[\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}]'
105            )
106            // somtimes there is no name, it's just the extension which is also strange
107            or .file_name in~ (".htm", ".html")
108            // or sometimes it has no name....
109            or .file_name is null
110          )
111      )
112    ),
113    // html attachment is small and contains javascript
114    (
115      any(attachments,
116          (
117            .content_type in ("html", "text", "text/html")
118            or .file_type in ("html", "unknown")
119          )
120          and (.size < 1500 and any(file.explode(.), length(.scan.html.scripts) > 0)
121          )
122      )
123    ),
124    (
125      any(attachments,
126          (
127              .content_type in ("html", "text", "text/html")
128              or .file_type in ("html", "unknown")
129          )
130          and any(recipients.to,
131                  // the html attachment contains a receipient email address
132                  strings.contains(file.parse_html(..).raw, .email.email)
133                  // the sld of the domain is in the attachment name
134                  or strings.contains(..file_name, .email.domain.sld)
135              )
136      )
137    ),
138    // the body links contain the recipients email
139    (
140      length(filter(recipients.to, .email.email != "" or .email.domain.valid)) > 0
141      and any(body.links,
142              any(recipients.to,
143                  strings.icontains(..href_url.url, .email.email)
144                  or strings.icontains(..href_url.url, .email.local_part)
145              )
146      )
147    ),
148    (
149      length(body.current_thread.text) < 700
150      and regex.icontains(body.current_thread.text,
151                          'Méssãge|Méssage|Recéived|Addréss'
152      )
153    ),
154    (
155      // sender domain matches no body domains
156      // only inspect "links" that have a display_text and display_url is null to remove "plain text" email address from being caught
157      length(filter(body.links,
158                    .display_text is not null and .display_url.url is null and .href_url.domain.valid
159             )
160      ) > 0
161      and all(filter(body.links,
162                     .display_text is not null and .display_url.url is null and .href_url.domain.valid
163              ),
164              .href_url.domain.root_domain != sender.email.domain.root_domain
165              and .href_url.domain.root_domain not in $org_domains
166              and .href_url.domain.root_domain not in ("aka.ms")
167              and .href_url.domain.root_domain not in (
168                "unitelvoice.com",
169                "googleapis.com",
170                "dialmycalls.com",
171                "ringcentral.biz"
172              )
173      )
174    ),
175    // the body links contain vm related phrases
176    (
177      any(body.links,
178          regex.contains(.display_text, '[^a-z]*[A-Z][^a-z]*')
179          and regex.icontains(.display_text,
180                              '(v[nm]|voice|audio|call|missed|caii)(\s?|-)(mail|message|recording|call|caii)|transcription|open mp3|audio note|listen|playback|\(?(?:\*\*\*|[0-9]{3})?.(?:\*\*\*|[0-9]{3})[^a-z]{0,2}(?:\*{4}|\d+\*+)|play'
181          )
182          // negate FP terms in link display texts
183          and not strings.icontains(.display_text, 'voice call center')
184      )
185    ),
186    (
187      any(body.links,
188          .href_url.path == "/ctt"
189          and regex.icontains(.display_text,
190                              '(v[nm]|voice|audio|call|missed|caii)(\s?|-)(mail|message|recording|call|caii)|transcription|open mp3|audio note|listen|playback|\(?(?:\*\*\*|[0-9]{3})?.(?:\*\*\*|[0-9]{3})[^a-z]{0,2}(?:\*{4}|\d+\*+)|play'
191          )
192          // negate FP terms in link display texts
193          and not strings.icontains(.display_text, 'voice call center')
194      )
195    ),
196    // new domains
197    (
198      any(body.links,
199          network.whois(.href_url.domain).days_old < 10
200          and not strings.icontains(.href_url.path, "unsubscribe")
201      )
202    ),
203    (
204      any(recipients.to,
205          // recipient's SLD is in the sender's display name
206          strings.icontains(sender.display_name, .email.domain.sld)
207          // recipient's SLD is in the sender's display name
208          or strings.icontains(subject.subject, .email.domain.sld)
209      )
210    ),
211    // often times the subject or sender display name will contain time references
212    (
213      any([sender.display_name, subject.subject, body.current_thread.text],
214          regex.icontains(.,
215                          // 01min , 60secs
216                          '0?[1-9]\s*min(?:(?:ute)?s)?\b',
217                          '\d{1,2}\s*s(?:ec(?:ond)?s)?\b',
218                          // (00:50s)
219                          // 3:26 seconds
220                          '[\(\[]?(?:\d{1,2}[\:\s-])\d{1,2}[\)\]]?\s*(?:s(?:(?:ecs?)onds)?)[\)\]]?',
221                          // 03min25secs
222                          '0?[1-9]\s*min(?:(?:ute)?s)?\d{1,2}\s*s(?:ec(?:ond)?s)?',
223                          // [0:39] 
224                          // (0:39) 
225                          '[\(\[](?:\d{1,2}[\:\s-])\d{1,2}[\)\]]\s'
226          )
227      )
228    ),
229    // often times the subject or sender display name will contain dates
230    (
231      any([sender.display_name, subject.subject],
232          // days of week
233          any([
234                'monday',
235                'tuesday',
236                'wednesday',
237                'thursday',
238                'friday',
239                'saturday',
240                'sunday'
241              ],
242              strings.icontains(.., .)
243          )
244          // months
245          // may is problematic for words like "Mayor", "Maybe", "MayFlower", etc
246          or any([
247                   "January",
248                   "February",
249                   "March",
250                   "April",
251                   "June",
252                   "July",
253                   "August",
254                   "September",
255                   "October",
256                   "November",
257                   "December"
258                 ],
259                 strings.icontains(.., .)
260          )
261          // use a regex for May
262          or regex.icontains(., '\bmay\b')
263          // common date formats
264          or regex.contains(.,
265                            // YYYY-MM-DD or YY-MM-DD (ISO 8601 format)
266                            '\d{2}(\d{2})?-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])',
267                            // MM/DD/YYYY or MM/DD/YY (US format)
268                            '(0[1-9]|1[0-2])/(0[1-9]|[12]\d|3[01])/\d{2}(\d{2})?',
269                            // DD/MM/YYYY or DD/MM/YY (European format)
270                            '(0[1-9]|[12]\d|3[01])/(0[1-9]|1[0-2])/\d{2}(\d{2})?',
271                            // Month DD, YYYY or Month DD, YY (e.g., March 15, 2024 or March 15, 24)
272                            '(January|February|March|April|May|June|July|August|September|October|November|December) (0[1-9]|[12]\d|3[01]), \d{2}(\d{2})?'
273          )
274          // common time formats
275          or regex.contains(.,
276                            // Example: 23:45, 08:30
277                            '([01]\d|2[0-3]):([0-5]\d)',
278                            // Example: 23:45:59, 08:30:12
279                            '([01]\d|2[0-3]):([0-5]\d):([0-5]\d)',
280                            // Example: 08:30 AM, 12:45 pm
281                            '(0[1-9]|1[0-2]):([0-5]\d)\s?([AaPp][Mm])',
282                            // Example: 08:30 AM, 12:45 pm
283                            '(0[1-9]|1[0-2]):([0-5]\d):([0-5]\d) ?([AaPp][Mm])'
284          )
285      )
286    ),
287    // there are often emoji in the sender display name
288    (
289      any([sender.display_name, subject.subject],
290          // contains an emoji
291          regex.contains(.,
292                         '[\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}]'
293          )
294          // negate where the emoji occur in tags
295          and not regex.contains(.,
296                                 '^(?:\[[^\]]*\]\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}][^\]]*\]'
297          )
298      )
299    ),
300    // an attachment is a pdf, image, or document that contains a url
301    (
302      1 <= length(attachments) <= 2
303      and any(attachments,
304              (
305                .file_type in $file_types_images
306                or .file_type == "pdf"
307                or .file_extension in $file_extensions_macros
308              )
309              and any(file.explode(.),
310                      .scan.qr.type == "url"
311                      or strings.icontains(.scan.qr.data, 'http')
312                      or any(recipients.to,
313                             strings.icontains(..scan.qr.data, .email.local_part)
314                             or strings.icontains(..scan.qr.data, .email.email)
315                      )
316              )
317      )
318    )
319  )
320  
321  // negating legit replies and legitimate audio file attachments and known voicemail senders
322  and not (
323    sender.email.domain.valid
324    and sender.email.domain.root_domain in (
325      "magicjack.com",
326      "unitelvoice.com",
327      "voipinterface.net",
328      "ringcentral.biz",
329      "verizonwireless.com",
330      "t-mobile.com",
331      "justcall.io"
332    )
333  )
334  and not any(attachments, strings.starts_with(.content_type, "audio"))
335  and not (
336    (
337      strings.istarts_with(subject.subject, "RE:")
338      // out of office auto-reply
339      // the NLU model will handle these better natively soon
340      or strings.istarts_with(subject.subject, "Automatic reply:")
341    )
342    and (
343      length(headers.references) > 0
344      or any(headers.hops, any(.fields, strings.ilike(.name, "In-Reply-To")))
345    )
346  )
347  // negate highly trusted sender domains unless they fail DMARC authentication
348  and (
349    (
350      sender.email.domain.root_domain in $high_trust_sender_root_domains
351      and not headers.auth_summary.dmarc.pass
352    )
353    or sender.email.domain.root_domain not in $high_trust_sender_root_domains
354  )
355  // bounce-back negations
356  and not any(attachments,
357            any(file.parse_eml(.).attachments,
358                .content_type == "message/delivery-status"
359            )
360      )
361  // bounce-back negations
362  and not (
363    any(attachments,
364        .content_type in ("message/delivery-status", "text/calendar")
365    )
366  )
367  // negate bouncebacks from proofpoint
368  and not (
369    sender.display_name == "Mail Delivery Subsystem"
370    and strings.ends_with(headers.message_id, "pphosted.com>")
371    and any(headers.hops,
372            .index == 0 and strings.contains(.received.server.raw, "pphosted.com")
373    )
374    and any(attachments, .content_type == "message/rfc822")
375  )
376  // sender profile
377  and (
378    (
379      profile.by_sender().prevalence not in ("common")
380      and not profile.by_sender().solicited
381    )
382    or (
383      profile.by_sender().any_messages_malicious_or_spam
384      and not profile.by_sender().any_false_positives
385    )
386  )  
387attack_types:
388  - "Credential Phishing"
389tactics_and_techniques:
390  - "Social engineering"
391detection_methods:
392  - "Content analysis"
393  - "Natural Language Understanding"
394  - "Sender analysis"
395  - "URL analysis"
396id: "74ba7787-e543-5ce8-b6eb-e1ecdb8f1d67"
to-top