VIP impersonation: Invoice fraud with mobile device sign-off

Detects inbound messages where a prior thread contains a reply from a known VIP - identifiable by display name or email address and signed off with a 'Sent from my iPhone' or 'Sent from my iPad' footer. However that VIP has been silently dropped from the current message's recipients. This pattern is consistent with fraudulent invoice and payment request lures where an attacker impersonates an internal executive in a thread, then removes them before requesting payment action from the remaining recipients.

Sublime rule (View on GitHub)

  1name: "VIP impersonation: Invoice fraud with mobile device sign-off"
  2description: "Detects inbound messages where a prior thread contains a reply from a known VIP - identifiable by display name or email address and signed off with a 'Sent from my iPhone' or 'Sent from my iPad' footer. However that VIP has been silently dropped from the current message's recipients. This pattern is consistent with fraudulent invoice and payment request lures where an attacker impersonates an internal executive in a thread, then removes them before requesting payment action from the remaining recipients."
  3type: "rule"
  4severity: "high"
  5source: |
  6  type.inbound
  7  and any(map(filter(body.previous_threads,
  8                     any($org_vips,
  9                         strings.icontains(..sender.display_name, .display_name)
 10                         or (
 11                           .email != ""
 12                           and strings.icontains(..sender.email.email, .email)
 13                         )
 14                     )
 15                     and (
 16                       strings.iends_with(.text,
 17                                          "sent from my iphone.",
 18                                          "sent from my ipad."
 19                       )
 20                       or regex.icontains(.text,
 21                                          '(?:sent (?:via|from) (?:my )?(?:iphone|ipad)[^\n]{0,140}siri|siri[^\n]{0,120}sent (?:via|from) (?:my )?(?:iphone|ipad))[^\n]{0,40}$'
 22                       )
 23                       // newer sign-offs ("Sent from this iPhone", "Sent from iPhone", optional period) are common in
 24                       // legitimate mail, so only count them on a one-to-one VIP message sent to internal recipients,
 25                       // with that VIP absent from the live message
 26                       or (
 27                         regex.icontains(.text,
 28                                         'sent (?:via|from) (?:my |this |an? )?(?:iphone|ipad)\.?\s*$'
 29                         )
 30                         // the fabricated VIP message is one-to-one; long legitimate threads cc many people
 31                         and length(.recipients.to) + length(.recipients.cc) <= 2
 32                         and (
 33                           all(flatten([
 34                                         recipients.to,
 35                                         recipients.cc,
 36                                         recipients.bcc
 37                                       ]
 38                               ),
 39                               .email.domain.root_domain in $org_domains
 40                           )
 41                           // or every recipient is at one domain that isn't the sender's (subsidiary domains)
 42                           or (
 43                             length(distinct(map(flatten([
 44                                                           recipients.to,
 45                                                           recipients.cc,
 46                                                           recipients.bcc
 47                                                         ]
 48                                                 ),
 49                                                 .email.domain.root_domain
 50                                             )
 51                                    )
 52                             ) == 1
 53                             and not any(flatten([
 54                                                   recipients.to,
 55                                                   recipients.cc,
 56                                                   recipients.bcc
 57                                                 ]
 58                                         ),
 59                                         .email.domain.root_domain == sender.email.domain.root_domain
 60                             )
 61                           )
 62                         )
 63                         // the VIP who wrote this segment is not on the live message, checked by their listed address.
 64                         // skipped when the segment has no sender address: the fabricated VIP message usually has none, and
 65                         // the actor sometimes sends the fake thread to the VIP's own inbox
 66                         and (
 67                           .sender.email.email == ""
 68                           or not any(flatten([
 69                                                recipients.to,
 70                                                recipients.cc,
 71                                                recipients.bcc
 72                                              ]
 73                                      ),
 74                                      any($org_vips,
 75                                          .email != ""
 76                                          and strings.icontains(..email.email,
 77                                                                .email
 78                                          )
 79                                          and (
 80                                            strings.icontains(...sender.display_name,
 81                                                              .display_name
 82                                            )
 83                                            or strings.icontains(...sender.email.email,
 84                                                                 .email
 85                                            )
 86                                          )
 87                                      )
 88                           )
 89                         )
 90                       )
 91                     )
 92                     and (
 93                       // any previous thread authored by the "VIP" has invoice/payment
 94                       any(ml.nlu_classifier(.text, subject=.subject.base).tags,
 95                           .name in ("invoice", "payment")
 96                           and .confidence != "low"
 97                       )
 98                       or any(ml.nlu_classifier(.text, subject=.subject.base).topics,
 99                              .name in (
100                                "Request to View Invoice",
101                                "Payment Information"
102                              )
103                              and .confidence != "low"
104                       )
105  
106                       // if we don't get NLU but there is a W9 or Inv attached, we can assume it's invoice related
107                       or any(attachments,
108                              strings.istarts_with(.file_name, 'INV', "W-9", 'W9')
109                       )
110                     )
111              ),
112              .sender.email.email
113          ),
114          . == ""
115          or (
116            not strings.icontains(sender.email.email, .)
117            and not any(flatten([recipients.to, recipients.cc, recipients.bcc]),
118                        strings.icontains(.email.email, ..)
119            )
120          )
121  )  
122attack_types:
123  - "BEC/Fraud"
124tactics_and_techniques:
125  - "Impersonation: VIP"
126  - "Social engineering"
127detection_methods:
128  - "Content analysis"
129  - "Sender analysis"
130id: "7b3b9dcb-adcf-5335-813b-51f1a42cb4d6"
to-top