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