Brand impersonation: Microsoft with low reputation links

Detects low reputation links with Microsoft specific indicators in the body.

Sublime rule (View on GitHub)

  1name: "Brand impersonation: Microsoft with low reputation links"
  2description: "Detects low reputation links with Microsoft specific indicators in the body."
  3type: "rule"
  4severity: "medium"
  5source: |
  6 type.inbound
  7 and 0 < length(body.links) < 50
  8 // suspicious link
  9 and any(body.links,
 10         (
 11           .href_url.domain.tld == "ru"
 12           or .href_url.domain.root_domain not in $tranco_1m
 13           or .href_url.domain.domain in $free_file_hosts
 14           or .href_url.domain.root_domain in $free_file_hosts
 15           or .href_url.domain.root_domain in $free_subdomain_hosts
 16           or .href_url.domain.domain in $url_shorteners
 17           or .href_url.domain.domain in $social_landing_hosts
 18           // account for URL rewrites
 19           or (
 20             any(.href_url.query_params_decoded["domain"],
 21                 strings.parse_domain(.).tld == "ru"
 22                 or strings.parse_domain(.).root_domain not in~ $tranco_1m
 23                 or strings.parse_domain(.).domain in~ $free_file_hosts
 24                 or strings.parse_domain(.).root_domain in~ $free_file_hosts
 25                 or strings.parse_domain(.).root_domain in~ $free_subdomain_hosts
 26                 or strings.parse_domain(.).domain in~ $url_shorteners
 27                 or strings.parse_domain(.).domain in~ $social_landing_hosts
 28             )
 29           )
 30           or 
 31 
 32           // mass mailer link, masks the actual URL
 33           .href_url.domain.root_domain in (
 34             "hubspotlinks.com",
 35             "mandrillapp.com",
 36             "sendgrid.net",
 37             "rs6.net"
 38           )
 39 
 40           // Google AMP redirect
 41           or (
 42             .href_url.domain.sld == "google"
 43             and strings.starts_with(.href_url.path, "/amp/")
 44           )
 45 
 46           // Recipient email address in link
 47           or any(body.links,
 48                  any(recipients.to,
 49                      strings.icontains(..href_url.url, .email.email)
 50                      and any(recipients.to, .email.domain.valid)
 51                  )
 52           )
 53           or .href_url.domain.root_domain == "beehiiv.com"
 54         )
 55 
 56         // exclude sources of potential FPs
 57         and (
 58           .href_url.domain.root_domain not in (
 59             "svc.ms",
 60             "sharepoint.com",
 61             "1drv.ms",
 62             "microsoft.com",
 63             "aka.ms",
 64             "msftauthimages.net",
 65             "office.com",
 66             "microsoftproject.com"
 67           )
 68           or any(body.links, .href_url.domain.domain in $free_file_hosts)
 69         )
 70         and .href_url.domain.root_domain not in $org_domains
 71         and .href_url.domain.valid
 72 )
 73 
 74 // not a reply
 75 and (length(headers.references) == 0 or headers.in_reply_to is null)
 76 
 77 // Microsoft logo
 78 and (
 79   any(attachments,
 80       .file_type in $file_types_images
 81       and any(ml.logo_detect(.).brands, strings.starts_with(.name, "Microsoft"))
 82   )
 83   or strings.istarts_with(strings.replace_confusables(body.current_thread.text),
 84                           "Microsoft "
 85   )
 86   or (
 87     regex.imatch(strings.replace_confusables(body.current_thread.text),
 88                  '[\n\s]*[o0O]ff[il1]ce\b.*',
 89                  '\[[a-z\s]{3,16}\]\smicrosoft.*'
 90     )
 91     and not regex.icontains(strings.replace_confusables(body.current_thread.text
 92                             ),
 93                             'office (for lease|rent|sale)'
 94     )
 95   )
 96   or any(ml.logo_detect(file.message_screenshot()).brands,
 97          strings.starts_with(.name, "Microsoft")
 98   )
 99   or (
100     regex.icontains(body.html.raw,
101                     '<table[^>]*>\s*<tbody[^>]*>\s*<tr[^>]*>\s*(<td[^>]*bgcolor="#[0-9A-Fa-f]{6}"[^>]*>\s*&nbsp;\s*</td>\s*){2}\s*</tr>\s*<tr[^>]*>\s*(<td[^>]*bgcolor="#[0-9A-Fa-f]{6}"[^>]*>\s*&nbsp;\s*</td>\s*){2}'
102     )
103     or regex.icontains(body.html.raw,
104                        '<td style="background:\s*rgb\(246,\s*93,\s*53\);\s*height:\d+px;">.*?<td style="background:\s*rgb\(129,\s*187,\s*5\);\s*height:\d+px;">.*?<td style="background:\s*rgb\(4,\s*165,\s*240\);\s*height:\d+px;">.*?<td style="background:\s*rgb\(255,\s*186,\s*7\);\s*height:\d+px;">'
105     )
106     or 4 of (
107       regex.icontains(body.html.raw,
108                       '<td style="width:.\d.px;.height:.\d.px;.background-color:.rgb\(245, 189, 67\);">.{0,10}</td>'
109       ),
110       regex.icontains(body.html.raw,
111                       '<td style="width:.\d.px;.height:.\d.px;.background-color:.rgb\(137, 184, 57\);">.{0,10}</td>'
112       ),
113       regex.icontains(body.html.raw,
114                       '<td style="width:.\d.px;.height:.\d.px;.background-color:.rgb\(217, 83, 51\);">.{0,10}</td>'
115       ),
116       regex.icontains(body.html.raw,
117                       '<td style="width:.\d.px;.height:.\d.px;.background-color:.rgb\(71, 160, 218\);">.{0,10}</td>'
118       )
119     )
120   )
121   or regex.icontains(body.html.raw,
122                      '<table[^>]*>\s*<tbody[^>]*>\s*<tr[^>]*>\s*(<td[^>]*bgcolor="#[0-9A-Fa-f]{6}"[^>]*>\s*&nbsp;\s*</td>\s*){2}\s*</tr>\s*<tr[^>]*>\s*(<td[^>]*bgcolor="#[0-9A-Fa-f]{6}"[^>]*>\s*&nbsp;\s*</td>\s*){2}'
123   )
124   or 3 of (
125     regex.icontains(body.html.raw, '.password-expiration'),
126     regex.icontains(body.html.raw, 'color: #2672ec;'),
127     regex.icontains(body.html.raw, 'M\x{00AD}ic\x{00AD}ro\x{00AD}so\x{00AD}ft')
128   )
129   or 4 of (
130     regex.icontains(body.html.raw, 'rgb\(246,\s?93,\s?53\)'),
131     regex.icontains(body.html.raw, 'rgb\(129,\s?187,\s?5\)'),
132     regex.icontains(body.html.raw, 'rgb\(4,\s?165,\s?240\)'),
133     regex.icontains(body.html.raw, 'rgb\(255,\s?186,\s?7\)'),
134   )
135   or 4 of (
136     regex.icontains(body.html.raw,
137                     '(background-color:|background:|bgcolor=)(.)red'
138     ),
139     regex.icontains(body.html.raw, 'rgb\(19,\s?186,\s?132\)'),
140     regex.icontains(body.html.raw, 'rgb\(4,\s?166,\s?240\)'),
141     regex.icontains(body.html.raw, 'rgb\(255,\s?186,\s?8\)'),
142   )
143   or 4 of (
144     regex.icontains(body.html.raw, 'rgb\(245,\s?189,\s?67\)'),
145     regex.icontains(body.html.raw, 'rgb\(137,\s?184,\s?57\)'),
146     regex.icontains(body.html.raw, 'rgb\(217,\s?83,\s?51\)'),
147     regex.icontains(body.html.raw, 'rgb\(71,\s?160,\s?218\)')
148   )
149   or 4 of (
150     regex.icontains(body.html.raw, 'rgb\(73,\s?161,\s?232\)'),
151     regex.icontains(body.html.raw, 'rgb\(224,\s?92,\s?53\)'),
152     regex.icontains(body.html.raw, 'rgb\(139,\s?183,\s?55\)'),
153     regex.icontains(body.html.raw, 'rgb\(244,\s?188,\s?65\)')
154   )
155   or 4 of (
156     regex.icontains(body.html.raw, 'rgb\(213,\s?56,\s?62\)'),
157     regex.icontains(body.html.raw, 'rgb\(0,\s?114,\s?30\)'),
158     regex.icontains(body.html.raw, 'rgb\(0,\s?110,\s?173\)'),
159     regex.icontains(body.html.raw, 'rgb\(227,\s?209,\s?43\)'),
160   )
161   or 4 of (
162     regex.icontains(body.html.raw, 'rgb\(246,\s?93,\s?53\)'),
163     regex.icontains(body.html.raw, 'rgb\(129,\s?187,\s?5\)'),
164     regex.icontains(body.html.raw, 'rgb\(4,\s?165,\s?240\)'),
165     regex.icontains(body.html.raw, 'rgb\(255,\s?186,\s?7\)')
166   )
167   or 4 of (
168     regex.icontains(body.html.raw, 'rgb\(242,\s?80,\s?34\)'),
169     regex.icontains(body.html.raw, 'rgb\(127,\s?186,\s?0\)'),
170     regex.icontains(body.html.raw, 'rgb\(0,\s?164,\s?239\)'),
171     regex.icontains(body.html.raw, 'rgb\(255,\s?185,\s?0\)'),
172   )
173   or 4 of (
174     regex.icontains(body.html.raw, 'rgb\(243,\s?83,\s?37\)'),
175     regex.icontains(body.html.raw, 'rgb\(129,\s?188,\s?6\)'),
176     regex.icontains(body.html.raw, 'rgb\(5,\s?166,\s?240\)'),
177     regex.icontains(body.html.raw, 'rgb\(255,\s?186,\s?8\)')
178   )
179   or 4 of (
180     regex.icontains(body.html.raw, 'rgb\(243,\s?80,\s?34\)'),
181     regex.icontains(body.html.raw, 'rgb\(128,\s?187,\s?3\)'),
182     regex.icontains(body.html.raw, 'rgb\(3,\s?165,\s?240\)'),
183     regex.icontains(body.html.raw, 'rgb\(255,\s?185,\s?3\)')
184   )
185   or 4 of (
186     regex.icontains(body.html.raw,
187                     '(background-color:|background:|bgcolor=)(.)?(#)?(FF1940|eb5024|F25022|FF1941|red)'
188     ),
189     regex.icontains(body.html.raw,
190                     '(background-color:|background:|bgcolor=)(.)?(#)?(36ba57|3eb55d|7db606|7FBA00|36ba58|green)'
191     ),
192     regex.icontains(body.html.raw,
193                     '(background-color:|background:|bgcolor=)(.)?#(04a1d6|04B5F0|05a1e8|00A4EF|01a4ef|04a5f0)'
194     ),
195     regex.icontains(body.html.raw,
196                     '(background-color:|background:|bgcolor=)(.)?#(FFCA07|f7b408|FFB900|FFCA08|ffb901|ffba07)'
197     ),
198   )
199   or 4 of (
200     regex.icontains(body.html.raw,
201                     '(background-color:|background:|bgcolor=)(.)?#(f65314|f65d35|49a1e8|E74F23|F35325)'
202     ),
203     regex.icontains(body.html.raw,
204                     '(background-color:|background:|bgcolor=)(.)?#(7cbf42|81bb05|e05c35|7AB206|81BC06)'
205     ),
206     regex.icontains(body.html.raw,
207                     '(background-color:|background:|bgcolor=)(.)?#(00a4ef|0078d7|8bb737|04a5f0|059EE4|05A6F0)'
208     ),
209     regex.icontains(body.html.raw,
210                     '(background-color:|background:|bgcolor=)(.)?#(ffb900|ffba07|f4bc41|F2B108|FFBA08)'
211     ),
212   )
213   // fuzzy approach
214   or 4 of (
215     regex.icontains(body.html.raw,
216                     'rgb\((2[1-4][0-9]|250),\s?(7[0-9]|8[0-9]|9[0-3]),\s?(3[0-9]|4[0-9]|5[0-3])\)'
217     ),
218     regex.icontains(body.html.raw,
219                     'rgb\((12[0-9]|13[0-9]),\s?(18[0-9]|190),\s?([0-9]|10)\)'
220     ),
221     regex.icontains(body.html.raw,
222                     'rgb\(([0-9]|1[0-5]),\s?(16[0-5]|166),\s?(23[0-9]|240)\)'
223     ),
224     regex.icontains(body.html.raw,
225                     'rgb\((25[0-5]),\s?(18[5-9]|19[0-9]),\s?([0-9]|10)\)'
226     )
227   )
228 )
229 
230 // suspicious content
231 and (
232   // current thread is empty, but an image attachment is branded as microsoft.
233   // common in image as content
234   (
235     body.current_thread.text == ""
236     and any(attachments,
237             .file_type in $file_types_images
238             and any(ml.logo_detect(.).brands,
239                     strings.starts_with(.name, "Microsoft")
240             )
241     )
242   )
243   or (
244     strings.ilike(body.plain.raw,
245                   "*password*",
246                   "*document*",
247                   "*voicemail*",
248                   "*cache*",
249                   "*fax*",
250                   "*storage*",
251                   "*quota*",
252                   "*message*"
253     )
254     and strings.ilike(body.plain.raw,
255                       "*terminated*",
256                       "*review*",
257                       "*expire*",
258                       "*click*",
259                       "*view*",
260                       "*exceed*",
261                       "*clear*",
262                       "*only works*",
263                       "*failed*",
264                       "*deleted*",
265                       "*revalidated*",
266                       "*renewal*"
267     )
268   )
269   or (
270     any(attachments,
271         .file_type in $file_types_images
272         and any(file.explode(.),
273                 strings.ilike(.scan.ocr.raw,
274                               "*password*",
275                               "*document*",
276                               "*voicemail*",
277                               "*cache*",
278                               "*fax*",
279                               "*storage*",
280                               "*quota*",
281                               "*messages*"
282                 )
283                 and strings.ilike(.scan.ocr.raw,
284                                   "*terminated*",
285                                   "*review*",
286                                   "*expire*",
287                                   "*click*",
288                                   "*view*",
289                                   "*exceed*",
290                                   "*clear*",
291                                   "*only works*",
292                                   "*failed*",
293                                   "*deleted*"
294                 )
295         )
296     )
297   )
298   or (
299     //
300     // This rule makes use of a beta feature and is subject to change without notice
301     // using the beta feature in custom rules is not suggested until it has been formally released
302     //
303     strings.ilike(beta.ocr(file.message_screenshot()).text,
304                   "*password*",
305                   "*document*",
306                   "*voicemail*",
307                   "*cache*",
308                   "*fax*",
309                   "*storage*",
310                   "*quota*",
311                   "*messages*"
312     )
313     and strings.ilike(beta.ocr(file.message_screenshot()).text,
314                       "*terminated*",
315                       "*review*",
316                       "*expire*",
317                       "*click*",
318                       "*view*",
319                       "*exceed*",
320                       "*clear*",
321                       "*only works*",
322                       "*failed*",
323                       "*deleted*",
324                       "*revalidated*",
325                       "*renewal*"
326     )
327   )
328   or (
329     any(ml.nlu_classifier(body.current_thread.text).intents,
330         .name == "cred_theft" and .confidence in~ ("medium", "high")
331     )
332     or any(attachments,
333            .file_type in $file_types_images
334            and any(file.explode(.),
335                    any(ml.nlu_classifier(.scan.ocr.raw).intents,
336                        .name == "cred_theft"
337                        and .confidence in ("medium", "high")
338                    )
339            )
340     )
341   )
342 )
343 and sender.email.domain.root_domain not in (
344   "bing.com",
345   "microsoft.com",
346   "microsoftonline.com",
347   "microsoftproject.com",
348   "microsoftstoreemail.com",
349   "microsoftsupport.com",
350   "microsoft365.com",
351   "office.com",
352   "office365.com",
353   "onedrive.com",
354   "sharepointonline.com",
355   "yammer.com",
356 )
357 
358 // negate legitimate Office 365 bouncebacks
359 and not (
360   length(attachments) > 0
361   and all(attachments,
362           .content_type in ("message/delivery-status", "message/rfc822")
363   )
364   and (sender.email.local_part in ('postmaster', 'mailer-daemon'))
365 )
366 
367 // negate Microsoft "welcome to the X group" notifications
368 and not (
369   headers.auth_summary.dmarc.pass
370   and length(attachments) == 6
371   and length(filter(attachments,
372                     strings.istarts_with(.file_name, "GuestWelcomeEmail")
373              )
374   ) == 5
375   and length(filter(body.links,
376                     (
377                       .href_url.domain.domain not in (
378                         "outlook.office365.com",
379                         "aka.ms",
380                         "go.microsoft.com"
381                       )
382                     )
383                     and not .href_url.domain.domain == sender.email.domain.domain
384              )
385   ) == 0
386   and subject.subject == strings.replace_confusables(subject.subject)
387 )
388 
389 // negate legitimate microsoft b2b applications invitations
390 and not (
391   sender.email.local_part == "invites"
392   and sender.email.domain.root_domain == "onmicrosoft.com"
393   // infra validated message id
394   and strings.icontains(headers.message_id, "pepf")
395 )
396 
397 // negate org domains unless they fail DMARC authentication
398 and (
399   (
400     sender.email.domain.root_domain in $org_domains
401     and (
402       not headers.auth_summary.dmarc.pass
403       // MS quarantine digest emails from an org domain are router "internally" to MS, therefore, there is no authentication information
404       or not (
405         headers.auth_summary.dmarc.pass is null
406         and all(headers.domains,
407                 .root_domain in ("outlook.com", "office365.com")
408         )
409         // typical emails from freemail Outlook accounts are from prod.outlook.com
410         and strings.ends_with(headers.message_id, "protection.outlook.com>")
411       )
412     )
413   )
414   or sender.email.domain.root_domain not in $org_domains
415 )
416 
417 // negate highly trusted sender domains unless they fail DMARC authentication
418 and not (
419   sender.email.domain.root_domain in $high_trust_sender_root_domains
420   and coalesce(headers.auth_summary.dmarc.pass, false)
421 )
422 and (
423   not profile.by_sender().solicited
424   or (
425     profile.by_sender().any_messages_malicious_or_spam
426     and not profile.by_sender().any_messages_benign
427   )
428 )
429 and not profile.by_sender().any_messages_benign
430 
431 // exclude marketing jargon from ms partners
432 and not regex.icontains(body.current_thread.text,
433                         '(schedul(e|ing)|set up).{0,20}(call|meeting|demo|zoom|conversation|time|tool|discussion)|book.{0,10}(meeting|demo|call|slot|time)|connect.{0,12}(with me|phone|email)|my.{0,10}(calendar|cal)|reserve.{0,10}s[pl]ot|break the ice|want to know more?|miss your chance|if you no longer wish|if you no longer want|if you wish to opt out|low-code (development|approach|solution|journey|platform)|(?:invite|virtual).{0,30}(webinar|presentation)'
434 ) 
435
436attack_types:
437  - "Credential Phishing"
438tactics_and_techniques:
439  - "Free file host"
440  - "Image as content"
441  - "Impersonation: Brand"
442  - "Social engineering"
443detection_methods:
444  - "Computer Vision"
445  - "Content analysis"
446  - "File analysis"
447  - "Header analysis"
448  - "Natural Language Understanding"
449  - "Optical Character Recognition"
450  - "Sender analysis"
451  - "URL analysis"
452id: "b59201b6-f253-55a6-9c0a-e1500a32a751"
to-top