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