Credential Phishing: Fake Password Expiration from New and Unsolicited sender

This rule looks for password expiration verbiage in the subject and body. Requiring between 1 - 9 links, a short body, and NLU in addition to statically specified term anchors. High trust senders are also negated.

Sublime rule (View on GitHub)

  1name: "Credential Phishing: Fake Password Expiration from New and Unsolicited sender"
  2description: "This rule looks for password expiration verbiage in the subject and body. Requiring between 1 - 9 links, a short body, and NLU in addition to statically specified term anchors. High trust senders are also negated."
  3type: "rule"
  4severity: "medium"
  5source: |
  6  type.inbound
  7  
  8  // few links
  9  and 0 < length(body.links) < 10
 10  
 11  // no attachments or suspicious attachment
 12  and (
 13    length(attachments) == 0
 14    or any(attachments,
 15           .file_type in ("pdf", "doc", "docx")
 16           and any(file.explode(.),
 17                   .scan.entropy.entropy > 7 and length(.scan.ocr.raw) < 20
 18           )
 19    )
 20  )
 21  
 22  // body contains expire, expiration, loose, lose 
 23  and (
 24    regex.icontains(body.current_thread.text,
 25                    '(expir(e)?(ation|s)|\blo(o)?se\b|office 365|re.{0,3}confirm)|due for update'
 26    )
 27    and not strings.icontains(body.current_thread.text, 'link expires in ')
 28  )
 29  
 30  and (
 31    // subject or body contains account or access
 32    any([subject.subject, body.current_thread.text],
 33        regex.icontains(body.current_thread.text, "account|access|your email")
 34    )
 35    // suspicious use of recipients email address
 36    or any(recipients.to,
 37           strings.icontains(strings.replace_confusables(subject.subject),
 38                             .email.local_part
 39           )
 40           or strings.icontains(strings.replace_confusables(body.current_thread.text
 41                                ),
 42                                .email.email
 43           )
 44    )
 45  )
 46  
 47  // subject or body must contains password
 48  and any([
 49            strings.replace_confusables(subject.subject),
 50            strings.replace_confusables(body.current_thread.text)
 51          ],
 52          regex.icontains(body.current_thread.text, '\bpassword\b')
 53  )
 54  and (
 55    any(ml.nlu_classifier(strings.replace_confusables(body.current_thread.text)).intents,
 56        .name == "cred_theft" and .confidence == "high"
 57    )
 58    or length(filter([
 59                       "password",
 60                       "expiration",
 61                       "expire",
 62                       "expiring",
 63                       "kindly",
 64                       "renew",
 65                       "review",
 66                       "click below",
 67                       "kicked out",
 68                       "prevent",
 69                       "required now",
 70                       "immediate action",
 71                       "security update",
 72                       "blocked",
 73                       "locked",
 74                       "interruption"
 75                     ],
 76                     strings.icontains(strings.replace_confusables(body.current_thread.text
 77                                       ),
 78                                       .
 79                     )
 80              )
 81    ) >= 3
 82  )
 83  
 84  // body length between 200 and 2000
 85  and (
 86    200 < length(body.current_thread.text) < 2000
 87    
 88    // excessive whitespace
 89    or (
 90      regex.icontains(body.html.raw, '((<br\s*/?>\s*){20,}|\n{20,})')
 91      or regex.icontains(body.html.raw, '(<p[^>]*>\s*<br\s*/?>\s*</p>\s*){30,}')
 92      or regex.icontains(body.html.raw,
 93                         '(<p class=".*?"><span style=".*?"><o:p>&nbsp;</o:p></span></p>\s*){30,}'
 94      )
 95      or regex.icontains(body.html.raw, '(<p>&nbsp;</p>\s*){7,}')
 96      or regex.icontains(body.html.raw, '(<p>&nbsp;</p><br>\s*){7,}')
 97      or regex.icontains(body.html.raw, '(<p[^>]*>\s*&nbsp;<br>\s*</p>\s*){5,}')
 98      or regex.icontains(body.html.raw, '(<p[^>]*>&nbsp;</p>\s*){7,}')
 99    )
100  )
101
102  // a body link does not match the sender domain
103  and any(body.links,
104          .href_url.domain.root_domain != sender.email.domain.root_domain
105          and .href_url.domain.root_domain not in $org_domains
106  )
107  
108  // and no false positives and not solicited
109  and (
110    not profile.by_sender().any_false_positives
111    and not profile.by_sender().solicited
112  )
113  
114  // not a reply
115  and (
116    length(headers.references) == 0
117    or not any(headers.hops, any(.fields, strings.ilike(.name, "In-Reply-To")))
118  )
119  
120  // negate highly trusted sender domains unless they fail DMARC authentication
121  and (
122    (
123      sender.email.domain.root_domain in $high_trust_sender_root_domains
124      and (
125        any(distinct(headers.hops, .authentication_results.dmarc is not null),
126            strings.ilike(.authentication_results.dmarc, "*fail")
127        )
128      )
129    )
130    or sender.email.domain.root_domain not in $high_trust_sender_root_domains
131  )  
132
133attack_types:
134  - "Credential Phishing"
135tactics_and_techniques:
136  - "Social engineering"
137detection_methods:
138  - "Content analysis"
139  - "Natural Language Understanding"
140  - "Sender analysis"
141id: "5d9c3a75-5f57-5d0c-a07f-0f300bbde076"
to-top