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

Authentication Attempt from New Country

Back
Idef895ada-e8e8-4cf0-9313-b1ab67fab69f
RulenameAuthentication Attempt from New Country
DescriptionDetects when there is a login attempt from a country that has not seen a successful login in the previous 14 days.

Threat actors may attempt to authenticate with credentials from compromised accounts - monitoring attempts from anomalous locations may help identify these attempts.

Authentication attempts should be investigated to ensure the activity was legitimate and if there is other similar activity.

Ref: https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-user-accounts#monitoring-for-failed-unusual-sign-ins
SeverityMedium
TacticsInitialAccess
TechniquesT1078.004
Required data connectorsAzureActiveDirectory
KindScheduled
Query frequency1d
Query period14d
Trigger threshold0
Trigger operatorgt
Source Urihttps://github.com/Azure/Azure-Sentinel/blob/master/Detections/SigninLogs/AuthenticationAttemptfromNewCountry.yaml
Version1.1.2
Arm templateef895ada-e8e8-4cf0-9313-b1ab67fab69f.json
Deploy To Azure
let CombinedSignInLogs = union isfuzzy=True AADNonInteractiveUserSignInLogs, SigninLogs;
  // Combine AADNonInteractiveUserSignInLogs and SigninLogs into a single table
  // Fetch Azure IP address ranges data from a JSON file hosted on GitHub
  let AzureRanges = externaldata(changeNumber: string, cloud: string, values: dynamic)
  ["https://raw.githubusercontent.com/microsoft/mstic/master/PublicFeeds/MSFTIPRanges/ServiceTags_Public.json"] with(format='multijson')
  // Load Azure IP address ranges from the JSON file hosted on GitHub
  | mv-expand values
  // Expand the values column into separate rows
  | extend Name = values.name, AddressPrefixes = tostring(values.properties.addressPrefixes);
  // Create additional columns for the name and address prefixes
  // Identify known locations to be excluded from analysis
  let ExcludedKnownLocations = CombinedSignInLogs
  // Filter the combined logs based on the specified time range
  | where TimeGenerated between (ago(14d)..ago(1d))
  // Filter by specific ResultType
  | where ResultType == 0
  // Summarize the logs by location
  | summarize by Location;
  // Find sign-in locations matching specific criteria
  let MatchedLocations = materialize(CombinedSignInLogs
  // Filter the combined logs based on the specified time range
  | where TimeGenerated > ago(1d)
  // Exclude specific ResultTypes
  | where ResultType !in (50126, 50053, 50074, 70044)
  // Exclude known locations
  | where Location !in (ExcludedKnownLocations));
  // Match IP addresses of matched locations with Azure IP address ranges
  let MatchedIPs = MatchedLocations
  // Use the 'ipv4_lookup' function to match IP addresses with Azure IP address ranges
  | evaluate ipv4_lookup(AzureRanges, IPAddress, AddressPrefixes)
  // Project only the IPAddress column
  | project IPAddress;
  // Exclude IP addresses that are already matched with Azure IP address ranges
  let MaxSetSize = 5; // Set the maximum size limit for make_set
  let ExcludedIPs = MatchedLocations
  // Filter out IP addresses that are already matched
  | where not (IPAddress in (MatchedIPs))
  // Exclude empty or null Location values
  | where isnotempty(Location)
  // Handle dynamic and string column values for LocationDetails and DeviceDetail
  | extend LocationDetails_dynamic = column_ifexists("LocationDetails_dynamic", "")
  | extend DeviceDetail_dynamic = column_ifexists("DeviceDetail_dynamic", "")
  | extend LocationDetails = iif(isnotempty(LocationDetails_dynamic), LocationDetails_dynamic, parse_json(LocationDetails_string))
  | extend DeviceDetail = iif(isnotempty(DeviceDetail_dynamic), DeviceDetail_dynamic, parse_json(DeviceDetail_string))
  // Extract location details (city and state)
  | extend City = tostring(LocationDetails.city)
  | extend State = tostring(LocationDetails.state)
  | extend Place = strcat(City, " - ", State)
  | extend DeviceId = tostring(DeviceDetail.deviceId)
  | extend Result = strcat(tostring(ResultType), " - ", ResultDescription)
  // Summarize the data based on UserPrincipalName, Location, and Category
  | summarize FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated),
  make_set(Result, MaxSetSize), make_set(IPAddress, MaxSetSize),
  make_set(UserAgent, MaxSetSize), make_set(Place, MaxSetSize),
  make_set(DeviceId, MaxSetSize) by UserPrincipalName, Location, Category
  // Extract the username prefix and suffix from UserPrincipalName
  | extend Name = tostring(split(UserPrincipalName,'@',0)[0]), UPNSuffix = tostring(split(UserPrincipalName,'@',1)[0]);
  ExcludedIPs // Output the final result set
  | extend IP = set_IPAddress[0]
