Brand impersonation: Capital One
This detection rule identifies inbound messages containing Capital One branding indicators in display names, sender addresses, message content, or embedded logos, while excluding legitimate Capital One domains and authenticated communications from known trusted senders.
Sublime rule (View on GitHub)
1name: "Brand impersonation: Capital One"
2description: "This detection rule identifies inbound messages containing Capital One branding indicators in display names, sender addresses, message content, or embedded logos, while excluding legitimate Capital One domains and authenticated communications from known trusted senders."
3type: "rule"
4severity: "high"
5source: |
6 type.inbound
7 // limit evaluation of a regex heavy rule
8 and length(body.current_thread.text) < 2000000
9 and (
10 any([
11 strings.replace_confusables(sender.display_name),
12 strings.replace_confusables(subject.subject),
13 // domain parts of sender
14 sender.email.local_part,
15 sender.email.domain.sld
16 ],
17 // quick checks first
18 strings.icontains(., 'Capital One')
19 or strings.icontains(., 'CapitalOne')
20
21 // slower checks next
22 or regex.icontains(., 'Capital.?One')
23 // levenshtein distince similar to captial one
24 or strings.ilevenshtein(., 'Capital One') <= 2
25 )
26 or any(ml.logo_detect(file.message_screenshot()).brands,
27 .name == "Capital One Bank" and .confidence != "low"
28 )
29 )
30 and not (
31 sender.email.domain.root_domain in (
32 "capitalone.co.uk",
33 "capitalone.com",
34 "capitaloneshopping.com",
35 "capitalonesoftware.com",
36 "capitalonebooking.com",
37 "capitalonetravel.com",
38 "olbanking.com", // a fiserv.one domain
39 "bynder.com", // Digital Assest Mgmt
40 "gcs-web.com", // investor relations run by capital one
41 "capitalonearena.com", // the arena
42 "monumentalsports.com", // the company that owns a bunch of teams that play at the arena?
43 "ticketmaster.com", // sell and advertises tickets at Capital One Arena
44 "credible.com", // known loan marketplace
45 "capitalonetradecredit.com" // domain associated with Capital One's trade credit platform
46 )
47 and headers.auth_summary.dmarc.pass
48 )
49 // and the sender is not from high trust sender root domains
50 and (
51 (
52 sender.email.domain.root_domain in $high_trust_sender_root_domains
53 and not headers.auth_summary.dmarc.pass
54 )
55 or sender.email.domain.root_domain not in $high_trust_sender_root_domains
56 )
57 and // suspicious indicators here
58 (
59 // // password theme
60 (
61 strings.icontains(body.current_thread.text, "new password")
62 or regex.icontains(body.current_thread.text,
63 '(?:credentials?|password)\s*(?:\w+\s+){0,3}\s*(?:compromise|reset|expir(?:ation|ed)|update|invalid|incorrect|changed|(?:mis)?match)',
64 '(?:compromise|reset|expir(?:ation|ed)|update|invalid|incorrect|changed|(?:mis)?match)\s*(?:\w+\s+){0,3}\s*(?:credentials?|password)',
65 '(?:short|weak|chang(?:e|ing)|reset)\s*(?:\w+\s+){0,3}\s*(?:credentials?|password)',
66 '(?:credentials?|password)\s*(?:\w+\s+){0,3}\s*(?:short|weak|chang(?:e|ing)|reset)',
67 )
68 )
69 // // login failures
70 or (
71 strings.icontains(body.current_thread.text, "unusual number of")
72 or strings.icontains(body.current_thread.text, "security breach")
73 or (
74 strings.icontains(body.current_thread.text, "security alert")
75 // some capital one notiifcaitons include directions to
76 // change notificaiton preferences to only security alerts
77 and (
78 strings.icount(body.current_thread.text, "security alert") > strings.icount(body.current_thread.text,
79 "sign in to your account and select Security Alerts."
80 )
81 )
82 )
83 or strings.icontains(body.current_thread.text, "account remains secure")
84 or strings.icontains(body.current_thread.text, "please verify your account")
85 or strings.icontains(body.current_thread.text,
86 "suspicious activity detected"
87 )
88 or strings.icontains(body.current_thread.text, "temporarily locked out")
89 or regex.icontains(body.current_thread.text,
90 '(?:invalid|unrecognized|unauthorized|fail(?:ed|ure)?|suspicious|unusual|attempt(?:ed)?\b|tried to)\s*(?:\w+\s+){0,3}\s*(?:log(?:.?in)?|sign(?:.?in)?|account|access|activity)',
91 '(?:log(?:.?in)?|sign(?:.?in)?|account|access|activity)\s*(?:\w+\s+){0,3}\s*(?:invalid|unrecognized|fail(?:ed|ure)?|suspicious|unusual|attempt(?:ed)?\b)'
92 )
93 )
94 // // account locked
95 or (
96 strings.icontains(body.current_thread.text, "been suspend")
97 or strings.icontains(body.current_thread.text, "will be restored")
98 or strings.icontains(body.current_thread.text, "security reasons")
99 or strings.icontains(body.current_thread.text,
100 "temporarily restricted access"
101 )
102 or regex.icontains(body.current_thread.text,
103 'acc(?:ou)?n?t\s*(?:\w+\s+){0,3}\s*(?:authenticat(?:e|ion)|activity|\bho[li]d\b|terminat|[il1]{2}m[il1]t(?:s|ed|ation)|b?locked|de-?activat|suspen(?:ed|sion)|restrict(?:ed|ion)?|expir(?:ed?|ing)|v[il]o[li]at|verif(?:y|ication))',
104 '(?:authenticat(?:e|ion)|activity|\bho[li]d\b|terminat|[il1]{2}m[il1]t(?:s|ed|ation)|b?locked|de-?activat|suspen(?:ed|sion)|restrict(?:ed|ion)?|expir(?:ed?|ing)|v[il]o[li]at|verif(?:y|ication))\s*(?:\w+\s+){0,3}\s*acc(?:ou)?n?t\b'
105 )
106 )
107 // // secure messages
108 or (
109 regex.icontains(body.current_thread.text,
110 '(?:encrypt(?:ion|ed)?|secur(?:ed?|ity)) (?:\w+\s+){0,3}\s*message'
111 )
112 or strings.icontains(body.current_thread.text, "document portal")
113 or regex.icontains(body.current_thread.text,
114 "has been (?:encrypt|sent secure)"
115 )
116 or regex.icontains(body.current_thread.text,
117 'encryption (?:\w+\s+){0,3}\s*tech'
118 )
119 )
120 // // documents to view
121 or (
122 // we can skip the regex if the diplay_text doesn't contain document
123 // this might need to be removed if the regex is expanded
124 strings.icontains(body.current_thread.text, 'document')
125 and regex.icontains(body.current_thread.text,
126 'document\s*(?:\w+\s+){0,3}\s*(?:ready|posted|review|available|online)',
127 '(?:ready|posted|review|available|online)\s*(?:\w+\s+){0,3}\s*document'
128 )
129 )
130 // // account/profile details
131 or (
132 strings.icontains(body.current_thread.text, "about your account")
133 or strings.icontains(body.current_thread.text, "action required")
134 or regex.icontains(body.current_thread.text,
135 '(update|\bedit\b|modify|revise|verif(?:y|ication)|discrepanc(?:y|ies)|mismatch(?:es)?|inconsistenc(?:y|ies)?|difference(?:s)?|anomal(?:y|ies)?|irregularit(?:y|ies)?)\s*(?:\w+\s+){0,4}\s*(?:account|ownership|detail|record|data|info(?:rmation)?)',
136 '(?:account|ownership|detail|record|data|info(?:rmation)?)\s*(?:\w+\s+){0,4}\s*(update|\bedit\b|modify|revise|verif(?:y|ication)|discrepanc(?:y|ies)|mismatch(?:es)?|inconsistenc(?:y|ies)?|difference(?:s)?|anomal(?:y|ies)?|irregularit(?:y|ies)?)'
137 )
138 )
139 // // other calls to action that are unexpected
140 or (strings.icontains(body.current_thread.text, "download the attachment"))
141
142 // the links contain suspect wording
143 or (
144 0 < length(body.links) <= 50
145 and any(body.links,
146 (
147 regex.icontains(.display_text, '(?:log|sign).?in')
148 or strings.icontains(.display_text, 'confirm')
149 or strings.icontains(.display_text, 'i recongize it')
150 or strings.icontains(.display_text, "something\'s wrong")
151 or regex.icontains(.display_text,
152 '(?:(?:re)?view|see|read)\s*(?:\w+\s*){0,3}\s*(?:document|message|now|account)'
153 )
154 or regex.icontains(.display_text,
155 'restore\s*(?:\w+\s*){0,3}\s*(?:account|access)'
156 )
157 or regex.icontains(.display_text,
158 'review\s*(?:\w+\s*){0,3}\s*(?:payment)'
159 )
160 )
161 and not regex.icontains(.display_text,
162 'confirm\s*(?:\w+\s*){0,3}\s*this message'
163 )
164 and .href_url.domain.root_domain != "capitalone.com"
165 )
166 )
167 // the message contains a disclaimer but isn't from capitalone
168 or (
169 regex.icontains(body.current_thread.text,
170 'To ensure delivery, add [^\@]+@[^\s]*capitalone.com to your address book.'
171 )
172 and sender.email.domain.root_domain != "capitalone.com"
173 )
174 )
175 // negation of inbound org domains which path eamil auth
176 and not (
177 type.inbound
178 and sender.email.domain.domain in $org_domains
179 and headers.auth_summary.spf.pass
180 and headers.auth_summary.dmarc.pass
181 and not 'fail' in~ distinct(map(headers.hops, .authentication_results.dkim))
182 )
183 and not any(beta.ml_topic(body.html.display_text).topics,
184 (
185 .name in (
186 // lots of newsletters talk about capital one
187 "Newsletters and Digests",
188 // lots of recruiting mention oppurtunties at capital one, often including the logo
189 "Professional and Career Development",
190 )
191 and .confidence == "high"
192 )
193 or (
194 .name in (
195 // Outage events are often news worthy
196 "News and Current Events"
197 )
198 and .confidence != "low"
199 )
200 )
201 // negating legit replies/forwards
202 // https://github.com/sublime-security/sublime-rules/blob/main/insights/authentication/org_inbound_auth_pass.yml
203 and not (
204 (
205 strings.istarts_with(subject.subject, "RE:")
206 or strings.istarts_with(subject.subject, "FW:")
207 or strings.istarts_with(subject.subject, "FWD:")
208 or regex.imatch(subject.subject,
209 '(\[[^\]]+\]\s?){0,3}(re|fwd?|automat.*)\s?:.*'
210 )
211 or strings.istarts_with(subject.subject, "Réponse automatique")
212 )
213 and (
214 length(headers.references) > 0
215 and any(headers.hops, any(.fields, strings.ilike(.name, "In-Reply-To")))
216 )
217 )
218 // negate bounce backs
219 and not (
220 strings.like(sender.email.local_part,
221 "*postmaster*",
222 "*mailer-daemon*",
223 "*administrator*"
224 )
225 and any(attachments,
226 .content_type in (
227 "message/rfc822",
228 "message/delivery-status",
229 "text/calendar"
230 )
231 )
232 )
233attack_types:
234 - "Credential Phishing"
235tactics_and_techniques:
236 - "Impersonation: Brand"
237 - "Lookalike domain"
238 - "Social engineering"
239detection_methods:
240 - "Computer Vision"
241 - "Sender analysis"
242 - "Header analysis"
243id: "d53848e4-fc40-5bd1-ad5e-c9c4e85a669f"