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