Credential Phishing: Fake Password Expiration from New and Unsolicited sender
This rule looks for password expiration verbiage in the subject and body. Requiring between 1 - 9 links, a short body, and NLU in addition to statically specified term anchors. High trust senders are also negated.
Sublime rule (View on GitHub)
1name: "Credential Phishing: Fake Password Expiration from New and Unsolicited sender"
2description: "This rule looks for password expiration verbiage in the subject and body. Requiring between 1 - 9 links, a short body, and NLU in addition to statically specified term anchors. High trust senders are also negated."
3type: "rule"
4severity: "medium"
5source: |
6 type.inbound
7
8 // few links which are not in $org_domains
9 and 0 < length(filter(body.links, .href_url.domain.domain not in $org_domains)) <= 10
10
11 // no attachments or suspicious attachment
12 and (
13 length(attachments) == 0
14 or any(attachments,
15 .file_type in ("pdf", "doc", "docx")
16 and any(file.explode(.),
17 .scan.entropy.entropy > 7 and length(.scan.ocr.raw) < 20
18 )
19 )
20 // or there are duplicate pdfs in name
21 or (
22 length(filter(attachments, .file_type == "pdf")) > length(distinct(filter(attachments,
23 .file_type == "pdf"
24 ),
25 .file_name
26 )
27 )
28 or
29 // all PDFs are the same MD5
30 length(distinct(filter(attachments, .file_type == "pdf"), .md5)) == 1
31 )
32 )
33
34 // body contains expire, expiration, loose, lose
35 and (
36 regex.icontains(body.current_thread.text,
37 '(expir(e)?(ation|s)|\blo(o)?se\b|(?:offices?|microsoft).365|re.{0,3}confirm)|due for update'
38 )
39 and not strings.icontains(body.current_thread.text, 'link expires in ')
40 )
41 and (
42 // subject or body contains account or access
43 any([subject.subject, body.current_thread.text],
44 regex.icontains(., "account|access|your email")
45 )
46 // suspicious use of recipients email address
47 or any(recipients.to,
48 any([subject.subject, body.current_thread.text],
49 strings.icontains(strings.replace_confusables(.),
50 ..email.local_part
51 )
52 or strings.icontains(strings.replace_confusables(.), ..email.email)
53 )
54 )
55 )
56
57 // subject or body must contains password
58 and any([
59 strings.replace_confusables(subject.subject),
60 strings.replace_confusables(body.current_thread.text)
61 ],
62 regex.icontains(., '\bpassword\b', '\bmulti.?factor\b')
63 )
64 and (
65 any(ml.nlu_classifier(strings.replace_confusables(body.current_thread.text)).intents,
66 .name == "cred_theft" and .confidence == "high"
67 )
68 or length(filter([
69 "password",
70 "expiration",
71 "expire",
72 "expiring",
73 "kindly",
74 "renew",
75 "review",
76 "click below",
77 "kicked out",
78 "prevent",
79 "required now",
80 "immediate action",
81 "security update",
82 "blocked",
83 "locked",
84 "interruption",
85 "stay connected",
86 ],
87 strings.icontains(strings.replace_confusables(body.current_thread.text
88 ),
89 .
90 )
91 )
92 ) >= 3
93 )
94
95 // body length between 200 and 2000
96 and (
97 200 < length(body.current_thread.text) < 2000
98
99 // excessive whitespace
100 or (
101 regex.icontains(body.html.raw, '(?:(?:<br\s*/?>\s*){20,}|\n{20,})')
102 or regex.icontains(body.html.raw, '(?:<p[^>]*>\s*<br\s*/?>\s*</p>\s*){30,}')
103 or regex.icontains(body.html.raw,
104 '(?:<p class=".*?"><span style=".*?"><o:p> </o:p></span></p>\s*){30,}'
105 )
106 or regex.icontains(body.html.raw, '(?:<p>\s* \s*</p>\s*){7,}')
107 or regex.icontains(body.html.raw, '(?:<p>\s* \s*</p>\s*<br>\s*){7,}')
108 or regex.icontains(body.html.raw,
109 '(?:<p[^>]*>\s* \s*<br>\s*</p>\s*){5,}'
110 )
111 or regex.icontains(body.html.raw, '(?:<p[^>]*> </p>\s*){7,}')
112 )
113 )
114
115 // a body link does not match the sender domain
116 and any(body.links,
117 .href_url.domain.root_domain != sender.email.domain.root_domain
118 and .href_url.domain.root_domain not in $org_domains
119 )
120
121 // and no false positives and not solicited
122 and (
123 not profile.by_sender().any_false_positives
124 and not profile.by_sender().solicited
125 )
126
127 // not a reply
128 and (
129 length(headers.references) == 0
130 or not any(headers.hops, any(.fields, strings.ilike(.name, "In-Reply-To")))
131 )
132
133 // negate highly trusted sender domains unless they fail DMARC authentication
134 and (
135 (
136 sender.email.domain.root_domain in $high_trust_sender_root_domains
137 and (
138 any(distinct(headers.hops, .authentication_results.dmarc is not null),
139 strings.ilike(.authentication_results.dmarc, "*fail")
140 )
141 )
142 )
143 or sender.email.domain.root_domain not in $high_trust_sender_root_domains
144 )
145
146attack_types:
147 - "Credential Phishing"
148tactics_and_techniques:
149 - "Social engineering"
150detection_methods:
151 - "Content analysis"
152 - "Natural Language Understanding"
153 - "Sender analysis"
154id: "5d9c3a75-5f57-5d0c-a07f-0f300bbde076"