External Microsoft Teams Sender Domain Risk
Description
This query ranks external Microsoft Teams sender domains by threat volume, split by conversation type, with threat rate and low-reputation URL counts.
Query · kql
//This query ranks external Microsoft Teams sender domains by threat volume over the last 30 days, split by
//conversation type, with threat-type breakdown, threat rate and a count of messages carrying low-reputation URLs.
//Senders skewing to 1:1 chats are notable because there are no colleagues in the conversation to challenge the request.
//Messages are de-duplicated to the latest record per message; only inbound external threads are counted.
let suspTlds = dynamic(["top","xyz","click","link","shop","online","site","live","icu","cyou","sbs","rest","quest","cfd","bond","buzz","fun","space","monster","work","tk","ml","ga","cf","gq","dev","app","zip","mov","info","win","loan","men","stream","country"]);
let suspUrlMsgs = MessageUrlInfo
| where Timestamp > ago(30d)
| where isnotempty(UrlDomain)
| extend Tld = tostring(split(UrlDomain, ".")[-1])
| where Tld in (suspTlds)
| distinct TeamsMessageId;
MessageEvents
| where Timestamp > ago(30d)
| where isnotempty(TeamsMessageId)
//Only the columns used below are carried through the de-duplication, to keep the shuffle cost down.
| summarize arg_max(Timestamp, IsExternalThread, IsOwnedThread, SenderEmailAddress, GroupId, RecipientDetails, ThreatTypes) by TeamsMessageId
| where IsExternalThread == 1 and IsOwnedThread == 0
| extend SenderDomain = tolower(tostring(split(SenderEmailAddress, "@")[1]))
| where isnotempty(SenderDomain)
| extend ConvType = case(isnotempty(GroupId), "Channel",
array_length(todynamic(RecipientDetails)) > 1, "Group chat",
"1:1 chat")
| extend HasSuspUrl = TeamsMessageId in (suspUrlMsgs)
| summarize TeamsMessages = count(), Senders = dcount(SenderEmailAddress),
OneToOne = countif(ConvType == "1:1 chat"),
GroupChat = countif(ConvType == "Group chat"),
ChannelMsgs = countif(ConvType == "Channel"),
ThreatMessages = countif(isnotempty(ThreatTypes)),
Malware = countif(ThreatTypes has "Malware"),
Phish = countif(ThreatTypes has "Phish"),
Spam = countif(ThreatTypes has "Spam"),
LowRepUrlMessages = countif(HasSuspUrl),
FirstSeen = min(Timestamp), LastSeen = max(Timestamp)
by SenderDomain
| where ThreatMessages > 0
| extend ThreatRatePct = round(100.0 * ThreatMessages / TeamsMessages, 1)
| top 20 by ThreatMessages desc
| project ['Sender Domain']=SenderDomain, ['Teams Messages']=TeamsMessages, ['Distinct Senders']=Senders,
['1:1 Chat']=OneToOne, ['Group Chat']=GroupChat, ['Channel']=ChannelMsgs,
['Threat Messages']=ThreatMessages, Malware, Phish, Spam,
['Threat Rate %']=ThreatRatePct, ['Low-Rep URL Messages']=LowRepUrlMessages,
['First Seen']=FirstSeen, ['Last Seen']=LastSeen