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