Impersonation: Internal corporate services

Detects phishing attempts that impersonate corporate services such as HR, helpdesk, and benefits, using specific language in the subject or sender's name and containing suspicious links from low-reputation or mass-mailing domains.

Sublime rule (View on GitHub)

  1name: "Impersonation: Internal corporate services"
  2description: "Detects phishing attempts that impersonate corporate services such as HR, helpdesk, and benefits, using specific language in the subject or sender's name and containing suspicious links from low-reputation or mass-mailing domains."
  3type: "rule"
  4severity: "high"
  5source: |
  6  type.inbound
  7  // use distinct "urls" (without query params) to determine number of links
  8  and 0 < length(distinct(body.links,
  9                          // strip out query params to determine
 10                          // the unique number of links
 11                          strings.concat(.href_url.scheme,
 12                                         .href_url.domain.domain,
 13                                         .href_url.path
 14                          )
 15                 )
 16  ) <= 8
 17  
 18  // HR language found in subject
 19  and (
 20    (
 21      length(subject.subject) > 20
 22      and regex.icontains(subject.subject,
 23                          '(time.{0,4}sheet)|(employ|complete|update(?:d| to| regarding our)|workplace).{0,30}(benefit|handbook|comp\b|compensation|salary|\bpay(?:roll)?\b|policy|policies|guidelines?|conduct|acknowl|PTO|vacation|assess|eval)|(HR|Human Resources).{0,5}ADM[il]N',
 24                          // shorten the distance to 3 or less words for the word "review"
 25                          // special handling of benefits
 26                          '\breview\b(?:\w+(?:\s\w+)?|[[:punct:]]+|\s+){0,3}(benefits?(?:$|.?(?:statement|enrollment))|handbook|comp\b|compensation|salary|bonus|\bpay(?:roll)?\b)',
 27                          // handle the year in the subject, and expand the distance to 5 or less words
 28                          '20\d{2}\b(?:\w+(?:\s\w+)?|[[:punct:]]+|\s+){0,5}(benefits?(?:$|.?(?:statement|enrollment))|handbook|comp\b|compensation|salary|bonus|\bpay(?:roll)?\b)'
 29      )
 30    )
 31  
 32    // or HR language found in sender
 33    or (
 34      regex.icontains(sender.display_name,
 35                      '(Employ|Time.{0,3}sheet|\bHR\b|Human R|Handbook|\bIT[- ]|Help.{0,3}Desk)|Internal.?Comm|Enroll?ment Service|Open Enroll?ment|Admin Support'
 36      )
 37      and not regex.icontains(sender.display_name,
 38                              "forum|employee voice|briefs|newsletter|screening"
 39      )
 40      and not regex.icontains(sender.display_name,
 41                              "HR (new|vue|view|tech admin|global)"
 42      )
 43      and not strings.icontains(sender.display_name, "get it")
 44    )
 45  
 46    // or assessment report language found in body
 47    or (
 48      regex.icontains(body.current_thread.text,
 49                      '20\d{2}(?:[[:punct:]](?:20)?\d{2})? (?:\w+ )?assessment report'
 50      )
 51    )
 52  
 53    // or HR department language found in body via NLU
 54    or any(ml.nlu_classifier(body.current_thread.text).entities,
 55           .name in ("org", "sender")
 56           and regex.icontains(.text,
 57                               '\bhr\b',
 58                               'human resources',
 59                               'operations department'
 60           )
 61    )
 62  )
 63  
 64  // suspicious display_text
 65  and (
 66    any(body.links,
 67        regex.icontains(.display_text,
 68                        '(?:verify|view|click|download|goto|keep|Vιew|release|access|open|allow|deny|new).{0,10}(?:request|handbook|here|report|attachment|current|download|fax|file|document|message|same|doc|access|polic(?:y|ie))s?'
 69        )
 70        and not strings.ilike(.display_text, "*unsub*")
 71        and not strings.ilike(.display_text, "*privacy?policy*")
 72        and not strings.ilike(.href_url.url, "*privacy?policy*")
 73        and not strings.ilike(.display_text, "*REGISTER*")
 74  
 75        // from a low reputation link
 76        and (
 77          not .href_url.domain.root_domain in $org_domains
 78          and (
 79            (
 80              .href_url.domain.root_domain not in $tranco_1m
 81              or .href_url.domain.domain in $free_file_hosts
 82              or .href_url.domain.root_domain in $free_file_hosts
 83              or .href_url.domain.root_domain in $free_subdomain_hosts
 84              or .href_url.domain.domain in $url_shorteners
 85              or .href_url.domain.domain in $social_landing_hosts
 86            )
 87            or 
 88            // or mass mailer link, masks the actual URL
 89            .href_url.domain.root_domain in (
 90              "hubspotlinks.com",
 91              "mandrillapp.com",
 92              "sendgrid.net",
 93              "rs6.net",
 94              "mailanyone.net",
 95              "perspectivefunnel.co"
 96            )
 97          )
 98        )
 99    )
100    // or credential theft confidence high
101    or (
102      length(body.links) > 0
103      and any(ml.nlu_classifier(body.current_thread.text).intents,
104              .name == "cred_theft" and .confidence == "high"
105      )
106      and not sender.email.domain.root_domain in (
107        "servicenowservices.com",
108        "workplaceextras.com",
109        "tempo.io",
110        "or.us",
111        "proofpoint.com"
112      )
113    )
114    or any(filter(attachments,
115                  .content_type == "message/rfc822" or .file_extension in ('eml')
116           ),
117           any(file.parse_eml(.).attachments,
118               any(file.explode(.),
119                   regex.icontains(.scan.ocr.raw, 'scan|camera')
120                   and regex.icontains(.scan.ocr.raw, '\bQR\b|Q\.R\.|barcode')
121               )
122           )
123    )
124  )
125  // negate messages where "click here" was found and was a link actually an unsub link
126  // this method allows for matching on other 'click here' links if they are present
127  and not (
128    length(filter(body.links, strings.icontains(.display_text, 'click here'))) > 0
129    and (
130      length(filter(body.links, strings.icontains(.display_text, 'click here'))) == strings.icount(body.current_thread.text,
131                                                                                                   'click here to unsubscribe'
132      )
133    )
134  )
135  
136  // negate highly trusted sender domains unless they fail DMARC authentication
137  and (
138    (
139      (
140        sender.email.domain.root_domain in $high_trust_sender_root_domains
141        or sender.email.domain.root_domain in $org_domains
142      )
143      and not headers.auth_summary.dmarc.pass
144    )
145    or (
146      sender.email.domain.root_domain not in $high_trust_sender_root_domains
147      and sender.email.domain.root_domain not in $org_domains
148    )
149  )
150  // not from sharepointonline actual
151  and not (
152    sender.email.domain.root_domain == "sharepointonline.com"
153    and strings.ends_with(headers.message_id, '@odspnotify>')
154    and strings.starts_with(headers.message_id, "<Spo")
155  )
156  // netate common FP topics
157  and not any(beta.ml_topic(body.current_thread.text).topics,
158              .name in (
159                "Events and Webinars",
160                "Advertising and Promotions",
161                "Newsletters and Digests"
162              )
163              and .confidence == "high"
164  )
165  // negate common helpdesk/HR platforms
166  and not any(headers.domains,
167              .root_domain in (
168                "freshemail.io",
169                "zendesk.com",
170                "employeenavigator.com",
171                "saashr.com" // Kronos owned Saas HR offering
172              )
173  )
174  // negate observed HR newsletters
175  and not (
176    any(headers.hops,
177        strings.icontains(.authentication_results.spf_details.designator,
178                          "constantcontact.com"
179        )
180    )
181    and strings.starts_with(sender.email.local_part, 'newsletters-hr')
182    and sender.email.domain.root_domain == "ccsend.com"
183  )
184  // the message is unsolicited and no false positives
185  and (
186    not profile.by_sender_email().solicited
187    or (
188      profile.by_sender().any_messages_malicious_or_spam
189      and not profile.by_sender().any_messages_benign
190    )
191    or (
192      profile.by_sender().any_messages_malicious_or_spam
193      and profile.by_sender().any_messages_benign
194      and not (headers.auth_summary.dmarc.pass and headers.auth_summary.spf.pass)
195    )
196  )
197  // negate instances where proofpoint sends a review of a reported message via analyzer
198  and not (
199    sender.email.email == "analyzer@analyzer.securityeducation.com"
200    and any(headers.domains, .root_domain == "pphosted.com")
201    and headers.auth_summary.spf.pass
202    and headers.auth_summary.dmarc.pass
203  )  
204attack_types:
205  - "Credential Phishing"
206tactics_and_techniques:
207  - "Impersonation: Employee"
208  - "Social engineering"
209detection_methods:
210  - "Content analysis"
211  - "Header analysis"
212  - "Natural Language Understanding"
213  - "Sender analysis"
214id: "3cd04f33-5519-5cc1-8740-e8ce6cddf8a0"
to-top