Back
Id884be6e7-e568-418e-9c12-89229865ffde
RulenameFailed Logins from Unknown or Invalid User
DescriptionThis rule alerts when an unknown or invalid user generates more than 15 failed Okta management console login attempts from the same IP address within 5 minutes.
SeverityMedium
TacticsCredentialAccess
TechniquesT1110.001
Required data connectorsOktaSSO
OktaSSOv2
KindScheduled
Query frequency1h
Query period1h
Trigger threshold0
Trigger operatorgt
Source Urihttps://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Okta%20Single%20Sign-On/Analytic%20Rules/FailedLoginsFromUnknownOrInvalidUser.yaml
Version1.1.2
Arm template884be6e7-e568-418e-9c12-89229865ffde.json
Deploy To Azure
// Adjust threshold for failed logins to reduce noise. This can be customized based on the organization's typical login patterns.
let FailureThreshold = 15;
// Adjust list of allowed users to reduce false positives. This can include service accounts, known test accounts, or any other accounts that are expected to generate failed login attempts.
let AllowedUsers = dynamic([]);
let FailedLogins = OktaSSO
| where eventType_s =~ "user.session.start" and outcome_reason_s =~ "VERIFICATION_ERROR"
| where isnotempty(actor_alternateId_s) and actor_alternateId_s !in (AllowedUsers)
| summarize count() by actor_alternateId_s, client_ipAddress_s, bin(TimeGenerated, 5m)
| where count_ > FailureThreshold
| project client_ipAddress_s, actor_alternateId_s;
OktaSSO
| join kind=inner (FailedLogins) on client_ipAddress_s, actor_alternateId_s
| where eventType_s =~ "user.session.start" and outcome_reason_s =~ "VERIFICATION_ERROR"
| summarize FailedLoginCount = count(), City = take_any(column_ifexists('client_geographicalContext_city_s', "")), Country = take_any(column_ifexists('client_geographicalContext_country_s', "")), LastSeen = max(column_ifexists('TimeGenerated', now())) by actor_alternateId_s, client_ipAddress_s
| sort by LastSeen desc
| extend AccountName = tostring(split(actor_alternateId_s, "@")[0]), AccountUPNSuffix = tostring(split(actor_alternateId_s, "@")[1])
requiredDataConnectors:
- dataTypes:
  - Okta_CL
  connectorId: OktaSSO
- dataTypes:
  - OktaSSO
  connectorId: OktaSSOv2
OriginalUri: https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Okta%20Single%20Sign-On/Analytic%20Rules/FailedLoginsFromUnknownOrInvalidUser.yaml
version: 1.1.2
status: Available
alertDetailsOverride:
  alertDescriptionFormat: Account {{actor_alternateId_s}} generated {{FailedLoginCount}} failed logins from IP {{client_ipAddress_s}} in the last 5 minutes.
  alertDisplayNameFormat: 'Failed logins from unknown or invalid user: {{actor_alternateId_s}} from IP {{client_ipAddress_s}}'
customDetails:
  City: City
  FailedLoginCount: FailedLoginCount
  Country: Country
queryFrequency: 1h
query: |
  // Adjust threshold for failed logins to reduce noise. This can be customized based on the organization's typical login patterns.
  let FailureThreshold = 15;
  // Adjust list of allowed users to reduce false positives. This can include service accounts, known test accounts, or any other accounts that are expected to generate failed login attempts.
  let AllowedUsers = dynamic([]);
  let FailedLogins = OktaSSO
  | where eventType_s =~ "user.session.start" and outcome_reason_s =~ "VERIFICATION_ERROR"
  | where isnotempty(actor_alternateId_s) and actor_alternateId_s !in (AllowedUsers)
  | summarize count() by actor_alternateId_s, client_ipAddress_s, bin(TimeGenerated, 5m)
  | where count_ > FailureThreshold
  | project client_ipAddress_s, actor_alternateId_s;
  OktaSSO
  | join kind=inner (FailedLogins) on client_ipAddress_s, actor_alternateId_s
  | where eventType_s =~ "user.session.start" and outcome_reason_s =~ "VERIFICATION_ERROR"
  | summarize FailedLoginCount = count(), City = take_any(column_ifexists('client_geographicalContext_city_s', "")), Country = take_any(column_ifexists('client_geographicalContext_country_s', "")), LastSeen = max(column_ifexists('TimeGenerated', now())) by actor_alternateId_s, client_ipAddress_s
  | sort by LastSeen desc
  | extend AccountName = tostring(split(actor_alternateId_s, "@")[0]), AccountUPNSuffix = tostring(split(actor_alternateId_s, "@")[1])
