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