Brand impersonation: Sharepoint fake file share
This rule detects messages impersonating a Sharepoint file sharing email where no links point to known Microsoft domains.
Sublime rule (View on GitHub)
1name: "Brand impersonation: Sharepoint fake file share"
2description: |
3 This rule detects messages impersonating a Sharepoint file sharing email where no links point to known Microsoft domains.
4type: "rule"
5severity: "medium"
6source: |
7 type.inbound
8
9 // Sharepoint body content looks like this
10 and (
11 (
12 (
13 any([body.current_thread.text, body.plain.raw],
14 strings.ilike(.,
15 "*shared a file with you*",
16 "*shared with you*",
17 "*invited you to access a file*",
18 "*received a document*",
19 "*shared a document*",
20 "*shared a new document*",
21 "*shared this document*"
22 )
23 )
24 or any(ml.nlu_classifier(body.current_thread.text).topics,
25 .name == "File Sharing and Cloud Services"
26 and .confidence == "high"
27 )
28 //
29 // This rule makes use of a beta feature and is subject to change without notice
30 // using the beta feature in custom rules is not suggested until it has been formally released
31 //
32 or strings.ilike(beta.ocr(file.message_screenshot()).text,
33 "*shared a file with you*",
34 "*shared with you*",
35 "*invited you to access a file*",
36 "*received a document*",
37 "*shared a document*",
38 "*shared a new document*",
39 "*shared this document*"
40 )
41 //
42 // This rule makes use of a beta feature and is subject to change without notice
43 // using the beta feature in custom rules is not suggested until it has been formally released
44 //
45 or any(ml.nlu_classifier(beta.ocr(file.message_screenshot()).text).topics,
46 .name == "File Sharing and Cloud Services"
47 and .confidence == "high"
48 )
49 )
50 and (
51 strings.ilike(subject.subject,
52 "*shared*",
53 "*updated*",
54 "*sign*",
55 "*review*",
56 "*scanned*"
57 )
58 or strings.ilike(subject.subject,
59 "*Excel*",
60 "*SharePoint*",
61 "*PowerPoint*",
62 "*OneNote*"
63 )
64 or strings.ilike(sender.display_name,
65 "*Excel*",
66 "*SharePoint*",
67 "*PowerPoint*",
68 "*OneNote*"
69 )
70 or any(body.links, strings.icontains(.display_text, "OPEN DOCUMENT"))
71 or subject.subject is null
72 or subject.subject == ""
73 // the org as determined by NLU is in the subject
74 or any(ml.nlu_classifier(body.current_thread.text).entities,
75 .name == "org" and strings.icontains(subject.subject, .text)
76 )
77 )
78 )
79 or any([
80 "Contigo", // Spanish
81 "Avec vous", // French
82 "Mit Ihnen", // German
83 "Con te", // Italian
84 "Com você", // Portuguese
85 "Met u", // Dutch
86 "С вами", // Russian
87 "与你", // Chinese (Simplified)
88 "與您", // Chinese (Traditional)
89 "あなたと", // Japanese
90 "당신과", // Korean
91 "معك", // Arabic
92 "آپ کے ساتھ", // Urdu
93 "আপনার সাথে", // Bengali
94 "आपके साथ", // Hindi
95 "Sizinle", // Turkish // Azerbaijani
96 "Med dig", // Swedish
97 "Z tobą", // Polish
98 "З вами", // Ukrainian
99 "Önnel", // Hungarian
100 "Μαζί σας", // Greek
101 "איתך", // Hebrew
102 "กับคุณ", // Thai
103 "Với bạn", // Vietnamese
104 "Dengan Anda", // Indonesian // Malay
105 "Nawe", // Swahili
106 "Cu dumneavoastră", // Romanian
107 "S vámi", // Czech
108 "Med deg", // Norwegian
109 "S vami", // Slovak
110 "Med dig", // Danish
111 "Amb vostè", // Catalan
112 "Teiega", // Estonian
113 "S vama", // Serbian
114 ],
115 strings.icontains(subject.subject, .)
116 )
117 )
118
119 // contains logic that impersonates Microsoft
120 and (
121 any(ml.logo_detect(file.message_screenshot()).brands,
122 strings.starts_with(.name, "Microsoft")
123 )
124 or any(attachments,
125 .file_type in $file_types_images
126 and any(ml.logo_detect(.).brands,
127 strings.starts_with(.name, "Microsoft")
128 )
129 )
130 or regex.icontains(body.html.raw,
131 '<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}'
132 )
133 or 3 of (
134 regex.icontains(body.html.raw, '.password-expiration'),
135 regex.icontains(body.html.raw, 'color: #2672ec;'),
136 regex.icontains(body.html.raw, 'Microsoft')
137 )
138 or 4 of (
139 regex.icontains(body.html.raw, 'rgb\(246,\s?93,\s?53\)'),
140 regex.icontains(body.html.raw, 'rgb\(129,\s?187,\s?5\)'),
141 regex.icontains(body.html.raw, 'rgb\(4,\s?165,\s?240\)'),
142 regex.icontains(body.html.raw, 'rgb\(255,\s?186,\s?7\)'),
143 )
144 or 4 of (
145 regex.icontains(body.html.raw,
146 '(background-color:|background:|bgcolor=)(.)red'
147 ),
148 regex.icontains(body.html.raw, 'rgb\(19,\s?186,\s?132\)'),
149 regex.icontains(body.html.raw, 'rgb\(4,\s?166,\s?240\)'),
150 regex.icontains(body.html.raw, 'rgb\(255,\s?186,\s?8\)'),
151 )
152 or 4 of (
153 regex.icontains(body.html.raw, 'rgb\(245,\s?189,\s?67\)'),
154 regex.icontains(body.html.raw, 'rgb\(137,\s?184,\s?57\)'),
155 regex.icontains(body.html.raw, 'rgb\(217,\s?83,\s?51\)'),
156 regex.icontains(body.html.raw, 'rgb\(71,\s?160,\s?218\)')
157 )
158 or 4 of (
159 regex.icontains(body.html.raw, 'rgb\(73,\s?161,\s?232\)'),
160 regex.icontains(body.html.raw, 'rgb\(224,\s?92,\s?53\)'),
161 regex.icontains(body.html.raw, 'rgb\(139,\s?183,\s?55\)'),
162 regex.icontains(body.html.raw, 'rgb\(244,\s?188,\s?65\)')
163 )
164 or 4 of (
165 regex.icontains(body.html.raw, 'rgb\(213,\s?56,\s?62\)'),
166 regex.icontains(body.html.raw, 'rgb\(0,\s?114,\s?30\)'),
167 regex.icontains(body.html.raw, 'rgb\(0,\s?110,\s?173\)'),
168 regex.icontains(body.html.raw, 'rgb\(227,\s?209,\s?43\)'),
169 )
170 or 4 of (
171 regex.icontains(body.html.raw, 'rgb\(246,\s?93,\s?53\)'),
172 regex.icontains(body.html.raw, 'rgb\(129,\s?187,\s?5\)'),
173 regex.icontains(body.html.raw, 'rgb\(4,\s?165,\s?240\)'),
174 regex.icontains(body.html.raw, 'rgb\(255,\s?186,\s?7\)')
175 )
176 or 4 of (
177 regex.icontains(body.html.raw, 'rgb\(242,\s?80,\s?34\)'),
178 regex.icontains(body.html.raw, 'rgb\(127,\s?186,\s?0\)'),
179 regex.icontains(body.html.raw, 'rgb\(0,\s?164,\s?239\)'),
180 regex.icontains(body.html.raw, 'rgb\(255,\s?185,\s?0\)'),
181 )
182 or 4 of (
183 regex.icontains(body.html.raw, 'rgb\(243,\s?83,\s?37\)'),
184 regex.icontains(body.html.raw, 'rgb\(129,\s?188,\s?6\)'),
185 regex.icontains(body.html.raw, 'rgb\(5,\s?166,\s?240\)'),
186 regex.icontains(body.html.raw, 'rgb\(255,\s?186,\s?8\)')
187 )
188 or 4 of (
189 regex.icontains(body.html.raw, 'rgb\(243,\s?80,\s?34\)'),
190 regex.icontains(body.html.raw, 'rgb\(128,\s?187,\s?3\)'),
191 regex.icontains(body.html.raw, 'rgb\(3,\s?165,\s?240\)'),
192 regex.icontains(body.html.raw, 'rgb\(255,\s?185,\s?3\)')
193 )
194 or 4 of (
195 regex.icontains(body.html.raw,
196 '(background-color:|background:|bgcolor=)(.)?(#)?(FF1940|eb5024|F25022|FF1941|red)'
197 ),
198 regex.icontains(body.html.raw,
199 '(background-color:|background:|bgcolor=)(.)?(#)?(36ba57|3eb55d|7db606|7FBA00|36ba58|green)'
200 ),
201 regex.icontains(body.html.raw,
202 '(background-color:|background:|bgcolor=)(.)?#(04a1d6|04B5F0|05a1e8|00A4EF|01a4ef|04a5f0)'
203 ),
204 regex.icontains(body.html.raw,
205 '(background-color:|background:|bgcolor=)(.)?#(FFCA07|f7b408|FFB900|FFCA08|ffb901|ffba07)'
206 ),
207 )
208 or 4 of (
209 regex.icontains(body.html.raw,
210 '(background-color:|background:|bgcolor=)(.)?#(f65314|f65d35|49a1e8|E74F23|F35325)'
211 ),
212 regex.icontains(body.html.raw,
213 '(background-color:|background:|bgcolor=)(.)?#(7cbf42|81bb05|e05c35|7AB206|81BC06)'
214 ),
215 regex.icontains(body.html.raw,
216 '(background-color:|background:|bgcolor=)(.)?#(00a4ef|0078d7|8bb737|04a5f0|059EE4|05A6F0)'
217 ),
218 regex.icontains(body.html.raw,
219 '(background-color:|background:|bgcolor=)(.)?#(ffb900|ffba07|f4bc41|F2B108|FFBA08)'
220 ),
221 )
222 // fuzzy approach
223 or 4 of (
224 regex.icontains(body.html.raw,
225 '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])\)'
226 ),
227 regex.icontains(body.html.raw,
228 'rgb\((12[0-9]|13[0-9]),\s?(18[0-9]|190),\s?([0-9]|10)\)'
229 ),
230 regex.icontains(body.html.raw,
231 'rgb\(([0-9]|1[0-5]),\s?(16[0-5]|166),\s?(23[0-9]|240)\)'
232 ),
233 regex.icontains(body.html.raw,
234 'rgb\((25[0-5]),\s?(18[5-9]|19[0-9]),\s?([0-9]|10)\)'
235 )
236 )
237 or 4 of (
238 regex.icontains(body.html.raw, 'rgb\((25[0-5]),\s?(2[0-5]),\s?(6[0-4])\)'),
239 regex.icontains(body.html.raw, 'rgb\((6[0-2]),\s?(18[0-1]),\s?(9[0-3])\)'),
240 regex.icontains(body.html.raw, 'rgb\(([0-4]),\s?(18[0-1]),\s?(24[0])\)'),
241 regex.icontains(body.html.raw, 'rgb\((25[0-5]),\s?(20[0-2]),\s?([0-7])\)')
242 )
243 or (
244 any(recipients.to,
245 strings.icontains(body.current_thread.text,
246 strings.concat(.email.domain.sld,
247 " shared a file with you"
248 )
249 )
250 )
251 )
252 or (
253 any(recipients.to,
254 strings.icontains(body.current_thread.text,
255 strings.concat("This link will work for ",
256 .email.email
257 )
258 )
259 )
260 )
261 // contains HTML and wording from the sharepoint template
262 or (
263 (
264 //
265 // This rule makes use of a beta feature and is subject to change without notice
266 // using the beta feature in custom rules is not suggested until it has been formally released
267 //
268
269 // alt text for the global icon
270 length(html.xpath(body.html, '//img[@alt="permission globe icon"]').nodes) > 0
271 // reference to the global icon id
272 or length(html.xpath(body.html, '//img[@id="Picture_x0020_1"]').nodes) > 0
273 // a comment reference the globe icon
274 or strings.icontains(body.html.raw,
275 ' <!-- Permission globe icon placeholder -->'
276 )
277 )
278 // the wording from the sharepoint share
279 and strings.contains(body.current_thread.text,
280 'This invite will only work for you and people with existing access'
281 )
282 )
283 or any(html.xpath(body.html,
284 "//*[contains(translate(@style, 'ABCDEF', 'abcdef'), 'color:#605e5c')]"
285 ).nodes,
286 .display_text =~ "Privacy Statement"
287 )
288 or 2 of (
289 strings.icontains(body.current_thread.text,
290 'Microsoft respects your privacy'
291 ),
292 strings.icontains(body.current_thread.text,
293 'please read our Privacy Statement'
294 ),
295 strings.icontains(body.current_thread.text,
296 'Microsoft Corporation, One Microsoft Way, Redmond, WA 98052'
297 ),
298 )
299 )
300
301 // Negate messages when the message-id indciates the message is from MS actual. DKIM/SPF domains can be custom and therefore are unpredictable.
302 and not (
303 strings.starts_with(headers.message_id, '<Share-')
304 and strings.ends_with(headers.message_id, '@odspnotify>')
305 )
306
307 // fake Sharepoint shares are easy to identify if there are any links
308 // that don't point to microsoft[.]com or *.sharepoint[.]com
309 and not all(body.links,
310 .href_url.domain.root_domain in (
311 "1drv.ms",
312 "aka.ms",
313 "microsoft.com",
314 "sharepoint.com"
315 )
316 )
317 // if there is a Sharepoint link, ensure the link doesn't match any org SLDs
318 and not any(body.links,
319 (
320 .href_url.domain.root_domain == "sharepoint.com"
321 and any($org_slds, . == ..href_url.domain.subdomain)
322 )
323 or .href_url.domain.domain in $tenant_domains
324 )
325 and sender.email.domain.root_domain not in $org_domains
326 and sender.email.domain.root_domain not in (
327 "bing.com",
328 "microsoft.com",
329 "microsoftonline.com",
330 "microsoftsupport.com",
331 "microsoft365.com",
332 "office.com",
333 "onedrive.com",
334 "sharepointonline.com",
335 "yammer.com",
336 // ignore microsoft privacy statement links
337 "aka.ms"
338 )
339 and not (
340 (
341 (
342 strings.istarts_with(subject.subject, "RE:")
343 or strings.istarts_with(subject.subject, "R:")
344 or strings.istarts_with(subject.subject, "ODG:")
345 or strings.istarts_with(subject.subject, "答复:")
346 or strings.istarts_with(subject.subject, "AW:")
347 or strings.istarts_with(subject.subject, "TR:")
348 or strings.istarts_with(subject.subject, "FWD:")
349 or regex.imatch(subject.subject, '(\[[^\]]+\]\s?){0,3}(re|fwd?)\s?:')
350 or regex.imatch(subject.subject,
351 '^\[?(EXT|EXTERNAL)\]?[: ]\s*(RE|FWD?|FW|AW|TR|ODG|答复):.*'
352 )
353 )
354 and (
355 (length(headers.references) > 0 or headers.in_reply_to is not null)
356 // ensure that there are actual threads
357 and (
358 length(body.previous_threads) > 0
359 or (length(body.html.display_text) - length(body.current_thread.text)) > 200
360 )
361 )
362 )
363 )
364
365 // negate highly trusted sender domains unless they fail DMARC authentication
366 and (
367 (
368 sender.email.domain.root_domain in $high_trust_sender_root_domains
369 and not headers.auth_summary.dmarc.pass
370 )
371 or sender.email.domain.root_domain not in $high_trust_sender_root_domains
372 )
373 and (
374 profile.by_sender().solicited == false
375 or profile.by_sender_email().prevalence == "new"
376 or profile.by_sender_email().days_since.last_contact > 30
377 or (
378 profile.by_sender().any_messages_malicious_or_spam
379 and not profile.by_sender().any_messages_benign
380 )
381 // or it's a spoof of the org_domain
382 or (
383 sender.email.domain.domain in $org_domains
384 and not (
385 headers.auth_summary.spf.pass
386 or coalesce(headers.auth_summary.dmarc.pass, false)
387 )
388 )
389 )
390 and not profile.by_sender().any_messages_benign
391
392attack_types:
393 - "Credential Phishing"
394 - "Malware/Ransomware"
395detection_methods:
396 - "Content analysis"
397 - "Header analysis"
398 - "URL analysis"
399 - "Computer Vision"
400tactics_and_techniques:
401 - "Impersonation: Brand"
402 - "Social engineering"
403id: "ff8b296b-aa0d-5df0-b4d2-0e599b688f6a"