diff --git a/Solutions/Teams/Workbooks/Images/Preview/MicrosoftTeamsBlack.png b/Solutions/Teams/Workbooks/Images/Preview/MicrosoftTeamsBlack.png new file mode 100644 index 00000000000..f4a40e1e2c1 Binary files /dev/null and b/Solutions/Teams/Workbooks/Images/Preview/MicrosoftTeamsBlack.png differ diff --git a/Solutions/Teams/Workbooks/Images/Preview/MicrosoftTeamsWhite.png b/Solutions/Teams/Workbooks/Images/Preview/MicrosoftTeamsWhite.png new file mode 100644 index 00000000000..bf0c06150ac Binary files /dev/null and b/Solutions/Teams/Workbooks/Images/Preview/MicrosoftTeamsWhite.png differ diff --git a/Solutions/Teams/Workbooks/MicrosoftTeams.json b/Solutions/Teams/Workbooks/MicrosoftTeams.json new file mode 100644 index 00000000000..9db8f327500 --- /dev/null +++ b/Solutions/Teams/Workbooks/MicrosoftTeams.json @@ -0,0 +1,546 @@ +{ + "version": "Notebook/1.0", + "items": [ + { + "type": 1, + "content": { + "json": "## Microsoft Teams\n---\n\nThis workbook is dedicated to Microsoft Teams Logs." + }, + "name": "text - 2" + }, + { + "type": 9, + "content": { + "version": "KqlParameterItem/1.0", + "crossComponentResources": [ + "{Subscription}" + ], + "parameters": [ + { + "id": "c59abf67-3610-461e-b263-c90ebaf6e59f", + "version": "KqlParameterItem/1.0", + "name": "Subscription", + "type": 6, + "isRequired": true, + "typeSettings": { + "additionalResourceOptions": [], + "includeAll": true, + "showDefault": false + } + }, + { + "id": "99e8ff0c-1981-41b9-ba6c-dec28a2f77c0", + "version": "KqlParameterItem/1.0", + "name": "Workspace", + "type": 5, + "isRequired": true, + "query": "where type =~ 'microsoft.operationalinsights/workspaces'\r\n| project id", + "crossComponentResources": [ + "{Subscription}" + ], + "typeSettings": { + "additionalResourceOptions": [], + "showDefault": false + }, + "queryType": 1, + "resourceType": "microsoft.resourcegraph/resources" + }, + { + "id": "9dd762f8-8594-432f-b1dc-9561e0b799c6", + "version": "KqlParameterItem/1.0", + "name": "TimeRange", + "type": 4, + "isRequired": true, + "value": { + "durationMs": 86400000 + }, + "typeSettings": { + "selectableValues": [ + { + "durationMs": 300000 + }, + { + "durationMs": 900000 + }, + { + "durationMs": 1800000 + }, + { + "durationMs": 3600000 + }, + { + "durationMs": 14400000 + }, + { + "durationMs": 43200000 + }, + { + "durationMs": 86400000 + }, + { + "durationMs": 172800000 + }, + { + "durationMs": 259200000 + }, + { + "durationMs": 604800000 + }, + { + "durationMs": 1209600000 + }, + { + "durationMs": 2419200000 + }, + { + "durationMs": 2592000000 + }, + { + "durationMs": 5184000000 + }, + { + "durationMs": 7776000000 + } + ], + "allowCustom": true + } + } + ], + "style": "pills", + "queryType": 1, + "resourceType": "microsoft.resourcegraph/resources" + }, + "name": "parameters - 1" + }, + { + "type": 11, + "content": { + "version": "LinkItem/1.0", + "style": "tabs", + "links": [ + { + "id": "2cfb496e-8b26-4c13-b91d-04109ee37efc", + "cellValue": "selectedTab", + "linkTarget": "parameter", + "linkLabel": "Overview", + "subTarget": "Overview", + "preText": "", + "style": "link" + }, + { + "id": "691ebec0-768b-4006-92b1-75660bc90398", + "cellValue": "selectedTab", + "linkTarget": "parameter", + "linkLabel": "Admin Activity", + "subTarget": "Admin Activity", + "preText": "", + "style": "link" + }, + { + "id": "5f1edb44-1df8-4632-a00b-79a027ae4bcd", + "cellValue": "selectedTab", + "linkTarget": "parameter", + "linkLabel": "External User Activity", + "subTarget": "External User Activity", + "style": "link" + }, + { + "id": "2658dec2-ed33-4a4d-a27a-c68b92fa6945", + "cellValue": "selectedTab", + "linkTarget": "parameter", + "linkLabel": "Hunting", + "subTarget": "Hunting", + "style": "link" + } + ] + }, + "name": "links - 6" + }, + { + "type": 12, + "content": { + "version": "NotebookGroup/1.0", + "groupType": "editable", + "title": "Overview", + "items": [ + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "OfficeActivity\n| where OfficeWorkload =~ \"MicrosoftTeams\"\n| summarize count() by Operation, bin_at(TimeGenerated, 1h, now())\n", + "size": 0, + "title": "Operations Over Time", + "timeContext": { + "durationMs": 0 + }, + "timeContextFromParameter": "TimeRange", + "timeBrushParameterName": "TimeBrush", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "visualization": "timechart" + }, + "name": "query - 2" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "OfficeActivity\r\n| where OfficeWorkload =~ \"MicrosoftTeams\"\r\n| summarize count() by Operation , bin_at(TimeGenerated, 1h, now())\r\n| summarize makelist(count_), sum(count_) by Operation\r\n| order by sum_count_", + "size": 0, + "title": "Types of Operation", + "timeContext": { + "durationMs": 0 + }, + "timeContextFromParameter": "TimeRange", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "visualization": "tiles", + "gridSettings": { + "filter": true + }, + "tileSettings": { + "titleContent": { + "columnMatch": "Operation", + "formatter": 1 + }, + "subtitleContent": { + "columnMatch": "sum_count_", + "formatter": 12, + "formatOptions": { + "palette": "blue" + } + }, + "secondaryContent": { + "columnMatch": "list_count_", + "formatter": 10, + "formatOptions": { + "palette": "blue" + } + }, + "showBorder": false + } + }, + "name": "query - 3" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "OfficeActivity\r\n| where OfficeWorkload =~ \"MicrosoftTeams\"\r\n| summarize OperationCount = count() , ['Last Activity Time'] = arg_max(TimeGenerated, *) by UserId, Operation\r\n| project OperationCount, UserId, Operation, ['Last Activity Time']\r\n| order by OperationCount", + "size": 0, + "title": "Last Activity based on User", + "timeContext": { + "durationMs": 82800000, + "endTime": "2022-01-28T17:33:18.000Z" + }, + "timeContextFromParameter": "TimeBrush", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "gridSettings": { + "formatters": [ + { + "columnMatch": "UserId", + "formatter": 5 + }, + { + "columnMatch": "OperationCount", + "formatter": 8, + "formatOptions": { + "palette": "blue" + } + } + ], + "rowLimit": 10000, + "hierarchySettings": { + "treeType": 1, + "groupBy": [ + "UserId" + ], + "expandTopLevel": true + } + } + }, + "name": "query - 4" + } + ], + "exportParameters": true + }, + "conditionalVisibility": { + "parameterName": "selectedTab", + "comparison": "isEqualTo", + "value": "Overview" + }, + "name": "tabOverview" + }, + { + "type": 12, + "content": { + "version": "NotebookGroup/1.0", + "groupType": "editable", + "title": "Admin Activity", + "items": [ + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "OfficeActivity\r\n| where OfficeWorkload == \"MicrosoftTeams\"\r\n| where Operation contains \"Added\" or Operation contains \"Removed\" or Operation contains \"Deleted\" or Operation contains \"Created\"\r\n| summarize count() by Operation", + "size": 0, + "timeContext": { + "durationMs": 86400000 + }, + "timeContextFromParameter": "TimeRange", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{Workspace}" + ], + "visualization": "piechart" + }, + "customWidth": "30", + "name": "query - 0" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "OfficeActivity\r\n| where OfficeWorkload == \"MicrosoftTeams\"\r\n| where Operation contains \"Added\" or Operation contains \"Removed\" or Operation contains \"Deleted\" or Operation contains \"Created\"\r\n| project TimeGenerated, Operation, TeamName, ChannelName, UserId\r\n| sort by TimeGenerated desc", + "size": 0, + "timeContext": { + "durationMs": 86400000 + }, + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{Workspace}" + ] + }, + "customWidth": "70", + "name": "query - 1" + } + ], + "exportParameters": true + }, + "conditionalVisibility": { + "parameterName": "selectedTab", + "comparison": "isEqualTo", + "value": "Admin Activity" + }, + "name": "Admin Activity" + }, + { + "type": 12, + "content": { + "version": "NotebookGroup/1.0", + "groupType": "editable", + "items": [ + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "OfficeActivity\r\n| where OfficeWorkload == \"MicrosoftTeams\"\r\n| where CommunicationType == \"Team\"\r\n| where Operation == \"MemberAdded\"\r\n| extend DisplayName_ = tostring(parse_json(Members)[0].DisplayName) \r\n| extend UPN_ = tostring(parse_json(Members)[0].UPN)\r\n| where UPN_ contains \"#EXT#\"\r\n| project TimeGenerated, [\"Added By\"] = UserId, TeamName, DisplayName_, UPN_\r\n| sort by TimeGenerated desc ", + "size": 0, + "title": "External Users Added to Team", + "timeContext": { + "durationMs": 86400000 + }, + "timeContextFromParameter": "TimeRange", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "gridSettings": { + "filter": true + } + }, + "name": "query - 0" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "OfficeActivity\r\n| where OfficeWorkload == \"MicrosoftTeams\"\r\n| where CommunicationType == \"Team\"\r\n| where Operation == \"MemberRemoved\"\r\n| extend DisplayName_ = tostring(parse_json(Members)[0].DisplayName) \r\n| extend UPN_ = tostring(parse_json(Members)[0].UPN)\r\n| where UPN_ contains \"#EXT#\"\r\n| project TimeGenerated, [\"Removed By\"] = UserId, TeamName, DisplayName_, UPN_\r\n| sort by TimeGenerated desc ", + "size": 0, + "title": "External Users Removed from the Team", + "timeContext": { + "durationMs": 86400000 + }, + "timeContextFromParameter": "TimeRange", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "gridSettings": { + "filter": true + } + }, + "name": "query - 0 - Copy" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "OfficeActivity\r\n| where OfficeWorkload == \"MicrosoftTeams\"\r\n| where UserId contains \"#EXT#\"\r\n| project TimeGenerated, Operation, UserId\r\n| sort by TimeGenerated desc", + "size": 0, + "title": "External Users Activity", + "timeContext": { + "durationMs": 86400000 + }, + "timeContextFromParameter": "TimeRange", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "gridSettings": { + "filter": true + } + }, + "name": "query - 0 - Copy - Copy" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "OfficeActivity\r\n| where OfficeWorkload == \"MicrosoftTeams\"\r\n| where UserId contains \"#EXT#\"\r\n| summarize count() by Operation, bin(TimeGenerated, 1d)", + "size": 0, + "title": "External Activity, by Day", + "timeContext": { + "durationMs": 86400000 + }, + "timeContextFromParameter": "TimeRange", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "visualization": "timechart", + "gridSettings": { + "filter": true + } + }, + "name": "query - 0 - Copy - Copy - Copy" + } + ] + }, + "conditionalVisibility": { + "parameterName": "selectedTab", + "comparison": "isEqualTo", + "value": "External User Activity" + }, + "name": "External User Activity" + }, + { + "type": 12, + "content": { + "version": "NotebookGroup/1.0", + "groupType": "editable", + "items": [ + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "// If you want to look at user added further than 7 days ago adjust this value \r\nlet time_ago = 7d; \r\n// If you want to change the timeframe of how quickly accounts need to be added and removed change this value \r\nlet time_delta = 1h; \r\nOfficeActivity\r\n| where OfficeWorkload == \"MicrosoftTeams\"\r\n| where TimeGenerated > ago(time_ago) \r\n| where Operation =~ \"MemberAdded\" \r\n| extend UPN = tostring(parse_json(Members)[0].UPN) \r\n| where UPN contains \"#EXT#\"\r\n| project TimeAdded=TimeGenerated, Operation, UPN, UserWhoAdded = UserId, TeamName, TeamGuid\r\n| join ( \r\nOfficeActivity\r\n| where OfficeWorkload == \"MicrosoftTeams\"\r\n| where TimeGenerated > ago(time_ago) \r\n| where Operation =~ \"MemberRemoved\" \r\n| extend UPN = tostring(parse_json(Members)[0].UPN) \r\n| where UPN contains \"#EXT#\"\r\n| project TimeDeleted=TimeGenerated, Operation, UPN, UserWhoDeleted = UserId, TeamName, TeamGuid) on UPN, TeamGuid \r\n| where TimeDeleted < (TimeAdded + time_delta) \r\n| project TimeAdded, TimeDeleted, UPN, UserWhoAdded, UserWhoDeleted, TeamName, TeamGuid \r\n// Uncomment the following line to map query entities is you plan to use this as a detection query \r\n| extend timestamp = TimeAdded, AccountCustomEntity = UPN \r\n", + "size": 0, + "title": "External Users Added and Removed", + "timeContext": { + "durationMs": 86400000 + }, + "timeContextFromParameter": "TimeRange", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces" + }, + "name": "query - 0" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "// If you have more than 14 days worth of Teams data change this value \r\nlet data_date = 14d; \r\n// If you want to look at users further back than the last day change this value \r\nlet lookback_data = 1d; \r\nlet known_orgs = ( \r\nOfficeActivity\r\n| where OfficeWorkload == \"MicrosoftTeams\"\r\n| where TimeGenerated > ago(data_date) \r\n| where Operation =~ \"MemberAdded\" or Operation =~ \"TeamsSessionStarted\" \r\n// Extract the correct UPN and parse our external organization domain \r\n| extend UPN = iif(Operation == \"MemberAdded\", tostring(parse_json(Members)[0].UPN), UserId) \r\n| extend Organization = tostring(split(split(UPN, \"_\")[1], \"#\")[0]) \r\n| where isnotempty(Organization) \r\n| summarize by Organization); \r\nOfficeActivity\r\n| where OfficeWorkload == \"MicrosoftTeams\"\r\n| where TimeGenerated > ago(lookback_data) \r\n| where Operation =~ \"MemberAdded\" \r\n| extend UPN = tostring(parse_json(Members)[0].UPN) \r\n| extend Organization = tostring(split(split(UPN, \"_\")[1], \"#\")[0]) \r\n| where isnotempty(Organization) \r\n| where Organization !in (known_orgs) \r\n// Uncomment the following line to map query entities is you plan to use this as a detection query \r\n| extend timestamp = TimeGenerated, AccountCustomEntity = UPN \r\n", + "size": 0, + "timeContext": { + "durationMs": 86400000 + }, + "timeContextFromParameter": "TimeRange", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces" + }, + "name": "query - 1" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": " // Adjust this value to change how many Teams should be deleted before including\r\n let max_delete = 3;\r\n // Adjust this value to change the timewindow the query runs over\r\n let time_window = 1d;\r\n let deleting_users = (\r\nOfficeActivity\r\n| where OfficeWorkload == \"MicrosoftTeams\"\r\n | where TimeGenerated > ago(time_window)\r\n | where Operation =~ \"TeamDeleted\"\r\n | summarize count() by UserId\r\n | where count_ > max_delete\r\n | project UserId);\r\nOfficeActivity\r\n| where OfficeWorkload == \"MicrosoftTeams\"\r\n | where TimeGenerated > ago(time_window)\r\n | where Operation =~ \"TeamDeleted\"\r\n | where UserId in (deleting_users)\r\n | project-away Members\r\n // Uncomment the following line to map query entities is you plan to use this as a detection query\r\n| extend timestamp = TimeGenerated, AccountCustomEntity = UserId", + "size": 0, + "title": "Multiple Teams Deleted by a single user", + "timeContext": { + "durationMs": 86400000 + }, + "timeContextFromParameter": "TimeRange", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces" + }, + "name": "query - 2" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "let timeFrame = 1d;\r\nlet logonDiff = 10m;\r\nSigninLogs \r\n | where TimeGenerated >= ago(timeFrame) \r\n | where ResultType == \"0\" \r\n | where AppDisplayName startswith \"Microsoft Teams\"\r\n | project SuccessLogonTime = TimeGenerated, UserPrincipalName, SuccessIPAddress = IPAddress, AppDisplayName, SuccessIPBlock = strcat(split(IPAddress, \".\")[0], \".\", split(IPAddress, \".\")[1])\r\n | join kind= inner (\r\n SigninLogs \r\n | where TimeGenerated >= ago(timeFrame) \r\n | where ResultType !in (\"0\", \"50140\") \r\n | where ResultDescription !~ \"Other\" \r\n | where AppDisplayName startswith \"Microsoft Teams\"\r\n | project FailedLogonTime = TimeGenerated, UserPrincipalName, FailedIPAddress = IPAddress, AppDisplayName, ResultType, ResultDescription\r\n ) on UserPrincipalName, AppDisplayName \r\n | where SuccessLogonTime < FailedLogonTime and FailedLogonTime - SuccessLogonTime <= logonDiff and FailedIPAddress !startswith SuccessIPBlock\r\n | summarize FailedLogonTime = max(FailedLogonTime), SuccessLogonTime = max(SuccessLogonTime) by UserPrincipalName, SuccessIPAddress, AppDisplayName, FailedIPAddress, ResultType, ResultDescription \r\n | extend timestamp = SuccessLogonTime, AccountCustomEntity = UserPrincipalName, IPCustomEntity = SuccessIPAddress", + "size": 0, + "title": "Successful logon from IP and failure from a different IP (Teams Signins)", + "timeContext": { + "durationMs": 0 + }, + "timeContextFromParameter": "TimeRange", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces" + }, + "name": "query - 3" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "// Adjust this value to change how many teams a user is made owner of before detecting \r\nlet max_owner_count = 3; \r\n// Change this value to adjust how larger timeframe the query is run over. \r\nlet time_window = 1d; \r\nlet high_owner_count = (OfficeActivity\r\n| where OfficeWorkload == \"MicrosoftTeams\" \r\n| where TimeGenerated > ago(time_window) \r\n| where Operation =~ \"MemberRoleChanged\" \r\n| extend Member = tostring(parse_json(Members)[0].UPN) \r\n| extend NewRole = toint(parse_json(Members)[0].Role) \r\n| where NewRole == 2 \r\n| summarize dcount(TeamName) by Member \r\n| where dcount_TeamName > max_owner_count \r\n| project Member); \r\nOfficeActivity\r\n| where OfficeWorkload == \"MicrosoftTeams\"\r\n| where TimeGenerated > ago(time_window) \r\n| where Operation =~ \"MemberRoleChanged\" \r\n| extend Member = tostring(parse_json(Members)[0].UPN) \r\n| extend NewRole = toint(parse_json(Members)[0].Role) \r\n| where NewRole == 2 \r\n| where Member in (high_owner_count) ", + "size": 0, + "title": "User is made owner", + "timeContext": { + "durationMs": 0 + }, + "timeContextFromParameter": "TimeRange", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces" + }, + "name": "query - 4" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "let suspiciousExtensions = dynamic(['exe', 'msi', 'bin', 'dll']);\r\nOfficeActivity \r\n| where RecordType =~ \"SharePointFileOperation\" \r\n| where UserId !~ \"app@sharepoint\"\r\n| where SourceRelativeUrl has \"Microsoft Teams Chat Files\" \r\n| where Operation =~ \"FileUploaded\" \r\n| project TimeGenerated, UserId, ClientIP, Site_Url, OfficeObjectId, SourceFileName, SourceFileExtension\r\n| where SourceFileExtension has_any(suspiciousExtensions)", + "size": 0, + "title": "Sharing - Suspicious FIle Extensions", + "timeContext": { + "durationMs": 86400000 + }, + "timeContextFromParameter": "TimeRange", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces" + }, + "name": "query - 5" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "//The bigger the window the better the data sample size, as we use IP prevalence, more sample data is better.\r\n let timeRange = 30d;\r\n //The minimum number of countries that the account has been accessed from [default: 2]\r\n let minimumCountries = 2;\r\n //The delta (%) between the largest in-use IP and the smallest [default: 90]\r\n let deltaThreshold = 95;\r\n //The maximum (%) threshold that the country appears in login data [default: 10]\r\n let countryPrevalenceThreshold = 10;\r\n //The time to project forward after the last login activity [default: 60min]\r\n let projectedEndTime = 60min; \r\n //Get Teams successful signins globally\r\n let signinData =\r\n SigninLogs\r\n | where TimeGenerated >= ago(timeRange)\r\n | where AppDisplayName has \"Teams\"\r\n | where ConditionalAccessStatus =~ \"success\"\r\n | extend country = tostring(LocationDetails['countryOrRegion'])\r\n | where isnotempty(country) and isnotempty(IPAddress);\r\n // Collect successful signins to teams\r\n let loginEvents = \r\n signinData\r\n | summarize count(), country=any(country), make_list(TimeGenerated) by IPAddress, UserPrincipalName;\r\n //Calcualte delta between logins\r\n let loginDelta =\r\n loginEvents\r\n | summarize max(count_), min(count_) by UserPrincipalName\r\n | extend delta = toreal(max_count_ - min_count_) / max_count_ * 100\r\n | where delta >= deltaThreshold;\r\n //Count number of countries used to sign in\r\n let countryCount =\r\n loginEvents\r\n | summarize Countries = dcount(country) by UserPrincipalName;\r\n //Join delta and sign in counts to successful logins\r\n loginDelta\r\n | join kind=rightouter (\r\n loginEvents\r\n ) on UserPrincipalName\r\n | join kind=rightouter (\r\n countryCount\r\n ) on UserPrincipalName\r\n //Check where the record meets the minimum required countries\r\n | where Countries >= minimumCountries\r\n | join kind=leftouter (\r\n signinData\r\n | summarize count() by country\r\n | join (\r\n //Now get the total number of logins from any country and join it to the previous count in a single table\r\n signinData\r\n | summarize count() by country\r\n | summarize sum(count_), make_list(country)\r\n | mv-expand list_country\r\n | extend country = tostring(list_country)\r\n ) on country\r\n | summarize by country, count_, sum_count_\r\n //Now calculate each countries prevalence within login events\r\n | extend prevalence = toreal(count_) / toreal(sum_count_) * 100\r\n | project-away sum_count_\r\n | order by prevalence\r\n ) on country\r\n //The % that suspicious country is prevalent in data, this can be configured, less than 10% is uncommon\r\n | where prevalence < countryPrevalenceThreshold\r\n | where min_count_ == count_\r\n //Login start and end times from the JSON object, this is the activity window the suspicious IP was active within\r\n | extend EventTimes = list_TimeGenerated\r\n | extend SuspiciousIP = IPAddress\r\n | project UserPrincipalName, SuspiciousIP, UserIPDelta = delta, SuspiciousLoginCountry = country, SuspiciousCountryPrevalence = prevalence, EventTimes\r\n //Teams join to collect operations the user account has performed within the given time range\r\n | join kind=inner( \r\n OfficeActivity\r\n | where OfficeWorkload == \"MicrosoftTeams\"\r\n | where TimeGenerated >= ago(timeRange)\r\n | where Operation in~ (\"TeamsAdminAction\", \"MemberAdded\", \"MemberRemoved\", \"MemberRoleChanged\", \"AppInstalled\", \"BotAddedToTeam\")\r\n | project Operation, UserId=tolower(UserId), OperationTime=TimeGenerated\r\n ) on $left.UserPrincipalName == $right.UserId\r\n | mv-expand StartTime = EventTimes\r\n | extend StartTime = make_datetime(StartTime)\r\n //The end time is projected 60 minutes forward, in case actions took place within the last hour of the final login for the suspicious IP\r\n | extend ProjectedEndTime = make_datetime(StartTime + projectedEndTime)\r\n //Limit to operations carried out by the user account in the timeframe the IP was active\r\n | where OperationTime between (StartTime .. ProjectedEndTime)\r\n | project UserPrincipalName, SuspiciousIP, StartTime, ProjectedEndTime, OperationTime, Operation, SuspiciousLoginCountry, SuspiciousCountryPrevalence\r\n //Filter on suspicious actions\r\n | extend activitySummary = pack(tostring(StartTime), pack(\"Operation\",tostring(Operation), \"OperationTime\", OperationTime))\r\n | summarize make_bag(activitySummary) by UserPrincipalName, SuspiciousIP, SuspiciousLoginCountry, SuspiciousCountryPrevalence\r\n | extend IPCustomEntity = SuspiciousIP, AccountCustomEntity = UserPrincipalName", + "size": 0, + "title": "Anomalous login followed by Teams action", + "timeContext": { + "durationMs": 0 + }, + "timeContextFromParameter": "TimeRange", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces" + }, + "name": "query - 6" + } + ] + }, + "name": "Hunting" + } + ], + "fromTemplateId": "sentinel-MicrosoftTeamsWorkbook", + "$schema": "https://github.com/Microsoft/Application-Insights-Workbooks/blob/master/schema/workbook.json" +}