Link: Suspicious SharePoint Document Name

The detection rule is intended to match on emails sent from SharePoint indicating a shared file to the recipient that contain suspicious content within the document name. The Link display text is leveraged to identify the name of the shared file.

Sublime rule (View on GitHub)

  1name: "Link: Suspicious SharePoint Document Name"
  2description: "The detection rule is intended to match on emails sent from SharePoint indicating a shared file to the recipient that contain suspicious content within the document name.  The Link display text is leveraged to identify the name of the shared file."
  3type: "rule"
  4severity: "low"
  5source: |
  6  type.inbound
  7  and strings.ilike(subject.subject, "*shared*", "*invit*")
  8  and strings.ilike(body.current_thread.text,
  9                    "*shared a file with you*",
 10                    "*shared with you*",
 11                    "*invited you to access a file*"
 12  )
 13  and not strings.ilike(body.current_thread.text, "invited you to edit")
 14  and (
 15    any(filter(body.links,
 16               (
 17                 .href_url.domain.root_domain == "sharepoint.com"
 18                 or .href_url.domain.root_domain == "1drv.ms"
 19                 // handle urls with mimecast rewriting
 20                 or (
 21                   .href_url.domain.root_domain == 'mimecastprotect.com'
 22                   and strings.icontains(.href_url.query_params,
 23                                         '.sharepoint.com'
 24                   )
 25                 )
 26               )
 27               and .display_text != "Open"
 28        ),
 29  
 30        // the file name does not include lowercase letters, while allowing for non letter chars
 31        regex.match(.display_text, '^[^a-z]+$')
 32  
 33        // file sharing service references
 34        or strings.icontains(.display_text, 'dropbox')
 35        or strings.icontains(.display_text, 'docusign')
 36  
 37        // file name lures
 38        // secure theme
 39        or regex.icontains(.display_text, 'secured?.*(?:file|document|docs|fax)')
 40        or regex.icontains(.display_text, 'important.*(?:file|document|docs|fax)')
 41        or regex.icontains(.display_text, 'shared?.*(?:file|document|docs|fax)')
 42        or regex.icontains(.display_text, 'protected.*(?:file|document|docs|fax)')
 43        or regex.icontains(.display_text, 'encrypted.*(?:file|document|docs|fax)')
 44  
 45        // scanner theme
 46        or strings.icontains(.display_text, 'scanne[rd]_')
 47        // image themed
 48        or strings.icontains(.display_text, '_IMG_')
 49        or regex.icontains(.display_text, '^IMG[_-](?:\d|\W)+$')
 50  
 51        // digits
 52        or regex.icontains(.display_text, 'doc(?:ument)?\s?\d+$')
 53        or regex.icontains(.display_text, '^\d+$')
 54  
 55        // onedrive theme
 56        or strings.icontains(.display_text, 'one_docx')
 57        or strings.icontains(.display_text, 'OneDrive')
 58        or regex.icontains(.display_text, 'A document.*One.?Drive')
 59  
 60        // action in file name
 61        or strings.icontains(.display_text, 'click here')
 62        or strings.icontains(.display_text, 'Download PDF')
 63        or strings.icontains(.display_text, 'Validate')
 64  
 65        // limited file name to "confidential"
 66        or .display_text =~ 'Confidentiality'
 67        or .display_text =~ 'Confidential'
 68  
 69        // invoice themes
 70        or any(ml.nlu_classifier(.display_text).entities, .name == "financial")
 71        or strings.icontains(.display_text, 'payment')
 72        or strings.icontains(.display_text, 'invoice')
 73        or regex.icontains(.display_text, 'INV(?:_|\s)?\d+$')
 74        // starts with INV_ or INV\x20
 75        or regex.icontains(.display_text, '^INV(?:_|\s)')
 76        or regex.icontains(.display_text, 'P[O0]\W+?\d+$')
 77        or strings.icontains(.display_text, 'receipt')
 78        or strings.icontains(.display_text, 'billing')
 79        or (
 80          strings.icontains(.display_text, 'statement')
 81          and not .display_text =~ "Privacy Statement"
 82        )
 83        or strings.icontains(.display_text, 'Past Due')
 84        or regex.icontains(.display_text, 'Remit(tance)?')
 85        or strings.icontains(.display_text, 'Purchase Order')
 86  
 87        // contract language
 88        or strings.icontains(.display_text, 'settlement')
 89        or strings.icontains(.display_text, 'contract agreement')
 90        or regex.icontains(.display_text, 'Pr[0o]p[0o]sal')
 91        or strings.icontains(.display_text, 'contract doc')
 92  
 93        // the document name is the same as the org name
 94        // as determined by the footer 
 95        or (
 96          strings.icontains(body.current_thread.text,
 97                            strings.concat('This email is generated through ',
 98                                           .display_text
 99                            )
100          )
101          and strings.icontains(body.current_thread.text,
102                                strings.concat("\'s use of Microsoft 365 and may contain content that is controlled by ",
103                                               .display_text
104                                )
105          )
106        )
107  
108        // use NLU to extract the org name from the Link and compare to the org
109        // as determined by the footer
110        or any(
111               // create a list of org names
112               // if the display_text contains things like "Acme Co. - RFQ"
113               // the "org" via NLU often becomes "Acme Co. -"
114               map(filter(ml.nlu_classifier(.display_text).entities,
115                          .name == "org"
116                          and .text is not null
117                          and not strings.icontains(.text, "Microsoft 365")
118                   ),
119                   .text
120               ),
121               // if we run NLU a second time, it cleans up the extra parts
122               any(ml.nlu_classifier(.).entities,
123                   strings.icontains(body.current_thread.text,
124                                     strings.concat('This email is generated through ',
125                                                    .text
126                                     )
127                   )
128                   and strings.icontains(body.current_thread.text,
129                                         strings.concat("\'s use of Microsoft 365 and may contain content that is controlled by ",
130                                                        .text
131                                         )
132                   )
133               )
134        )
135    )
136  )
137  
138  // and it's not an internal share
139  and not any(headers.hops,
140              any(.fields,
141                  .name == "X-MS-Exchange-CrossTenant-AuthAs"
142                  and .value == "Internal"
143              )
144  )
145  // and sender has never had email sent to them
146  and not profile.by_sender().solicited  
147
148attack_types:
149  - "Credential Phishing"
150tactics_and_techniques:
151  - "Free file host"
152  - "Evasion"
153detection_methods:
154  - "Content analysis"
155id: "f95fee6e-8127-5888-a9a9-4bbeabfe33a3"
to-top