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])
name: Failed Logins from Unknown or Invalid User
triggerOperator: gt
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])
queryFrequency: 1h
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.
id: 884be6e7-e568-418e-9c12-89229865ffde
triggerThreshold: 0
queryPeriod: 1h
version: 1.1.2
kind: Scheduled
customDetails:
  Country: Country
  City: City
  FailedLoginCount: FailedLoginCount
status: Available
severity: Medium
requiredDataConnectors:
- connectorId: OktaSSO
  dataTypes:
  - Okta_CL
- connectorId: OktaSSOv2
  dataTypes:
  - OktaSSO
OriginalUri: https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Okta%20Single%20Sign-On/Analytic%20Rules/FailedLoginsFromUnknownOrInvalidUser.yaml
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}}'
relevantTechniques:
- T1110.001
tactics:
- CredentialAccess
entityMappings:
- fieldMappings:
  - identifier: Name
    columnName: AccountName
  - identifier: UPNSuffix
    columnName: AccountUPNSuffix
  entityType: Account
- fieldMappings:
  - identifier: Address
    columnName: client_ipAddress_s
  entityType: IP
{
  "$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"
    }
  ]
}