Detection rules › Panther
Panther rules: sign
| Rule | Severity |
|---|---|
| Sign In from Rogue State | medium |
Sign In from Rogue State
#Detects when an entity signs in from a nation associated with cyber attacks
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| 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_typeissuccessful_loginsource_ipis 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.
| Field | Kind | Values | Search |
|---|---|---|---|
event_type | eq |
| field:"event_type" kind:eq value:"successful_login" |
source_ip | is_not_null | 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"
}
}