Detection rules › Panther

Panther rules: sign

RuleSeverity
Sign In from Rogue Statemedium

Sign In from Rogue State

#
Severity
medium
Entities
ip_addresses
Log types
Asana.Audit, Atlassian.Audit, AWS.CloudTrail, Azure.Audit, Box.Event, Notion.AuditLogs, Okta.SystemLog, OneLogin.Events, OnePassword.SignInAttempt, Zendesk.Audit, Zoom.Activity
Tags
DataModel, Configuration Required
Source
github.com/panther-labs/panther-analysis

Detects when an entity signs in from a nation associated with cyber attacks

MITRE ATT&CK coverage

TacticTechniques
Initial Access

Telemetry coverage

Detection logic

import panther_event_type_helpers as event_type
import pycountry

# Configuration Required:
#   Configure the below list of rogue states according to your needs/experience
#   Refer to the link below to find the alpha-2 code corresponding to your country
#   https://www.iban.com/country-codes
ROGUE_STATES = {"CN", "IR", "RU"}


def rule(event):
    # Only evaluate successful logins
    if event.udm("event_type") != event_type.SUCCESSFUL_LOGIN:
        return False

    # Ignore events with no IP data
    if not event.udm("source_ip"):
        return False

    # Get contry of request origin and compare to identified rogue state list
    country = get_country(event)
    if country is None:
        # We weren't able to find a matching country, therefore we don't have enough information
        #   to alert on
        return False
    #   Wrapping in 'bool' so that we can use mocking for 'is_rogue_state'
    return bool(is_rogue_state(country.alpha_2))


def title(event):
    log_type = event.get("p_log_type")
    country = get_country(event)
    account_name = get_account_name(event)
    return f"{log_type}: Sign-In for account {account_name} from Rogue State '{country.name}'"


def alert_context(event):
    return {
        "source_ip": event.udm("source_ip"),
        "country": get_country(event).name,
        "account_name": get_account_name(event),
    }


def get_country(event) -> str:
    """Returns the country code from an event's IPinfo data."""
    location_data = event.deep_get("p_enrichment", "ipinfo_location", event.udm_path("source_ip"))
    if not location_data:
        return None  # Ignore event if we have no enrichment to analyze
    return pycountry.countries.get(alpha_2=location_data.get("country").upper())


def get_account_name(event) -> str:
    """Returns the account name."""
    if account_name := event.udm("actor_user"):
        return account_name
    return "UNKNOWN ACCOUNT"


def is_rogue_state(country_code: str) -> bool:
    """Returns whether the country code provided belongs to an identified rogue state."""
    # This function makes it easy for us to use unit test mocks to ensure altering the ROGUE_STATES
    #   dict doesn't break our test suite.
    return country_code in ROGUE_STATES

Rule specification

AnalysisType: rule
Filename: sign_in_from_rogue_state.py
RuleID: "Standard.SignInFromRogueState"
DisplayName: "Sign In from Rogue State"
Enabled: true
LogTypes:
  - Asana.Audit
  - Atlassian.Audit
  - AWS.CloudTrail
  - Azure.Audit
  - Box.Event
  - Notion.AuditLogs
  - Okta.SystemLog
  - OneLogin.Events
  - OnePassword.SignInAttempt
  - Zendesk.Audit
  - Zoom.Activity
Severity: Medium
Reports:
  MITRE ATT&CK:
    - TA0001:T1078.004
Description: Detects when an entity signs in from a nation associated with cyber attacks
DedupPeriodMinutes: 60
Tags:
  - DataModel
  - Configuration Required

Stages and Predicates

Fires on Asana.Audit, Atlassian.Audit, AWS.CloudTrail (and 8 more) events when all of the conditions below hold.

Condition

  • event_type is successful_login
  • source_ip is present

This rule also runs imperative logic the parser cannot express as a filter. The conditions above are the structured part it could extract.

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
event_typeeq
  • successful_login
field:"event_type" kind:eq value:"successful_login"
source_ipis_not_null
  • (no value, null check)
field:"src_ip" kind:is_not_null

Output fields

Fields the rule emits when it matches, drawn from the rule's alert_context.

Field
source_ip
p_log_type

Worked example

A sample event from the rule's unit tests that triggers a match.

Sample Test Event
{
  "actor": {
    "actor_type": "user",
    "email": "dude.lightbulb@example.co",
    "gid": "xxx",
    "name": "Dude Lightbulb"
  },
  "context": {
    "client_ip_address": "1.1.1.1",
    "context_type": "web",
    "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36"
  },
  "created_at": "2023-02-16 06:47:34.903",
  "details": {
    "method": [
      "SAML"
    ]
  },
  "event_category": "logins",
  "event_type": "user_login_succeeded",
  "gid": "xxx",
  "p_enrichment": {
    "ipinfo_location": {
      "context.client_ip_address": {
        "city": "Moscow",
        "country": "RU",
        "lat": "55.7520",
        "lng": "37.6150",
        "p_match": "1.1.1.1",
        "postal_code": "119021",
        "region": "Moscow",
        "region_code": "RU",
        "timezone": "Europe/Moscow"
      }
    }
  },
  "p_event_time": "2023-02-16 06:47:34.903",
  "p_log_type": "Asana.Audit",
  "p_parse_time": "2023-02-16 06:53:22.561",
  "p_row_id": "22bc6744332dc49e86f4a9b816f18a0f",
  "p_schema_version": 0,
  "p_source_id": "46bc875b-172f-4e9b-b475-6efac507c9a2",
  "p_source_label": "Asana",
  "resource": {
    "email": "dude.lightbulb@example.co",
    "gid": "xxx",
    "name": "Dude Lightbulb",
    "resource_type": "user"
  }
}