tactics:
- CredentialAccess
relevantTechniques:
- T1110.001
id: 884be6e7-e568-418e-9c12-89229865ffde
triggerThreshold: 0
triggerOperator: gt
name: Failed Logins from Unknown or Invalid User
queryPeriod: 1h
severity: Medium
entityMappings:
- fieldMappings:
  - columnName: AccountName
    identifier: Name
  - columnName: AccountUPNSuffix
    identifier: UPNSuffix
  entityType: Account
- fieldMappings:
  - columnName: client_ipAddress_s
    identifier: Address
  entityType: IP
kind: Scheduled
description: |
  This rule alerts when an unknown or invalid user generates more than 15 failed Okta management console login attempts from the same IP address within 5 minutes.
{
  "$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/884be6e7-e568-418e-9c12-89229865ffde')]",
      "kind": "Scheduled",
      "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/884be6e7-e568-418e-9c12-89229865ffde')]",
      "properties": {
        "alertDetailsOverride": {
          "alertDescriptionFormat": "Account {{actor_alternateId_s}} generated {{FailedLoginCount}} failed logins from IP {{client_ipAddress_s}} in the last 5 minutes.",
          "alertDisplayNameFormat": "Failed logins from unknown or invalid user: {{actor_alternateId_s}} from IP {{client_ipAddress_s}}"
        },
        "alertRuleTemplateName": "884be6e7-e568-418e-9c12-89229865ffde",
        "customDetails": {
          "City": "City",
          "Country": "Country",
          "FailedLoginCount": "FailedLoginCount"
        },
        "description": "This rule alerts when an unknown or invalid user generates more than 15 failed Okta management console login attempts from the same IP address within 5 minutes.\n",
        "displayName": "Failed Logins from Unknown or Invalid User",
        "enabled": true,
        "entityMappings": [
          {
            "entityType": "Account",
            "fieldMappings": [
              {
                "columnName": "AccountName",
                "identifier": "Name"
              },
              {
                "columnName": "AccountUPNSuffix",
                "identifier": "UPNSuffix"
              }
            ]
          },
          {
            "entityType": "IP",
            "fieldMappings": [
              {
                "columnName": "client_ipAddress_s",
                "identifier": "Address"
              }
            ]
          }
        ],
        "OriginalUri": "https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Okta%20Single%20Sign-On/Analytic%20Rules/FailedLoginsFromUnknownOrInvalidUser.yaml",
        "query": "// Adjust threshold for failed logins to reduce noise. This can be customized based on the organization's typical login patterns.\nlet FailureThreshold = 15;\n// Adjust list of allowed users to reduce false positives. This can include service accounts, known test accounts, or any other accounts that are expected to generate failed login attempts.\nlet AllowedUsers = dynamic([]);\nlet FailedLogins = OktaSSO\n| where eventType_s =~ \"user.session.start\" and outcome_reason_s =~ \"VERIFICATION_ERROR\"\n| where isnotempty(actor_alternateId_s) and actor_alternateId_s !in (AllowedUsers)\n| summarize count() by actor_alternateId_s, client_ipAddress_s, bin(TimeGenerated, 5m)\n| where count_ > FailureThreshold\n| project client_ipAddress_s, actor_alternateId_s;\nOktaSSO\n| join kind=inner (FailedLogins) on client_ipAddress_s, actor_alternateId_s\n| where eventType_s =~ \"user.session.start\" and outcome_reason_s =~ \"VERIFICATION_ERROR\"\n| summarize FailedLoginCount = count(), City = take_any(column_ifexists('client_geographicalContext_city_s', \"\")), Country = take_any(column_ifexists('client_geographicalContext_country_s', \"\")), LastSeen = max(column_ifexists('TimeGenerated', now())) by actor_alternateId_s, client_ipAddress_s\n| sort by LastSeen desc\n| extend AccountName = tostring(split(actor_alternateId_s, \"@\")[0]), AccountUPNSuffix = tostring(split(actor_alternateId_s, \"@\")[1])\n",
        "queryFrequency": "PT1H",
        "queryPeriod": "PT1H",
        "severity": "Medium",
        "status": "Available",
        "subTechniques": [
          "T1110.001"
        ],
        "suppressionDuration": "PT1H",
        "suppressionEnabled": false,
        "tactics": [
          "CredentialAccess"
        ],
        "techniques": [
          "T1110"
        ],
        "templateVersion": "1.1.2",
        "triggerOperator": "GreaterThan",
        "triggerThreshold": 0
      },
      "type": "Microsoft.OperationalInsights/workspaces/providers/alertRules"
    }
  ]
}