Credential phishing: Suspicious e-sign agreement document notification
Detects phishing attempts disguised as e-signature requests, characterized by common document sharing phrases, unusual HTML padding, and suspicious link text.
Sublime rule (View on GitHub)
1name: "Credential phishing: Suspicious e-sign agreement document notification"
2description: "Detects phishing attempts disguised as e-signature requests, characterized by common document sharing phrases, unusual HTML padding, and suspicious link text."
3type: "rule"
4severity: "medium"
5source: |
6 type.inbound
7 and any([subject.subject, sender.display_name],
8 regex.icontains(strings.replace_confusables(.),
9 "D[0o]cuLink",
10 "Agreement",
11 "Access.&.Appr[0o]ved",
12 "Agreement.{0,5}Review",
13 "Attend.and.Review",
14 "action.re?quired",
15 "Completed.File",
16 "D[0o]chsared",
17 "D[0o]cshared",
18 "D[0o]csPoint",
19 "D[0o]cument.Shared",
20 "D[0o]cuCentre",
21 "D[0o]cuCenter",
22 "D[0o]cCenter",
23 "D[0o]csOnline",
24 "D[0o]cSend",
25 "D[0o]cu?Send",
26 "d[0o]csign",
27 "D[0o]cu-eSin",
28 "D[0o]cu-management",
29 "\\beSign",
30 "e\\.sign",
31 "esign.[0o]nline",
32 "e-d[0o]c",
33 "e-signature",
34 "eSignature",
35 "eSign&Return",
36 "eSign[0o]nline",
37 "Fileshare",
38 "Review.and.C[0o]mplete",
39 "Review.&.Sign",
40 "Sign[0o]nline",
41 "Signature.Request",
42 "Shared.C[0o]mpleted",
43 "Sign.and.Seal",
44 "viaSign",
45 "D[0o]cuSign",
46 "D[0o]csID",
47 "Complete.{0,10}D[0o]cuSign",
48 "Enroll & Sign",
49 "Review and Sign",
50 "SignReport",
51 "SignD[0o]c",
52 "D[0o]cxxx",
53 "d[0o]cufile",
54 "E-Sign&Return",
55 "d[0o]cument.signature",
56 "Electr[0o]nic.?Signature",
57 "Complete: ",
58 "Please (?:Review|Sign)",
59 "^REVIEW$",
60 "requests your signature",
61 "signature on.*contract",
62 "Independent Contract",
63 "Contract.*signature",
64 "add your signature",
65 "signature needed"
66 )
67 or (
68 regex.icontains(strings.replace_confusables(.), "action.re?quired")
69 and not (
70 sender.email.domain.root_domain == "sharepointonline.com"
71 and headers.auth_summary.dmarc.pass
72 and strings.icontains(subject.subject, "asked to edit")
73 )
74 )
75 )
76 and (
77 // unusual repeated patterns in HTML
78 regex.icontains(body.html.raw, '((<br\s*/?>\s*){20,}|\n{20,})')
79 or regex.icontains(body.html.raw, '(<p[^>]*>\s*<br\s*/?>\s*</p>\s*){30,}')
80 or regex.icontains(body.html.raw,
81 '(<p class=".*?"><span style=".*?"><o:p> </o:p></span></p>\s*){30,}'
82 )
83 or regex.icontains(body.html.raw, '(<p> </p>\s*){7,}')
84 or regex.icontains(body.html.raw, '(<p[^>]*>\s* <br>\s*</p>\s*){5,}')
85 or regex.icontains(body.html.raw, '(<p[^>]*> </p>\s*){7,}')
86 or strings.count(body.html.raw, '  ') > 50
87 or regex.count(body.html.raw,
88 '<span\s*class\s*=\s*"[^\"]+"\s*>\s*[a-z]\s*<\/span><span\s*class\s*=\s*"[^\"]+"\s*>\s*[a-z]+\s*<\/span>'
89 ) > 50
90 // lookalike docusign
91 or regex.icontains(body.html.raw, '>Docus[1l]gn<')
92 or strings.icontains(body.current_thread.text, 'completed by all parties')
93 or (
94 regex.icontains(body.html.inner_text, 'Document')
95 and length(body.html.inner_text) < 500
96 )
97 // common greetings via email.local_part
98 or any(recipients.to,
99 // use count to ensure the email address is not part of a disclaimer
100 strings.icount(body.current_thread.text, .email.local_part) >
101 // sum allows us to add more logic as needed
102 sum([
103 strings.icount(body.current_thread.text,
104 strings.concat('was sent to ', .email.email)
105 ),
106 strings.icount(body.current_thread.text,
107 strings.concat('intended for ', .email.email)
108 )
109 ]
110 )
111 )
112 // common greetings via mailbox display name
113 or strings.icount(body.current_thread.text, mailbox.display_name) >
114 // sum allows us to add more logic as needed
115 sum([
116 strings.icount(body.current_thread.text,
117 strings.concat('was sent to ', mailbox.display_name)
118 ),
119 strings.icount(body.current_thread.text,
120 strings.concat('intended for ', mailbox.display_name)
121 )
122 ]
123 )
124 // Abnormally high count of mailto links in raw html
125 or regex.count(body.html.raw,
126 'mailto:[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}'
127 ) > 50
128
129 // High count of empty elements (padding)
130 or regex.count(body.html.raw,
131 '<(?:p|div|span|td)[^>]*>\s*(?: |\s)*\s*</(?:p|div|span|td)>'
132 ) > 30
133
134 // HR impersonation
135 or strings.ilike(sender.display_name, "HR", "H?R", "*Human Resources*")
136 )
137 and (
138 any(body.links,
139
140 // suspicious content within link display_text
141 regex.icontains(strings.replace_confusables(.display_text),
142 "activate",
143 "re-auth",
144 "verify",
145 "acknowledg",
146 "(keep|change).{0,20}(active|password|access)",
147 '((verify|view|click|download|goto|keep|Vιew|release).{0,15}(attachment|current|download|fax|file|document|message|same)s?)',
148 'use.same.pass',
149 'validate.{0,15}account',
150 'recover.{0,15}messages',
151 '(retry|update).{0,10}payment',
152 'check activity',
153 '(listen|play).{0,10}(vm|voice)',
154 'clarify.{0,20}(deposit|wallet|funds)',
155 'enter.{0,15}teams',
156 'Review and sign',
157 'REVIEW.*DOCUMENT'
158 )
159 // check that the display_text is all lowercase
160 or (
161 regex.contains(.display_text,
162 "\\bVIEW",
163 "DOWNLOAD",
164 "CHECK",
165 "KEEP.(SAME|MY)",
166 "VERIFY",
167 "ACCESS\\b",
168 "SIGN\\b",
169 "ENABLE\\b",
170 "RETAIN",
171 "PLAY",
172 "LISTEN",
173 )
174 and regex.match(.display_text, "^[^a-z]*[A-Z][^a-z]*$")
175 )
176
177 // the display text is _exactly_
178 or .display_text in~ ("Open")
179
180 // URL fragment containing recipient's address
181 or .href_url.fragment in map(recipients.to, .email.email)
182 )
183 // one hyperlinked image that's not a tracking pixel
184 or (
185 length(html.xpath(body.html,
186 "//a//img[(number(@width) > 5 or not(@width)) and (number(@height) > 5 or not(@height))]"
187 ).nodes
188 ) == 1
189 and length(body.current_thread.text) < 500
190 )
191 or (
192 length(attachments) > 0
193 and any(attachments,
194 (
195 regex.icontains(beta.ocr(.).text,
196 "activate",
197 "re-auth",
198 "verify",
199 "acknowledg",
200 "(keep|change).{0,20}(active|password|access)",
201 '((verify|view|click|download|goto|keep|Vιew|release).{0,15}(attachment|current|download|fax|file|document|message|same)s?)',
202 'use.same.pass',
203 'validate.{0,15}account',
204 'recover.{0,15}messages',
205 '(retry|update).{0,10}payment',
206 'check activity',
207 '(listen|play).{0,10}(vm|voice)',
208 'clarify.{0,20}(deposit|wallet|funds)',
209 'enter.{0,15}teams',
210 'Review and sign'
211 )
212 )
213 or (
214 any(file.explode(.),
215 regex.icontains(.scan.ocr.raw,
216 "activate",
217 "re-auth",
218 "verify",
219 "acknowledg",
220 "(keep|change).{0,20}(active|password|access)",
221 '((verify|view|click|download|goto|keep|Vιew|release).{0,15}(attachment|current|download|fax|file|document|message|same)s?)',
222 'use.same.pass',
223 'validate.{0,15}account',
224 'recover.{0,15}messages',
225 '(retry|update).{0,10}payment',
226 'check activity',
227 '(listen|play).{0,10}(vm|voice)',
228 'clarify.{0,20}(deposit|wallet|funds)',
229 'enter.{0,15}teams',
230 'Review and sign'
231 )
232 )
233 )
234 )
235 )
236 )
237 and (
238 not profile.by_sender_email().solicited
239 or profile.by_sender_email().prevalence == "new"
240 or (
241 profile.by_sender_email().any_messages_malicious_or_spam
242 and not profile.by_sender_email().any_messages_benign
243 )
244 )
245 and not profile.by_sender_email().any_messages_benign
246
247 // negate replies/fowards containing legitimate docs
248 and not (
249 length(headers.references) > 0
250 or any(headers.hops, any(.fields, strings.ilike(.name, "In-Reply-To")))
251 )
252
253 // negate highly trusted sender domains unless they fail DMARC authentication
254 and (
255 (
256 sender.email.domain.root_domain in $high_trust_sender_root_domains
257 and (
258 any(distinct(headers.hops, .authentication_results.dmarc is not null),
259 strings.ilike(.authentication_results.dmarc, "*fail")
260 )
261 )
262 )
263 or sender.email.domain.root_domain not in $high_trust_sender_root_domains
264 )
265attack_types:
266 - "Credential Phishing"
267tactics_and_techniques:
268 - "Social engineering"
269detection_methods:
270 - "Content analysis"
271 - "Header analysis"
272 - "HTML analysis"
273 - "URL analysis"
274 - "Sender analysis"
275id: "9b68c2d8-951e-5e04-9fa3-2ca67d9226a6"