Brand impersonation: Microsoft Teams invitation
Detects messages impersonating a Microsoft Teams invites by matching known invite text patterns while containing join links that do not resolve to Microsoft domains. Additional verification includes checking for absent phone dial-in options and missing standard Teams help text or HTML meeting components.
Sublime rule (View on GitHub)
1name: "Brand impersonation: Microsoft Teams invitation"
2description: "Detects messages impersonating a Microsoft Teams invites by matching known invite text patterns while containing join links that do not resolve to Microsoft domains. Additional verification includes checking for absent phone dial-in options and missing standard Teams help text or HTML meeting components."
3type: "rule"
4severity: "high"
5source: |
6 type.inbound
7 and (
8 (
9 strings.icontains(body.current_thread.text, 'Microsoft Teams')
10 and strings.icontains(body.current_thread.text,
11 'join the meeting',
12 'confirm your attendance',
13 'confirm attendance'
14 )
15 and strings.contains(body.current_thread.text, 'Meeting ID:')
16 and strings.contains(body.current_thread.text, 'Passcode:')
17 )
18 or (
19 strings.icontains(body.current_thread.text, "teams")
20 // strings that give us confidence it's teams
21 and 2 of (
22 strings.icontains(body.current_thread.text, "internal"),
23 strings.icontains(body.current_thread.text, "message"),
24 strings.icontains(body.current_thread.text, "meeting"),
25 strings.icontains(body.current_thread.text, "Download Teams")
26 )
27 )
28 // either the subject or sender.display name containt Microsoft Teams and Meeting
29 or (
30 any([subject.base, sender.display_name],
31 strings.icontains(., 'Microsoft Teams')
32 and strings.icontains(., 'meeting')
33 )
34 )
35 or length(filter(ml.nlu_classifier(body.current_thread.text).entities,
36 .name == "org"
37 and strings.icontains(.text, "microsoft teams")
38 )
39 ) >= 2
40 )
41 and (
42 (
43 // not a reply
44 length(headers.references) == 0
45 and headers.in_reply_to is null
46 // few links
47 and length(distinct(body.links, .href_url.url)) < 10
48 // short body
49 and (
50 length(body.current_thread.text) < 600
51 // account for NLU disclaimer in length
52 or any(map(filter(ml.nlu_classifier(body.current_thread.text).entities,
53 .name == "disclaimer"
54 ),
55 .text
56 ),
57 (length(body.current_thread.text) - length(.)) < 600
58 )
59 // NLU only tags the first english disclaimer, so pick up repeat disclaimers or non-english disclaimers as well
60 or any(regex.iextract(body.current_thread.text,
61 '^(?P<disclaimer>.{100,}?)(?:confidentiality notice|nota di riservatezza)'
62 ),
63 length(.named_groups["disclaimer"]) < 600
64 )
65 )
66 ) // or sender domain is new
67 or network.whois(sender.email.domain).days_old < 365
68 )
69 // no unsubscribe links
70 // common in newsletters which link to a webinar style event
71 and not any(body.links, strings.icontains(.display_text, "unsub"))
72
73 // one of the links contains is a CTA that doesn't link to MS
74 and any(body.current_thread.links,
75 (
76 .display_text =~ "join the meeting"
77 or strings.icontains(.display_text,
78 "join the meeting",
79 "play recording",
80 "open in teams"
81 )
82 // is a mismatched domain via .display_url
83 or (
84 .display_url.domain.root_domain in (
85 "microsoft.com",
86 "microsoft.us",
87 "microsoft.cn",
88 "live.com"
89 )
90 and .mismatched
91 )
92 )
93 and .href_url.domain.root_domain not in (
94 "microsoft.com",
95 "microsoft.us",
96 "microsoft.cn",
97 "live.com"
98 )
99 and not (
100 .href_url.domain.root_domain == "mimecastprotect.com"
101 and (
102 strings.parse_domain(.href_url.query_params_decoded["domain"][0]).root_domain in (
103 "microsoft.com",
104 "microsoft.us",
105 "microsoft.cn",
106 "live.com"
107 )
108 or strings.parse_domain(.href_url.query_params_decoded["domain"][0]).root_domain in $bulk_mailer_url_root_domains
109 )
110 )
111 // rewriters often abstract the link
112 and .href_url.domain.root_domain not in $bulk_mailer_url_root_domains
113 )
114 // missing the dial by phone element
115 and not strings.icontains(body.current_thread.text, 'Dial in by phone')
116
117 // any of these suspicious elements from the body
118 and (
119 // malicious samples leveraged recipient domain branding here
120 not strings.icontains(body.current_thread.text, 'Microsoft Teams Need help?')
121 // malicious samples contained unique html elements not present in legit ones
122 or strings.icontains(body.html.raw, '<div class="meeting-title">')
123 or strings.icontains(body.html.raw, '<div class="meeting-time">')
124 or strings.icontains(body.html.raw, '<div class="meeting-location">')
125 or strings.icontains(body.html.raw, '<span class="conflict-badge">')
126 or strings.icontains(body.html.raw, 'class="join-button"')
127 )
128
129 // negate highly trusted sender domains unless they fail DMARC authentication
130 and not (
131 sender.email.domain.root_domain in $high_trust_sender_root_domains
132 and coalesce(headers.auth_summary.dmarc.pass, false)
133 )
134attack_types:
135 - "Credential Phishing"
136tactics_and_techniques:
137 - "Impersonation: Brand"
138 - "Social engineering"
139detection_methods:
140 - "Content analysis"
141 - "Header analysis"
142 - "HTML analysis"
143 - "URL analysis"
144id: "46410ad8-3465-505f-a78e-f77704910a91"