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",
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 (
93 regex.icontains(body.html.inner_text, 'Document')
94 and length(body.html.inner_text) < 500
95 )
96 // common greetings via email.local_part
97 or any(recipients.to,
98 // use count to ensure the email address is not part of a disclaimer
99 strings.icount(body.current_thread.text, .email.local_part) >
100 // sum allows us to add more logic as needed
101 sum([
102 strings.icount(body.current_thread.text,
103 strings.concat('was sent to ', .email.email)
104 ),
105 strings.icount(body.current_thread.text,
106 strings.concat('intended for ', .email.email)
107 )
108 ]
109 )
110 )
111 // Abnormally high count of mailto links in raw html
112 or regex.count(body.html.raw,
113 'mailto:[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}'
114 ) > 50
115
116 // High count of empty elements (padding)
117 or regex.count(body.html.raw,
118 '<(?:p|div|span|td)[^>]*>\s*(?: |\s)*\s*</(?:p|div|span|td)>'
119 ) > 30
120
121 // HR impersonation
122 or strings.ilike(sender.display_name, "HR", "H?R", "*Human Resources*")
123 )
124 and (
125 any(body.links,
126
127 // suspicious content within link display_text
128 regex.icontains(strings.replace_confusables(.display_text),
129 "activate",
130 "re-auth",
131 "verify",
132 "acknowledg",
133 "(keep|change).{0,20}(active|password|access)",
134 '((verify|view|click|download|goto|keep|Vιew|release).{0,15}(attachment|current|download|fax|file|document|message|same)s?)',
135 'use.same.pass',
136 'validate.{0,15}account',
137 'recover.{0,15}messages',
138 '(retry|update).{0,10}payment',
139 'check activity',
140 '(listen|play).{0,10}(vm|voice)',
141 'clarify.{0,20}(deposit|wallet|funds)',
142 'enter.{0,15}teams',
143 'Review and sign',
144 'REVIEW.*DOCUMENT'
145 )
146 // check that the display_text is all lowercase
147 or (
148 regex.contains(.display_text,
149 "\\bVIEW",
150 "DOWNLOAD",
151 "CHECK",
152 "KEEP.(SAME|MY)",
153 "VERIFY",
154 "ACCESS\\b",
155 "SIGN\\b",
156 "ENABLE\\b",
157 "RETAIN",
158 "PLAY",
159 "LISTEN",
160 )
161 and regex.match(.display_text, "^[^a-z]*[A-Z][^a-z]*$")
162 )
163
164 // the display text is _exactly_
165 or .display_text in~ ("Open")
166 )
167 // one hyperlinked image that's not a tracking pixel
168 or (
169 length(html.xpath(body.html,
170 "//a//img[(number(@width) > 5 or not(@width)) and (number(@height) > 5 or not(@height))]"
171 ).nodes
172 ) == 1
173 and length(body.current_thread.text) < 500
174 )
175 or (
176 length(attachments) > 0
177 and any(attachments,
178 (
179 regex.icontains(beta.ocr(.).text,
180 "activate",
181 "re-auth",
182 "verify",
183 "acknowledg",
184 "(keep|change).{0,20}(active|password|access)",
185 '((verify|view|click|download|goto|keep|Vιew|release).{0,15}(attachment|current|download|fax|file|document|message|same)s?)',
186 'use.same.pass',
187 'validate.{0,15}account',
188 'recover.{0,15}messages',
189 '(retry|update).{0,10}payment',
190 'check activity',
191 '(listen|play).{0,10}(vm|voice)',
192 'clarify.{0,20}(deposit|wallet|funds)',
193 'enter.{0,15}teams',
194 'Review and sign'
195 )
196 )
197 or (
198 any(file.explode(.),
199 regex.icontains(.scan.ocr.raw,
200 "activate",
201 "re-auth",
202 "verify",
203 "acknowledg",
204 "(keep|change).{0,20}(active|password|access)",
205 '((verify|view|click|download|goto|keep|Vιew|release).{0,15}(attachment|current|download|fax|file|document|message|same)s?)',
206 'use.same.pass',
207 'validate.{0,15}account',
208 'recover.{0,15}messages',
209 '(retry|update).{0,10}payment',
210 'check activity',
211 '(listen|play).{0,10}(vm|voice)',
212 'clarify.{0,20}(deposit|wallet|funds)',
213 'enter.{0,15}teams',
214 'Review and sign'
215 )
216 )
217 )
218 )
219 )
220 )
221 and (
222 not profile.by_sender_email().solicited
223 or profile.by_sender_email().prevalence == "new"
224 or (
225 profile.by_sender_email().any_messages_malicious_or_spam
226 and not profile.by_sender_email().any_messages_benign
227 )
228 )
229 and not profile.by_sender_email().any_messages_benign
230
231 // negate replies/fowards containing legitimate docs
232 and not (
233 length(headers.references) > 0
234 or any(headers.hops, any(.fields, strings.ilike(.name, "In-Reply-To")))
235 )
236
237 // negate highly trusted sender domains unless they fail DMARC authentication
238 and (
239 (
240 sender.email.domain.root_domain in $high_trust_sender_root_domains
241 and (
242 any(distinct(headers.hops, .authentication_results.dmarc is not null),
243 strings.ilike(.authentication_results.dmarc, "*fail")
244 )
245 )
246 )
247 or sender.email.domain.root_domain not in $high_trust_sender_root_domains
248 )
249attack_types:
250 - "Credential Phishing"
251tactics_and_techniques:
252 - "Social engineering"
253detection_methods:
254 - "Content analysis"
255 - "Header analysis"
256 - "HTML analysis"
257 - "URL analysis"
258 - "Sender analysis"
259id: "9b68c2d8-951e-5e04-9fa3-2ca67d9226a6"