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)?)',
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"