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