Microsoft Sentinel Analytic Rules
cloudbrothers.infoAzure Sentinel RepoToggle Dark/Light/Auto modeToggle Dark/Light/Auto modeToggle Dark/Light/Auto modeBack to homepage

Microsoft Entra ID Rare UserAgent App Sign-in

Back
Id87d5cd18-211d-4fd4-9b86-65d23fed87ea
RulenameMicrosoft Entra ID Rare UserAgent App Sign-in
DescriptionThis 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.
SeverityMedium
TacticsDefenseEvasion
TechniquesT1036
Required data connectorsAzureActiveDirectory
KindScheduled
Query frequency1d
Query period7d
Trigger threshold0
Trigger operatorgt
Source Urihttps://github.com/Azure/Azure-Sentinel/blob/master/Solutions/FalconFriday/Analytic Rules/AzureADRareUserAgentAppSignin.yaml
Version1.0.1
Arm template87d5cd18-211d-4fd4-9b86-65d23fed87ea.json
Deploy To Azure
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
severity: Medium
id: 87d5cd18-211d-4fd4-9b86-65d23fed87ea
status: Available
requiredDataConnectors:
- dataTypes:
  - SigninLogs
  connectorId: AzureActiveDirectory
- dataTypes:
  - AADNonInteractiveUserSignInLogs
  connectorId: AzureActiveDirectory
kind: Scheduled
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.  
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  
tactics:
- DefenseEvasion
triggerThreshold: 0
triggerOperator: gt
OriginalUri: https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/FalconFriday/Analytic Rules/AzureADRareUserAgentAppSignin.yaml
relevantTechniques:
- T1036
entityMappings:
- entityType: Account
  fieldMappings:
  - identifier: FullName
    columnName: UserPrincipalName
version: 1.0.1
name: Microsoft Entra ID Rare UserAgent App Sign-in
queryPeriod: 7d
{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "workspace": {
      "type": "String"
    }
  },
  "resources": [
    {
      "apiVersion": "2023-02-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",
        "suppressionDuration": "PT1H",
        "suppressionEnabled": false,
        "tactics": [
          "DefenseEvasion"
        ],
        "techniques": [
          "T1036"
        ],
        "templateVersion": "1.0.1",
        "triggerOperator": "GreaterThan",
        "triggerThreshold": 0
      },
      "type": "Microsoft.OperationalInsights/workspaces/providers/alertRules"
    }
  ]
}