Microsoft Entra ID Rare UserAgent App Sign-in
Id | 87d5cd18-211d-4fd4-9b86-65d23fed87ea |
Rulename | Microsoft Entra ID Rare UserAgent App Sign-in |
Description | This query establishes a baseline of the type of UserAgent (i.e. browser, office application, etc) that is typically used for a particular application by looking back for a number of days. It then searches the current day for any deviations from this pattern, i.e. types of UserAgents not seen before in combination with this application. |
Severity | Medium |
Tactics | DefenseEvasion |
Techniques | T1036 |
Required data connectors | AzureActiveDirectory |
Kind | Scheduled |
Query frequency | 1d |
Query period | 7d |
Trigger threshold | 0 |
Trigger operator | gt |
Source Uri | https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/FalconFriday/Analytic Rules/AzureADRareUserAgentAppSignin.yaml |
Version | 1.0.1 |
Arm template | 87d5cd18-211d-4fd4-9b86-65d23fed87ea.json |
let minimumAppThreshold = 100;
let timeframe = 1d;
let lookback_timeframe= 7d;
let ExtractBrowserTypeFromUA=(ua:string) {
// Note: these are in a specific order since, for example, Edge contains "Chrome/" and "Edge/" strings.
case(
ua has "Edge/", dynamic({"AgentType": "Browser", "AgentName": "Edge"}),
ua has "Edg/", dynamic({"AgentType": "Browser", "AgentName": "Edge"}),
ua has "Trident/", dynamic({"AgentType": "Browser", "AgentName": "Internet Explorer"}),
ua has "Chrome/" and ua has "Safari/", dynamic({"AgentType": "Browser", "AgentName": "Chrome"}),
ua has "Gecko/" and ua has "Firefox/", dynamic({"AgentType": "Browser", "AgentName": "Firefox"}),
not(ua has "Mobile/") and ua has "Safari/" and ua has "Version/", dynamic({"AgentType": "Browser", "AgentName": "Safari"}),
ua startswith "Dalvik/" and ua has "Android", dynamic({"AgentType": "Browser", "AgentName": "Android Browser"}),
ua startswith "MobileSafari//", dynamic({"AgentType": "Browser", "AgentName": "Mobile Safari"}),
ua has "Mobile/" and ua has "Safari/" and ua has "Version/", dynamic({"AgentType": "Browser", "AgentName": "Mobile Safari"}),
ua has "Mobile/" and ua has "FxiOS/", dynamic({"AgentType": "Browser", "AgentName": "IOS Firefox"}),
ua has "Mobile/" and ua has "CriOS/", dynamic({"AgentType": "Browser", "AgentName": "IOS Chrome"}),
ua has "Mobile/" and ua has "WebKit/", dynamic({"AgentType": "Browser", "AgentName": "Mobile Webkit"}),
//
ua startswith "Excel/", dynamic({"AgentType": "OfficeApp", "AgentName": "Excel"}),
ua startswith "Outlook/", dynamic({"AgentType": "OfficeApp", "AgentName": "Outlook"}),
ua startswith "OneDrive/", dynamic({"AgentType": "OfficeApp", "AgentName": "OneDrive"}),
ua startswith "OneNote/", dynamic({"AgentType": "OfficeApp", "AgentName": "OneNote"}),
ua startswith "Office/", dynamic({"AgentType": "OfficeApp", "AgentName": "Office"}),
ua startswith "PowerPoint/", dynamic({"AgentType": "OfficeApp", "AgentName": "PowerPoint"}),
ua startswith "PowerApps/", dynamic({"AgentType": "OfficeApp", "AgentName": "PowerApps"}),
ua startswith "SharePoint/", dynamic({"AgentType": "OfficeApp", "AgentName": "SharePoint"}),
ua startswith "Word/", dynamic({"AgentType": "OfficeApp", "AgentName": "Word"}),
ua startswith "Visio/", dynamic({"AgentType": "OfficeApp", "AgentName": "Visio"}),
ua startswith "Whiteboard/", dynamic({"AgentType": "OfficeApp", "AgentName": "Whiteboard"}),
ua =~ "Mozilla/5.0 (compatible; MSAL 1.0)", dynamic({"AgentType": "OfficeApp", "AgentName": "Office Telemetry"}),
//
ua has ".NET CLR", dynamic({"AgentType": "Custom", "AgentName": "Dotnet"}),
ua startswith "Java/", dynamic({"AgentType": "Custom", "AgentName": "Java"}),
ua startswith "okhttp/", dynamic({"AgentType": "Custom", "AgentName": "okhttp"}),
ua has "Drupal/", dynamic({"AgentType": "Custom", "AgentName": "Drupal"}),
ua has "PHP/", dynamic({"AgentType": "Custom", "AgentName": "PHP"}),
ua startswith "curl/", dynamic({"AgentType": "Custom", "AgentName": "curl"}),
ua has "python-requests", dynamic({"AgentType": "Custom", "AgentName": "Python"}),
pack("AgentType","Other","AgentName", extract(@"^([^/]*)/",1,ua))
)
};
// Query to obtain 'simplified' user agents in a given timespan.
let QueryUserAgents = (start_time:timespan, end_time:timespan) {
union withsource=tbl_name AADNonInteractiveUserSignInLogs, SigninLogs
| where TimeGenerated >= ago(start_time)
| where TimeGenerated < ago(end_time)
| where ResultType == 0 // Only look at succesful logins
| extend ParsedUserAgent=ExtractBrowserTypeFromUA(UserAgent)
| extend UserAgentType=tostring(ParsedUserAgent.AgentType)
| extend UserAgentName=tostring(ParsedUserAgent.AgentName)
//| extend SimpleUserAgent=strcat(UserAgentType,"_",UserAgentName)
| extend SimpleUserAgent=UserAgentType
| where not(isempty(UserAgent))
| where not(isempty(AppId))
};
// Get baseline usage per application.
let BaselineUserAgents=materialize(
QueryUserAgents(lookback_timeframe+timeframe, timeframe)
| summarize RequestCount=count() by AppId, AppDisplayName, SimpleUserAgent
);
let BaselineSummarizedAgents=(
BaselineUserAgents
| summarize BaselineUAs=make_set(SimpleUserAgent),BaselineRequestCount=sum(RequestCount) by AppId, AppDisplayName
);
QueryUserAgents(timeframe, 0d)
| summarize count() by AppId, AppDisplayName, UserAgent, SimpleUserAgent
| join kind=leftanti BaselineUserAgents on AppId, AppDisplayName, SimpleUserAgent
| join BaselineSummarizedAgents on AppId, AppDisplayName
| where BaselineRequestCount > minimumAppThreshold // Search only for actively used applications.
// Get back full original requests.
| join QueryUserAgents(timeframe, 0d) on AppId, UserAgent
| project-away ParsedUserAgent, UserAgentName
| project-reorder TimeGenerated, AppDisplayName, UserPrincipalName, UserAgent, BaselineUAs
// Begin allow-list.
// End allow-list.
| summarize count() by UserPrincipalName, AppDisplayName, AppId, UserAgentType, SimpleUserAgent, UserAgent
OriginalUri: https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/FalconFriday/Analytic Rules/AzureADRareUserAgentAppSignin.yaml
version: 1.0.1
queryPeriod: 7d
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: UserPrincipalName
triggerThreshold: 0
query: |
let minimumAppThreshold = 100;
let timeframe = 1d;
let lookback_timeframe= 7d;
let ExtractBrowserTypeFromUA=(ua:string) {
// Note: these are in a specific order since, for example, Edge contains "Chrome/" and "Edge/" strings.
case(
ua has "Edge/", dynamic({"AgentType": "Browser", "AgentName": "Edge"}),
ua has "Edg/", dynamic({"AgentType": "Browser", "AgentName": "Edge"}),
ua has "Trident/", dynamic({"AgentType": "Browser", "AgentName": "Internet Explorer"}),
ua has "Chrome/" and ua has "Safari/", dynamic({"AgentType": "Browser", "AgentName": "Chrome"}),
ua has "Gecko/" and ua has "Firefox/", dynamic({"AgentType": "Browser", "AgentName": "Firefox"}),
not(ua has "Mobile/") and ua has "Safari/" and ua has "Version/", dynamic({"AgentType": "Browser", "AgentName": "Safari"}),
ua startswith "Dalvik/" and ua has "Android", dynamic({"AgentType": "Browser", "AgentName": "Android Browser"}),
ua startswith "MobileSafari//", dynamic({"AgentType": "Browser", "AgentName": "Mobile Safari"}),
ua has "Mobile/" and ua has "Safari/" and ua has "Version/", dynamic({"AgentType": "Browser", "AgentName": "Mobile Safari"}),
ua has "Mobile/" and ua has "FxiOS/", dynamic({"AgentType": "Browser", "AgentName": "IOS Firefox"}),
ua has "Mobile/" and ua has "CriOS/", dynamic({"AgentType": "Browser", "AgentName": "IOS Chrome"}),
ua has "Mobile/" and ua has "WebKit/", dynamic({"AgentType": "Browser", "AgentName": "Mobile Webkit"}),
//
ua startswith "Excel/", dynamic({"AgentType": "OfficeApp", "AgentName": "Excel"}),
ua startswith "Outlook/", dynamic({"AgentType": "OfficeApp", "AgentName": "Outlook"}),
ua startswith "OneDrive/", dynamic({"AgentType": "OfficeApp", "AgentName": "OneDrive"}),
ua startswith "OneNote/", dynamic({"AgentType": "OfficeApp", "AgentName": "OneNote"}),
ua startswith "Office/", dynamic({"AgentType": "OfficeApp", "AgentName": "Office"}),
ua startswith "PowerPoint/", dynamic({"AgentType": "OfficeApp", "AgentName": "PowerPoint"}),
ua startswith "PowerApps/", dynamic({"AgentType": "OfficeApp", "AgentName": "PowerApps"}),
ua startswith "SharePoint/", dynamic({"AgentType": "OfficeApp", "AgentName": "SharePoint"}),
ua startswith "Word/", dynamic({"AgentType": "OfficeApp", "AgentName": "Word"}),
ua startswith "Visio/", dynamic({"AgentType": "OfficeApp", "AgentName": "Visio"}),
ua startswith "Whiteboard/", dynamic({"AgentType": "OfficeApp", "AgentName": "Whiteboard"}),
ua =~ "Mozilla/5.0 (compatible; MSAL 1.0)", dynamic({"AgentType": "OfficeApp", "AgentName": "Office Telemetry"}),
//
ua has ".NET CLR", dynamic({"AgentType": "Custom", "AgentName": "Dotnet"}),
ua startswith "Java/", dynamic({"AgentType": "Custom", "AgentName": "Java"}),
ua startswith "okhttp/", dynamic({"AgentType": "Custom", "AgentName": "okhttp"}),
ua has "Drupal/", dynamic({"AgentType": "Custom", "AgentName": "Drupal"}),
ua has "PHP/", dynamic({"AgentType": "Custom", "AgentName": "PHP"}),
ua startswith "curl/", dynamic({"AgentType": "Custom", "AgentName": "curl"}),
ua has "python-requests", dynamic({"AgentType": "Custom", "AgentName": "Python"}),
pack("AgentType","Other","AgentName", extract(@"^([^/]*)/",1,ua))
)
};
// Query to obtain 'simplified' user agents in a given timespan.
let QueryUserAgents = (start_time:timespan, end_time:timespan) {
union withsource=tbl_name AADNonInteractiveUserSignInLogs, SigninLogs
| where TimeGenerated >= ago(start_time)
| where TimeGenerated < ago(end_time)
| where ResultType == 0 // Only look at succesful logins
| extend ParsedUserAgent=ExtractBrowserTypeFromUA(UserAgent)
| extend UserAgentType=tostring(ParsedUserAgent.AgentType)
| extend UserAgentName=tostring(ParsedUserAgent.AgentName)
//| extend SimpleUserAgent=strcat(UserAgentType,"_",UserAgentName)
| extend SimpleUserAgent=UserAgentType
| where not(isempty(UserAgent))
| where not(isempty(AppId))
};
// Get baseline usage per application.
let BaselineUserAgents=materialize(
QueryUserAgents(lookback_timeframe+timeframe, timeframe)
| summarize RequestCount=count() by AppId, AppDisplayName, SimpleUserAgent
);
let BaselineSummarizedAgents=(
BaselineUserAgents
| summarize BaselineUAs=make_set(SimpleUserAgent),BaselineRequestCount=sum(RequestCount) by AppId, AppDisplayName
);
QueryUserAgents(timeframe, 0d)
| summarize count() by AppId, AppDisplayName, UserAgent, SimpleUserAgent
| join kind=leftanti BaselineUserAgents on AppId, AppDisplayName, SimpleUserAgent
| join BaselineSummarizedAgents on AppId, AppDisplayName
| where BaselineRequestCount > minimumAppThreshold // Search only for actively used applications.
// Get back full original requests.
| join QueryUserAgents(timeframe, 0d) on AppId, UserAgent
| project-away ParsedUserAgent, UserAgentName
| project-reorder TimeGenerated, AppDisplayName, UserPrincipalName, UserAgent, BaselineUAs
// Begin allow-list.
// End allow-list.
| summarize count() by UserPrincipalName, AppDisplayName, AppId, UserAgentType, SimpleUserAgent, UserAgent
queryFrequency: 1d
description: |
This query establishes a baseline of the type of UserAgent (i.e. browser, office application, etc) that is typically used for a particular application by looking back for a number of days.
It then searches the current day for any deviations from this pattern, i.e. types of UserAgents not seen before in combination with this application.
relevantTechniques:
- T1036
id: 87d5cd18-211d-4fd4-9b86-65d23fed87ea
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- SigninLogs
- connectorId: AzureActiveDirectory
dataTypes:
- AADNonInteractiveUserSignInLogs
status: Available
severity: Medium
tactics:
- DefenseEvasion
kind: Scheduled
triggerOperator: gt
name: Microsoft Entra ID Rare UserAgent App Sign-in
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"workspace": {
"type": "String"
}
},
"resources": [
{
"apiVersion": "2024-01-01-preview",
"id": "[concat(resourceId('Microsoft.OperationalInsights/workspaces/providers', parameters('workspace'), 'Microsoft.SecurityInsights'),'/alertRules/87d5cd18-211d-4fd4-9b86-65d23fed87ea')]",
"kind": "Scheduled",
"name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/87d5cd18-211d-4fd4-9b86-65d23fed87ea')]",
"properties": {
"alertRuleTemplateName": "87d5cd18-211d-4fd4-9b86-65d23fed87ea",
"customDetails": null,
"description": "This query establishes a baseline of the type of UserAgent (i.e. browser, office application, etc) that is typically used for a particular application by looking back for a number of days. \nIt then searches the current day for any deviations from this pattern, i.e. types of UserAgents not seen before in combination with this application.\n",
"displayName": "Microsoft Entra ID Rare UserAgent App Sign-in",
"enabled": true,
"entityMappings": [
{
"entityType": "Account",
"fieldMappings": [
{
"columnName": "UserPrincipalName",
"identifier": "FullName"
}
]
}
],
"OriginalUri": "https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/FalconFriday/Analytic Rules/AzureADRareUserAgentAppSignin.yaml",
"query": "let minimumAppThreshold = 100;\nlet timeframe = 1d;\nlet lookback_timeframe= 7d;\nlet ExtractBrowserTypeFromUA=(ua:string) {\n // Note: these are in a specific order since, for example, Edge contains \"Chrome/\" and \"Edge/\" strings.\n case(\n ua has \"Edge/\", dynamic({\"AgentType\": \"Browser\", \"AgentName\": \"Edge\"}),\n ua has \"Edg/\", dynamic({\"AgentType\": \"Browser\", \"AgentName\": \"Edge\"}),\n ua has \"Trident/\", dynamic({\"AgentType\": \"Browser\", \"AgentName\": \"Internet Explorer\"}),\n ua has \"Chrome/\" and ua has \"Safari/\", dynamic({\"AgentType\": \"Browser\", \"AgentName\": \"Chrome\"}),\n ua has \"Gecko/\" and ua has \"Firefox/\", dynamic({\"AgentType\": \"Browser\", \"AgentName\": \"Firefox\"}),\n not(ua has \"Mobile/\") and ua has \"Safari/\" and ua has \"Version/\", dynamic({\"AgentType\": \"Browser\", \"AgentName\": \"Safari\"}),\n ua startswith \"Dalvik/\" and ua has \"Android\", dynamic({\"AgentType\": \"Browser\", \"AgentName\": \"Android Browser\"}),\n ua startswith \"MobileSafari//\", dynamic({\"AgentType\": \"Browser\", \"AgentName\": \"Mobile Safari\"}),\n ua has \"Mobile/\" and ua has \"Safari/\" and ua has \"Version/\", dynamic({\"AgentType\": \"Browser\", \"AgentName\": \"Mobile Safari\"}),\n ua has \"Mobile/\" and ua has \"FxiOS/\", dynamic({\"AgentType\": \"Browser\", \"AgentName\": \"IOS Firefox\"}),\n ua has \"Mobile/\" and ua has \"CriOS/\", dynamic({\"AgentType\": \"Browser\", \"AgentName\": \"IOS Chrome\"}),\n ua has \"Mobile/\" and ua has \"WebKit/\", dynamic({\"AgentType\": \"Browser\", \"AgentName\": \"Mobile Webkit\"}),\n //\n ua startswith \"Excel/\", dynamic({\"AgentType\": \"OfficeApp\", \"AgentName\": \"Excel\"}),\n ua startswith \"Outlook/\", dynamic({\"AgentType\": \"OfficeApp\", \"AgentName\": \"Outlook\"}),\n ua startswith \"OneDrive/\", dynamic({\"AgentType\": \"OfficeApp\", \"AgentName\": \"OneDrive\"}),\n ua startswith \"OneNote/\", dynamic({\"AgentType\": \"OfficeApp\", \"AgentName\": \"OneNote\"}),\n ua startswith \"Office/\", dynamic({\"AgentType\": \"OfficeApp\", \"AgentName\": \"Office\"}),\n ua startswith \"PowerPoint/\", dynamic({\"AgentType\": \"OfficeApp\", \"AgentName\": \"PowerPoint\"}),\n ua startswith \"PowerApps/\", dynamic({\"AgentType\": \"OfficeApp\", \"AgentName\": \"PowerApps\"}),\n ua startswith \"SharePoint/\", dynamic({\"AgentType\": \"OfficeApp\", \"AgentName\": \"SharePoint\"}),\n ua startswith \"Word/\", dynamic({\"AgentType\": \"OfficeApp\", \"AgentName\": \"Word\"}),\n ua startswith \"Visio/\", dynamic({\"AgentType\": \"OfficeApp\", \"AgentName\": \"Visio\"}),\n ua startswith \"Whiteboard/\", dynamic({\"AgentType\": \"OfficeApp\", \"AgentName\": \"Whiteboard\"}),\n ua =~ \"Mozilla/5.0 (compatible; MSAL 1.0)\", dynamic({\"AgentType\": \"OfficeApp\", \"AgentName\": \"Office Telemetry\"}),\n //\n ua has \".NET CLR\", dynamic({\"AgentType\": \"Custom\", \"AgentName\": \"Dotnet\"}),\n ua startswith \"Java/\", dynamic({\"AgentType\": \"Custom\", \"AgentName\": \"Java\"}),\n ua startswith \"okhttp/\", dynamic({\"AgentType\": \"Custom\", \"AgentName\": \"okhttp\"}),\n ua has \"Drupal/\", dynamic({\"AgentType\": \"Custom\", \"AgentName\": \"Drupal\"}),\n ua has \"PHP/\", dynamic({\"AgentType\": \"Custom\", \"AgentName\": \"PHP\"}),\n ua startswith \"curl/\", dynamic({\"AgentType\": \"Custom\", \"AgentName\": \"curl\"}),\n ua has \"python-requests\", dynamic({\"AgentType\": \"Custom\", \"AgentName\": \"Python\"}),\n pack(\"AgentType\",\"Other\",\"AgentName\", extract(@\"^([^/]*)/\",1,ua))\n )\n};\n// Query to obtain 'simplified' user agents in a given timespan.\nlet QueryUserAgents = (start_time:timespan, end_time:timespan) {\n union withsource=tbl_name AADNonInteractiveUserSignInLogs, SigninLogs\n | where TimeGenerated >= ago(start_time)\n | where TimeGenerated < ago(end_time)\n | where ResultType == 0 // Only look at succesful logins\n | extend ParsedUserAgent=ExtractBrowserTypeFromUA(UserAgent)\n | extend UserAgentType=tostring(ParsedUserAgent.AgentType)\n | extend UserAgentName=tostring(ParsedUserAgent.AgentName)\n //| extend SimpleUserAgent=strcat(UserAgentType,\"_\",UserAgentName)\n | extend SimpleUserAgent=UserAgentType\n | where not(isempty(UserAgent))\n | where not(isempty(AppId))\n};\n// Get baseline usage per application.\nlet BaselineUserAgents=materialize(\n QueryUserAgents(lookback_timeframe+timeframe, timeframe)\n | summarize RequestCount=count() by AppId, AppDisplayName, SimpleUserAgent\n);\nlet BaselineSummarizedAgents=(\n BaselineUserAgents\n | summarize BaselineUAs=make_set(SimpleUserAgent),BaselineRequestCount=sum(RequestCount) by AppId, AppDisplayName\n);\nQueryUserAgents(timeframe, 0d)\n| summarize count() by AppId, AppDisplayName, UserAgent, SimpleUserAgent\n| join kind=leftanti BaselineUserAgents on AppId, AppDisplayName, SimpleUserAgent\n| join BaselineSummarizedAgents on AppId, AppDisplayName\n| where BaselineRequestCount > minimumAppThreshold // Search only for actively used applications.\n// Get back full original requests.\n| join QueryUserAgents(timeframe, 0d) on AppId, UserAgent\n| project-away ParsedUserAgent, UserAgentName\n| project-reorder TimeGenerated, AppDisplayName, UserPrincipalName, UserAgent, BaselineUAs\n// Begin allow-list.\n// End allow-list.\n| summarize count() by UserPrincipalName, AppDisplayName, AppId, UserAgentType, SimpleUserAgent, UserAgent\n",
"queryFrequency": "P1D",
"queryPeriod": "P7D",
"severity": "Medium",
"status": "Available",
"subTechniques": [],
"suppressionDuration": "PT1H",
"suppressionEnabled": false,
"tactics": [
"DefenseEvasion"
],
"techniques": [
"T1036"
],
"templateVersion": "1.0.1",
"triggerOperator": "GreaterThan",
"triggerThreshold": 0
},
"type": "Microsoft.OperationalInsights/workspaces/providers/alertRules"
}
]
}