relevantTechniques:
- T1078.004
OriginalUri: https://github.com/Azure/Azure-Sentinel/blob/master/Detections/SigninLogs/AuthenticationAttemptfromNewCountry.yaml
metadata:
  categories:
    domains:
    - Security - Others
  source:
    kind: Community
  author:
    name: Microsoft Security Research
  support:
    tier: Community
triggerOperator: gt
kind: Scheduled
entityMappings:
- fieldMappings:
  - columnName: UserPrincipalName
    identifier: FullName
  - columnName: Name
    identifier: Name
  - columnName: UPNSuffix
    identifier: UPNSuffix
  entityType: Account
- fieldMappings:
  - columnName: IP
    identifier: Address
  entityType: IP
requiredDataConnectors:
- dataTypes:
  - SigninLogs
  - AADNonInteractiveUserSignInLogs
  connectorId: AzureActiveDirectory
queryPeriod: 14d
query: |
  let CombinedSignInLogs = union isfuzzy=True AADNonInteractiveUserSignInLogs, SigninLogs;
    // Combine AADNonInteractiveUserSignInLogs and SigninLogs into a single table
    // Fetch Azure IP address ranges data from a JSON file hosted on GitHub
    let AzureRanges = externaldata(changeNumber: string, cloud: string, values: dynamic)
    ["https://raw.githubusercontent.com/microsoft/mstic/master/PublicFeeds/MSFTIPRanges/ServiceTags_Public.json"] with(format='multijson')
    // Load Azure IP address ranges from the JSON file hosted on GitHub
    | mv-expand values
    // Expand the values column into separate rows
    | extend Name = values.name, AddressPrefixes = tostring(values.properties.addressPrefixes);
    // Create additional columns for the name and address prefixes
    // Identify known locations to be excluded from analysis
    let ExcludedKnownLocations = CombinedSignInLogs
    // Filter the combined logs based on the specified time range
    | where TimeGenerated between (ago(14d)..ago(1d))
    // Filter by specific ResultType
    | where ResultType == 0
    // Summarize the logs by location
    | summarize by Location;
    // Find sign-in locations matching specific criteria
    let MatchedLocations = materialize(CombinedSignInLogs
    // Filter the combined logs based on the specified time range
    | where TimeGenerated > ago(1d)
    // Exclude specific ResultTypes
    | where ResultType !in (50126, 50053, 50074, 70044)
    // Exclude known locations
    | where Location !in (ExcludedKnownLocations));
    // Match IP addresses of matched locations with Azure IP address ranges
    let MatchedIPs = MatchedLocations
    // Use the 'ipv4_lookup' function to match IP addresses with Azure IP address ranges
    | evaluate ipv4_lookup(AzureRanges, IPAddress, AddressPrefixes)
    // Project only the IPAddress column
    | project IPAddress;
    // Exclude IP addresses that are already matched with Azure IP address ranges
    let MaxSetSize = 5; // Set the maximum size limit for make_set
    let ExcludedIPs = MatchedLocations
    // Filter out IP addresses that are already matched
    | where not (IPAddress in (MatchedIPs))
    // Exclude empty or null Location values
    | where isnotempty(Location)
    // Handle dynamic and string column values for LocationDetails and DeviceDetail
    | extend LocationDetails_dynamic = column_ifexists("LocationDetails_dynamic", "")
    | extend DeviceDetail_dynamic = column_ifexists("DeviceDetail_dynamic", "")
    | extend LocationDetails = iif(isnotempty(LocationDetails_dynamic), LocationDetails_dynamic, parse_json(LocationDetails_string))
    | extend DeviceDetail = iif(isnotempty(DeviceDetail_dynamic), DeviceDetail_dynamic, parse_json(DeviceDetail_string))
    // Extract location details (city and state)
    | extend City = tostring(LocationDetails.city)
    | extend State = tostring(LocationDetails.state)
    | extend Place = strcat(City, " - ", State)
    | extend DeviceId = tostring(DeviceDetail.deviceId)
    | extend Result = strcat(tostring(ResultType), " - ", ResultDescription)
    // Summarize the data based on UserPrincipalName, Location, and Category
    | summarize FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated),
    make_set(Result, MaxSetSize), make_set(IPAddress, MaxSetSize),
    make_set(UserAgent, MaxSetSize), make_set(Place, MaxSetSize),
    make_set(DeviceId, MaxSetSize) by UserPrincipalName, Location, Category
    // Extract the username prefix and suffix from UserPrincipalName
    | extend Name = tostring(split(UserPrincipalName,'@',0)[0]), UPNSuffix = tostring(split(UserPrincipalName,'@',1)[0]);
    ExcludedIPs // Output the final result set
    | extend IP = set_IPAddress[0]  
