Detection rules › Splunk

PingID Mismatch Auth Source and Verification Response

Status
production
Severity
medium
Group by
auth_Country, dest, group.name, reason, session_id, signature, src, status, user, verify_Country
Author
Steven Dick
Source
github.com/splunk/security_content

The following analytic identifies discrepancies between the IP address of an authentication event and the IP address of the verification response event, focusing on differences in the originating countries. It leverages JSON logs from PingID, comparing the 'auth_Country' and 'verify_Country' fields. This activity is significant as it may indicate suspicious sign-in behavior, such as account compromise or unauthorized access attempts. If confirmed malicious, this could allow attackers to bypass authentication mechanisms, potentially leading to unauthorized access to sensitive systems and data.

Known false positives

  • False positives may be generated by users working out the geographic region where the organizations services or technology is hosted.

MITRE ATT&CK coverage

Rule body

name: PingID Mismatch Auth Source and Verification Response
id: 15b0694e-caa2-4009-8d83-a1f98b86d086
version: 9
creation_date: '2023-12-20'
modification_date: '2026-05-13'
author: Steven Dick
status: production
type: TTP
description: The following analytic identifies discrepancies between the IP address of an authentication event and the IP address of the verification response event, focusing on differences in the originating countries. It leverages JSON logs from PingID, comparing the 'auth_Country' and 'verify_Country' fields. This activity is significant as it may indicate suspicious sign-in behavior, such as account compromise or unauthorized access attempts. If confirmed malicious, this could allow attackers to bypass authentication mechanisms, potentially leading to unauthorized access to sensitive systems and data.
data_source:
    - PingID
search: "`pingid` (\"result.status\" IN (\"SUCCESS*\",\"FAIL*\",\"UNSUCCESSFUL*\" ) NOT \"result.message\" IN (\"*pair*\",\"*create*\",\"*delete*\")) | eval user = upper('actors{}.name'), session_id = 'resources{}.websession', dest = 'resources{}.ipaddress', reason = 'result.message', object = 'resources{}.devicemodel', status = 'result.status' | join user session_id [ search `pingid` (\"result.status\" IN (\"POLICY\") AND \"resources{}.ipaddress\"=*) AND \"result.message\" IN(\"*Action: Authenticate*\",\"*Action: Approve*\",\"*Action: Allowed*\") | rex field=result.message \"IP Address: (?:N\\/A)?(?<policy_ipaddress>.+)?\\n\" | rex field=result.message \"Action: (?:N\\/A)?(?<signature>.+)?\\n\" | rex field=result.message \"Requested Application Name: (?:N\\/A)?(?<Requested_Application_Name>.+)?\\n\" | rex field=result.message \" Requested Application ID: (?:N\\/A)?(?<Requested_Application_ID>.+)?\\n\" | eval user = upper('actors{}.name'), session_id = 'resources{}.websession', src = coalesce('resources{}.ipaddress',policy_ipaddress), app = coalesce(Requested_Application_ID,Requested_Application_Name) | fields app, user, session_id, src, signature ] | iplocation prefix=auth_ dest | iplocation prefix=verify_ src | stats count min(_time) as firstTime max(_time) as lastTime values(app) as app values(session_id) as session_id by user, dest, auth_Country, src, verify_Country, object, signature, status, reason | where auth_Country != verify_Country | `security_content_ctime(firstTime)` | `security_content_ctime(lastTime)` | `pingid_mismatch_auth_source_and_verification_response_filter`"
how_to_implement: Target environment must ingest JSON logging from a PingID(PingOne) enterprise environment, either via Webhook or Push Subscription.
known_false_positives: False positives may be generated by users working out the geographic region where the organizations services or technology is hosted.
references:
    - https://twitter.com/jhencinski/status/1618660062352007174
    - https://attack.mitre.org/techniques/T1098/005/
    - https://attack.mitre.org/techniques/T1556/006/
    - https://docs.pingidentity.com/r/en-us/pingoneforenterprise/p14e_subscriptions?tocId=3xhnxjX3VzKNs3SXigWnQA
finding:
    title: An authentication by [$user$] was detected from [$dest$ - $auth_Country$] and the verification was received from [$src$ - $verify_Country$].
    entity:
        field: user
        type: user
        score: 50
intermediate_findings:
    entities:
        - field: src
          type: system
          score: 50
          message: An authentication by [$user$] was detected from [$dest$ - $auth_Country$] and the verification was received from [$src$ - $verify_Country$].
analytic_story:
    - Compromised User Account
asset_type: Identity
mitre_attack_id:
    - T1621
    - T1556.006
    - T1098.005
product:
    - Splunk Enterprise
    - Splunk Enterprise Security
    - Splunk Cloud
category: application
security_domain: access

Stages and Predicates

Stage 1: search

`pingid` ("result.status" IN ("SUCCESS*","FAIL*","UNSUCCESSFUL*" ) NOT "result.message" IN ("*pair*","*create*","*delete*"))

Stage 2: eval

| eval user = upper('actors{}.name'), session_id = 'resources{}.websession', dest = 'resources{}.ipaddress', reason = 'result.message', object = 'resources{}.devicemodel', status = 'result.status'

Stage 3: join

| join user session_id [ search `pingid` ("result.status" IN ("POLICY") AND "resources{}.ipaddress"=*) AND "result.message" IN("*Action: Authenticate*","*Action: Approve*","*Action: Allowed*") | rex field=result.message "IP Address: (?:N\/A)?(?<policy_ipaddress>.+)?\n" | rex field=result.message "Action: (?:N\/A)?(?<signature>.+)?\n" | rex field=result.message "Requested Application Name: (?:N\/A)?(?<Requested_Application_Name>.+)?\n" | rex field=result.message " Requested Application ID: (?:N\/A)?(?<Requested_Application_ID>.+)?\n" | eval user = upper('actors{}.name'), session_id = 'resources{}.websession', src = coalesce('resources{}.ipaddress',policy_ipaddress), app = coalesce(Requested_Application_ID,Requested_Application_Name) | fields app, user, session_id, src, signature ]

Stage 4: search

| iplocation prefix=auth_ dest

Stage 5: search

| iplocation prefix=verify_ src

Stage 6: stats

| stats count min(_time) as firstTime max(_time) as lastTime values(app) as app values(session_id) as session_id by user, dest, auth_Country, src, verify_Country, object, signature, status, reason

Stage 7: where

| where auth_Country != verify_Country

Stage 8: search

| `security_content_ctime(firstTime)`

Stage 9: search

| `security_content_ctime(lastTime)`

Stage 10: search

| `pingid_mismatch_auth_source_and_verification_response_filter`

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
result.messagein"*create*", "*delete*", "*pair*"excludes:result.message

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
auth_Countrycross_field_compare
  • verify_Country transforms: op:ne
field:"auth_Country" kind:cross_field_compare value:"verify_Country"
prefixeq
  • auth_
  • verify_
field:"prefix" kind:eq
resources{}.ipaddresseq
  • *
field:"resources{}.ipaddress" kind:eq value:"*"
result.messagein
  • "*Action: Allowed*"
  • "*Action: Approve*"
  • "*Action: Authenticate*"
field:"result.message" kind:in
result.statusin
  • "FAIL*"
  • "POLICY"
  • "SUCCESS*"
  • "UNSUCCESSFUL*"
field:"result.status" kind:in

Search terms

These SPL tokens match against raw event text.

StageTerm
4iplocation
4dest
5iplocation
5src