Skip to content

Commit e9135d0

Browse files
[PR #3431] added rule: Credential phishing: Suspicious e-sign agreement document notification
1 parent 8bd27ab commit e9135d0

File tree

1 file changed

+281
-0
lines changed

1 file changed

+281
-0
lines changed
Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
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­-­S­i­g­n­&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>&nbsp;</o:p></span></p>\s*){30,}'
84+
)
85+
or regex.icontains(body.html.raw, '(<p>&nbsp;</p>\s*){7,}')
86+
or regex.icontains(body.html.raw, '(<p[^>]*>\s*&nbsp;<br>\s*</p>\s*){5,}')
87+
or regex.icontains(body.html.raw, '(<p[^>]*>&nbsp;</p>\s*){7,}')
88+
or strings.count(body.html.raw, '&nbsp;‌&nbsp;‌&nbsp') > 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*(?:&nbsp;|\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

Comments
 (0)