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"
to-top