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