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|(?:offices?|microsoft).365|re.{0,3}confirm)|due for update'
 26    )
 27    and not strings.icontains(body.current_thread.text, 'link expires in ')
 28  )
 29  and (
 30    // subject or body contains account or access
 31    any([subject.subject, body.current_thread.text],
 32        regex.icontains(., "account|access|your email")
 33    )
 34    // suspicious use of recipients email address
 35    or any(recipients.to,
 36           any([subject.subject, body.current_thread.text],
 37               strings.icontains(strings.replace_confusables(.),
 38                                 ..email.local_part
 39               )
 40               or strings.icontains(strings.replace_confusables(.), ..email.email)
 41           )
 42    )
 43  )
 44  
 45  // subject or body must contains password
 46  and any([
 47            strings.replace_confusables(subject.subject),
 48            strings.replace_confusables(body.current_thread.text)
 49          ],
 50          regex.icontains(., '\bpassword\b', '\bmulti.?factor\b')
 51  )
 52  and (
 53    any(ml.nlu_classifier(strings.replace_confusables(body.current_thread.text)).intents,
 54        .name == "cred_theft" and .confidence == "high"
 55    )
 56    or length(filter([
 57                       "password",
 58                       "expiration",
 59                       "expire",
 60                       "expiring",
 61                       "kindly",
 62                       "renew",
 63                       "review",
 64                       "click below",
 65                       "kicked out",
 66                       "prevent",
 67                       "required now",
 68                       "immediate action",
 69                       "security update",
 70                       "blocked",
 71                       "locked",
 72                       "interruption",
 73                       "stay connected",
 74                     ],
 75                     strings.icontains(strings.replace_confusables(body.current_thread.text
 76                                       ),
 77                                       .
 78                     )
 79              )
 80    ) >= 3
 81  )
 82  
 83  // body length between 200 and 2000
 84  and (
 85    200 < length(body.current_thread.text) < 2000
 86  
 87    // excessive whitespace
 88    or (
 89      regex.icontains(body.html.raw, '(?:(?:<br\s*/?>\s*){20,}|\n{20,})')
 90      or regex.icontains(body.html.raw, '(?:<p[^>]*>\s*<br\s*/?>\s*</p>\s*){30,}')
 91      or regex.icontains(body.html.raw,
 92                         '(?:<p class=".*?"><span style=".*?"><o:p>&nbsp;</o:p></span></p>\s*){30,}'
 93      )
 94      or regex.icontains(body.html.raw, '(?:<p>\s*&nbsp;\s*</p>\s*){7,}')
 95      or regex.icontains(body.html.raw, '(?:<p>\s*&nbsp;\s*</p>\s*<br>\s*){7,}')
 96      or regex.icontains(body.html.raw, '(?:<p[^>]*>\s*&nbsp;\s*<br>\s*</p>\s*){5,}')
 97      or regex.icontains(body.html.raw, '(?:<p[^>]*>&nbsp;</p>\s*){7,}')
 98    )
 99  )
100
101  // a body link does not match the sender domain
102  and any(body.links,
103          .href_url.domain.root_domain != sender.email.domain.root_domain
104          and .href_url.domain.root_domain not in $org_domains
105  )
106  
107  // and no false positives and not solicited
108  and (
109    not profile.by_sender().any_false_positives
110    and not profile.by_sender().solicited
111  )
112  
113  // not a reply
114  and (
115    length(headers.references) == 0
116    or not any(headers.hops, any(.fields, strings.ilike(.name, "In-Reply-To")))
117  )
118  
119  // negate highly trusted sender domains unless they fail DMARC authentication
120  and (
121    (
122      sender.email.domain.root_domain in $high_trust_sender_root_domains
123      and (
124        any(distinct(headers.hops, .authentication_results.dmarc is not null),
125            strings.ilike(.authentication_results.dmarc, "*fail")
126        )
127      )
128    )
129    or sender.email.domain.root_domain not in $high_trust_sender_root_domains
130  )  
131
132attack_types:
133  - "Credential Phishing"
134tactics_and_techniques:
135  - "Social engineering"
136detection_methods:
137  - "Content analysis"
138  - "Natural Language Understanding"
139  - "Sender analysis"
140id: "5d9c3a75-5f57-5d0c-a07f-0f300bbde076"
to-top