Skip to content

Commit 6dc11ca

Browse files
[PR #3438] added rule: Credential phishing: Suspicious e-sign agreement document notification
1 parent 3f018f1 commit 6dc11ca

File tree

1 file changed

+278
-0
lines changed

1 file changed

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

Comments
 (0)