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 // use the display text of the link to determine the name of the file
16 any(filter(body.links,
17 (
18 .href_url.domain.root_domain == "sharepoint.com"
19 or .href_url.domain.root_domain == "1drv.ms"
20 // handle urls with mimecast rewriting
21 or (
22 .href_url.domain.root_domain == 'mimecastprotect.com'
23 and strings.icontains(.href_url.query_params,
24 '.sharepoint.com'
25 )
26 )
27 )
28 and .display_text != "Open"
29 ),
30
31 // the file name does not include lowercase letters, while allowing for non letter chars
32 regex.match(.display_text, '^[^a-z]+$')
33
34 // file sharing service references
35 or strings.icontains(.display_text, 'dropbox')
36 or strings.icontains(.display_text, 'docusign')
37
38 // file name lures
39 // secure theme
40 or regex.icontains(.display_text, 'secured?.*(?:file|document|docs|fax)')
41 or regex.icontains(.display_text, 'important.*(?:file|document|docs|fax)')
42 or regex.icontains(.display_text, 'shared?.*(?:file|document|docs|fax)')
43 or regex.icontains(.display_text, 'protected.*(?:file|document|docs|fax)')
44 or regex.icontains(.display_text, 'encrypted.*(?:file|document|docs|fax)')
45
46 // scanner theme
47 or strings.icontains(.display_text, 'scanne[rd]_')
48 // image themed
49 or strings.icontains(.display_text, '_IMG_')
50 or regex.icontains(.display_text, '^IMG[_-](?:\d|\W)+$')
51
52 // digits
53 or regex.icontains(.display_text, 'doc(?:ument)?\s?\d+$')
54 or regex.icontains(.display_text, '^\d+$')
55
56 // onedrive theme
57 or strings.icontains(.display_text, 'one_docx')
58 or strings.icontains(.display_text, 'OneDrive')
59 or regex.icontains(.display_text, 'A document.*One.?Drive')
60
61 // action in file name
62 or strings.icontains(.display_text, 'click here')
63 or strings.icontains(.display_text, 'Download PDF')
64 or strings.icontains(.display_text, 'Validate')
65
66 // limited file name to "confidential"
67 or .display_text =~ 'Confidentiality'
68 or .display_text =~ 'Confidential'
69
70 // invoice themes
71 or any(ml.nlu_classifier(.display_text).entities, .name == "financial")
72 or strings.icontains(.display_text, 'payment')
73 or strings.icontains(.display_text, 'invoice')
74 or regex.icontains(.display_text, 'INV(?:_|\s)?\d+$')
75 // starts with INV_ or INV\x20
76 or regex.icontains(.display_text, '^INV(?:_|\s)')
77 or regex.icontains(.display_text, 'P[O0]\W+?\d+$')
78 or strings.icontains(.display_text, 'receipt')
79 or strings.icontains(.display_text, 'billing')
80 or (
81 strings.icontains(.display_text, 'statement')
82 and not .display_text =~ "Privacy Statement"
83 )
84 or strings.icontains(.display_text, 'Past Due')
85 or regex.icontains(.display_text, 'Remit(tance)?')
86 or strings.icontains(.display_text, 'Purchase Order')
87
88 // contract language
89 or strings.icontains(.display_text, 'settlement')
90 or strings.icontains(.display_text, 'contract agreement')
91 or regex.icontains(.display_text, 'Pr[0o]p[0o]sal')
92 or strings.icontains(.display_text, 'contract doc')
93
94 // generic document name AND additional suspicious indicator
95 or (
96 regex.imatch(.display_text, 'documents?')
97 and (
98 // Find the share comment in the HTML and check for reply/forward "impersonation"
99 regex.icontains(body.html.raw, '<p style="font-size:16px;color:#323130;margin:40px 20px 28px">(re|fwd?)')
100 )
101 )
102
103 // Payroll/HR
104 // section also used in abuse_dropbox_sus_names.yml with modified input
105 or strings.icontains(.display_text, 'Payroll')
106 or strings.icontains(.display_text, 'Employee Pay\b')
107 or strings.icontains(.display_text, 'Salary')
108 or strings.icontains(.display_text, 'Benefit Enrollment')
109 or strings.icontains(.display_text, 'Employee Handbook')
110 or strings.icontains(.display_text, 'Reimbursement Approved')
111 or regex.icontains(.display_text, '(?:Faculty|Staff)\s*(?:\w+\s+){0,3}\s*Eval(?:uation)?')
112
113 // the document name is the same as the org name
114 // as determined by the footer
115 or (
116 strings.icontains(body.current_thread.text,
117 strings.concat('This email is generated through ',
118 .display_text
119 )
120 )
121 and strings.icontains(body.current_thread.text,
122 strings.concat("\'s use of Microsoft 365 and may contain content that is controlled by ",
123 .display_text
124 )
125 )
126 )
127
128 // use NLU to extract the org name from the Link and compare to the org
129 // as determined by the footer
130 or any(
131 // create a list of org names
132 // if the display_text contains things like "Acme Co. - RFQ"
133 // the "org" via NLU often becomes "Acme Co. -"
134 map(filter(ml.nlu_classifier(.display_text).entities,
135 .name == "org"
136 and .text is not null
137 and not strings.icontains(.text, "Microsoft 365")
138 ),
139 .text
140 ),
141 // if we run NLU a second time, it cleans up the extra parts
142 any(ml.nlu_classifier(.).entities,
143 strings.icontains(body.current_thread.text,
144 strings.concat('This email is generated through ',
145 .text
146 )
147 )
148 and strings.icontains(body.current_thread.text,
149 strings.concat("\'s use of Microsoft 365 and may contain content that is controlled by ",
150 .text
151 )
152 )
153 )
154 )
155 )
156 )
157
158 // and it's not an internal share
159 and not any(headers.hops,
160 any(.fields,
161 .name == "X-MS-Exchange-CrossTenant-AuthAs"
162 and .value == "Internal"
163 )
164 )
165 and (
166 // and sender has never had email sent to them
167 profile.by_sender().solicited == false
168 // often times no-reply is soliticed due to various behaviors
169 or sender.email.email == "no-reply@sharepointonline.com"
170 )
171
172attack_types:
173 - "Credential Phishing"
174tactics_and_techniques:
175 - "Free file host"
176 - "Evasion"
177detection_methods:
178 - "Content analysis"
179id: "f95fee6e-8127-5888-a9a9-4bbeabfe33a3"