name: Authentication Attempt from New Country
description: |
  Detects when there is a login attempt from a country that has not seen a successful login in the previous 14 days.
  Threat actors may attempt to authenticate with credentials from compromised accounts - monitoring attempts from anomalous locations may help identify these attempts.
  Authentication attempts should be investigated to ensure the activity was legitimate and if there is other similar activity.
  Ref: https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-user-accounts#monitoring-for-failed-unusual-sign-ins  
tactics:
- InitialAccess
severity: Medium
triggerThreshold: 0
tags:
- AADSecOpsGuide
queryFrequency: 1d
version: 1.1.2
id: ef895ada-e8e8-4cf0-9313-b1ab67fab69f
{
  "$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/ef895ada-e8e8-4cf0-9313-b1ab67fab69f')]",
      "kind": "Scheduled",
      "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/ef895ada-e8e8-4cf0-9313-b1ab67fab69f')]",
      "properties": {
        "alertRuleTemplateName": "ef895ada-e8e8-4cf0-9313-b1ab67fab69f",
        "customDetails": null,
        "description": "Detects when there is a login attempt from a country that has not seen a successful login in the previous 14 days.\nThreat actors may attempt to authenticate with credentials from compromised accounts - monitoring attempts from anomalous locations may help identify these attempts.\nAuthentication attempts should be investigated to ensure the activity was legitimate and if there is other similar activity.\nRef: https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-user-accounts#monitoring-for-failed-unusual-sign-ins\n",
        "displayName": "Authentication Attempt from New Country",
        "enabled": true,
        "entityMappings": [
          {
            "entityType": "Account",
            "fieldMappings": [
              {
                "columnName": "UserPrincipalName",
                "identifier": "FullName"
              },
              {
                "columnName": "Name",
                "identifier": "Name"
              },
              {
                "columnName": "UPNSuffix",
                "identifier": "UPNSuffix"
              }
            ]
          },
          {
            "entityType": "IP",
            "fieldMappings": [
              {
                "columnName": "IP",
                "identifier": "Address"
              }
            ]
          }
        ],
        "OriginalUri": "https://github.com/Azure/Azure-Sentinel/blob/master/Detections/SigninLogs/AuthenticationAttemptfromNewCountry.yaml",
        "query": "let CombinedSignInLogs = union isfuzzy=True AADNonInteractiveUserSignInLogs, SigninLogs;\n  // Combine AADNonInteractiveUserSignInLogs and SigninLogs into a single table\n  // Fetch Azure IP address ranges data from a JSON file hosted on GitHub\n  let AzureRanges = externaldata(changeNumber: string, cloud: string, values: dynamic)\n  [\"https://raw.githubusercontent.com/microsoft/mstic/master/PublicFeeds/MSFTIPRanges/ServiceTags_Public.json\"] with(format='multijson')\n  // Load Azure IP address ranges from the JSON file hosted on GitHub\n  | mv-expand values\n  // Expand the values column into separate rows\n  | extend Name = values.name, AddressPrefixes = tostring(values.properties.addressPrefixes);\n  // Create additional columns for the name and address prefixes\n  // Identify known locations to be excluded from analysis\n  let ExcludedKnownLocations = CombinedSignInLogs\n  // Filter the combined logs based on the specified time range\n  | where TimeGenerated between (ago(14d)..ago(1d))\n  // Filter by specific ResultType\n  | where ResultType == 0\n  // Summarize the logs by location\n  | summarize by Location;\n  // Find sign-in locations matching specific criteria\n  let MatchedLocations = materialize(CombinedSignInLogs\n  // Filter the combined logs based on the specified time range\n  | where TimeGenerated > ago(1d)\n  // Exclude specific ResultTypes\n  | where ResultType !in (50126, 50053, 50074, 70044)\n  // Exclude known locations\n  | where Location !in (ExcludedKnownLocations));\n  // Match IP addresses of matched locations with Azure IP address ranges\n  let MatchedIPs = MatchedLocations\n  // Use the 'ipv4_lookup' function to match IP addresses with Azure IP address ranges\n  | evaluate ipv4_lookup(AzureRanges, IPAddress, AddressPrefixes)\n  // Project only the IPAddress column\n  | project IPAddress;\n  // Exclude IP addresses that are already matched with Azure IP address ranges\n  let MaxSetSize = 5; // Set the maximum size limit for make_set\n  let ExcludedIPs = MatchedLocations\n  // Filter out IP addresses that are already matched\n  | where not (IPAddress in (MatchedIPs))\n  // Exclude empty or null Location values\n  | where isnotempty(Location)\n  // Handle dynamic and string column values for LocationDetails and DeviceDetail\n  | extend LocationDetails_dynamic = column_ifexists(\"LocationDetails_dynamic\", \"\")\n  | extend DeviceDetail_dynamic = column_ifexists(\"DeviceDetail_dynamic\", \"\")\n  | extend LocationDetails = iif(isnotempty(LocationDetails_dynamic), LocationDetails_dynamic, parse_json(LocationDetails_string))\n  | extend DeviceDetail = iif(isnotempty(DeviceDetail_dynamic), DeviceDetail_dynamic, parse_json(DeviceDetail_string))\n  // Extract location details (city and state)\n  | extend City = tostring(LocationDetails.city)\n  | extend State = tostring(LocationDetails.state)\n  | extend Place = strcat(City, \" - \", State)\n  | extend DeviceId = tostring(DeviceDetail.deviceId)\n  | extend Result = strcat(tostring(ResultType), \" - \", ResultDescription)\n  // Summarize the data based on UserPrincipalName, Location, and Category\n  | summarize FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated),\n  make_set(Result, MaxSetSize), make_set(IPAddress, MaxSetSize),\n  make_set(UserAgent, MaxSetSize), make_set(Place, MaxSetSize),\n  make_set(DeviceId, MaxSetSize) by UserPrincipalName, Location, Category\n  // Extract the username prefix and suffix from UserPrincipalName\n  | extend Name = tostring(split(UserPrincipalName,'@',0)[0]), UPNSuffix = tostring(split(UserPrincipalName,'@',1)[0]);\n  ExcludedIPs // Output the final result set\n  | extend IP = set_IPAddress[0]\n",
        "queryFrequency": "P1D",
        "queryPeriod": "P14D",
        "severity": "Medium",
        "suppressionDuration": "PT1H",
        "suppressionEnabled": false,
        "tactics": [
          "InitialAccess"
        ],
        "tags": [
          "AADSecOpsGuide"
        ],
        "techniques": [
          "T1078"
        ],
        "templateVersion": "1.1.2",
        "triggerOperator": "GreaterThan",
        "triggerThreshold": 0
      },
      "type": "Microsoft.OperationalInsights/workspaces/providers/alertRules"
    }
  ]
}