From ca54661ad9a46d845bee988316296f41673d35cd Mon Sep 17 00:00:00 2001 From: jmorascalyr <42879226+jmorascalyr@users.noreply.github.com> Date: Sat, 21 Feb 2026 13:15:31 -0700 Subject: [PATCH] feat: Add Okta OCSF parser support and enhance M365 collaboration parser field mappings - Added okta_ocsf_logs parser to SCENARIO_SOURCE_TO_PARSER and SOURCETYPE_MAP_OVERRIDES mappings - Updated Finance Employee MFA Fatigue Attack scenario to use okta_ocsf_logs instead of okta_authentication - Added okta_ocsf_logs to JSON_PRODUCTS list for JSON format handling - Rewrote okta_ocsf_logs parser with proper JSON formatting and OCSF field mappings (actor.user, src_endpoint, http_request, activity_name, status --- .../api/app/services/parser_sync_service.py | 1 + Backend/api/app/services/scenario_service.py | 6 +- Backend/event_generators/shared/hec_sender.py | 2 + .../microsoft_365_collaboration.json | 60 +++ .../okta_ocsf_logs-latest/okta_ocsf_logs.json | 366 +++++++++--------- .../scenarios/finance_mfa_fatigue_scenario.py | 243 +++++++++++- .../okta_ocsf_logs-latest/okta_ocsf_logs.conf | 366 +++++++++--------- 7 files changed, 649 insertions(+), 395 deletions(-) diff --git a/Backend/api/app/services/parser_sync_service.py b/Backend/api/app/services/parser_sync_service.py index a79c20b..4841ef7 100644 --- a/Backend/api/app/services/parser_sync_service.py +++ b/Backend/api/app/services/parser_sync_service.py @@ -32,6 +32,7 @@ SCENARIO_SOURCE_TO_PARSER = { # Identity & Access "okta_authentication": "okta_authentication-latest", + "okta_ocsf_logs": "okta_ocsf_logs-latest", "microsoft_azuread": "microsoft_azuread-latest", "microsoft_azure_ad_signin": "microsoft_azure_ad_signin-latest", diff --git a/Backend/api/app/services/scenario_service.py b/Backend/api/app/services/scenario_service.py index 6dad8e4..339fdf5 100644 --- a/Backend/api/app/services/scenario_service.py +++ b/Backend/api/app/services/scenario_service.py @@ -120,10 +120,10 @@ def __init__(self): "name": "Finance Employee MFA Fatigue Attack", "description": "8-day scenario with baseline behavior, MFA fatigue attack from Russia, OneDrive exfiltration, and SOAR response", "phases": [ - {"name": "Normal Behavior (Days 1-7)", "generators": ["okta_authentication", "microsoft_azuread", "microsoft_365_collaboration"], "duration": 7}, - {"name": "MFA Fatigue Attack", "generators": ["okta_authentication"], "duration": 1}, + {"name": "Normal Behavior (Days 1-7)", "generators": ["okta_ocsf_logs", "microsoft_azuread", "microsoft_365_collaboration"], "duration": 7}, + {"name": "MFA Fatigue Attack", "generators": ["okta_ocsf_logs"], "duration": 1}, {"name": "Data Exfiltration", "generators": ["microsoft_365_collaboration"], "duration": 1}, - {"name": "Detection & Response", "generators": ["okta_authentication"], "duration": 1} + {"name": "Detection & Response", "generators": ["okta_ocsf_logs"], "duration": 1} ] }, "insider_cloud_download_exfiltration": { diff --git a/Backend/event_generators/shared/hec_sender.py b/Backend/event_generators/shared/hec_sender.py index 14e358c..30c67f6 100644 --- a/Backend/event_generators/shared/hec_sender.py +++ b/Backend/event_generators/shared/hec_sender.py @@ -850,6 +850,7 @@ def _send_batch(lines: list, is_json: bool, product: str): # Identity and access management "okta_authentication": "okta_authentication-latest", + "okta_ocsf_logs": "okta_ocsf_logs-latest", "microsoft_azuread": "microsoft_azuread-latest", "microsoft_azure_ad": "microsoft_azure_ad_logs-latest", "microsoft_azure_ad_signin": "microsoft_azure_ad_signin-latest", @@ -1051,6 +1052,7 @@ def _build_qs(product: str) -> str: "zscaler", # JSON format for gron parser "microsoft_azuread", "okta_authentication", + "okta_ocsf_logs", # "crowdstrike_falcon", # Returns CEF format, not JSON "cyberark_pas", "darktrace", diff --git a/Backend/parsers/community/microsoft_365_collaboration-latest/microsoft_365_collaboration.json b/Backend/parsers/community/microsoft_365_collaboration-latest/microsoft_365_collaboration.json index 2d5869a..93eb0ff 100644 --- a/Backend/parsers/community/microsoft_365_collaboration-latest/microsoft_365_collaboration.json +++ b/Backend/parsers/community/microsoft_365_collaboration-latest/microsoft_365_collaboration.json @@ -86,12 +86,36 @@ "type": "iso8601TimestampToEpochSec" } }, + { + "copy": { + "from": "unmapped.TimeStamp", + "to": "metadata.original_time" + } + }, + { + "copy": { + "from": "unmapped.UserId", + "to": "user.email_addr" + } + }, + { + "copy": { + "from": "unmapped.UserId", + "to": "user.uid" + } + }, { "rename": { "from": "unmapped.UserId", "to": "actor.user.email_addr" } }, + { + "copy": { + "from": "unmapped.Operation", + "to": "status_detail" + } + }, { "rename": { "from": "unmapped.Operation", @@ -104,20 +128,56 @@ "to": "src_endpoint.url.url_string" } }, + { + "copy": { + "from": "unmapped.ObjectId", + "to": "process.file.path" + } + }, { "rename": { "from": "unmapped.ObjectId", "to": "file.path" } }, + { + "copy": { + "from": "unmapped.FileName", + "to": "process.file.name" + } + }, { "rename": { "from": "unmapped.FileName", "to": "file.name" } }, + { + "copy": { + "from": "unmapped.FileSize", + "to": "process.file.size" + } + }, + { + "rename": { + "from": "unmapped.FileSize", + "to": "file.size" + } + }, { "rename": { + "from": "unmapped.EventType", + "to": "event.type" + } + }, + { + "copy": { + "from": "unmapped.TargetUser", + "to": "unmapped.target_user" + } + }, + { + "copy": { "from": "unmapped.TargetUser", "to": "user.email_addr" } diff --git a/Backend/parsers/community/okta_ocsf_logs-latest/okta_ocsf_logs.json b/Backend/parsers/community/okta_ocsf_logs-latest/okta_ocsf_logs.json index f2fcc9c..de331f0 100644 --- a/Backend/parsers/community/okta_ocsf_logs-latest/okta_ocsf_logs.json +++ b/Backend/parsers/community/okta_ocsf_logs-latest/okta_ocsf_logs.json @@ -1,191 +1,179 @@ { - attributes: { - source: "okta" - "dataSource.category": "security", - "dataSource.name": "Okta", - "dataSource.vendor": "Okta", + "attributes": { + "dataSource.category": "security", + "dataSource.name": "Okta", + "dataSource.vendor": "Okta" + }, + "formats": [ + { + "format": ".*published", + "attributes": { + "category_uid": "3", + "category_name": "Audit Activity", + "class_uid": "3006", + "class_name": "Access Activity", + "severity_id": "99", + "activity_id": "99", + "type_uid": "300699", + "metadata.product.vendor_name": "Okta", + "metadata.product.name": "Okta", + "metadata.version": "1.0.0-rc.2" + } }, - formats: [ - { - format: ".*${parse=dottedJson}{attrBlacklist=target}$" - rewrites: [ - { - input: "actor.id", - output: "user.account_uid", - match: ".*", - replace: "$0" - }, - { - input: "actor.type", - output: "user.account_type", - match: ".*", - replace: "$0" - }, - { - input: "actor.alternateId", - output: "user.email_addr", - match: ".*", - replace: "$0" - }, - { - input: "actor.displayName", - output: "user.name", - match: ".*", - replace: "$0" - }, - { - input: "authenticationContext.authenticationStep", - output: "authenticationStep", - match: ".*", - replace: "$0" - }, - { - input: "authenticationContext.externalSessionId", - output: "externalSessionId", - match: ".*", - replace: "$0" - }, - { - input: "client.ipAddress", - output: "client.ip", - match: ".*", - replace: "$0" - }, - { - input: "client.userAgent.browser", - output: "client.browser", - match: ".*", - replace: "$0" - }, - { - input: "client.userAgent.os", - output: "clinet.os", - match: ".*", - replace: "$0" - }, - { - input: "client.userAgent.rawUserAgent", - output: "client.userAgent", - match: ".*", - replace: "$0" - }, - { - input: "client.zone", - output: "client.location.zone", - match: ".*", - replace: "$0" - }, - { - input: "client.geographicalContext.city", - output: "client.location.city", - match: ".*", - replace: "$0" - }, - { - input: "client.geographicalContext.country", - output: "client.location.country", - match: ".*", - replace: "$0" - }, - { - input: "client.geographicalContext.geolocation.lat", - output: "client.location.lat", - match: ".*", - replace: "$0" - }, - { - input: "client.geographicalContext.geolocation.lon", - output: "client.location.lon", - match: ".*", - replace: "$0" - }, - { - input: "client.geographicalContext.postalCode", - output: "client.location.postal_code", - match: ".*", - replace: "$0" - }, - { - input: "client.geographicalContext.state", - output: "client.location.state", - match: ".*", - replace: "$0" - }, - { - input: "debugContext.debugData.requestUri", - output: "observables", - match: ".*", - replace: "$0" - }, - { - input: "debugContext.debugData.state", - output: "status", - match: ".*", - replace: "$0" - }, - { - input: "displayMessage", - output: "msg", - match: ".*", - replace: "$0" - }, - { - input: "eventType", - output: "category_name", - match: ".*", - replace: "$0" - }, - { - input: "legacyEventType", - output: "legacy_category_name", - match: ".*", - replace: "$0" - }, - { - input: "outcome.result", - output: "result", - match: ".*", - replace: "$0" - }, - { - input: "published", - output: "time", - match: ".*", - replace: "$0" - }, - { - input: "transaction.id", - output: "type_uid", - match: ".*", - replace: "$0" - }, - { - input: "transaction.type", - output: "type_name", - match: ".*", - replace: "$0" - }, - { - input: "version", - output: "metadata.version", - match: ".*", - replace: "$0" - }, - { - input: "uuid", - output: "activity_id", - match: ".*", - replace: "$0" - }, - { - input: "time", - output: "timestamp", - match: ".*", - replace: "$0" - } - ] - }, - {format: ".*target\": \\[$target.=json{parse=dottedJson}$"} - - ] - } \ No newline at end of file + { + "format": ".*${parse=dottedJson}{attrBlacklist=(detailEntry|type|authenticationStep|target|request|userId|timeUnit|timeSpan|threshold|requestId|rateLimitSecondsToReset|rateLimitScopeType|rateLimitBucketUuid|dtHash|securityContext|requestApiTokenId)}$", + "rewrites": [ + { + "input": "actor.alternateId", + "output": "actor.user.email_addr", + "match": ".*", + "replace": "$0" + }, + { + "input": "actor.displayName", + "output": "actor.user.name", + "match": ".*", + "replace": "$0" + }, + { + "input": "actor.id", + "output": "actor.user.uid", + "match": ".*", + "replace": "$0" + }, + { + "input": "actor.type", + "output": "actor.user.type", + "match": ".*", + "replace": "$0" + }, + { + "input": "client.ipAddress", + "output": "src_endpoint.ip", + "match": ".*", + "replace": "$0" + }, + { + "input": "client.device", + "output": "unmapped.client.device", + "match": ".*", + "replace": "$0" + }, + { + "input": "client.id", + "output": "src_endpoint.uid", + "match": ".*", + "replace": "$0" + }, + { + "input": "client.zone", + "output": "src_endpoint.domain", + "match": ".*", + "replace": "$0" + }, + { + "input": "client.geographicalContext.city", + "output": "src_endpoint.location.city", + "match": ".*", + "replace": "$0" + }, + { + "input": "client.geographicalContext.country", + "output": "src_endpoint.location.country", + "match": ".*", + "replace": "$0" + }, + { + "input": "client.geographicalContext.postalCode", + "output": "src_endpoint.location.postal_code", + "match": ".*", + "replace": "$0" + }, + { + "input": "client.geographicalContext.state", + "output": "src_endpoint.location.region", + "match": ".*", + "replace": "$0" + }, + { + "input": "securityContext.isp", + "output": "src_endpoint.location.isp", + "match": ".*", + "replace": "$0" + }, + { + "input": "client.userAgent.rawUserAgent", + "output": "http_request.user_agent", + "match": ".*", + "replace": "$0" + }, + { + "input": "eventType", + "output": "unmapped.eventType", + "match": ".*", + "replace": "$0" + }, + { + "input": "eventType", + "output": "event.type", + "match": ".*", + "replace": "$0" + }, + { + "input": "eventType", + "output": "activity_name", + "match": ".*", + "replace": "$0" + }, + { + "input": "legacyEventType", + "output": "unmapped.legacyEventType", + "match": ".*", + "replace": "$0" + }, + { + "input": "outcome.result", + "output": "status", + "match": ".*", + "replace": "$0" + }, + { + "input": "outcome.reason", + "output": "status_detail", + "match": ".*", + "replace": "$0" + }, + { + "input": "_time", + "output": "timestamp", + "match": ".*", + "replace": "$0" + }, + { + "input": "published", + "output": "time", + "match": ".*", + "replace": "$0" + }, + { + "input": "displayMessage", + "output": "message", + "match": ".*", + "replace": "$0" + }, + { + "input": "uuid", + "output": "metadata.uid", + "match": ".*", + "replace": "$0" + }, + { + "input": "severity", + "output": "severity", + "match": ".*", + "replace": "$0" + } + ] + } + ] +} \ No newline at end of file diff --git a/Backend/scenarios/finance_mfa_fatigue_scenario.py b/Backend/scenarios/finance_mfa_fatigue_scenario.py index c0c57bc..b6e5350 100644 --- a/Backend/scenarios/finance_mfa_fatigue_scenario.py +++ b/Backend/scenarios/finance_mfa_fatigue_scenario.py @@ -29,8 +29,15 @@ import os import errno import random +import copy +import gzip +import uuid from datetime import datetime, timezone, timedelta -from typing import Dict, List +from typing import Dict, List, Optional + +import requests + +backend_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # Add event_generators to path sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'event_generators')) @@ -38,7 +45,7 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'event_generators', 'cloud_infrastructure')) # Import generators -from okta_authentication import okta_authentication_log +from okta_system_log import okta_system_log from microsoft_azuread import azuread_log from microsoft_365_collaboration import microsoft_365_collaboration_log @@ -60,6 +67,171 @@ "timezone_offset": 10 # Moscow is UTC+3, Denver is UTC-7 = 10 hour difference } +# Alert configuration for scenario phases +# Maps scenario detection phases to existing UAM alert templates with overrides +ALERT_PHASE_MAPPING = { + "mfa_fatigue": { + "template": "o365_brute_force_success", + "offset_minutes": 0, + "overrides": { + "finding_info.title": "HELIOS - Okta MFA Fatigue Attack Detected", + "finding_info.desc": f"15 consecutive MFA push requests detected for {JAKE_PROFILE['email']} within 15 minutes from {ATTACKER_PROFILE['ip']} ({ATTACKER_PROFILE['location']}), followed by user acceptance. MITRE ATT&CK: T1621 - Multi-Factor Authentication Request Generation.", + "severity_id": 5, + "severity": "critical", + } + }, + "impossible_traveler": { + "template": "o365_noncompliant_login", + "offset_minutes": 1, + "overrides": { + "finding_info.title": "HELIOS - Impossible Traveler Detected", + "finding_info.desc": f"Login from {ATTACKER_PROFILE['location']} ({ATTACKER_PROFILE['ip']}) detected for {JAKE_PROFILE['email']} 30 minutes after Denver login. Geographic distance: 8,000+ miles. MITRE ATT&CK: T1078 - Valid Accounts.", + "severity_id": 5, + "severity": "critical", + } + }, + "ueba_irregular_login": { + "template": "o365_sneaky_2fa", + "offset_minutes": 2, + "overrides": { + "finding_info.title": "HELIOS - UEBA Irregular Login Pattern", + "finding_info.desc": f"Login detected for {JAKE_PROFILE['email']} at 7:30 PM from {ATTACKER_PROFILE['ip']} - outside normal working hours (8 AM - 5 PM). Baseline deviation: 11.5 hours. Risk score: 85. MITRE ATT&CK: T1078 - Valid Accounts.", + "severity_id": 4, + "severity": "high", + } + }, + "data_exfiltration": { + "template": "sharepoint_data_exfil_alert", + "offset_minutes": 3, + "overrides": { + "finding_info.title": "HELIOS - Irregular Data Download Activity", + "finding_info.desc": f"27 sensitive financial documents downloaded by {JAKE_PROFILE['email']} from {ATTACKER_PROFILE['ip']} ({ATTACKER_PROFILE['location']}) in 30 minutes - 15x normal daily average. Data volume: 4.2 GB. Sensitive data types: PII, Financial Records, Client Data. MITRE ATT&CK: T1530 - Data from Cloud Storage Object.", + "severity_id": 5, + "severity": "critical", + } + }, +} + + +def load_alert_template(template_id: str) -> Optional[Dict]: + """Load an alert template JSON from the templates directory""" + candidate_dirs = [ + os.path.join(backend_dir, 'api', 'app', 'alerts', 'templates'), # local dev + os.path.join(backend_dir, 'app', 'alerts', 'templates'), # Docker + ] + for templates_dir in candidate_dirs: + template_path = os.path.join(templates_dir, f"{template_id}.json") + if os.path.exists(template_path): + with open(template_path, 'r') as f: + return json.load(f) + print(f" āš ļø Template not found: {template_id}.json (searched {candidate_dirs})") + return None + + +def send_phase_alert( + phase_name: str, + alert_time: datetime, + uam_config: dict +) -> bool: + """Send alert for a specific phase with correct timing. + + Standalone implementation — loads template from disk and sends + directly via requests + gzip. No AlertService dependency. + """ + if phase_name not in ALERT_PHASE_MAPPING: + return False + + mapping = ALERT_PHASE_MAPPING[phase_name] + + # Load template + template = load_alert_template(mapping["template"]) + if not template: + return False + + alert = copy.deepcopy(template) + + # Calculate alert timestamp + offset_time = alert_time + timedelta(minutes=mapping["offset_minutes"]) + time_ms = int(offset_time.timestamp() * 1000) + + # Inject fresh UID + if "finding_info" not in alert: + alert["finding_info"] = {} + alert["finding_info"]["uid"] = str(uuid.uuid4()) + + # Set timestamps + alert["time"] = time_ms + if "metadata" not in alert: + alert["metadata"] = {} + alert["metadata"]["logged_time"] = time_ms + alert["metadata"]["modified_time"] = time_ms + + # Set resource to Jake's email with a consistent GUID + email_asset_uid = uam_config.get('email_asset_uid') + if not email_asset_uid: + email_asset_uid = str(uuid.uuid5(uuid.NAMESPACE_DNS, JAKE_PROFILE["email"])) + uam_config['email_asset_uid'] = email_asset_uid + alert["resources"] = [{ + "uid": email_asset_uid, + "name": JAKE_PROFILE["email"] + }] + + # Apply overrides + overrides = mapping.get("overrides", {}) + for key, value in overrides.items(): + if "." in key: + keys = key.split(".") + current = alert + for k in keys[:-1]: + if k not in current: + current[k] = {} + current = current[k] + current[keys[-1]] = value + else: + alert[key] = value + + # Send alert via UAM ingest API + try: + ingest_url = uam_config['uam_ingest_url'].rstrip('/') + '/v1/alerts' + scope = uam_config['uam_account_id'] + if uam_config.get('uam_site_id'): + scope = f"{scope}:{uam_config['uam_site_id']}" + + headers = { + "Authorization": f"Bearer {uam_config['uam_service_token']}", + "S1-Scope": scope, + "Content-Encoding": "gzip", + "Content-Type": "application/json", + "S1-Trace-Id": "helios-ingest-uam:alwayslog", + } + + payload = json.dumps(alert).encode("utf-8") + gzipped = gzip.compress(payload) + + print(f"\n šŸ“¤ Alert Details:") + print(f" Template: {mapping['template']}") + print(f" Title: {alert.get('finding_info', {}).get('title', 'N/A')}") + print(f" User: {JAKE_PROFILE['email']}") + print(f" Time: {offset_time.isoformat()} ({time_ms}ms)") + print(f" URL: {ingest_url}") + print(f" Scope: {scope}") + print(f" Payload: {len(payload)} bytes -> {len(gzipped)} bytes (gzip)") + + resp = requests.post(ingest_url, headers=headers, data=gzipped, timeout=30) + + print(f" Response: {resp.status_code} {resp.reason}") + if resp.content: + print(f" Body: {resp.text[:200]}") + + return resp.status_code == 202 + + except Exception as e: + print(f" āœ— Alert send failed: {e}") + import traceback + traceback.print_exc() + return False + + def get_scenario_time(base_time: datetime, day: int, hour: int, minute: int = 0, second: int = 0) -> str: """Calculate timestamp for scenario event""" event_time = base_time + timedelta(days=day, hours=hour, minutes=minute, seconds=second) @@ -80,7 +252,7 @@ def generate_normal_day_events(base_time: datetime, day: int) -> List[Dict]: # Morning login (8:30 AM) login_time = get_scenario_time(base_time, day, 8, 30) - okta_login_str = okta_authentication_log() + okta_login_str = okta_system_log() okta_login = json.loads(okta_login_str) if isinstance(okta_login_str, str) else okta_login_str # Customize for normal login and set published to scenario timestamp okta_login['published'] = login_time @@ -96,7 +268,7 @@ def generate_normal_day_events(base_time: datetime, day: int) -> List[Dict]: okta_login['displayMessage'] = 'User successfully authenticated' okta_login['severity'] = 'INFO' - events.append(create_event(login_time, "okta_authentication", "normal_behavior", okta_login)) + events.append(create_event(login_time, "okta_ocsf_logs", "normal_behavior", okta_login)) # Azure AD sign-in azuread_signin_str = azuread_log() @@ -159,7 +331,7 @@ def generate_mfa_fatigue_attack(base_time: datetime) -> List[Dict]: # IMPOSSIBLE TRAVELER: Normal Denver login at 7:00 PM denver_login_time = get_scenario_time(base_time, day, 19, 0) # 7:00 PM - okta_denver_str = okta_authentication_log() + okta_denver_str = okta_system_log() okta_denver = json.loads(okta_denver_str) if isinstance(okta_denver_str, str) else okta_denver_str okta_denver['published'] = denver_login_time okta_denver['eventType'] = 'user.session.start' @@ -174,7 +346,7 @@ def generate_mfa_fatigue_attack(base_time: datetime) -> List[Dict]: okta_denver['displayMessage'] = 'Evening login from Denver office' okta_denver['severity'] = 'INFO' - events.append(create_event(denver_login_time, "okta_authentication", "normal_behavior", okta_denver)) + events.append(create_event(denver_login_time, "okta_ocsf_logs", "normal_behavior", okta_denver)) # Azure AD sign-in from Denver at 7:00 PM azuread_denver_str = azuread_log() @@ -197,7 +369,7 @@ def generate_mfa_fatigue_attack(base_time: datetime) -> List[Dict]: attempt_time = get_scenario_time(base_time, day, attack_start_hour, attack_start_minute + i) # Failed Okta MFA attempt - okta_mfa_str = okta_authentication_log() + okta_mfa_str = okta_system_log() okta_mfa = json.loads(okta_mfa_str) if isinstance(okta_mfa_str, str) else okta_mfa_str okta_mfa['published'] = attempt_time okta_mfa['eventType'] = 'user.mfa.challenge' @@ -212,11 +384,11 @@ def generate_mfa_fatigue_attack(base_time: datetime) -> List[Dict]: okta_mfa['displayMessage'] = f'MFA push request #{i+1} - Waiting for user approval' okta_mfa['severity'] = 'WARN' - events.append(create_event(attempt_time, "okta_authentication", "mfa_fatigue", okta_mfa)) + events.append(create_event(attempt_time, "okta_ocsf_logs", "mfa_fatigue", okta_mfa)) # User accepts MFA (attempt #14) accept_time = get_scenario_time(base_time, day, attack_start_hour, attack_start_minute + 14) - okta_success_str = okta_authentication_log() + okta_success_str = okta_system_log() okta_success = json.loads(okta_success_str) if isinstance(okta_success_str, str) else okta_success_str okta_success['published'] = accept_time okta_success['eventType'] = 'user.mfa.challenge' @@ -231,11 +403,11 @@ def generate_mfa_fatigue_attack(base_time: datetime) -> List[Dict]: okta_success['displayMessage'] = 'User approved MFA push - Access granted' okta_success['severity'] = 'INFO' - events.append(create_event(accept_time, "okta_authentication", "initial_access", okta_success)) + events.append(create_event(accept_time, "okta_ocsf_logs", "initial_access", okta_success)) # Session start immediately after successful MFA (30 seconds later) session_time = get_scenario_time(base_time, day, attack_start_hour, attack_start_minute + 14, 30) - okta_session_str = okta_authentication_log() + okta_session_str = okta_system_log() okta_session = json.loads(okta_session_str) if isinstance(okta_session_str, str) else okta_session_str okta_session['published'] = session_time okta_session['eventType'] = 'user.session.start' @@ -250,12 +422,12 @@ def generate_mfa_fatigue_attack(base_time: datetime) -> List[Dict]: okta_session['displayMessage'] = 'Session established after MFA' okta_session['severity'] = 'INFO' - events.append(create_event(session_time, "okta_authentication", "initial_access", okta_session)) + events.append(create_event(session_time, "okta_ocsf_logs", "initial_access", okta_session)) print(f" āœ“ MFA accepted after 15 attempts") # Attacker tries to access Okta Admin Console - BLOCKED (1 minute later) admin_attempt_time = get_scenario_time(base_time, day, attack_start_hour, attack_start_minute + 15, 30) - okta_admin_str = okta_authentication_log() + okta_admin_str = okta_system_log() okta_admin = json.loads(okta_admin_str) if isinstance(okta_admin_str, str) else okta_admin_str okta_admin['published'] = admin_attempt_time okta_admin['eventType'] = 'user.session.access_admin_app' @@ -271,7 +443,7 @@ def generate_mfa_fatigue_attack(base_time: datetime) -> List[Dict]: okta_admin['displayMessage'] = 'User attempted to access Okta admin console but was denied' okta_admin['severity'] = 'WARN' - events.append(create_event(admin_attempt_time, "okta_authentication", "initial_access", okta_admin)) + events.append(create_event(admin_attempt_time, "okta_ocsf_logs", "initial_access", okta_admin)) print(f" āœ“ Failed attempt to access Okta admin console from Moscow") # Azure AD sign-in from Russia @@ -600,6 +772,33 @@ def generate_finance_mfa_fatigue_scenario(): # Start scenario 8 days ago base_time = datetime.now(timezone.utc) - timedelta(days=8) + # Initialize alert detonation from env vars + alerts_enabled = os.getenv('SCENARIO_ALERTS_ENABLED', 'false').lower() == 'true' + uam_config = None + + if alerts_enabled: + uam_ingest_url = os.getenv('UAM_INGEST_URL', '') + uam_account_id = os.getenv('UAM_ACCOUNT_ID', '') + uam_service_token = os.getenv('UAM_SERVICE_TOKEN', '') + uam_site_id = os.getenv('UAM_SITE_ID', '') + + if uam_ingest_url and uam_account_id and uam_service_token: + uam_config = { + 'uam_ingest_url': uam_ingest_url, + 'uam_account_id': uam_account_id, + 'uam_service_token': uam_service_token, + 'uam_site_id': uam_site_id, + } + print("\n🚨 ALERT DETONATION ENABLED") + print(f" UAM Ingest: {uam_ingest_url}") + print(f" Account ID: {uam_account_id}") + if uam_site_id: + print(f" Site ID: {uam_site_id}") + print("=" * 80) + else: + print("āš ļø SCENARIO_ALERTS_ENABLED=true but UAM credentials missing") + alerts_enabled = False + all_events = [] # Phase 1: Normal Behavior Baseline (Days 1-7) @@ -637,6 +836,22 @@ def generate_finance_mfa_fatigue_scenario(): all_events.extend(detection_events) print(f"\nTotal detection/response events: {len(detection_events)}") + # Send UAM alerts for each detection phase + if alerts_enabled and uam_config: + # Detection time = Day 8, 8:15 PM (same as SOAR detections) + detection_time = base_time + timedelta(days=7, hours=20, minutes=15) + print(f"\nšŸ”” SENDING UAM ALERTS") + alert_phases = [ + ("mfa_fatigue", "MFA Fatigue Attack"), + ("impossible_traveler", "Impossible Traveler"), + ("ueba_irregular_login", "UEBA Irregular Login"), + ("data_exfiltration", "Data Exfiltration"), + ] + for alert_key, alert_desc in alert_phases: + print(f" šŸ“¤ {alert_desc}...", end=" ") + success = send_phase_alert(alert_key, detection_time, uam_config) + print(f"{'āœ“' if success else 'āœ—'}") + # Sort all events by timestamp all_events.sort(key=lambda x: x['timestamp']) diff --git a/Backend/utilities/parsers/community_new/ai-siem-main/parsers/community/okta_ocsf_logs-latest/okta_ocsf_logs.conf b/Backend/utilities/parsers/community_new/ai-siem-main/parsers/community/okta_ocsf_logs-latest/okta_ocsf_logs.conf index f2fcc9c..de331f0 100644 --- a/Backend/utilities/parsers/community_new/ai-siem-main/parsers/community/okta_ocsf_logs-latest/okta_ocsf_logs.conf +++ b/Backend/utilities/parsers/community_new/ai-siem-main/parsers/community/okta_ocsf_logs-latest/okta_ocsf_logs.conf @@ -1,191 +1,179 @@ { - attributes: { - source: "okta" - "dataSource.category": "security", - "dataSource.name": "Okta", - "dataSource.vendor": "Okta", + "attributes": { + "dataSource.category": "security", + "dataSource.name": "Okta", + "dataSource.vendor": "Okta" + }, + "formats": [ + { + "format": ".*published", + "attributes": { + "category_uid": "3", + "category_name": "Audit Activity", + "class_uid": "3006", + "class_name": "Access Activity", + "severity_id": "99", + "activity_id": "99", + "type_uid": "300699", + "metadata.product.vendor_name": "Okta", + "metadata.product.name": "Okta", + "metadata.version": "1.0.0-rc.2" + } }, - formats: [ - { - format: ".*${parse=dottedJson}{attrBlacklist=target}$" - rewrites: [ - { - input: "actor.id", - output: "user.account_uid", - match: ".*", - replace: "$0" - }, - { - input: "actor.type", - output: "user.account_type", - match: ".*", - replace: "$0" - }, - { - input: "actor.alternateId", - output: "user.email_addr", - match: ".*", - replace: "$0" - }, - { - input: "actor.displayName", - output: "user.name", - match: ".*", - replace: "$0" - }, - { - input: "authenticationContext.authenticationStep", - output: "authenticationStep", - match: ".*", - replace: "$0" - }, - { - input: "authenticationContext.externalSessionId", - output: "externalSessionId", - match: ".*", - replace: "$0" - }, - { - input: "client.ipAddress", - output: "client.ip", - match: ".*", - replace: "$0" - }, - { - input: "client.userAgent.browser", - output: "client.browser", - match: ".*", - replace: "$0" - }, - { - input: "client.userAgent.os", - output: "clinet.os", - match: ".*", - replace: "$0" - }, - { - input: "client.userAgent.rawUserAgent", - output: "client.userAgent", - match: ".*", - replace: "$0" - }, - { - input: "client.zone", - output: "client.location.zone", - match: ".*", - replace: "$0" - }, - { - input: "client.geographicalContext.city", - output: "client.location.city", - match: ".*", - replace: "$0" - }, - { - input: "client.geographicalContext.country", - output: "client.location.country", - match: ".*", - replace: "$0" - }, - { - input: "client.geographicalContext.geolocation.lat", - output: "client.location.lat", - match: ".*", - replace: "$0" - }, - { - input: "client.geographicalContext.geolocation.lon", - output: "client.location.lon", - match: ".*", - replace: "$0" - }, - { - input: "client.geographicalContext.postalCode", - output: "client.location.postal_code", - match: ".*", - replace: "$0" - }, - { - input: "client.geographicalContext.state", - output: "client.location.state", - match: ".*", - replace: "$0" - }, - { - input: "debugContext.debugData.requestUri", - output: "observables", - match: ".*", - replace: "$0" - }, - { - input: "debugContext.debugData.state", - output: "status", - match: ".*", - replace: "$0" - }, - { - input: "displayMessage", - output: "msg", - match: ".*", - replace: "$0" - }, - { - input: "eventType", - output: "category_name", - match: ".*", - replace: "$0" - }, - { - input: "legacyEventType", - output: "legacy_category_name", - match: ".*", - replace: "$0" - }, - { - input: "outcome.result", - output: "result", - match: ".*", - replace: "$0" - }, - { - input: "published", - output: "time", - match: ".*", - replace: "$0" - }, - { - input: "transaction.id", - output: "type_uid", - match: ".*", - replace: "$0" - }, - { - input: "transaction.type", - output: "type_name", - match: ".*", - replace: "$0" - }, - { - input: "version", - output: "metadata.version", - match: ".*", - replace: "$0" - }, - { - input: "uuid", - output: "activity_id", - match: ".*", - replace: "$0" - }, - { - input: "time", - output: "timestamp", - match: ".*", - replace: "$0" - } - ] - }, - {format: ".*target\": \\[$target.=json{parse=dottedJson}$"} - - ] - } \ No newline at end of file + { + "format": ".*${parse=dottedJson}{attrBlacklist=(detailEntry|type|authenticationStep|target|request|userId|timeUnit|timeSpan|threshold|requestId|rateLimitSecondsToReset|rateLimitScopeType|rateLimitBucketUuid|dtHash|securityContext|requestApiTokenId)}$", + "rewrites": [ + { + "input": "actor.alternateId", + "output": "actor.user.email_addr", + "match": ".*", + "replace": "$0" + }, + { + "input": "actor.displayName", + "output": "actor.user.name", + "match": ".*", + "replace": "$0" + }, + { + "input": "actor.id", + "output": "actor.user.uid", + "match": ".*", + "replace": "$0" + }, + { + "input": "actor.type", + "output": "actor.user.type", + "match": ".*", + "replace": "$0" + }, + { + "input": "client.ipAddress", + "output": "src_endpoint.ip", + "match": ".*", + "replace": "$0" + }, + { + "input": "client.device", + "output": "unmapped.client.device", + "match": ".*", + "replace": "$0" + }, + { + "input": "client.id", + "output": "src_endpoint.uid", + "match": ".*", + "replace": "$0" + }, + { + "input": "client.zone", + "output": "src_endpoint.domain", + "match": ".*", + "replace": "$0" + }, + { + "input": "client.geographicalContext.city", + "output": "src_endpoint.location.city", + "match": ".*", + "replace": "$0" + }, + { + "input": "client.geographicalContext.country", + "output": "src_endpoint.location.country", + "match": ".*", + "replace": "$0" + }, + { + "input": "client.geographicalContext.postalCode", + "output": "src_endpoint.location.postal_code", + "match": ".*", + "replace": "$0" + }, + { + "input": "client.geographicalContext.state", + "output": "src_endpoint.location.region", + "match": ".*", + "replace": "$0" + }, + { + "input": "securityContext.isp", + "output": "src_endpoint.location.isp", + "match": ".*", + "replace": "$0" + }, + { + "input": "client.userAgent.rawUserAgent", + "output": "http_request.user_agent", + "match": ".*", + "replace": "$0" + }, + { + "input": "eventType", + "output": "unmapped.eventType", + "match": ".*", + "replace": "$0" + }, + { + "input": "eventType", + "output": "event.type", + "match": ".*", + "replace": "$0" + }, + { + "input": "eventType", + "output": "activity_name", + "match": ".*", + "replace": "$0" + }, + { + "input": "legacyEventType", + "output": "unmapped.legacyEventType", + "match": ".*", + "replace": "$0" + }, + { + "input": "outcome.result", + "output": "status", + "match": ".*", + "replace": "$0" + }, + { + "input": "outcome.reason", + "output": "status_detail", + "match": ".*", + "replace": "$0" + }, + { + "input": "_time", + "output": "timestamp", + "match": ".*", + "replace": "$0" + }, + { + "input": "published", + "output": "time", + "match": ".*", + "replace": "$0" + }, + { + "input": "displayMessage", + "output": "message", + "match": ".*", + "replace": "$0" + }, + { + "input": "uuid", + "output": "metadata.uid", + "match": ".*", + "replace": "$0" + }, + { + "input": "severity", + "output": "severity", + "match": ".*", + "replace": "$0" + } + ] + } + ] +} \ No newline at end of file