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 python_truthy

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.

Output Fields

These fields are emitted when the rule matches.

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"
  }
}

Back to all Panther rules