1+ name : " Credential phishing: Suspicious e-sign agreement document notification"
2+ description : " Detects phishing attempts disguised as e-signature requests, characterized by common document sharing phrases, unusual HTML padding, and suspicious link text."
3+ type : " rule"
4+ severity : " medium"
5+ source : |
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.{0,4}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+ )
265+ attack_types :
266+ - " Credential Phishing"
267+ tactics_and_techniques :
268+ - " Social engineering"
269+ detection_methods :
270+ - " Content analysis"
271+ - " Header analysis"
272+ - " HTML analysis"
273+ - " URL analysis"
274+ - " Sender analysis"
275+ id : " 2a37b008-5bb3-5e3d-9e45-b6d92e21d4ef"
276+ og_id : " 9b68c2d8-951e-5e04-9fa3-2ca67d9226a6"
277+ testing_pr : 3438
278+ testing_sha : bf0b2d29224d1b8e8fdd93d9db9ef67eae85c347
0 commit comments