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(beta.ml_topic(body.current_thread.text).topics,
 25               .name == "File Sharing and Cloud Services"
 26               and .confidence == "high"
 27        )
 28        or any(file.explode(beta.message_screenshot()),
 29               strings.ilike(.scan.ocr.raw,
 30                             "*shared a file with you*",
 31                             "*shared with you*",
 32                             "*invited you to access a file*",
 33                             "*received a document*",
 34                             "*shared a document*",
 35                             "*shared a new document*",
 36                             "*shared this document*"
 37               )
 38               or any(beta.ml_topic(.scan.ocr.raw).topics,
 39                      .name == "File Sharing and Cloud Services"
 40                      and .confidence == "high"
 41               )
 42        )
 43      )
 44      and (
 45        strings.ilike(subject.subject,
 46                      "*shared*",
 47                      "*updated*",
 48                      "*sign*",
 49                      "*review*",
 50                      "*scanned*"
 51        )
 52        or strings.ilike(subject.subject,
 53                         "*Excel*",
 54                         "*SharePoint*",
 55                         "*PowerPoint*",
 56                         "*OneNote*"
 57        )
 58        or any(body.links, strings.icontains(.display_text, "OPEN DOCUMENT"))
 59        or subject.subject is null
 60        or subject.subject == ""
 61        // the org as determined by NLU is in the subject
 62        or any(ml.nlu_classifier(body.current_thread.text).entities,
 63               .name == "org" and strings.icontains(subject.subject, .text)
 64        )
 65      )
 66    )
 67    or any([
 68             "Contigo", // Spanish
 69             "Avec vous", // French
 70             "Mit Ihnen", // German
 71             "Con te", // Italian
 72             "Com você", // Portuguese
 73             "Met u", // Dutch
 74             "С вами", // Russian
 75             "与你", // Chinese (Simplified)
 76             "與您", // Chinese (Traditional)
 77             "あなたと", // Japanese
 78             "당신과", // Korean
 79             "معك", // Arabic
 80             "آپ کے ساتھ", // Urdu
 81             "আপনার সাথে", // Bengali
 82             "आपके साथ", // Hindi
 83             "Sizinle", // Turkish // Azerbaijani
 84             "Med dig", // Swedish
 85             "Z tobą", // Polish
 86             "З вами", // Ukrainian
 87             "Önnel", // Hungarian
 88             "Μαζί σας", // Greek
 89             "איתך", // Hebrew
 90             "กับคุณ", // Thai
 91             "Với bạn", // Vietnamese
 92             "Dengan Anda", // Indonesian // Malay
 93             "Nawe", // Swahili
 94             "Cu dumneavoastră", // Romanian
 95             "S vámi", // Czech
 96             "Med deg", // Norwegian
 97             "S vami", // Slovak
 98             "Med dig", // Danish
 99             "Amb vostè", // Catalan
100             "Teiega", // Estonian
101             "S vama", // Serbian
102           ],
103           strings.icontains(subject.subject, .)
104    )
105  )
106  
107  // contains logic that impersonates Microsoft
108  and (
109    any(ml.logo_detect(beta.message_screenshot()).brands,
110        strings.starts_with(.name, "Microsoft")
111    )
112    or any(attachments,
113           .file_type in $file_types_images
114           and any(ml.logo_detect(.).brands,
115                   strings.starts_with(.name, "Microsoft")
116           )
117    )
118    or (
119      regex.icontains(body.html.raw,
120                      '<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}'
121      )
122      or 3 of (
123        regex.icontains(body.html.raw, '.password-expiration'),
124        regex.icontains(body.html.raw, 'color: #2672ec;'),
125        regex.icontains(body.html.raw, 'M­ic­ro­so­ft')
126      )
127      or 4 of (
128        regex.icontains(body.html.raw, 'rgb\(246,\s?93,\s?53\)'),
129        regex.icontains(body.html.raw, 'rgb\(129,\s?187,\s?5\)'),
130        regex.icontains(body.html.raw, 'rgb\(4,\s?165,\s?240\)'),
131        regex.icontains(body.html.raw, 'rgb\(255,\s?186,\s?7\)'),
132      )
133      or 4 of (
134        regex.icontains(body.html.raw,
135                        '(background-color:|background:|bgcolor=)(.)red'
136        ),
137        regex.icontains(body.html.raw, 'rgb\(19,\s?186,\s?132\)'),
138        regex.icontains(body.html.raw, 'rgb\(4,\s?166,\s?240\)'),
139        regex.icontains(body.html.raw, 'rgb\(255,\s?186,\s?8\)'),
140      )
141      or 4 of (
142        regex.icontains(body.html.raw, 'rgb\(245,\s?189,\s?67\)'),
143        regex.icontains(body.html.raw, 'rgb\(137,\s?184,\s?57\)'),
144        regex.icontains(body.html.raw, 'rgb\(217,\s?83,\s?51\)'),
145        regex.icontains(body.html.raw, 'rgb\(71,\s?160,\s?218\)')
146      )
147      or 4 of (
148        regex.icontains(body.html.raw, 'rgb\(73,\s?161,\s?232\)'),
149        regex.icontains(body.html.raw, 'rgb\(224,\s?92,\s?53\)'),
150        regex.icontains(body.html.raw, 'rgb\(139,\s?183,\s?55\)'),
151        regex.icontains(body.html.raw, 'rgb\(244,\s?188,\s?65\)')
152      )
153      or 4 of (
154        regex.icontains(body.html.raw, 'rgb\(213,\s?56,\s?62\)'),
155        regex.icontains(body.html.raw, 'rgb\(0,\s?114,\s?30\)'),
156        regex.icontains(body.html.raw, 'rgb\(0,\s?110,\s?173\)'),
157        regex.icontains(body.html.raw, 'rgb\(227,\s?209,\s?43\)'),
158      )
159      or 4 of (
160        regex.icontains(body.html.raw, 'rgb\(246,\s?93,\s?53\)'),
161        regex.icontains(body.html.raw, 'rgb\(129,\s?187,\s?5\)'),
162        regex.icontains(body.html.raw, 'rgb\(4,\s?165,\s?240\)'),
163        regex.icontains(body.html.raw, 'rgb\(255,\s?186,\s?7\)')
164      )
165      or 4 of (
166        regex.icontains(body.html.raw, 'rgb\(242,\s?80,\s?34\)'),
167        regex.icontains(body.html.raw, 'rgb\(127,\s?186,\s?0\)'),
168        regex.icontains(body.html.raw, 'rgb\(0,\s?164,\s?239\)'),
169        regex.icontains(body.html.raw, 'rgb\(255,\s?185,\s?0\)'),
170      )
171      or 4 of (
172        regex.icontains(body.html.raw, 'rgb\(243,\s?83,\s?37\)'),
173        regex.icontains(body.html.raw, 'rgb\(129,\s?188,\s?6\)'),
174        regex.icontains(body.html.raw, 'rgb\(5,\s?166,\s?240\)'),
175        regex.icontains(body.html.raw, 'rgb\(255,\s?186,\s?8\)')
176      )
177      or 4 of (
178        regex.icontains(body.html.raw, 'rgb\(243,\s?80,\s?34\)'),
179        regex.icontains(body.html.raw, 'rgb\(128,\s?187,\s?3\)'),
180        regex.icontains(body.html.raw, 'rgb\(3,\s?165,\s?240\)'),
181        regex.icontains(body.html.raw, 'rgb\(255,\s?185,\s?3\)')
182      )
183      or 4 of (
184        regex.icontains(body.html.raw,
185                        '(background-color:|background:|bgcolor=)(.)?(#)?(FF1940|eb5024|F25022|FF1941|red)'
186        ),
187        regex.icontains(body.html.raw,
188                        '(background-color:|background:|bgcolor=)(.)?(#)?(36ba57|3eb55d|7db606|7FBA00|36ba58|green)'
189        ),
190        regex.icontains(body.html.raw,
191                        '(background-color:|background:|bgcolor=)(.)?#(04a1d6|04B5F0|05a1e8|00A4EF|01a4ef|04a5f0)'
192        ),
193        regex.icontains(body.html.raw,
194                        '(background-color:|background:|bgcolor=)(.)?#(FFCA07|f7b408|FFB900|FFCA08|ffb901|ffba07)'
195        ),
196      )
197      or 4 of (
198        regex.icontains(body.html.raw,
199                        '(background-color:|background:|bgcolor=)(.)?#(f65314|f65d35|49a1e8|E74F23|F35325)'
200        ),
201        regex.icontains(body.html.raw,
202                        '(background-color:|background:|bgcolor=)(.)?#(7cbf42|81bb05|e05c35|7AB206|81BC06)'
203        ),
204        regex.icontains(body.html.raw,
205                        '(background-color:|background:|bgcolor=)(.)?#(00a4ef|0078d7|8bb737|04a5f0|059EE4|05A6F0)'
206        ),
207        regex.icontains(body.html.raw,
208                        '(background-color:|background:|bgcolor=)(.)?#(ffb900|ffba07|f4bc41|F2B108|FFBA08)'
209        ),
210      )
211      // fuzzy approach
212      or 4 of (
213        regex.icontains(body.html.raw,
214                        '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])\)'
215        ),
216        regex.icontains(body.html.raw,
217                        'rgb\((12[0-9]|13[0-9]),\s?(18[0-9]|190),\s?([0-9]|10)\)'
218        ),
219        regex.icontains(body.html.raw,
220                        'rgb\(([0-9]|1[0-5]),\s?(16[0-5]|166),\s?(23[0-9]|240)\)'
221        ),
222        regex.icontains(body.html.raw,
223                        'rgb\((25[0-5]),\s?(18[5-9]|19[0-9]),\s?([0-9]|10)\)'
224        )
225      )
226      or 4 of (
227        regex.icontains(body.html.raw, 'rgb\((25[0-5]),\s?(2[0-5]),\s?(6[0-4])\)'),
228        regex.icontains(body.html.raw, 'rgb\((6[0-2]),\s?(18[0-1]),\s?(9[0-3])\)'),
229        regex.icontains(body.html.raw, 'rgb\(([0-4]),\s?(18[0-1]),\s?(24[0])\)'),
230        regex.icontains(body.html.raw, 'rgb\((25[0-5]),\s?(20[0-2]),\s?([0-7])\)')
231      )
232      or regex.icontains(body.html.raw,
233                         '<a[^>]+style="[^"]*background-color:\s*#[0-9A-F]{2}[5-9A-F]{2}[0-9A-F]{2}[^"]*"[^>]*>[^<]*(?:open)[^<]*</a>' // blue button containing the word "open"
234      )
235      or (
236        any(recipients.to,
237            strings.icontains(body.current_thread.text,
238                              strings.concat(.email.domain.sld,
239                                             " shared a file with you"
240                              )
241            )
242        )
243      )
244    )
245  )
246  
247  // Negate messages when the message-id indciates the message is from MS actual. DKIM/SPF domains can be custom and therefore are unpredictable.
248  and not (
249    strings.starts_with(headers.message_id, '<Share-')
250    and strings.ends_with(headers.message_id, '@odspnotify>')
251  )
252  
253  // fake Sharepoint shares are easy to identify if there are any links
254  // that don't point to microsoft[.]com or *.sharepoint[.]com
255  and not all(body.links,
256              .href_url.domain.root_domain in (
257                "1drv.ms",
258                "aka.ms",
259                "microsoft.com",
260                "sharepoint.com"
261              )
262  )
263  // if there is a Sharepoint link, ensure the link doesn't match any org SLDs
264  and not any(body.links,
265              .href_url.domain.root_domain == "sharepoint.com"
266              and any($org_slds, . == ..href_url.domain.subdomain)
267  )
268  and sender.email.domain.root_domain not in $org_domains
269  and sender.email.domain.root_domain not in (
270    "bing.com",
271    "microsoft.com",
272    "microsoftonline.com",
273    "microsoftsupport.com",
274    "microsoft365.com",
275    "office.com",
276    "onedrive.com",
277    "sharepointonline.com",
278    "yammer.com",
279    // ignore microsoft privacy statement links
280    "aka.ms"
281  )
282  
283  // negate highly trusted sender domains unless they fail DMARC authentication
284  and (
285    (
286      sender.email.domain.root_domain in $high_trust_sender_root_domains
287      and not headers.auth_summary.dmarc.pass
288    )
289    or sender.email.domain.root_domain not in $high_trust_sender_root_domains
290  )
291  and (
292    profile.by_sender().solicited == false
293    or profile.by_sender_email().prevalence == "new"
294    or (
295      profile.by_sender().any_messages_malicious_or_spam
296      and not profile.by_sender().any_false_positives
297    )
298  )
299  and not profile.by_sender().any_false_positives  
300attack_types:
301  - "Credential Phishing"
302  - "Malware/Ransomware"
303detection_methods:
304  - "Content analysis"
305  - "Header analysis"
306  - "URL analysis"
307  - "Computer Vision"
308tactics_and_techniques:
309  - "Impersonation: Brand"
310  - "Social engineering"
311id: "ff8b296b-aa0d-5df0-b4d2-0e599b688f6a"
to-top