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