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