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